/// /// /// import { Asearch } from "../deno-asearch/mod.ts"; import { editor, textInput, insertText, press, caret, getInternalLink, takeCursor, takeSelection, } from "../scrapbox-userscript-std/dom.ts"; import { loadEmojis } from "./load.ts"; import { makeBox } from "./ui.ts"; import { Candidate } from "./types.ts"; import { getMaxDistance } from "./distance.ts"; const projectName = scrapbox.Project.name; const emojis: Candidate[] = await loadEmojis(projectName); scrapbox.PageMenu.addMenu({ title: "emoji", image: "https://gyazo.com/d57fea8a143650375af1c8bba1fc1370/raw", }); scrapbox.PageMenu("emoji").addItem({ title: "load emojis from /emoji", onClick: async () => { for (const emoji of await loadEmojis("emoji")) { if(emojis.some((e) => emoji.name === e.name)) continue; emojis.push(emoji); } }, }); const detectLink = () => { const { line, char } = caret().position; const link = getInternalLink(line, char); if (!link) return undefined; const text = /^\[[^\]]+\]$/.test(link.textContent) ? link.textContent.replace(/^\[([^\]]+)\]$/, "$1").trim() : link.textContent.trim(); const start = getIndex(getChars(link).next().value); return text.startsWith(":") ? { text: text.slice(1), raw: `[${text}]`, pos: { line, char: start, } : undefined; }; let completing = false; const callback = () => { const text = detectLink()?.text; if (text !== undefined) return; handleEnd(); }; const handleStart = () => { if (completing) return; completing = true; const cursor = takeCursor(); cursor.addChangeListener(callback); }; const handleEnd = () => { if (!completing) return; completing = false; const cursor = takeCursor(); cursor.removeChangeListener(callback); close(); }; textInput!.addEventListener("input", (e) => { if (e.isComposing) return; const text = detectLink()?.text; if (text === undefined) { handleEnd(); return; } const { match } = Asearch(` ${text} `); const compare = new Intl.Collator().compare; setItems(emojis .flatMap((emoji) => { const result = match(emoji.name, getMaxDistance[text.length]); if (!result.found) return []; return [{ distance: result.distance, onClick: () => { const link = detectLink(); if (link === undefined) { handleEnd(); return; } const selection = takeSelection(); selection.setSelection({ start: { line: link.pos.line, char: link.pos.char, }, end: { line: link.pos.line, char: link.pos.char + link.raw.length -1, }, }); await insertText(); }, ...emoji }]; }) .sort((a, b) => a.distance === b.distance ? compare(a.name, b.name) : a.distance - b.distance) ); open(); }); editor.keydown( e => { const key = e.key; const cursor = $('#text-input')[0]; if( key.match(/^[\w\s\-\:\+]$/) ){ stack += e.key; let focused = $(':focus'); if(focused.is(items.find('li > a'))){ cursor.focus(); } } switch(key){ case 'Backspace': stack = stack.slice(0, stack.length - 1); if(stack.length === 0){ close(); return; } break; case 'ArrowUp': let focusedUp = $(':focus'); if( focusedUp.is(items.find('li > a').eq(0)) ){ e.stopPropagation(); cursor.focus(); }else if( !focusedUp.is(items.find('li > a')) ){ close(); return; } break; case 'ArrowDown': let focusedDown = $(':focus'); if( !focusedDown.is(items.find('li > a'))) { e.stopPropagation(); e.preventDefault(); items.find("li > a").eq(0).focus(); } break; case 'Escape': case 'ArrowLeft': case 'ArrowRight': case 'Home': case 'End': case 'PageUp': case 'PageDown': close(); break; case 'Enter': if( stack.length === 1 ){ close(); break; } let focused = $(':focus'); if(!focused.is(items.find('li > a'))){ e.stopPropagation(); e.preventDefault(); items.find('li > a').eq(0).click(); } break; } if( stack.length <= 1 || !key.match(/^[\w\s\:\-\+]$|Backspace/)) return; const matchedEmoji = fizzSearch(stack, emojis) if( matchedEmoji.length === 0){ close(); return; } const newItems = $('
    ').addClass('dropdown-menu'); matchedEmoji.forEach( ( emoji, index) => { if( index > 30 ) return; newItems.append(makeItem(emoji.name, emoji.src)); a.on('click', () => { cursor.focus(); replaceText(stack, cursor, emoji.path); }) a.on('keypress', ev => { if(ev.key === "Enter"){ ev.preventDefault(); ev.stopPropagation(); replaceText(stack, cursor, emoji.path); } }) }) items.replaceWith(newItems); items = newItems; let css = {}; cursor.style.cssText.split(';').filter( text => text !== '' ) .forEach( text => { const props = text.split(':').map( text => text.replace(' ', '').replace('px', '')); css[props[0]] = props[1]; }); box.css({ top: `${parseInt(css.top) + parseInt(css.height) + 3}px`, left: `${css.left}px`, }); })