external-completion-for-personal
既知の問題
tabを押すと、標準の入力補完windowではなく、こちらのwindowにfocusが移ってしまう キーバインドを変えたほうが良さそう
_keydownEventHandler
をoverrideすることにした
Ctrl+Space
で補完windowにfocusを移す
もしくは、別の手段を使う?
導入方法
import.jsimport {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
この値に入力候補が入り切らなかった場合は、スクロール表示に切り替わる
default: calc(50vh - 100px)
import.js // includeYourProject: false,
// maxSuggestionNum: 30,
// maxHeight: `calc(50vh - 100px)`
});
externalCompletionForPersonal.start();
以下、本体のscript
以下をimportする
script.jsimport {ICompletion} from '/api/code/takker/ICompletion/script.js';
メイン関数
script.jsexport 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() {
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)
を消すのを忘れていた
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)];
}
キーを押したときに発火する関数
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;
}
}
入力候補を更新する関数
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}]`),
});
});
}
script.js _postMessage(word) {
this.searchWorker.postMessage({word: word, list: this.links, maxSuggestionNum: this.maxSuggestionNum});
}
}