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`
${item.emoji} ${item.name}
` ) : html`
No result
`} `; } 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);