463 lines
18 KiB
JavaScript
463 lines
18 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 emojis = [
|
|
{ emoji: '😀', name: 'grinning face' },
|
|
{ emoji: '😃', name: 'grinning face with big eyes' },
|
|
{ emoji: '😄', name: 'grinning face with smiling eyes' },
|
|
{ emoji: '😁', name: 'beaming face with smiling eyes' },
|
|
{ emoji: '😆', name: 'grinning squinting face' },
|
|
{ emoji: '😅', name: 'grinning face with sweat' },
|
|
{ emoji: '🤣', name: 'rolling on the floor laughing' },
|
|
{ emoji: '😂', name: 'face with tears of joy' },
|
|
{ emoji: '🙂', name: 'slightly smiling face' },
|
|
{ emoji: '🙃', name: 'upside-down face' },
|
|
{ emoji: '😉', name: 'winking face' },
|
|
{ emoji: '😊', name: 'smiling face with smiling eyes' },
|
|
{ emoji: '😇', name: 'smiling face with halo' },
|
|
{ emoji: '🥰', name: 'smiling face with hearts' },
|
|
{ emoji: '😍', name: 'smiling face with heart-eyes' },
|
|
{ emoji: '🤩', name: 'star-struck' },
|
|
{ emoji: '😘', name: 'face blowing a kiss' },
|
|
{ emoji: '😗', name: 'kissing face' },
|
|
{ emoji: '😚', name: 'kissing face with closed eyes' },
|
|
{ emoji: '😙', name: 'kissing face with smiling eyes' },
|
|
{ emoji: '😋', name: 'savoring food' },
|
|
{ emoji: '😛', name: 'face with tongue' },
|
|
{ emoji: '😜', name: 'winking face with tongue' },
|
|
{ emoji: '🤪', name: 'zany face' },
|
|
{ emoji: '😝', name: 'squinting face with tongue' },
|
|
{ emoji: '🤑', name: 'money-mouth face' },
|
|
{ emoji: '🤗', name: 'hugging face' },
|
|
{ emoji: '🤭', name: 'hand over mouth' },
|
|
{ emoji: '🤫', name: 'shushing face' },
|
|
{ emoji: '🤔', name: 'thinking face' },
|
|
{ emoji: '🤐', name: 'zipper-mouth face' },
|
|
{ emoji: '🤨', name: 'face with raised eyebrow' },
|
|
{ emoji: '😐', name: 'neutral face' },
|
|
{ emoji: '😑', name: 'expressionless face' },
|
|
{ emoji: '😶', name: 'face without mouth' },
|
|
{ emoji: '😏', name: 'smirking face' },
|
|
{ emoji: '😒', name: 'unamused face' },
|
|
{ emoji: '🙄', name: 'face with rolling eyes' },
|
|
{ emoji: '😬', name: 'grimacing face' },
|
|
{ emoji: '🤥', name: 'lying face' },
|
|
{ emoji: '😌', name: 'relieved face' },
|
|
{ emoji: '😔', name: 'pensive face' },
|
|
{ emoji: '😪', name: 'sleepy face' },
|
|
{ emoji: '🤤', name: 'drooling face' },
|
|
{ emoji: '😴', name: 'sleeping face' },
|
|
{ emoji: '😷', name: 'face with medical mask' },
|
|
{ emoji: '🤒', name: 'face with thermometer' },
|
|
{ emoji: '🤕', name: 'face with head-bandage' },
|
|
{ emoji: '🤢', name: 'nauseated face' },
|
|
{ emoji: '🤮', name: 'face vomiting' },
|
|
{ emoji: '🤧', name: 'sneezing face' },
|
|
{ emoji: '🥵', name: 'hot face' },
|
|
{ emoji: '🥶', name: 'cold face' },
|
|
{ emoji: '🥴', name: 'woozy face' },
|
|
{ emoji: '😵', name: 'dizzy face' },
|
|
{ emoji: '🤯', name: 'exploding head' },
|
|
{ emoji: '🤠', name: 'cowboy hat face' },
|
|
{ emoji: '🥳', name: 'partying face' },
|
|
{ emoji: '😎', name: 'smiling face with sunglasses' },
|
|
{ emoji: '🤓', name: 'nerd face' },
|
|
{ emoji: '🧐', name: 'face with monocle' },
|
|
{ emoji: '😕', name: 'confused face' },
|
|
{ emoji: '😟', name: 'worried face' },
|
|
{ emoji: '🙁', name: 'slightly frowning face' },
|
|
{ emoji: '😮', name: 'face with open mouth' },
|
|
{ emoji: '😯', name: 'hushed face' },
|
|
{ emoji: '😲', name: 'astonished face' },
|
|
{ emoji: '😳', name: 'flushed face' },
|
|
{ emoji: '🥺', name: 'pleading face' },
|
|
{ emoji: '😦', name: 'frowning face with open mouth' },
|
|
{ emoji: '😧', name: 'anguished face' },
|
|
{ emoji: '😨', name: 'fearful face' },
|
|
{ emoji: '😰', name: 'anxious face with sweat' },
|
|
{ emoji: '😥', name: 'sad but relieved face' },
|
|
{ emoji: '😢', name: 'crying face' },
|
|
{ emoji: '😭', name: 'loudly crying face' },
|
|
{ emoji: '😱', name: 'face screaming in fear' },
|
|
{ emoji: '😖', name: 'confounded face' },
|
|
{ emoji: '😣', name: 'persevering face' },
|
|
{ emoji: '😩', name: 'weary face' },
|
|
{ emoji: '😫', name: 'tired face' },
|
|
{ emoji: '😤', name: 'face with steam from nose' },
|
|
{ emoji: '😡', name: 'pouting face' },
|
|
{ emoji: '😠', name: 'angry face' },
|
|
{ emoji: '🤬', name: 'face with symbols on mouth' },
|
|
{ emoji: '😈', name: 'smiling face with horns' },
|
|
{ emoji: '👿', name: 'angry face with horns' },
|
|
{ emoji: '💀', name: 'skull' },
|
|
{ emoji: '☠️', name: 'skull and crossbones' },
|
|
{ emoji: '💩', name: 'pile of poo' },
|
|
{ emoji: '🤡', name: 'clown face' },
|
|
{ emoji: '👹', name: 'ogre' },
|
|
{ emoji: '👺', name: 'goblin' },
|
|
{ emoji: '👻', name: 'ghost' },
|
|
{ emoji: '👽', name: 'alien' },
|
|
{ emoji: '👾', name: 'alien monster' },
|
|
{ emoji: '🤖', name: 'robot face' },
|
|
{ emoji: '🎃', name: 'jack-o-lantern' },
|
|
{ emoji: '😺', name: 'grinning cat' },
|
|
{ emoji: '😸', name: 'grinning cat with smiling eyes' },
|
|
{ emoji: '😹', name: 'cat with tears of joy' },
|
|
{ emoji: '😻', name: 'smiling cat with heart-eyes' },
|
|
{ emoji: '😼', name: 'wry cat' },
|
|
{ emoji: '😽', name: 'kissing cat' },
|
|
{ emoji: '🙀', name: 'weary cat' },
|
|
{ emoji: '😿', name: 'crying cat' },
|
|
{ emoji: '😾', name: 'pouting cat' },
|
|
{ emoji: '👋', name: 'waving hand' },
|
|
{ emoji: '🤚', name: 'raised back of hand' },
|
|
{ emoji: '🖐️', name: 'hand with fingers splayed' },
|
|
{ emoji: '✋', name: 'raised hand' },
|
|
{ emoji: '🖖', name: 'vulcan salute' },
|
|
{ emoji: '👌', name: 'ok hand' },
|
|
{ emoji: '🤏', name: 'pinching hand' },
|
|
{ emoji: '✌️', name: 'victory hand' },
|
|
{ emoji: '🤞', name: 'crossed fingers' },
|
|
{ emoji: '🤟', name: 'love-you gesture' },
|
|
{ emoji: '🤘', name: 'sign of the horns' },
|
|
{ emoji: '🤙', name: 'call me hand' },
|
|
{ emoji: '👈', name: 'backhand index pointing left' },
|
|
{ emoji: '👉', name: 'backhand index pointing right' },
|
|
{ emoji: '👆', name: 'backhand index pointing up' },
|
|
{ emoji: '🖕', name: 'middle finger' },
|
|
{ emoji: '👇', name: 'backhand index pointing down' },
|
|
{ emoji: '☝️', name: 'index pointing up' },
|
|
{ emoji: '👍', name: 'thumbs up' },
|
|
{ emoji: '👎', name: 'thumbs down' },
|
|
{ emoji: '✊', name: 'raised fist' },
|
|
{ emoji: '👊', name: 'oncoming fist' },
|
|
{ emoji: '🤛', name: 'left-facing fist' },
|
|
{ emoji: '🤜', name: 'right-facing fist' },
|
|
{ emoji: '👏', name: 'clapping hands' },
|
|
{ emoji: '🙌', name: 'raising hands' },
|
|
{ emoji: '👐', name: 'open hands' },
|
|
{ emoji: '🤲', name: 'palms up together' },
|
|
{ emoji: '🤝', name: 'handshake' },
|
|
{ emoji: '🙏', name: 'folded hands' },
|
|
{ emoji: '✍️', name: 'writing hand' },
|
|
{ emoji: '💅', name: 'nail polish' },
|
|
{ emoji: '🤳', name: 'selfie' },
|
|
{ emoji: '💪', name: 'flexed biceps' },
|
|
{ emoji: '🦵', name: 'leg' },
|
|
{ emoji: '🦶', name: 'foot' },
|
|
{ emoji: '👂', name: 'ear' },
|
|
{ emoji: '👃', name: 'nose' },
|
|
{ emoji: '🧠', name: 'brain' },
|
|
{ emoji: '🦷', name: 'tooth' },
|
|
{ emoji: '🦴', name: 'bone' },
|
|
{ emoji: '👀', name: 'eyes' },
|
|
{ emoji: '👁️', name: 'eye' },
|
|
{ emoji: '👅', name: 'tongue' },
|
|
{ emoji: '👄', name: 'mouth' },
|
|
{ emoji: '👶', name: 'baby' },
|
|
{ emoji: '🧒', name: 'child' },
|
|
{ emoji: '👦', name: 'boy' },
|
|
{ emoji: '👧', name: 'girl' },
|
|
{ emoji: '🧑', name: 'person' },
|
|
{ emoji: '👨', name: 'man' },
|
|
{ emoji: '👩', name: 'woman' },
|
|
{ emoji: '🧓', name: 'older person' },
|
|
{ emoji: '👴', name: 'old man' },
|
|
{ emoji: '👵', name: 'old woman' },
|
|
{ emoji: '👨⚕️', name: 'man health worker' },
|
|
{ emoji: '👩⚕️', name: 'woman health worker' },
|
|
{ emoji: '👨🎓', name: 'man student' },
|
|
{ emoji: '👩🎓', name: 'woman student' },
|
|
{ emoji: '👨🏫', name: 'man teacher' },
|
|
{ emoji: '👩🏫', name: 'woman teacher' },
|
|
{ emoji: '👨⚖️', name: 'man judge' },
|
|
{ emoji: '👩⚖️', name: 'woman judge' },
|
|
{ emoji: '👨🌾', name: 'man farmer' },
|
|
{ emoji: '👩🌾', name: 'woman farmer' },
|
|
{ emoji: '👨🍳', name: 'man cook' },
|
|
{ emoji: '👩🍳', name: 'woman cook' },
|
|
{ emoji: '👨🔧', name: 'man mechanic' },
|
|
{ emoji: '👩🔧', name: 'woman mechanic' },
|
|
{ emoji: '👨🏭', name: 'man factory worker' },
|
|
{ emoji: '👩🏭', name: 'woman factory worker' },
|
|
{ emoji: '👨 사무실', name: 'man office worker' },
|
|
{ emoji: '👩 사무실', name: 'woman office worker' },
|
|
{ emoji: '👨🔬', name: 'man scientist' },
|
|
{ emoji: '👩🔬', name: 'woman scientist' },
|
|
{ emoji: '👨💻', name: 'man technologist' },
|
|
{ emoji: '👩💻', name: 'woman technologist' },
|
|
{ emoji: '👨🎤', name: 'man singer' },
|
|
{ emoji: '👩🎤', name: 'woman singer' },
|
|
{ emoji: '👨🎨', name: 'man artist' },
|
|
{ emoji: '👩🎨', name: 'woman artist' },
|
|
{ emoji: '👨✈️', name: 'man pilot' },
|
|
{ emoji: '👩✈️', name: 'woman pilot' },
|
|
{ emoji: '👨🚀', name: 'man astronaut' },
|
|
{ emoji: '👩🚀', name: 'woman astronaut' },
|
|
{ emoji: '👨🚒', name: 'man firefighter' },
|
|
{ emoji: '👩🚒', name: 'woman firefighter' },
|
|
{ emoji: '👮', name: 'police officer' },
|
|
{ emoji: '👮♂️', name: 'man police officer' },
|
|
{ emoji: '👮♀️', name: 'woman police officer' },
|
|
{ emoji: '🕵️', name: 'detective' },
|
|
{ emoji: '🕵️♂️', name: 'man detective' },
|
|
{ emoji: '🕵️♀️', name: 'woman detective' },
|
|
{ emoji: '💂', name: 'guard' },
|
|
{ emoji: '💂♂️', name: 'man guard' },
|
|
{ emoji: '💂♀️', name: 'woman guard' },
|
|
{ emoji: '👷', name: 'construction worker' },
|
|
{ emoji: '👷♂️', name: 'man construction worker' },
|
|
{ emoji: '👷♀️', name: 'woman construction worker' },
|
|
{ emoji: '🤴', name: 'prince' },
|
|
{ emoji: '👸', name: 'princess' },
|
|
{ emoji: '👳', name: 'person wearing turban' },
|
|
{ emoji: '👳♂️', name: 'man wearing turban' },
|
|
{ emoji: '👳♀️', name: 'woman wearing turban' },
|
|
{ emoji: '👲', name: 'man with skullcap' },
|
|
{ emoji: '🧕', name: 'woman with headscarf' },
|
|
{ emoji: '🤵', name: 'man in tuxedo' },
|
|
{ emoji: '👰', name: 'bride with veil' },
|
|
{ emoji: '🤰', name: 'pregnant woman' },
|
|
{ emoji: '🤱', name: 'breast-feeding' },
|
|
{ emoji: '👼', name: 'baby angel' },
|
|
{ emoji: '🎅', name: 'santa claus' },
|
|
{ emoji: '🤶', name: 'mrs. claus' },
|
|
{ emoji: '🦸', name: 'superhero' },
|
|
{ emoji: '🦸♂️', name: 'man superhero' },
|
|
{ emoji: '🦸♀️', name: 'woman superhero' },
|
|
{ emoji: '🦹', name: 'supervillain' },
|
|
{ emoji: '🦹♂️', name: 'man supervillain' },
|
|
{ emoji: '🦹♀️', name: 'woman supervillain' },
|
|
{ emoji: '🧙', name: 'mage' },
|
|
{ emoji: '🧙♂️', name: 'man mage' },
|
|
{ emoji: '🧙♀️', name: 'woman mage' },
|
|
{ emoji: '🧚', name: 'fairy' },
|
|
{ emoji: '🧚♂️', name: 'man fairy' },
|
|
{ emoji: '🧚♀️', name: 'woman fairy' },
|
|
{ emoji: '🧛', name: 'vampire' },
|
|
{ emoji: '🧛♂️', name: 'man vampire' },
|
|
{ emoji: '🧛♀️', name: 'woman vampire' },
|
|
{ emoji: '🧜', name: 'merperson' },
|
|
{ emoji: '🧜♂️', name: 'merman' },
|
|
{ emoji: '🧜♀️', name: 'mermaid' },
|
|
{ emoji: '🧝', name: 'elf' },
|
|
{ emoji: '🧝♂️', name: 'man elf' },
|
|
{ emoji: '🧝♀️', name: 'woman elf' },
|
|
{ emoji: '🧞', name: 'genie' },
|
|
{ emoji: '🧞♂️', name: 'man genie' },
|
|
{ emoji: '🧞♀️', name: 'woman genie' },
|
|
{ emoji: '🧟', name: 'zombie' },
|
|
{ emoji: '🧟♂️', name: 'man zombie' },
|
|
{ emoji: '🧟♀️', name: 'woman zombie' },
|
|
];
|
|
|
|
export class TpRtbEmojiSuggestion extends TpRtbBaseExtension {
|
|
static getEmojis() {
|
|
return emojis;
|
|
}
|
|
|
|
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;
|
|
}
|
|
.item {
|
|
padding: 4px 8px;
|
|
cursor: pointer;
|
|
}
|
|
.item.is-selected {
|
|
background-color: #eee;
|
|
}
|
|
`
|
|
];
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.items = [];
|
|
this.selectedIndex = 0;
|
|
this.label = 'Emoji';
|
|
}
|
|
|
|
getSuggestionConfig() {
|
|
return {
|
|
char: ':',
|
|
allowSpaces: false,
|
|
startOfLine: false,
|
|
items: ({ query }) => {
|
|
console.log('Emoji query:', query);
|
|
return TpRtbEmojiSuggestion.getEmojis().filter(item => item.name.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10);
|
|
},
|
|
render: () => {
|
|
let component;
|
|
let parentEditor;
|
|
let selectItemHandler;
|
|
|
|
return {
|
|
onStart: props => {
|
|
console.log('Emoji suggestion 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 emoji suggestion');
|
|
return;
|
|
}
|
|
|
|
// Find specifically the emoji suggestion component
|
|
component = parentEditor.querySelector('tp-rtb-emoji-suggestion[slot="suggestion-content"]');
|
|
|
|
if (!component) {
|
|
console.error('Emoji suggestion 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 emoji type
|
|
if (parentEditor.showSuggestionMenu) {
|
|
parentEditor.showSuggestionMenu(props.clientRect, 'emoji');
|
|
} else {
|
|
console.error('showSuggestionMenu method not available');
|
|
}
|
|
|
|
// Store the handler to remove it later
|
|
selectItemHandler = (event) => {
|
|
const emoji = event.detail;
|
|
console.log('Emoji selected:', emoji);
|
|
|
|
// Call command directly like in TipTap Vue example
|
|
if (component._command) {
|
|
component._command({
|
|
id: `emoji-${Date.now()}-${Math.random()}`,
|
|
label: emoji.emoji
|
|
});
|
|
}
|
|
};
|
|
|
|
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 emoji type
|
|
if (parentEditor.showSuggestionMenu) {
|
|
parentEditor.showSuggestionMenu(props.clientRect, 'emoji');
|
|
}
|
|
},
|
|
onKeyDown: (props) => {
|
|
if (!component) return false;
|
|
return component.onKeyDown(props);
|
|
},
|
|
onExit: () => {
|
|
console.log('Emoji suggestion 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() {
|
|
// Emoji suggestions 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)}"
|
|
>
|
|
${item.emoji} ${item.name}
|
|
</div>
|
|
`
|
|
)
|
|
: html`<div class="item">No result</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-emoji-suggestion', TpRtbEmojiSuggestion);
|