generated at
補完windowのonClickテスト
scrapbox-suggest-containerで候補確定処理のテストをしますtakker
コードは補完windowの選択テストをほぼそのまま使う
やること
テキスト入力
テキストを挿入するUserScript onClick に埋め込めばいい
別のactionを登録可能にする
Ctrl +クリック/ Enter で新しいタブでページを開く
<C-i> でアイコン記法を入力する

見つかったバグ
done特になし

js
(async () => { const projectName = 'programming-notes'; const pageTitle = '補完windowのonClickテスト'; const promises = [ import(`/api/code/${projectName}/scrapbox-suggest-container/test-dom.js`), //import(`/api/code/${projectName}/scrapbox-suggest-container/test-dark-theme.js`), import(`/api/code/${projectName}/scrapbox-suggest-container/script.js`), ]; const promise = import(`/api/code/${projectName}/${pageTitle}/test1-project-list.js`); const worker = new Worker(`/api/code/${projectName}/${pageTitle}/test1-worker.js`); worker.postMessage({type: 'fetch', projects: (await promise).projects}); const [{editor, cursor},] = await Promise.all(promises); // 入力補完windowを作る const suggestBox = document.createElement('suggest-container'); editor.append(suggestBox); // とりあえず、cursorに追随するだけにする const observer = new MutationObserver(() =>{ suggestBox.show({ top: parseInt(cursor.style.top) + 14, left: parseInt(cursor.style.left), }); }); observer.observe(cursor, {attributes: true}); // tabキーで選択する editor.addEventListener('keydown', e => { if (this.hidden) return; if (e.key === 'i' && e.ctrlKey) { e.preventDefault(); e.stopPropagation(); suggestBox.selectedItem.click({}, true); return; } if (e.key !== 'Tab') return; if (e.altKey || e.ctrlKey) return; e.preventDefault(); e.stopPropagation(); if (e.shiftKey) { suggestBox.selectPrevious({wrap: true}); } else { suggestBox.selectNext({wrap: true}); } }); // あいまい検索して、候補を入力補完windowに追加する window.search = (word, {limit = 30, timeout = 10000,} = {}) => { // 時間がかかるようであればLoading表示をする const timer = setTimeout(() => { const image = /paper-dark-dark|default-dark/ .test(document.head.parentElement.dataset.projectTheme) ? 'https://img.icons8.com/ios/180/FFFFFF/loading.png' : 'https://img.icons8.com/ios/180/loading.png'; suggestBox.pushFirst({text: 'Loading...', image,}); }, 1000); worker.postMessage({type: 'search', word, limit, timeout}); worker.addEventListener('message', ({data: {links}}) => { clearTimeout(timer); suggestBox.clear(); suggestBox.push(...links.flat().map(link => { return { text: link, link: `https://scrapbox.io${link}`, onClick: (e, icon) => { if (e.ctrlKey) { window.open(`https://scrapbox.io${link}`); return; } const text = icon ? `[${link}.icon]` : `[${link}]`; insertText(text); }, }; })); }, {once: true}); }; function insertText(text) { const cursor = document.getElementById('text-input'); cursor.focus(); cursor.value = text; const uiEvent = document.createEvent('UIEvent'); uiEvent.initEvent('input', true, false); cursor.dispatchEvent(uiEvent); } })();

入力補完に使うscrapbox projects
test1-project-list.js
export const projects = [ 'hub', 'villagepump', ];

worker code
test1-worker.js
const pageTitle = '補完windowのonClickテスト'; self.importScripts('/api/code/programming-notes/WebWorker用asearch/script.js'); // 検索候補 const list = []; self.addEventListener('message', message => { switch (message.data.type) { case 'search': search(message.data); break; case 'fetch': fetch_(message.data.projects); break; } }); async function search({word, limit, timeout}) { //_log(`start searching for ${word}...: limit = ${limit}`); const result = fuzzySearch({ query: word.split('/').join(' '), source: list, limit, timeout, }); //_log('finished: %o', result); self.postMessage({links: result,}); } async function fetch_(projects) { _log('Loading links from %o', projects); const result = (await Promise.all(projects .map(project => fetchExternalLinks(project)) )).flat(); _log(`Finish loading ${result.length} links from %o`, projects); list.push(...result); } async function fetchExternalLinks(project) { let followingId = null; let temp = []; _log(`Start loading links from ${project}...`); do { _log(`Loading links from ${project}: followingId = ${followingId}`); const json = await (!followingId ? fetch(`/api/pages/${project}/search/titles`) : fetch(`/api/pages/${project}/search/titles?followingId=${followingId}`) ).then(res => { followingId = res.headers.get('X-Following-Id'); return res.json(); }); temp.push(...json.flatMap(page => [...page.links, page.title]).map(link => `/${project}/${link}`)); } while(followingId); const links = [...new Set(temp)]; // 重複を取り除く _log(`Loaded ${links.length} links from /${project}`); return links; } // debug用 function _log(msg, ...objects) { console.log(`[search-worker @${pageTitle}/test1-worker.js] ${msg}`, ...objects); }