generated at
external-completion-for-personal
external-completionを普通のブラケティングで発動するようにしたversion

既知の問題
donetabを押すと、標準の入力補完windowではなく、こちらのwindowにfocusが移ってしまう
キーバインドを変えたほうが良さそう
_keydownEventHandler をoverrideすることにした
Ctrl+Space で補完windowにfocusを移す
もしくは、別の手段を使う?

導入方法
impot.jsをコピーし、自分のページのscript.jsに貼り付ける
import.js
import {ExternalCompletionForPersonal} from '/api/code/takker/external-completion-for-personal/script.js'; const externalCompletionForPersonal = new ExternalCompletionForPersonal({ projects: ['takker', 'takker-private']
Optionの説明
projects : ここに列挙したprojectsのページを入力候補とする。
必須変数
includeYourProject : このUserScriptを使用するprojectのpageも入力候補に加えるかどうか
default: false
falseの場合、 projects に入っている該当projectも除外する
maxSuggestionNum : 一度に表示する入力候補の最大数
default: 30
maxHeight : 入力補完windowの最大の高さ
この値に入力候補が入り切らなかった場合は、スクロール表示に切り替わる
default: calc(50vh - 100px)
import.js
// includeYourProject: false, // maxSuggestionNum: 30, // maxHeight: `calc(50vh - 100px)` }); externalCompletionForPersonal.start();

以下、本体のscript
hr

以下をimportする
script.js
import {ICompletion} from '/api/code/takker/ICompletion/script.js';

メイン関数
検索処理は、external-completionのコードを流用する
script.js
export class ExternalCompletionForPersonal extends ICompletion { constructor({projects, includeYourProject = false, maxSuggestionNum = 30, maxHeight = 'calc(50vh - 100px)'} = {}) { super({ id: 'external-completion-for-personal', projects: projects, includeYourProject: includeYourProject, maxSuggestionNum: maxSuggestionNum, maxHeight: maxHeight, regex: /\[[^!"#%&'()\*\+,\-\.\/\{\|\}<>_~\]:](?!.*.icon\])[^\]]*\]/ug, makeRaw: string => string.substr(1, string.length - 2), searchWorkerCode: '/api/code/takker/external-completion/searchWorker.js' }); this.links = []; }


script.js
//external-completionを起動する async start() {

全ページを読み込み終わったら、入力候補をシャッフルして/motoso/Serendipityを上げる
script.js
const shuffle = array => { let result = array; for (let i = result.length; 1 < i; i--) { const k = Math.floor(Math.random() * i); [result[k], result[i - 1]] = [result[i - 1], result[k]]; } return result; } super.start().then(_ => this.links = shuffle(this.links)); }

外部プロジェクトリンクを非同期に読み込む
await で待たないようにしたので、ページの読み込み途中でも補完を使用できる
ページ読み込み抜けが出ている?
Promise.addで全部読み込みできるまで補完を待つことにする
.filter(page => page.image !== null) を消すのを忘れていた
emoji-completionでは必要だが、本scriptでは不要

script.js
_importDataList() { return this.projects.map(project => this._fetchAllLinks(project) .then(links => { links.forEach(link => this.links.push(`/${project}/${link}`)); return links.length; }) .then(length => console.log(`Loaded ${length} links from /${project}`)) ); } async _fetchAllLinks(projectName) { // 自分のprojectの場合は、global objectから取得する if(scrapbox.Project.name === projectName) { return scrapbox.Project.pages.map(page => page.title); } let followingId = null; let result =[]; //console.log(`Start loading links from ${projectName}...`); do { //console.log(`Loading links from ${projectName}: followingId = ${followingId}`); await (!followingId ? fetch(`/api/pages/${projectName}/search/titles`) : fetch(`/api/pages/${projectName}/search/titles?followingId=${followingId}`)) .then(res => { followingId = res.headers.get('X-Following-Id'); return res.json(); }) .then(json => json.flatMap(page => [...page.links, page.title])) .then(links => result = [...result, ...links]); } while(followingId); //console.log(`Finish loading links from ${projectName}.`); //重複を取り除く return [...new Set(result)]; }

キーを押したときに発火する関数
リンク入力補完 (scrapbox)との兼ね合いで、key bindを変更する
script.js
_keydownEventHandler(e) { if (!e.key) return; // 入力補完windowが表示されていないときは何もしない if(!this.window.isOpen()) return; // 候補がなかったらwindowを閉じる if(this.window.length == 0) { this.window.close(); return; } // focusをwindowに移す // Ctrl+Shift if (e.key === ' ' && e.ctrlKey) { e.stopPropagation(); e.preventDefault(); this.window.selectFirstItem(); return; } // 文字入力の場合はfocusをtext-inputに戻す if(e.key.match(/^.$/)) { if(this.window.hasFocus()){ document.getElementById('text-input').focus(); } return; } //以下、windowにfocusがあるときのみ有効 if (!this.window.hasFocus()) return; // 次候補選択 if ((e.key == 'Tab' && e.shiftKey) || e.key == 'ArrowUp') { e.stopPropagation(); e.preventDefault(); this.window.selectPreviousItem({wrap: true}); return; } // 前候補選択 if (e.key == 'Tab' || e.key == 'ArrowDown') { e.stopPropagation(); e.preventDefault(); this.window.selectNextItem({wrap: true}); return; } // 選択項目で確定する if(e.key == 'Enter') { e.stopPropagation(); e.preventDefault(); this.window.focusedItem.click(); return; } }

入力候補を更新する関数
WebWorkerの処理が終わるたびに実行される
script.js
//補完windowに表示する項目を作成する _createGuiList(matchedList) { const cursor = document.getElementById('text-input'); return matchedList .map(link => { const div = document.createElement('div'); div.textContent = link; return new Object({ elements: [div], onComfirm: () => this._comfirm(cursor, `[${link}]`), }); }); }

WebWorkerに検索を依頼する
script.js
_postMessage(word) { this.searchWorker.postMessage({word: word, list: this.links, maxSuggestionNum: this.maxSuggestionNum}); } }