関連ページリストを計算する
仕様
どうするか
現在のページにあるリンクの情報を保持
それぞれのリンクに対して関連ページを紐付けておく
ページが編集される度に、追加されたリンクがないか確認
追加されていたら、そのリンクをリストにいれる
削除されたリンクについては何もしない
データ構造
code:javascript
const pageCache = [
{
project: "",
title: "",
parsedHtml: "", // なくても可
cardHtml: "",
},
];
pageCache[].cardHtmlは/api/pages/$projectを叩いて事前に全部作っておく
/api/pages/$project/$page/textを叩く
code:test.js
class PageCache {
constructor({project, title, descriptions, imageUrl}){
this.project = project;
this.title = title,
this.cardHtml = createPageCard({
project: project, title: title,
description: descriptions.join('\n'),
imageUrl: imageUrl});
this.parsedHtml = undefined;
}
}
code:javascript
const relatedLinkCache = [
{
project: "",
title: "",
link1hop: "", // /project/titleの形式
links2hop: "",, // /project/titleの形式 }
];
指定したページ内のリンクの2 hop links を取得するのに使う
project, title, link1hopを指定して、links2hopを取得する
/${project}/${title}中にあるリンクとそのリンクの2 hop linkを入れておく
relatedLinkCache[].links2hopは重複しないように計算する
scrapboxの挙動に合わせる
カードが多い場合は、各projectごとに100以下に納める
並び順
特に考えない
もしくは、linkedで並び替える
links1hopは、projectが違っても同じリンクだとみなして一緒にしてしまうか?
いや、data構造は別々でいい
あとで区別して使うかも知れない
dataを取り出すときに、/^\/.+\/{link}\/$/で取り出して合体できる
データはmain threadで持つ
UI threadとは、表示するのに必要なHTMLテキストだけやり取りする
やはりUI threadにもたせたほうがいい
LinkCacheはUI threadで使用しないのでWebWorkerに持たせておく
2 hop linkを計算するためのdata
code:javascript
const LinkCache = [
{
project: "",
title: "",
links: "",,// /project/titleの形式 },
];
/api/pages/$project/search/titlesで取得する
現在ページからDOM操作で取得するしかないか
linksには/project/titleが参照しているリンクを入れる
relatedLinkCache[].links2hopを検索するときに使う
検索しやすいように、被リンクのリストをもたせたdataに変換する
変換後のdata
code:javascript
const backLinksData = [
{
title: "",// /project/titleの形式
backLinks: "",,// /project/titleの形式 },
];
↓使わない
/icons/hr.icon
APIを叩いて、リンクを探す
予めリンクをもっておく
code:javascript
const externalProjectLinks = [
{
project: "",
title: "",
},
];
/api/pages/$project/search/titlesで取得する
linkedには被リンクのみ入れておく
linkCache[].links2hopを検索するときに使う
被リンクではなく順リンクを保持したほうがいい?
/icons/hr.icon
↑使わない
link networkの更新
現在ページに紐付いているlinkについては、関連ページリストの更新に応じて再計算する どうやって既存のcacheを更新する?
一からリンクを計算し直すのか?
LinkCacheの対象の要素のみ更新する
そのあと、BackLinksを通じてrelatedLinkCacheを再計算する
更新はdiv.related-page-list.clearfix ul.gridをMutationObserverで監視する
#editor .linesを監視しよう
それ以外のlinkは更新しない
頻繁にAPIを叩くのは負荷が高い
↓更新していない
code:test.js
import {createPageCard} from '/api/code/takker/scrapboxのページカードを作成するscript/script.js';
let test_list =[];
makePageCache(project_list).then(pageCache => {
test_list = pageCache;
console.log(test_list);
});
function makeRelatedLinkCache(linkCache, backLinksData) {
// 重複を除く処理をやっていないので正確ではない
return linkCache.flatMap(page =>page.links.map(link =>new Object({
project: page.project, title: page.title, link1hop: link,
links2hop: backLinksData.find(data => data.title === link).backLinks
})));
}
async function makePageCache(projects) {
const promises = projects.map(project =>loadAllPages(project));
return await Promise.all(promises).then(pageCaches => pageCaches.flat());
}
async function loadAllPages(project) {
// projectの全ページ数を取得する
const pageNum = await fetch(/api/pages/${project}/?limit=1)
.then(response => response.json())
.then(json => parseInt(json.count));
const maxIndex = Math.floor(pageNum / 1000) + 1;
const pageCache = [];
const json = await fetch(/api/pages/${project}/?limit=1000&skip=${index*1000})
.then(res => res.json());
const pages = json.pages;
pages.forEach(page =>
pageCache.push(new PageCache({
project: project,
title: page.title,
descriptions: page.descriptions,
imageUrl: page.image})));
});
await Promise.all(promises);
console.log(Loaded ${pageCache.length} page data from /${project}.);
return pageCache;
}
code:test.js
async function loadLinkCache(projects) {
const promises = projects.map(async project => {
let followingId = null;
const linkCache =[];
//console.log(Start loading links from ${project}...);
do {
//console.log(Loading links from ${project}: followingId = ${followingId});
const json = await (!followingId ?
fetch(/api/pages/${project}/search/titles) :
fetch(/api/pages/${project}/search/titles?followingId=${followingId}))
.then(res => {
followingId = res.headers.get('X-Following-Id');
return res.json();
});
linkCache.push(...json.map(page => new Object({
project: project, title: page.title,
links: page.links.map(link => /${project}/link)})));
} while(followingId);
return linkCache;
});
return await Promise.all(promises).then(LinkCaches => LinkCaches.flat());
}
// linkCacheから、[{title:"",backLinks: ...},...]を作る // link名はprojectで区別しない
function tobackLinksData(linkCache) {
// 被リンクを持つページを抽出して先にlistを作っておく
.map(link => new Object({title: link, backLinks: []}));
linkCache.forEach(page => result.filter(value => page.links.includes(value.title))
.forEach(value => value.backLinks.push(/${page.project}/${page.title})));
return result;
}