scrapbox-history-slider@0.2.1
https://gyazo.com/5d526a6c3137c2dfd51be9cdd6371a0f
使い方
お試し
code:js
import('/api/code/programming-notes/scrapbox-history-slider@0.2.1/script.js');
bundle用
preactを既に自分のprojectに入れている人向け
code:sh
履歴の日時を付与できるようにした
表示は不完全
UIの配色の調節
実装したいこと
具体的な更新日時を表示したい
<input>にtitle属性をもたせる
脇に更新日時を表示する
sliderに目盛りをつける
1時間前
1日前
1週間前
etc.
読み込みをweb workerに委託する
巨大なページの差分を計算していると時間がかかる
読み込み中表示を詳細に出したい
以下の3項目には最低限分けたい
web workerの用意
頻繁に使う機能ではないので、開くたびに生成・破棄してもperformance上の問題はないだろう
データのfetch
履歴の変換
進捗も出す
行の更新日時ごとに細かく差分を表示できるようにする
2021-07-04
20:21:40 枠のかどを少し丸めて、厚みを増やした
19:43:19 rangeが変更されたときだけsliderの位置をresetする
19:32:19 sliderと履歴の間に区切り線を入れた
空白が空行なのかcomponentの余白なのかがわかりにくかった
19:23:54 枠の色と厚みを変えた
19:19:54 文字色と背景色を直した
19:18:11 行が生成される前の時刻をgetSnapshot()に渡すとエラーになっていた
dependencies
code:script.js
import {html, render} from '../htm@3.0.4%2Fpreact/script.js';
import {useState, useRef, useEffect} from '../preact@10.5.13/hooks.js';
import {useLoader} from '../use-loader/script.js';
import {convert} from '../scrapbox-snapshot@0.1.0/script.js';
const App = () => {
const {loading} = useLoader(async () => {
if (!open) return;
const {range, getSnapshot} = await createSnapshot();
setRange(old => JSON.stringify(old) === JSON.stringify(range) ? old : range);
setSnapshot(() => getSnapshot);
// rangeが変更されたときだけsliderの位置をresetする
useEffect(() => {
setMax(range.length - 1);
setIndex(range.length - 1);
useEffect(() => {
scrapbox.PageMenu.addMenu({
title: '履歴スライダー',
onClick: () => setOpen(true),
});
}, []);
const close = () => setOpen(false);
const onSliderChange = ({target: {value}}) => setIndex(parseInt(value));
return html`
<style>
:host {
}
.background {
position: fixed;
top: 0;
right: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
z-index: 89999;
}
.container {
position: fixed;
top: 5px;
left: 5px;
width: calc(100% - 10px);
max-height: 80vh;
border-radius: 4px;
z-index: 90000;
}
.close-button {
font-size: 30px;
line-height: 1em;
padding: 0;
width: 30px;
height: 30px;
}
pre {
width: 100%;
max-height: 70vh; /* なぜか100%だとはみ出てしまった */
overflow-y: auto;
font-family: var(--history-slider-pre-font, Menlo,Monaco,Consolas,"Courier New",monospace);
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
}
input {
width: 90%;
}
</style>
${open && html`
<div class="background" onClick="${close}"/>
<div class="container">
<div style="display: inline">
<input type="range" max="${max}" min="0" step="1" value="${index}"
title="${new Date(rangeindex * 1000)}" onInput="${onSliderChange}" />
<button class="close-button" onClick="${close}">x</button>
</div>
<hr />
${loading && 'Loading...'}
<pre>${snapshot(rangeindex)}</pre> </div>
`}
`;
};
const app = document.createElement('div');
app.dataset.userscriptName= 'history-slider';
app.attachShadow({mode: 'open'});
document.body.append(app);
render(html<${App} />, app.shadowRoot);
履歴を作成する
code:script.js
async function createSnapshot() {
const getSnapshot = time => {
// 範囲外ならpageHistoryから取得する
if (!commitHistory.range.includes(time)) {
if (!pageHistory.range.includes(time)) return '';
return pageHistory.pagestime.map(line => line.text).join('\n'); }
return commitHistory.history.flatMap(({id, snapshots}) => {
// timeの時の文字列を取得する
const times = Object.keys(snapshots).map(key => parseInt(key));
const key = times.includes(time) ? time : times.filter(key => key <= time).pop();
// timeの時まだ行が生成されていなければundefinedになる
if (key === undefined) return [];
const line = snapshotskey; }).join('\n');
}
return {
getSnapshot,
};
}
async function getPageSnapshots() {
const res = await fetch(/api/page-snapshots/${scrapbox.Project.name}/${scrapbox.Page.id});
const {snapshots} = await res.json();
const pages = Object.fromEntries(snapshots.map(({lines, created}) => created, lines)); const range = Object.keys(pages).map(created => parseInt(created)).sort();
return {range, pages};
}
async function getCommitsHistory() {
const res = await fetch(/api/commits/${scrapbox.Project.name}/${scrapbox.Page.id});
const {commits} = await res.json();
return convert(commits);
}