ポップアップメニューをキーボードで移動するUserScript
https://gyazo.com/280c2a630742f8d7a345dfccd877b128
なにがやりたいのか
文字列を選択したときに表示されるポップアップメニューをキーボードから選択・実行したい
現在、マウスで選択するしか実行方法がない(2021/11/09)
文字列選択中にTabで順送り、Shift + Tabで逆送りに進め、Enterで実行するようにした
Scrapbox標準における文字列選択中のTabの挙動をむりやり上書きした
code:script.js
import '/api/code/gosyujin/mousetrap.min.js/script.js';
function loadMouseEvent() {
let mousetrapOnEdit = new Mousetrap(document.querySelector('#text-input'));
mousetrapOnEdit.stopCallback = (e, el) => false;
window.scrapboxShortcut = {
onEdit: (key, f) => {
mousetrapOnEdit.bind(key, e => { let cancel = f(e) === false; if (cancel) return false; });
}
};
let currentIndex = -1; // なにも指していない時は-1と決める
let totalPopupCount = 0;
let prevTotalPopupCount = 0;
// 入力範囲の変化がありそうな場合、初期化する(どうやって検知するといいのかよくわからない)
scrapboxShortcut.onEdit(
, e => {
currentIndex = -1;
removeStyle();
});
// ポップアップ選択中に'数値'番目のタブを実行する、0は10個飛ばしと見なす
scrapboxShortcut.onEdit('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', e => { if ((isViewSelection() || isViewSelections()) && isShowPopupMenu()) {
let counter;
if (e.key === '0') {
counter = 10;
} else {
counter = Number(e.key);
}
return false;
}
// 満たさなければ何もしない
return true;
});
// ポップアップ表示中に特定キーでポップアップ実行(ポップアップメニューの名前を変えると動かなくなる)
scrapboxShortcut.onEdit('"', '+', '.', ';', '~', '#', e => {
// ポップアップメニューが表示されていなければそのまま実行
if (!isShowPopupMenu()) return true;
const popupMenu = getPopupMenu();
const menuList = [
//{key: '', value: '🔧'},
//{key: '', value: '⌨'},
//{key: '', value: '🍞'},
//{key: '', value: '🔥'},
//{key: ';', value: '🅰️'},
{key: '"', value: '❝❝'},
{key: '>', value: '>'},
{key: '+', value: 'N.'},
{key: '~', value: '〜'},
//{key: '', value: '□'},
{key: '#', value: '#'},
{key: '.', value: '•'}
];
const link = menuList.find((m) => m.key === e.key).value
console.log(link);
Array.from(popupMenu).map(m => {
if (m.innerText.includes(link)) m.click();
});
return false;
});
// ポップアップ表示中にenter
scrapboxShortcut.onEdit('enter', e => {
// 複数行選択かつタブを押してない状態はNew page
// (New pageは複数行選択しないと出現しないため)
if (currentIndex === -1 && isViewSelections()) {
const popupMenu = getPopupMenu();
// 0番目 = New pageをクリック
currentIndex = -1;
return false;
}
// タブを押したあとは選択しているポップアップメニューを選択
if (currentIndex !== -1 && (isViewSelection() || isViewSelections())) {
const popupMenu = getPopupMenu();
// 選択しているポップアップメニューをクリック
currentIndex = -1;
return false;
}
// 全部満たさなければ改行
return true;
});
// ポップアップ表示中tabで右へ移動(表示中は既存のtabの動きを無効)
// ブラケティング補完表示中のみtab有効
// ポップアップメニュー表示時は独自にメニュー移動させる
// それ以外はtabではなく半角スペースを送る
scrapboxShortcut.onEdit('tab', e => {
if (isShowBraketingMenu()) {
// ブラケティング補完表示中のみtabを機能させる(デフォルトのメニュー移動の機能)
return true;
} else if ((isViewSelection() || isViewSelections()) && isShowPopupMenu()) {
shiftForwardPopupMenuCursor(true);
return false;
} else {
// tabを半角スペースに置き換える
document.execCommand('insertText', null, ' ');
return false;
}
});
// ポップアップ表示中shift+tabで左へ移動(表示中は既存のtabの動きを無効)
// tabと違ってポップアップメニュー表示時以外はデフォルト機能
scrapboxShortcut.onEdit('shift+tab', e => {
if ((isViewSelection() || isViewSelections()) && isShowPopupMenu()) {
shiftForwardPopupMenuCursor(false);
return false;
}
return true;
});
// ポップアップメニューのカーソルを一つずらす
// isForwardがtrueなら順方向
const shiftForwardPopupMenuCursor = function (isForward) {
const popupMenu = getPopupMenu();
totalPopupCount = popupMenu.length;
if (!isFitTotalCount()) {
currentIndex = -1;
}
// indexをずらす、端まで到達したらループ
if (isForward) {
currentIndex++;
if (currentIndex > totalPopupCount - 1) currentIndex = 0;
} else {
currentIndex--;
if (currentIndex < 0) currentIndex = totalPopupCount - 1;
}
prevTotalPopupCount = totalPopupCount;
// display: none;なメニューはスキップする
if (document.defaultView.getComputedStyle(popupMenucurrentIndex, null).display === 'none') { shiftForwardPopupMenuCursor(isForward);
}
removeStyle();
}
// 直前の操作時とポップアップメニューの総数が同じかどうか
// ポップアップメニューの総数が変わるとき: 未選択/一行選択/複数行選択?
const isFitTotalCount = function () {
return prevTotalPopupCount === totalPopupCount;
}
}
// カーソルの選択行(状況?)を取得する
// 選択していない: 0、一行選択している: 1、複数行選択しているか: たぶん3?
function getSelectionCount() {
const selectLineCount = document.getElementsByClassName('selection').length;
// 右端折り返すくらい長い一行の場合、3になるが一行として扱いたいので1として返す
if (isShowPopupMenu() && selectLineCount >= 3) {
if (getPopupMenu()0.classList.contains('link-button')) return 1; }
return document.getElementsByClassName('selection').length;
}
// ポップアップメニュー要素を取得
function getPopupMenu() {
return document.querySelector('#editor .selections .popup-menu .button-container').children;
}
// 一行選択されていればtrue
function isViewSelection() {
if (getSelectionCount() == 1) return true;
return false;
}
// 一行以上選択されていればtrue
function isViewSelections() {
if (getSelectionCount() > 1) return true;
return false;
}
// []の補完のポップアップメニューが表示されているかどうかの確認
function isShowBraketingMenu() {
if (document.querySelector('#editor .lines .line .text .popup-menu') === null) return false;
return true;
}
// ポップアップメニューが表示されているかどうかの確認
function isShowPopupMenu() {
if (document.querySelector('#editor .selections .popup-menu') === null) return false;
// selectionsが定義されていて、ポップアップメニューが表示されており、
// display: none;にしているものを除いたメニューの総数が0じゃない時のみOK
// 全部display: none;になっていてもダメ
if (...getPopupMenu().filter(p => document.defaultView.getComputedStyle(p, null).display !== 'none') .length !== 0) return true;
return false;
}
// ポップアップメニューのスタイルをすべてremoveする
function removeStyle() {
if (!isShowPopupMenu()) return;
for (const p of getPopupMenu()) { p.removeAttribute('style'); }
}
(() => {
const layoutChanged = () => {
switch(scrapbox.Layout) {
case 'page':
loadMouseEvent();
break;
case 'list':
break;
default:
break;
}
};
loadMouseEvent();
scrapbox.on(layout:changed, layoutChanged);
})();
更新履歴
複数行選択している時にEnterでNew pageを選択したことにする(2020/11/22)
選択後enterとタブ選択時の挙動を変更(2021/11/09)
やっぱりやめた
キーボードの数字でn番目に飛ぶようにした(2022/01/25)
>でむりやり引用(2022/06/30)
>以外もをショートカットで実行できるようにした(2022/08/05)
ショートカットにチルダを追加した(2023/09/23)
ページ遷移のタイミングで効かなくなる時がでるようになった?(2024/04/18)
調べてみないとわからない
いや、効いてるな。デフォルトのTab挙動が復活しているみたい?
ページ遷移するとTabが活きるようになる…
Tabが活きるせいで「Tab」プラス「書いた処理」って処理が重複している
今まではなんでずっと無効化できてたんだろう?
なんかトップページに遷移したタイミングで#text-inputがなくなっているのは関係ある?