pin-diary
ひとまず単発で実行できるものは書けた
ただSPAとしてページが書き換わるときにうまく再描画しないと正しく表示されない
pushStateをハンドリングできるイベントがなさそう...
ただ、それだとURLが変わるタイミングであってScrapboxのUIが書き変わるタイミングではないので、結局ページ内を監視することになりますが
対策
F5
を押す
タイトル監視
MutationObserver
で監視
トップページにタイトル行ないから無理か
代わりに <title>
を監視する
よさそう!
試しに実装してみた うまく動いてそう
ソート順を変えると崩れるから、そこも監視して再描画する必要がありそう
# dropdownMenuSortのinnerTextを監視する
ページ遷移時にDOMが消えてしまうので、監視がややこしくなる
監視の開始/終了のタイミングを測るのがややこしい
.page-list.clearfix .gridを監視する
どのページでもDOMが残る
scrapboxが大量にページカードを作るのにいちいち反応してpageがfreezeしてしまう。
一度空の日付ページが作られてしまうと、それがずっとPinされてしまうバグがある
検索結果ページでも同じ list
になってしまうっぽい
<title>
の中身と併せて判定すればいけるかも
これはこれで便利だと思います
検索結果の先頭に日付ページが表示されるようになるのか。面白いな
同じタブで別のprojectに移動すると、UserScriptが残ってしまう 直ってた
URL欄に別のprojectのURLを入力して飛んだとき
2020-10-23 23:39:03 直った
直って無さそう
scrapbox.Project.name
で比較してるから全てのプロジェクトで動いてしまう
villagepump
以外のサイトにいったら作動させないようなflagを導入すればいいかな
const isVillagepump = true
if (location.pathname.slice(1, -1) !== scrapbox.Project.name) return
で直接指定するだけでいいと思う
flagだと対して変わらないかー
意図
なるべく判定処理を減らすようにしたかった
villagepump以外のscrapbox projectで handlePageChange
を実行させないのは最初からわかっている
しかし現状は別projectでもタイトルが変更される度にEvent Handlerを呼び出して判定処理をするようになっている
いらない判定処理なので、最初から判定処理自体をしないようにしたかった
具体的には、villagepump以外のscrapbox projectに移動したタイミングで、 observer
の監視を止めるようにする
しかし改めて考えてみると、そんな事する必要がないことに気づいた
タイトルは頻繁に変わるわけではないので、処理速度に影響を与えることはない
処理速度を向上させたいなら、もっと別の部分をtuningすべき
streamページからトップページへの遷移だとtitleが変わらずに再描画ができてない
titleが更新されたときにtitleの内容ではなくてlocation.hrefを比較するのがよさそう
実装してみた
なぜか期待通りに動かないと思ったらプロジェクト判定の条件式が逆だった
ページ遷移時の再描画が全く実行されてなかった
多分これでちゃんと動くようになった
<title>
以外を使ってページ遷移を監視する方法を探すかあ
雑にやるなら #app-container
を監視すればいいけど、無駄にevent handlerが呼び出されてしまう。
程よい頻度で発行されるeventないかなあ
でも後方互換性を保つのが難しいな
import /api/code/villagepump/pin-diary/user.js
を使っている人に配慮しないといけない
user.jsconst targetProject = 'villagepump';
const handlePageChange = callback => {
callback()
let path = null
const handleObserve = (mutations) => {
// 全部実行する必要はないので、1個だけ取り出す
if (scrapbox.Project.name !== targetProject) return
// URLが変わったときだけ実行する
if (location.pathname !== path) callback()
path = location.pathname
}
const observer1 = new MutationObserver(handleObserve);
observer1.observe(document.querySelector('title'), { childList: true })
}
const $ = (query, parent = document) => parent.querySelector(query)
const $$ = query => [...document.querySelectorAll(query)]
const zero = n => String(n).padStart(2, '0');
const timestamp = date => `${date.getFullYear()}/${zero(date.getMonth() + 1)}/${zero(date.getDate())}`
const today = timestamp(new Date());
const path = `/${targetProject}/${encodeURIComponent(today)}`
handlePageChange(() => {
// トップページ以外では実行しない
if (location.pathname.slice(1, -1) !== targetProject) return
const pinCards = $$('.page-list-item.grid-style-item.pin')
const [actualLastPin, lastPin] = pinCards.slice(pinCards.length - 2)
console.log('lastPin: %o',lastPin)
↑これは何でしょうか?
最後にpinしたページを削除している?
最後にpinしたページを配列から取り出してるだけです
[...document.querySelectorAll(query)]
で新たに生成した配列なのでDOMとは関係ないです
あ、そうでした。これコピーしたことになるんですね
user.js // 日付ページを移動する
const diary = $(`.page-list-item a[href="${path}"]`)?.parentNode
console.log('The diary page card: %o',diary)
top pageに戻ったときにPinがおかしなことになる現象はこれで防げるはず
日付ページよりも更新日時が新しいページがあるときに発生する現象
user.old.js if (lastPin === diary) {
console.log('The diary page card is already pinned.');
return;
}
防げなかった
top pageに戻るたびに、日付ページの位置を直したほうが確実だな
2020-12-05 19:27:12 pinしたページがが一つだけだと誤判定が起こってしまっていた
lastPin===undefined
かつ diary===undefined
だと最後のpinが日付ページだと誤判定してしまう
undefined判定を追加しておいた
user.js // pinの最後が日付ページだったらつけ直す
if (actualLastPin && (lastPin ?? actualLastPin) === diary) {
console.log('The diary page card is already pinned.');
console.log('actualLastPin: %o',actualLastPin)
actualLastPin?.after(lastPin);
console.log('Finish pinning the today\'s diary page.')
return;
}
日付ページがなかったら、新しくカードを作る
user.js if (diary) {
diary.classList.add('pin');
// pinを作る
const pinMark = document.createElement('div');
pinMark.classList.add('pin');
$('.hover', diary).after(pinMark)
(lastPin ?? actualLastPin)?.after(diary)
} else {
以下のコードだと、 lastPin
のサムネイルが画像だった場合にバグる
div.description
ではなく div.icon
になるため
user.old.js console.log('No diary page card found. Creating it....')
const pin = lastPin.cloneNode(true)
pin.style.opacity = 0.7
console.log('The new pinned card: %o',pin)
$('a', pin).href = path
$('.title', pin).textContent = today
$('.description', pin).innerHTML = ''
(lastPin ?? actualLastPin)?.after(pin)
下手にcloneせず、1からDOMを作ったほうが無難かも
user.js console.log('No diary page card found. Creating it....')
lastPin?.insertAdjacentHTML('afterend', `
<li class="page-list-item grid-style-item pin" style="opacity: 0.7;">
<a href="${path}" rel="route">
<div class="hover"></div>
<div class="pin"></div>
<div class="content">
<div class="header">
<div class="title">${today}</div>
</div>
<div class="description"></div>
</div>
</a>
</li>`)
}
console.log('Finish pinning the today\'s diary page.')
});
lastPinが空になる場合は何かおかしい状況なので、そもそも止めたほうがいいかもしれない
一つもPinがない状態とか
別のprojectにも転用することをちょっと想定しました
その場合なら最初に追加しないといけなさそう
これでどうかな?
'
を`に変えようとしたらインラインコードがそこで切れてしまうことに気づいた
\
`のようにエスケープできます
サンクスです!
よさそう
親要素ってどうやってcss selectorで取得するんだろう?
取得できないっぽい
まあ
の
HTMLElement.parentNode
で取得できるから別ににいいか。
うまいこと .description
の中身作れないかな
javascriptawait fetch(`/api/pages/${path}`)
.then(res => res.json())
.then(json => json.descriptions.join('\n'))
記法をうまく描画しないといけ無さそうだなぁ
でもそこまでする必要あるかなあ?
まず入れるだけ入れてみるか
いや当日のページが存在するなら、下に表示されているはずだから、単に移すだけでいいんじゃないか?
下に表示されている場合はそれでいけます
ただし、たくさんページを更新して当日ページが描画領域外に押し出されると、スクロールするまでDOMに入らなくなります。
document.querySelector('.grid').children.length
を実行したら101が返ってきました
その場合を考慮すると、やはりAPIを叩くのが確実かと思います
100ページも更新されるのはそうそうないんじゃないか?
それもそうですね。じゃあ大丈夫かな
画面が小さいと描画領域外に押し出されてしまうことがある