tomiokario
https://scrapbox.io/files/66407c8e26d826001d275643.png
プロジェクト設定
code:script.js
(async function() {
// プロジェクト名を設定してください
const projectName = "TOMIOKARIO";
const baseUrl = https://scrapbox.io/api/pages/${projectName}/search/titles;
// 索引ページのタイトルを設定してください(ここからリンクをたどります)
const indexPageTitle = "索引";
// 全ページ情報を格納する配列
let allPages = [];
// 次ページの取得用 URL(最初はベースURL)
let nextUrl = baseUrl;
// API の応答がなくなるまで全件取得(1000件ずつ)
while (nextUrl) {
try {
const response = await fetch(nextUrl);
if (!response.ok) {
console.error(HTTPエラー: ${response.status});
break;
}
const pages = await response.json();
allPages.push(...pages);
// 応答ヘッダ「X-Following-Id」から次回用のIDを取得
const followingId = response.headers.get('X-Following-Id');
if (followingId) {
nextUrl = ${baseUrl}?followingId=${followingId};
} else {
nextUrl = null;
}
} catch (error) {
console.error("データ取得中にエラーが発生しました:", error);
break;
}
}
// 全ページ情報を元に、タイトルをキーとしたマップ(辞書型)を作成
const pagesMap = {};
allPages.forEach(page => {
});
// 索引ページからリンクをたどって到達可能なページを集合で収集
const reachable = new Set();
const queue = [];
// 初期状態として索引ページが存在する場合はキューに追加
queue.push(indexPageTitle);
} else {
console.error(索引ページ "${indexPageTitle}" が存在しません。);
}
// 幅優先探索(BFS)で到達可能なページ群を求める
while (queue.length > 0) {
const currentTitle = queue.shift();
if (reachable.has(currentTitle)) continue;
reachable.add(currentTitle);
if (currentPage && Array.isArray(currentPage.links)) {
currentPage.links.forEach(linkTitle => {
if (pagesMaplinkTitle && !reachable.has(linkTitle)) { queue.push(linkTitle);
}
});
}
}
// プロジェクト内のすべてのページの中から、索引経由でたどり着けなかったページを抽出
const unreachablePages = allPages.filter(page => !reachable.has(page.title));
// 到達できないページがある場合のみポップアップを表示
if (unreachablePages.length > 0) {
let resultHtml = `
<html>
<head>
<meta charset="utf-8">
<title>到達できないページ一覧</title>
</head>
<body>
<h1>索引からたどって到達できないページ一覧</h1>
<ul>
`;
unreachablePages.forEach(page => {
resultHtml += <li>${page.title}</li>;
});
resultHtml += `
</ul>
</body>
</html>
`;
const popup = window.open("", "UnreachablePages", "width=400,height=600,scrollbars=yes");
if (popup) {
popup.document.open();
popup.document.write(resultHtml);
popup.document.close();
} else {
alert("ポップアップウィンドウの表示がブロックされました。ブラウザのポップアップブロック設定を確認してください。");
}
} else {
console.log("すべてのページは索引から到達可能です。");
}
})();
code:script.js
(function () {
scrapbox.PageMenu.addMenu({
title: 'リンク関係',
onClick: async () => {
const menu = scrapbox.PageMenu('リンク関係')
menu.removeAllItems()
const project = scrapbox.Project.name
const title = scrapbox.Page.title
// 1) 自ページ取得(links と relatedPages.links1hop を使う)
let self
try {
const res = await fetch(/api/pages/${project}/${encodeURIComponent(title)})
self = await res.json()
} catch (e) {
console.warn('failed to fetch self page', e)
return
}
const outPages = new Set(self.links || [])
// 関連ページ(リンク+被リンクが混在する候補集合)
const candidates = new Set(
(self.relatedPages?.links1hop || []).map(p => p.title)
)
// 2) 分類:リンク(相互リンクもここに含める)
// 3) バックリンク:自分はリンクしていないが、相手が自分にリンクしている
const backlinks = []
for (const page of candidates) {
if (outPages.has(page)) continue // 自分がリンクしてるなら「リンク」側
try {
const res = await fetch(/api/pages/${project}/${encodeURIComponent(page)})
const json = await res.json()
if ((json.links || []).includes(title)) {
backlinks.push(page)
}
} catch (e) {
console.warn('failed to check backlink:', page, e)
}
}
// 4) 表示
addSection(menu, 'リンク', links, project)
addSection(menu, 'バックリンク', backlinks, project)
}
})
function addSection(menu, label, pages, project) {
if (!pages || pages.length === 0) return
menu.addItem({ title: 【${label}】(${pages.length}), onClick: () => {} })
pages.forEach(page => {
menu.addItem({
title: ' ' + page,
onClick: () => {
location.href = /${project}/${encodeURIComponent(page)}
}
})
})
}
})()
? (停止中)
code:script.js
(async function() {
const popup = window.open("", "IndexShortestTree", "width=600,height=800,scrollbars=yes");
if (!popup) {
alert("ポップアップがブロックされています");
return;
}
const projectName = "TOMIOKARIO";
const baseUrl = https://scrapbox.io/api/pages/${projectName}/search/titles;
const indexPageTitle = "索引";
let allPages = [];
let nextUrl = baseUrl;
while (nextUrl) {
const response = await fetch(nextUrl);
const pages = await response.json();
allPages.push(...pages);
const followingId = response.headers.get("X-Following-Id");
nextUrl = followingId ? ${baseUrl}?followingId=${followingId} : null;
}
const pagesMap = {};
allPages.forEach(p => { pagesMapp.title = p; }); popup.document.write("<p>索引ページがありません</p>");
return;
}
// --- BFS 最短構造 ---
const parent = {};
while (queue.length) {
const t = queue.shift();
if (!p || !p.links) continue;
for (const l of p.links) {
if (pagesMapl && !(l in parent)) { queue.push(l);
}
}
}
// --- parent → children ---
const children = {};
Object.keys(parent).forEach(t => childrent = []); if (par !== null) childrenpar.push(child); });
Object.values(children).forEach(arr => arr.sort((a,b)=>a.localeCompare(b,"ja")));
function esc(s) {
return String(s)
.replace(/&/g,"&")
.replace(/</g,"<")
.replace(/>/g,">")
.replace(/"/g,""")
.replace(/'/g,"'");
}
function writeTree(title) {
popup.document.write("<li>" + esc(title));
const cs = childrentitle; if (cs && cs.length) {
popup.document.write("<ul>");
cs.forEach(c => writeTree(c));
popup.document.write("</ul>");
}
popup.document.write("</li>");
}
// ------- 出力 -------
popup.document.open();
popup.document.write("<html><head><meta charset='utf-8'>");
popup.document.write("<title>索引ツリー</title>");
popup.document.write("<style>ul{list-style:none;padding-left:1em;border-left:1px solid #ccc;}</style>"); popup.document.write("</head><body>");
popup.document.write("<h1>索引からの最短階層ツリー</h1>");
popup.document.write("<ul>");
writeTree(indexPageTitle);
popup.document.write("</ul>");
popup.document.write("</body></html>");
popup.document.close();
})();