デイリーページに、特定のページへのリンクを持つページ一覧を表示し、さらに内容を開閉できるようにする
「【kasten】(kastenページ)」では、記述のブロックごとに3桁の番号を振っているので、その行を含む行を抜き出す仕様。 Zettelkastenページには「探究用ノート」というリンクを記述しており、
探究用ノートというリンクを持つページ一覧とその内容を取得
デイリーページ下部にそのページへのリンク一覧を表示
リンク一覧には右横に「+」と書いてあり、そこをクリックすると番号を降っている行を表示
もう一度押すと表示したものを閉じる
というもの。
閉じた状態
https://gyazo.com/ed13c17f46dffb6f410fe541837616fd
開いた状態
https://gyazo.com/879b81d18297d60a1dc0d0b602c074af
code:script.js
requestAnimationFrame(() => {
if (window.KCS?.role === 'SV') return;
// ページの内容が編集されたときにフック
cosense.on('lines:changed', ({ by }) => {
if (by === 'edit') updateLinkedPages();
});
// 【kasten】からリンクされているページを収集
async function updateLinkedPages() {
if (cosense.Layout !== 'page') return;
if (!/^\d{8}/.test(cosense.Page.title)) return; // 8桁数字タイトルのみ対象
try {
const { relatedPages } = await fetchPage("【kasten】");
const titles = (relatedPages?.links1hop || []).map(p => p.title).sort();
await renderList(titles);
} catch (err) {
console.error("リンク取得エラー:", err);
}
}
// Scrapbox API からページ JSON を取得
async function fetchPage(title) {
const url = https://scrapbox.io/api/pages/${scrapbox.Project.name}/${encodeURIComponent(title)};
const res = await fetch(url);
if (!res.ok) throw new Error(${res.status} ${res.statusText});
return res.json();
}
// Scrapbox リンク記法を HTML の aタグに変換
const linkify = (text, project = scrapbox.Project.name) =>
text.replace(/\[(^\]+)\]/g, (_, t) => <a href="https://scrapbox.io/${project}/${encodeURIComponent(t.trim())}" target="_blank">${t}</a>);
// メインのリスト描画処理
async function renderList(titles) {
const container = document.querySelector(".editor");
if (!container) return;
// リスト表示領域を生成 or 取得
let div = document.getElementById("tempRINKList");
if (!div) {
div = Object.assign(document.createElement("div"), { id: "tempRINKList" });
Object.assign(div.style, {
background: "floralwhite", padding: "1em", borderRadius: "5px",
marginTop: "1em", border: "1px solid #ccc", fontSize: "14px", lineHeight: "1.6" });
container.appendChild(div);
}
// 初回のみ CSS を追加
if (!document.getElementById("rink-style")) {
const style = document.createElement("style");
style.id = "rink-style";
style.textContent = `
list-style: none; position: relative;
padding: 0.2em 1.5em 0.2em 1.2em;
}
/* 自作バレット */
content: "•"; position: absolute; left: 0; top: 0.5em;
font-size: 1em; line-height: 1; color: black;
}
/* トグルボタンの描画 (CSSだけで+/-を表現) */
position: absolute; right: 0; top: 0.3em;
width: 1.2em; height: 1.2em;
cursor: pointer;
}
content: ""; position: absolute;
background: #666; left: 50%; top: 50%; transform: translate(-50%, -50%);
}
/* 横棒 */
width: 70%; height: 1.5px;
border-radius: 1px;
}
/* 縦棒(閉じているときだけ表示) */
width: 1.5px; height: 70%;
border-radius: 1px;
}
/* 開いているときは縦棒を消してマイナス記号に */
margin-top: .5em; padding-left: 1em; border-left: 2px solid #ddd; font-size: 13px; display: none;
}
`;
document.head.appendChild(style);
}
// リストHTML生成
let html = "<ul>";
for (const title of titles) {
const url = /${scrapbox.Project.name}/${encodeURIComponent(title)};
let subLines = "";
try {
const { lines = [] } = await fetchPage(title);
const matched = lines.map(l => l.text).filter(t => /^\d{3}/.test(t));
if (matched.length) {
subLines = <div class="sub-lines">${matched.map(l => <div>${linkify(l)}</div>).join("")}</div>;
}
} catch (err) {
console.error(ページ取得失敗: ${title}, err);
}
html += `
<li data-title="${title}">
<a href="${url}" target="_blank" class="page-link">${title}</a>
<span class="toggle-btn"></span>
${subLines}
</li>`;
}
html += "</ul>";
div.innerHTML = html;
// トグル動作: サブラインを開閉
div.querySelectorAll(".toggle-btn").forEach(btn => {
btn.onclick = () => {
const sub = btn.closest("li").querySelector(".sub-lines");
if (sub) {
const open = sub.classList.toggle("open");
btn.classList.toggle("open", open); // CSSで + / - を切り替え
}
};
});
}
});