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`