This commit is contained in:
2025-11-20 15:23:20 +01:00
parent 980048b6ab
commit 52f7478b5f
4 changed files with 255 additions and 115 deletions

View File

@@ -1,6 +1,5 @@
import { LitElement, html, css } from 'lit';
import { 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 = [
@@ -17,18 +16,6 @@ const sampleUsers = [
];
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,
@@ -45,6 +32,7 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
z-index: 9999;
min-width: 200px;
}
.item {
padding: 8px;
cursor: pointer;
@@ -53,24 +41,30 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
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;
@@ -79,11 +73,67 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
];
}
render() {
return html`
<div>
${this.items.length > 0
? this.items.map(
(item, index) => html`
<div
class="item ${index === this.selectedIndex ? 'is-selected' : ''}"
@mousedown=${(e) => this._handleItemMouseDown(e, index)}
@click=${(e) => this._handleItemClick(e, 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>`}
</div>
`;
}
static get properties() {
return {
items: { type: Array },
selectedIndex: { type: Number },
getUsersCallback: { type: Function, state: true },
};
}
constructor() {
super();
this.items = [];
this.selectedIndex = 0;
this.label = 'User Mention';
this.getUsersCallback = null;
}
connectedCallback() {
super.connectedCallback();
this.addEventListener('mousedown', this._handleContainerMouseDown);
}
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('mousedown', this._handleContainerMouseDown);
}
_getFilteredUsers(query) {
// Use callback if provided, otherwise fall back to sample users
if (this.getUsersCallback && typeof this.getUsersCallback === 'function') {
return this.getUsersCallback(query);
}
// Fallback to sample users with default filtering
return sampleUsers.filter(user =>
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.username.toLowerCase().includes(query.toLowerCase())
).slice(0, 10);
}
getSuggestionConfig() {
@@ -92,11 +142,21 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
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);
return this._getFilteredUsers(query);
},
renderHTML({ options, node, suggestion }) {
return [
'span',
{
class: 'user-mention',
'data-type': 'mention',
'data-id': node.attrs.id,
'data-label': node.attrs.label,
'data-mention-suggestion-char': '@',
contenteditable: 'false'
},
node.attrs.label || node.attrs.id
];
},
render: () => {
let component;
@@ -193,27 +253,6 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
// 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');
@@ -233,18 +272,37 @@ export class TpRtbUserMention extends TpRtbBaseExtension {
}
}
_handleItemClick(event, index) {
event.stopPropagation();
event.preventDefault();
this._selectItem(index);
}
_handleItemMouseDown(event, index) {
// Prevent blur event from firing on the editor
event.preventDefault();
}
_handleContainerMouseDown(event) {
// Prevent blur event from firing when clicking anywhere in the suggestion popup
// This includes scrollbars, empty areas, etc.
event.preventDefault();
}
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') {
event.preventDefault();
event.stopPropagation();
this._selectItem(this.selectedIndex);
return true;
}