generated at
関連ページリストを計算する
外部projectを自分のprojectとみなして関連ページリストを計算したい

仕様

どうするか
現在のページにあるリンクの情報を保持
それぞれのリンクに対して関連ページを紐付けておく
ページが編集される度に、追加されたリンクがないか確認
追加されていたら、そのリンクをリストにいれる
削除されたリンクについては何もしない
データ構造
javascript
const pageCache = [ { project: "", title: "", parsedHtml: "", // なくても可 cardHtml: "", }, ];
scrapboxのページカードとtext bubbleを作るのに使う
pageCache[].cardHtml /api/pages/$project を叩いて事前に全部作っておく
pageCache[].parsedHtml scrapbox-text-bubbleを使う段階で取得する
/api/pages/$project/$page/text を叩く
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; } }
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}\/$/ で取り出して合体できる
WebWorkerにもたせる
データはmain threadで持つ
WebWorkerに持たせても構わないか
UI threadとは、表示するのに必要なHTMLテキストだけやり取りする
やはりUI threadにもたせたほうがいい
WebWorkerが重い計算をやっているとき、dataにaccessできなくなる
fetchと計算だけ複数のWebWorkerにやらせる
LinkCache はUI threadで使用しないのでWebWorkerに持たせておく
scrapbox-parser.min.jsで予めparseしておく
2 hop linkを計算するためのdata
javascript
const LinkCache = [ { project: "", title: "", links: ["",],// /project/titleの形式 }, ];
/api/pages/$project/search/titles で取得する
いやまて、このままだと外部project linkを取得できないのか
現在ページからDOM操作で取得するしかないか
scrapbox.Page.linesで取得できる
links には /project/title が参照しているリンクを入れる
relatedLinkCache[].links2hop を検索するときに使う
検索しやすいように、被リンクのリストをもたせたdataに変換する
変換後のdata
javascript
const backLinksData = [ { title: "",// /project/titleの形式 backLinks: ["",],// /project/titleの形式 }, ];
↓使わない
hr
APIを叩いて、リンクを探す
予めリンクをもっておく
javascript
const externalProjectLinks = [ { project: "", title: "", linked: ["",], }, ];
/api/pages/$project/search/titles で取得する
linkedには被リンクのみ入れておく
linkCache[].links2hop を検索するときに使う
被リンクではなく順リンクを保持したほうがいい?
自分のprojectの更新を反映しやすくなる
hr
↑使わない
link networkの更新
現在ページに紐付いているlinkについては、関連ページリストの更新に応じて再計算する
DOMを検索してscrapbox.Page.linesから外部projectも含めたlinkを取得する
どうやって既存のcacheを更新する?
一からリンクを計算し直すのか?
LinkCache の対象の要素のみ更新する
そのあと、 BackLinks を通じて relatedLinkCache を再計算する
更新は div.related-page-list.clearfix ul.grid をMutationObserverで監視する
これだと外部project linkの変更を取得できないか
#editor .lines を監視しよう
それ以外のlinkは更新しない
頻繁にAPIを叩くのは負荷が高い

↓更新していない
最新はadvanced-related-pagesにある
test.js
import {createPageCard} from '/api/code/takker/scrapboxのページカードを作成するscript/script.js'; let test_list =[]; const project_list = ['takker','takker-memex','takker-private']; 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 promises = [...Array(maxIndex)].map(async (_, index) => { 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; }

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を作っておく const result = [...new Set(linkCache.flatMap(page => page.links))] .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; }

#2020-11-14 13:44:13
#2020-10-26 07:06:58
#2020-10-23 09:01:00