Scrapboxで暗記シートを作る
更新履歴
2019/8/26
公開
2019/8/31
[! 文字]
が途中で折り返されているとクリックによる表示・非表示がおかしくなる不具合を修正
----------
デスクトップとモバイルの両方で同じように使えます。
[! ]
で囲まれた文字列を非表示にする
クリックで隠された文字が表示される
初期状態で非表示になっていますので、シートを作成する時は表示に切り替えてから行って下さい
選択されている文字列に [! ]
を付ける
選択範囲に [! ]
が含まれる場合は一括で外す
スクリプトの先頭にある decoChar
が装飾に使用する文字 ( !
) になっていますので、適宜変更して下さい。
装飾に使用できる文字についてはこちら。
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 < arr[minimumIndex] ? currentIndex : minimumIndex;
}, 0);
const firstLineLastCharRect = decoCharRectArray[secondLineFirstCharIndex - 1];
const secondLineFirstCharRect = decoCharRectArray[secondLineFirstCharIndex];
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: '暗記シート',
image: 'https://i.gyazo.com/55485bbf213f80632f7dee725e74a910.png',
/*
アイコンは
https://scrapbox.io/nyaagoo/ScrapBox%E3%81%A7%E6%9A%97%E8%A8%98%E3%82%AB%E3%83%BC%E3%83%89
のものをそのまま使用させていただいています。リンクが無効になっている場合は適当な画像に差し替えて下さい。
*/
onClick: () => hideStyleElm.disabled = !hideStyleElm.disabled
});
// [! ]を付け外しするPopupMenu
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});
}
})();