script.js(() => {
main();
function main() {
if (!location.href.startsWith("https://stakiran.github.io/gtd_kamikudaku")) return;
const body = document.querySelector("div.uk-width-medium-3-4");
const sections = makeHierarchy(body);
const pages = [];
for (const section of sections) {
if (section[0].id === "脚注") continue;
const pages_ = [...toPages(section)];
if (pages_.length === 0) continue;
pages.push(pages_.pop());
pages.push(...pages_);
}
const footnotes = Array.from(body.querySelectorAll("li[id] p"))
.map((p, i) => ({
title: `脚注${i + 1}`,
lines: [`脚注${i + 1}`, p.firstChild.textContent.trim()],
}));
const index = {
title: "目次",
lines: [
"目次",
...pages.map(({ id, level }) => `${" ".repeat(level - 1)}[${id}]`),
],
};
const json = {
pages: [index, ...pages.map(({id,lines}) => ({ title: id, lines: [id, ...lines] })), ...footnotes],
};
// download dataを作成
const blob = new Blob([JSON.stringify(json)], {type: 'application/json'});
// download linkを生成
const url = URL.createObjectURL(blob);
window.open(url);
}
function* toPages(section) {
const data = section[0]
const lines = [];
for (const node of section.slice(1)) {
if (Array.isArray(node)) {
const pages = [...toPages(node)];
if (pages.length === 0) continue;
const link = pages[pages.length - 1].id;
lines.push(`[${link}]`, "");
// 先に親ページを返す
yield pages.pop();
for (const page of pages) {
yield page;
}
continue;
}
switch (node.nodeName) {
case "P":
lines.push(...convertP(node).split("\n"), "");
break;
case "TABLE":
lines.push("table:table", ...node.innerText.split("\n").map((text) => ` ${text}`), "");
break;
case "UL":
lines.push(...convertUl(node), "");
break;
case "PRE":
lines.push("code:txt", ...node.innerText.split("\n").map((text) => ` ${text}`), "");
break;
case "FIGURE": {
const img = node.querySelector("img");
const src = img.src.trim();
const text = img.alt.trim();
lines.push(text, ` [${src}]`, "");
break;
}
default:
lines.push(...node.innerText.split("\n"), "");
break;
}
}
yield { ...data, lines };
}
function convertP(p) {
return Array.from(p.childNodes).map((node) => {
switch (node.nodeName) {
case "STRONG":
return node.textContent.split("\n").map((text) => `[* ${text}]`).join("\n");
case "CODE":
return node.textContent.split("\n").map((text) => `\`${text}\``).join("\n");
case "A": {
if (node.id) {
const n = node.textContent.trim();
return `([脚注${n}])`;
}
const url = node.href.trim();
const text = node.textContent.trim();
return url === text ? ` ${url} ` : `[${url} ${text}]`;
}
case "UL":
return convertUl(node).join("\n");
default:
return node.textContent;
}
}).join("");
}
function convertUl(ul) {
const list = Array.from(ul.children).filter((li) => li.tagName === "LI");
return list.flatMap(
(li) => convertP(li).split("\n").map((text, i) => i === 0 ? ` ${text}` : ` ${text}`)
);
}
function makeHierarchy(body) {
let node = body.querySelector("h1");
const sections = [];
while (node) {
const [section, next] = crawl(node);
node = next;
sections.push(section);
}
return sections;
}
function crawl(node, split = 1) {
if (!node) return [[], undefined];
const section = [{id: node.id, text: node.textContent, level: split}];
let child = node.nextElementSibling;
while (child && child.tagName !== `H${split}`) {
if (parseInt(child.tagName.slice(1)) > split) {
const result = crawl(child, split + 1);
section.push(result[0]);
child = result[1];
continue;
}
if (parseInt(child.tagName.slice(1)) <= split) break;
section.push(child);
child = child.nextElementSibling;
}
return [section, child];
}
})();