jsimport('/api/code/scrapboxlab/類似したタイトルのページを関連ページとして表示する/script.js');
script.jsif (!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) {
titleLcMap[p.titleLc] = p.title;
}
}
function pathToTitle(path) {
const a = path.replace(/#.*$/, '').split('/');
if (a.length === 3 && a[2].length > 0) {
const title = decodeURIComponent(a[2]);
// Project.pagesを元にタイトル取得
// (`_`が実際は空白なのか`_`なのか分からないため)
const titleLc = title.toLowerCase();
return titleLcMap[titleLc];
}
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;
}
}
worker.js// Asearch (https://github.com/shokai/node-asearch)
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.shiftpat[p]=0;for(n=0,o=(u=this.unpack(this.source)).length;n<o;n++)32===(p=u[n])?this.epsilon|=e:(this.shiftpat[p]|=e,this.shiftpat[this.toupper(p)]|=e,this.shiftpat[this.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=t[0],o=t[1],e=t[2],h=t[3],r=0,u=(c=this.unpack(s)).length;r<u;r++)p=c[r],a=this.shiftpat[p],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;return[n,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!=(p[s]&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=n[p].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 }];
}))]);
};