Scrapboxで暗記シートを作る
更新履歴
2019/8/26
公開
2019/8/31
[! 文字]が途中で折り返されているとクリックによる表示・非表示がおかしくなる不具合を修正
----------
デスクトップとモバイルの両方で同じように使えます。
[! ]で囲まれた文字列を非表示にする
https://gyazo.com/d77b1f943d2af0c28b49629ff632e0c5
クリックで隠された文字が表示される
https://gyazo.com/168a63c6100ec64944cc8bdcddb0bef9
初期状態で非表示になっていますので、シートを作成する時は表示に切り替えてから行って下さい
https://gyazo.com/d0fb8991cefcf4e48f0dce54a5638db0
選択されている文字列に[! ]を付ける
選択範囲に[! ]が含まれる場合は一括で外す
スクリプトの先頭にあるdecoCharが装飾に使用する文字 (!) になっていますので、適宜変更して下さい。
装飾に使用できる文字についてはこちら。
code:js
(() => {
const decoChar = '!';
const displayColor = '#3cb371';
const showCssString = `
.deco-\\${decoChar} {
color: ${displayColor};
border-bottom: 2px solid ${displayColor};
}
`;
const hideCssString = `
.deco-\\${decoChar} {
color: transparent;
border-bottom: 2px solid ${displayColor};
}
`;
const showStyleElm = document.createElement('style');
document.head.appendChild(showStyleElm);
showStyleElm.sheet.insertRule(showCssString);
const hideStyleElm = document.createElement('style');
document.head.appendChild(hideStyleElm);
hideStyleElm.sheet.insertRule(hideCssString);
// カーソルの移動を検出して文字を表示する
const cursorObserver = new MutationObserver(records => {
const decoElmArray = Array.from(document.getElementsByClassName(deco-${decoChar}));
if (!decoElmArray || hideStyleElm.disabled) return;
for (const record of records) {
const cursorRect = record.target.getBoundingClientRect();
let isDecoAtCursorPositionProcessed = false;
for (const decoElm of decoElmArray) {
if (isDecoAtCursorPositionProcessed) {
decoElm.style.color = '';
continue;
}
const decoElmRect = decoElm.getBoundingClientRect();
const openingBracketRect = decoElm.firstElementChild.getBoundingClientRect();
const closingBracketRect = decoElm.lastElementChild.getBoundingClientRect();
const isWordWrapped = closingBracketRect.left < openingBracketRect.left;
if (isWordWrapped) {
// 文字 が途中で折り返されている時
const decoCharRectArray = Array.from(decoElm.children, value => value.getBoundingClientRect());
const secondLineFirstCharIndex = decoCharRectArray.map(rect => {
return rect.left;
}).reduce((minimumIndex, currentValue, currentIndex, arr) => {
return currentValue < arrminimumIndex ? currentIndex : minimumIndex; }, 0);
const isCursorInsideFirstLine = openingBracketRect.left <= cursorRect.left
&& cursorRect.right <= firstLineLastCharRect.right
&& openingBracketRect.top <= cursorRect.top
&& cursorRect.bottom <= openingBracketRect.bottom;
const isCursorInsideSecondLine = secondLineFirstCharRect.left <= cursorRect.left
&& cursorRect.right <= closingBracketRect.right
&& closingBracketRect.top <= cursorRect.top
&& cursorRect.bottom <= closingBracketRect.bottom;
if (isCursorInsideFirstLine || isCursorInsideSecondLine) {
decoElm.style.color = displayColor;
isDecoAtCursorPositionProcessed = true;
} else {
decoElm.style.color = '';
}
} else {
// 文字 が途中で折り返されていない時
const isCursorInside = decoElmRect.left <= cursorRect.left
&& cursorRect.right <= decoElmRect.right
&& decoElmRect.top <= cursorRect.top
&& cursorRect.bottom <= decoElmRect.bottom;
if (isCursorInside) {
decoElm.style.color = displayColor;
isDecoAtCursorPositionProcessed = true;
} else {
decoElm.style.color = '';
}
}
}
}
});
cursorObserver.observe(document.getElementsByClassName('cursor')0, {attributes: true, attributeFilter: 'style'}); // 表示、非表示を切り替えるPageMenu
scrapbox.PageMenu.addMenu({
title: '暗記シート',
/*
アイコンは
のものをそのまま使用させていただいています。リンクが無効になっている場合は適当な画像に差し替えて下さい。
*/
onClick: () => hideStyleElm.disabled = !hideStyleElm.disabled
});
scrapbox.PopupMenu.addButton({
title: text => {
const reg = new RegExp(\\[${decoChar} (.+?)\\], 'g');
return reg.test(text) ? remove [${decoChar}] : add [${decoChar}];
},
onClick: text => {
const reg = new RegExp(\\[${decoChar} (.+?)\\], 'g');
if (reg.test(text)) {
return text.replace(reg, '$1');
} else {
if (!/\n/.test(text)) {
return [${decoChar} ${text}];
}
}
}
});
// 他のプロジェクトに移動したら終了する
const targetProject = scrapbox.Project.name;
observeUrlChange((newUrl, oldUrl, observer) => {
if (scrapbox.Project.name !== targetProject) {
showStyleElm.parentNode.removeChild(showStyleElm);
hideStyleElm.parentNode.removeChild(hideStyleElm);
cursorObserver.disconnect();
observer.disconnect();
}
});
function observeUrlChange(handler) {
let lastUrl = location.href;
const titleObserver = new MutationObserver((records, observer) => {
for (const record of records) {
if (lastUrl !== location.href) {
const newUrl = location.href;
const oldUrl = lastUrl;
lastUrl = location.href;
handler(newUrl, oldUrl, observer);
}
}
});
const title = document.querySelector('head title');
titleObserver.observe(title, {childList: true});
}
})();
UserScript.icon