wheel操作で見出しにジャンプするUserScript
お試し
jsimport('/api/code/programming-notes/wheel操作で見出しにジャンプするUserScript/script.js');
実装したいこと
開閉ボタンをつける
position: fixed
で、brand iconのすぐ下に置きたい
2021-07-16
2021-07-12
12:33:57 ちょっとだけrefactoring
2021-07-07
17:05:12 Page Menuから開閉できるようにした
UIが自然じゃない
ボタンとリストが反対側にある
遠すぎ
画面が小さいときリストが表示されなくなる
16:44:07 これは仕方のないことなのだが、切り替えるたびにweb browser履歴に残ってしまう
たくさんwheelをコロコロすると大量の履歴が発生する
16:43:24
wheelで切り替えられるようにした
16:17:44 とりあえずできた
選択すると太字になるようにした
欄が小さすぎるのが欠点
.expandable-menu
みたく開閉できるようにするか?
dependencies
script.jsimport {html, render} from '../htm@3.0.4%2Fpreact/script.js';
import {useState, useEffect, useCallback} from '../preact@10.5.13/hooks.js';
import {useToggle} from '../use-toggle@0.1.0/script.js';
import {useLinesChange} from '../use-lines-change@0.1.0/script.js';
const App = () => {
const [{id: selectedId, sections}, update] = useSections();
// ページが編集されるたびにリストを更新する
useLinesChange(update, {initialize: true});
// wheel操作で見出しを切り替える
const handleWheel = useCallback(e => {
const index = sections.findIndex(({id}) => id === selectedId);
const {deltaY} = e;
if (index <= 0 && deltaY < 0) return;
if (index === sections.length - 1 && deltaY > 0) return;
e.preventDefault();
e.stopPropagation();
if (deltaY > 0) {
sections[index + 1].jump();
return;
}
if (deltaY < 0) {
sections[index - 1].jump();
return;
}
}, [sections, selectedId]);
// Page Menuで開閉する
const [open, toggleOpen] = useToggle(false);
useEffect(() => scrapbox.PageMenu.addMenu({
title: 'wheel見出し',
image: 'https://gyazo.com/bc38721e0980f2188f1c831754ac8da4/raw',
onClick: () => toggleOpen(),
}), []);
return html`
<style>
:host {
position: sticky;
top: 45px;
z-index: 1001;
}
ul {
margin-right: 10px;
border-radius: 3px;
min-width: 160px;
max-width: 50vw;
overflow-x: hidden;
overflow-y: auto;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
background-color: var(--page-bg, #fefefe);
border: 1px solid var(--dropdown-border-color, rgba(0,0,0,0.15));
border-radius: 4px;
box-shadow: 0 6px 12px var(--dropdown-shadow-color, rgba(0,0,0,0.175));
background-clip: padding-box;
white-space: nowrap;
text-align: left;
}
a {
display: flex;
clear: both;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
color: var(--page-text-color, #4a4a4a);
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
a.selected {
font-weight: bold;
outline: 0;
}
a:hover {
background-color: var(--card-hover-bg, #f5f5f5);
}
</style>
${open && html`<ul onWheel="${handleWheel}">
${sections.map(({id, text, jump}) => id === selectedId ?
html`<li><a class="selected" href="#${id}" onClick="${jump}">${text}</a></li>` :
html`<li><a href="#${id}" onClick="${jump}">${text}</a></li>`
)}
</ul>`}
`
}
見出しを作るcustom hook
script.jsfunction useSections() {
const [sections, setSections] = useState([]);
const [selected, setSelected] = useState('');
const jump = useCallback(id => {
setSelected(id);
location.hash = id;
}, []);
const update = useCallback(() =>
setSections(
scrapbox.Page.lines.flatMap(
({section: {start}, id, text}) => start ? [{
id,
text,
jump: () => jump(id),
}] : []
)
), [jump]);
return [{id: selected, sections}, update];
}
script.jsconst app = document.createElement('div');
app.attachShadow({mode: 'open'});
app.dataset.userscriptName = 'section-jumper';
document.querySelector('.col-page-side')?.append?.(app);
render(html`<${App} />`, app.shadowRoot);