類似したタイトルのページを関連ページとして表示する
https://gyazo.com/77e17c35a17c5cb21b16362330d4e3eb
リンクが繋がっているページはちゃんと除外される
リンクは繋がっていないが関係のあるページを発見できることがあってたのしい
表記ゆれで同じようなページが複数作られたりしてても把握できるかも /icons/すごい.icontakker.icon/villagepump/😸.iconerniogi.iconpollenjp.icon
今のバージョンだと、モバイル版ではクリックできるが、PCではクリックできない
ページカードが高速にappend・destroyされているからだと思う
右クリックで開くのはできる
Page Historyにある一番古いバージョンは普通にできる
使い方
以下を自分のページに書く
code:js
import('/api/code/scrapboxlab/類似したタイトルのページを関連ページとして表示する/script.js');
code:script.js
if (!document.getElementById('asearch-list-grid')) {
$('.page-wrapper .related-page-list.clearfix').append(
'<ul class="grid" id="asearch-list-grid">' +
'<li class="splitter" style="height: 30px !important" />' +
'<li class="relation-label"><a><span class="title">Similar Pages</span>' +
'<span class="kamon kamon-search icon-lg"/></a><span class="arrow"/></li></ul>'
);
const grid = $('#asearch-list-grid');
let worker = new Worker('/api/code/scrapboxlab/類似したタイトルのページを関連ページとして表示する/worker.js');
worker.onmessage = (e) => {
resetList();
const fragment = $(document.createDocumentFragment());
for (let { exists, title } of e.data) {
const item = $(`<li class="page-list-item grid-style-item${
exists ? '' : ' empty'
}">
<a href="/${scrapbox.Project.name}/${encodeURIComponent(title.replace(' ', '_'))}" rel="route">
<div class="hover"></div>
<div class="content">
<div class="header"><div class="title"></div></div>
<div class="description"><div class="line-img">
<div></div><div></div><div></div><div></div><div></div>
</div></div>
</div>
</a>`);
$('.title', item).text(title);
fragment.append(item);
}
grid.append(fragment);
};
let prevId = null;
let titleLcMap;
const observer = new MutationObserver(() => updateIfPage());
observer.observe($('title')0, { childList: true }); observer.observe($('div.page-wrapper > div.related-page-list.clearfix > ul.grid:nth(0)')0, { childList: true }); updateIfPage();
update(location.pathname);
function resetList() {
grid.children().slice(2).remove();
}
function regenTitleLcMap() {
titleLcMap = {};
for (let p of scrapbox.Project.pages) {
}
}
function pathToTitle(path) {
const a = path.replace(/#.*$/, '').split('/');
if (a.length === 3 && a2.length > 0) { const title = decodeURIComponent(a2); // Project.pagesを元にタイトル取得
// (_が実際は空白なのか_なのか分からないため)
const titleLc = title.toLowerCase();
}
return null;
}
function update() {
if (scrapbox.Page.title === 'new') {
resetList();
return;
}
// 既に関連リンクに入っているページのタイトルを取得する
let links = new Set();
regenTitleLcMap();
$('div.page-wrapper > div.related-page-list.clearfix > ul.grid:nth(0) > li a').each(
(i, e) => {
links.add(pathToTitle(e.pathname));
}
);
worker.postMessage({
title: scrapbox.Page.title,
pages: scrapbox.Project.pages,
links,
replace: prevId === scrapbox.Page.id, // ページタイトルを変更中の場合
prevId,
});
}
function updateIfPage() {
if (scrapbox.Layout !== 'page') return;
update();
prevId = scrapbox.Page.id;
}
}
あと、Scrapbox側の関連ページリストが更新されたらこのスクリプトが生成するリストも更新されるようになったはず(以前はページ遷移時のみでした)
code:worker.js
const Asearch=function(){var t,i,s;function p(i){var p,r,n,o,e,h,u;for(this.source=i,this.shiftpat=[],this.epsilon=0,this.acceptpat=0,e=t,p=r=0,h=s;0<=h?r<h:r>h;p=0<=h?++r:--r)this.shiftpatp=0;for(n=0,o=(u=this.unpack(this.source)).length;n<o;n++)32===(p=un)?this.epsilon|=e:(this.shiftpatp|=e,this.shiftpatthis.toupper(p)|=e,this.shiftpatthis.tolower(p)|=e,e>>>=1);return this.acceptpat=e,this}return s=256,i=t=2147483648,0,0,0,p.prototype.isupper=function(t){return t>=65&&t<=90},p.prototype.islower=function(t){return t>=97&&t<=122},p.prototype.tolower=function(t){return this.isupper(t)?t+32:t},p.prototype.toupper=function(t){return this.islower(t)?t-32:t},p.prototype.state=function(t,s){var p,r,n,o,e,h,u,a,c;for(null==t&&(t=i),null==s&&(s=""),n=t0,o=t1,e=t2,h=t3,r=0,u=(c=this.unpack(s)).length;r<u;r++)p=cr,a=this.shiftpatp,h=h&this.epsilon|(h&a)>>>1|e>>>1|e,e=e&this.epsilon|(e&a)>>>1|o>>>1|o,o=o&this.epsilon|(o&a)>>>1|n>>>1|n,h|=(e|=(o|=(n=n&this.epsilon|(n&a)>>>1)>>>1)>>>1)>>>1;returnn,o,e,h},p.prototype.match=function(t,s){var p;return null==s&&(s=0),p=this.state(i,t),s<i.length||(s=i.length-1),0!=(ps&this.acceptpat)},p.prototype.unpack=function(t){var i,s,p,r,n;for(i=[],p=0,r=(n=t.split("")).length;p<r;p++)(s=np.charCodeAt(0))>255&&i.push((65280&s)>>>8),i.push(255&s);return i},p}(); onmessage = (e) => {
const { title, pages, links, replace, prevId } = e.data;
const search = new Asearch( ${title} );
const ambig = Math.min(
title.split('').every((s) => s.charCodeAt(0) < 0xff) ? 1 : 2,
title.length - 3
);
const titleLc = title.replace(/ /g, '_').toLowerCase();
postMessage([...new Set(pages.flatMap(p => {
if (p.title === title) return [];
if (replace && p.id === prevId) return [];
if (!(
p.titleLc.includes(titleLc) ||
titleLc.includes(p.titleLc) ||
(ambig >= 0 && search.match(p.title, ambig))
)) return [];
return exists: p.exists, title: p.title };
}))]);
};