external-completion-for-personal
既知の問題
/icons/done.icontabを押すと、標準の入力補完windowではなく、こちらのwindowにfocusが移ってしまう
キーバインドを変えたほうが良さそう
_keydownEventHandlerをoverrideすることにした
Ctrl+Spaceで補完windowにfocusを移す
もしくは、別の手段を使う?
導入方法
code:import.js
import {ExternalCompletionForPersonal}
from '/api/code/takker/external-completion-for-personal/script.js';
const externalCompletionForPersonal = new ExternalCompletionForPersonal({
Optionの説明
projects: ここに列挙したprojectsのページを入力候補とする。
必須変数
includeYourProject: このUserScriptを使用するprojectのpageも入力候補に加えるかどうか
default: false
falseの場合、projectsに入っている該当projectも除外する
maxSuggestionNum: 一度に表示する入力候補の最大数
default: 30
この値に入力候補が入り切らなかった場合は、スクロール表示に切り替わる
default: calc(50vh - 100px)
code:import.js
// includeYourProject: false,
// maxSuggestionNum: 30,
// maxHeight: calc(50vh - 100px)
});
externalCompletionForPersonal.start();
以下、本体のscript
/icons/hr.icon
以下をimportする
code:script.js
import {ICompletion} from '/api/code/takker/ICompletion/script.js';
メイン関数
code: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 = [];
}
code:script.js
//external-completionを起動する
async start() {
code:script.js
const shuffle = array => {
let result = array;
for (let i = result.length; 1 < i; i--) {
const k = Math.floor(Math.random() * i);
}
return result;
}
super.start().then(_ => this.links = shuffle(this.links));
}
awaitで待たないようにしたので、ページの読み込み途中でも補完を使用できる
ページ読み込み抜けが出ている?
Promise.addで全部読み込みできるまで補完を待つことにする
.filter(page => page.image !== null)を消すのを忘れていた
code: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();
})
} while(followingId);
//console.log(Finish loading links from ${projectName}.);
//重複を取り除く
}
キーを押したときに発火する関数
code: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;
}
}
入力候補を更新する関数
code: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({
onComfirm: () => this._comfirm(cursor, [${link}]),
});
});
}
code:script.js
_postMessage(word) {
this.searchWorker.postMessage({word: word, list: this.links, maxSuggestionNum: this.maxSuggestionNum});
}
}