256 lines
7.7 KiB
JavaScript
256 lines
7.7 KiB
JavaScript
import { LitElement, html, css } from 'lit';
|
|
import { TpRtbBaseExtension } from './tp-rtb-base-extension.js';
|
|
import Mention from '@tiptap/extension-mention';
|
|
import { closest } from '@tp/helpers/closest.js';
|
|
|
|
const sampleUsers = [
|
|
{ id: 'user1', name: 'John Doe', username: 'johndoe', avatar: '👤' },
|
|
{ id: 'user2', name: 'Jane Smith', username: 'janesmith', avatar: '👩' },
|
|
{ id: 'user3', name: 'Bob Johnson', username: 'bobjohnson', avatar: '👨' },
|
|
{ id: 'user4', name: 'Alice Brown', username: 'alicebrown', avatar: '👩💼' },
|
|
{ id: 'user5', name: 'Charlie Wilson', username: 'charliewilson', avatar: '👨💻' },
|
|
{ id: 'user6', name: 'Diana Garcia', username: 'dianagarcia', avatar: '👩🔬' },
|
|
{ id: 'user7', name: 'Edward Davis', username: 'edwarddavis', avatar: '👨🎨' },
|
|
{ id: 'user8', name: 'Fiona Miller', username: 'fionamiller', avatar: '👩🎓' },
|
|
{ id: 'user9', name: 'George Taylor', username: 'georgetaylor', avatar: '👨⚕️' },
|
|
{ id: 'user10', name: 'Helen Anderson', username: 'helenaderson', avatar: '👩🏫' },
|
|
];
|
|
|
|
export class TpRtbUserMention extends TpRtbBaseExtension {
|
|
static getUsers() {
|
|
return sampleUsers;
|
|
}
|
|
|
|
static get properties() {
|
|
return {
|
|
...super.properties,
|
|
items: { type: Array },
|
|
selectedIndex: { type: Number },
|
|
};
|
|
}
|
|
|
|
static get styles() {
|
|
return [
|
|
super.styles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
background-color: #fff;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
padding: 8px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
z-index: 9999;
|
|
min-width: 200px;
|
|
}
|
|
.item {
|
|
padding: 8px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
.item.is-selected {
|
|
background-color: #e3f2fd;
|
|
}
|
|
.item:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
.avatar {
|
|
font-size: 20px;
|
|
}
|
|
.user-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-grow: 1;
|
|
}
|
|
.name {
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
}
|
|
.username {
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
`
|
|
];
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.items = [];
|
|
this.selectedIndex = 0;
|
|
this.label = 'User Mention';
|
|
}
|
|
|
|
getSuggestionConfig() {
|
|
return {
|
|
char: '@',
|
|
allowSpaces: false,
|
|
startOfLine: false,
|
|
items: ({ query }) => {
|
|
console.log('User mention query:', query);
|
|
return TpRtbUserMention.getUsers().filter(user =>
|
|
user.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
user.username.toLowerCase().includes(query.toLowerCase())
|
|
).slice(0, 10);
|
|
},
|
|
render: () => {
|
|
let component;
|
|
let parentEditor;
|
|
let selectItemHandler;
|
|
|
|
return {
|
|
onStart: props => {
|
|
console.log('User mention onStart called', props);
|
|
console.log('Props clientRect:', props.clientRect);
|
|
// Find the parent editor using tp/helpers closest
|
|
parentEditor = closest(this, 'tp-rich-text-box', true);
|
|
if (!parentEditor) {
|
|
console.error('Parent editor not found for user mention');
|
|
return;
|
|
}
|
|
|
|
// Find the user mention component
|
|
component = parentEditor.querySelector('tp-rtb-user-mention[slot="suggestion-content"]');
|
|
|
|
if (!component) {
|
|
console.error('User mention component not found.');
|
|
return;
|
|
}
|
|
|
|
component.items = props.items;
|
|
component.selectedIndex = 0;
|
|
// Pass the command function to the component
|
|
component._command = props.command;
|
|
|
|
// Show suggestion menu with user type
|
|
if (parentEditor.showSuggestionMenu) {
|
|
parentEditor.showSuggestionMenu(props.clientRect, 'user');
|
|
} else {
|
|
console.error('showSuggestionMenu method not available');
|
|
}
|
|
|
|
// Store the handler to remove it later
|
|
selectItemHandler = (event) => {
|
|
const user = event.detail;
|
|
console.log('User selected:', user);
|
|
|
|
// Call command directly like in TipTap Vue example
|
|
if (component._command) {
|
|
component._command({
|
|
id: user.id,
|
|
label: user.username
|
|
});
|
|
}
|
|
};
|
|
|
|
component.addEventListener('select-item', selectItemHandler);
|
|
},
|
|
onUpdate: (props) => {
|
|
if (!component || !parentEditor) return;
|
|
|
|
component.items = props.items;
|
|
component.selectedIndex = 0;
|
|
// Update the command function
|
|
component._command = props.command;
|
|
|
|
// Update suggestion menu position with user type
|
|
if (parentEditor.showSuggestionMenu) {
|
|
parentEditor.showSuggestionMenu(props.clientRect, 'user');
|
|
}
|
|
},
|
|
onKeyDown: (props) => {
|
|
if (!component) return false;
|
|
return component.onKeyDown(props);
|
|
},
|
|
onExit: () => {
|
|
console.log('User mention onExit called');
|
|
if (component && selectItemHandler) {
|
|
component.removeEventListener('select-item', selectItemHandler);
|
|
component.items = [];
|
|
component.selectedIndex = 0;
|
|
}
|
|
|
|
// Hide the suggestion menu
|
|
if (parentEditor && parentEditor.hideMenu) {
|
|
parentEditor.hideMenu();
|
|
}
|
|
component = null;
|
|
parentEditor = null;
|
|
selectItemHandler = null;
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
_handleClick() {
|
|
// User mentions are triggered by typing @ not by clicking a button
|
|
// This method can be left empty or used for other purposes
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
${this.items.length > 0
|
|
? this.items.map(
|
|
(item, index) => html`
|
|
<div
|
|
class="item ${index === this.selectedIndex ? 'is-selected' : ''}"
|
|
@click="${() => this._selectItem(index)}"
|
|
>
|
|
<span class="avatar">${item.avatar}</span>
|
|
<div class="user-info">
|
|
<span class="name">${item.name}</span>
|
|
<span class="username">@${item.username}</span>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
: html`<div class="item">No users found</div>`}
|
|
`;
|
|
}
|
|
|
|
updated(changedProperties) {
|
|
if (changedProperties.has('selectedIndex')) {
|
|
const selectedElement = this.shadowRoot.querySelector('.item.is-selected');
|
|
if (selectedElement) {
|
|
selectedElement.scrollIntoView({
|
|
block: 'nearest',
|
|
inline: 'nearest',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
_selectItem(index) {
|
|
const item = this.items[index];
|
|
if (item) {
|
|
this.dispatchEvent(new CustomEvent('select-item', { detail: item }));
|
|
}
|
|
}
|
|
|
|
onKeyDown({ event }) {
|
|
if (event.key === 'ArrowUp') {
|
|
this.selectedIndex = (this.selectedIndex + this.items.length - 1) % this.items.length;
|
|
return true;
|
|
}
|
|
|
|
if (event.key === 'ArrowDown') {
|
|
this.selectedIndex = (this.selectedIndex + 1) % this.items.length;
|
|
return true;
|
|
}
|
|
|
|
if (event.key === 'Enter') {
|
|
this._selectItem(this.selectedIndex);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
customElements.define('tp-rtb-user-mention', TpRtbUserMention); |