From 568ea271a403fec724ce6b05bb0f226f9ad12a67 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Thu, 21 Sep 2023 03:18:11 -0400 Subject: [PATCH] Modularize proof of concept --- .gitignore | 2 + package-lock.json | 10 ++ package.json | 4 + src/main/breadcrumbs.ts | 31 ++++ src/main/externallinks.ts | 15 ++ src/main/fixactivelink.ts | 11 ++ src/main/fixtableheaders.ts | 5 + src/main/highlight.ts | 31 ++++ src/main/index.ts | 314 ++---------------------------------- src/main/sidenav.ts | 19 +++ src/main/submenu.ts | 38 +++++ src/main/toc.ts | 76 +++++++++ tsconfig.json | 2 +- 13 files changed, 255 insertions(+), 303 deletions(-) create mode 100644 src/main/breadcrumbs.ts create mode 100644 src/main/externallinks.ts create mode 100644 src/main/fixactivelink.ts create mode 100644 src/main/fixtableheaders.ts create mode 100644 src/main/highlight.ts create mode 100644 src/main/sidenav.ts create mode 100644 src/main/submenu.ts create mode 100644 src/main/toc.ts diff --git a/.gitignore b/.gitignore index 3ec544c7a..01a022b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ +legacy/ +dist/ .env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 54bf50753..1415c70da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.3.1", "esbuild": "^0.19.3", "eslint": "^8.49.0", + "highlight.js": "^11.8.0", "trilium-etapi": "^0.1.2", "typescript": "^5.2.2" } @@ -1811,6 +1812,15 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz", + "integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", diff --git a/package.json b/package.json index 23d56be20..ffe0518a2 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "", "main": "index.js", "scripts": { + "build": "esrun scripts/build.ts", + "build-main": "esrun scripts/build.ts -- --module=main", + "build-styles": "esrun scripts/build.ts -- --module=styles", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", @@ -15,6 +18,7 @@ "dotenv": "^16.3.1", "esbuild": "^0.19.3", "eslint": "^8.49.0", + "highlight.js": "^11.8.0", "trilium-etapi": "^0.1.2", "typescript": "^5.2.2" } diff --git a/src/main/breadcrumbs.ts b/src/main/breadcrumbs.ts new file mode 100644 index 000000000..01e34c61d --- /dev/null +++ b/src/main/breadcrumbs.ts @@ -0,0 +1,31 @@ +export default function buildBreadcrumbsFromNav() { + const container = document.createElement("ul"); + container.id = "breadcrumbs"; + + const current = document.querySelector("#menu .active"); + if (!current) return; // Something went really wrong + const wrap = document.createElement("li"); + wrap.append(current.cloneNode(true)); + container.prepend(wrap); + let next = current.closest("ul"); + while (next) { + const clone = next?.previousElementSibling?.querySelector("a")?.cloneNode(true); + if (!clone) continue; // This also means something went very wrong + const wrap = document.createElement("li"); + wrap.append(clone); + container.prepend(wrap); + next = next?.parentElement?.closest("ul") ?? null; + if (!next) { + clone.textContent = ""; + const logo = document.createElement("img"); + logo.src = "https://raw.githubusercontent.com/zadam/trilium/master/images/icon-black.svg"; + clone.appendChild(logo); + } + } + + // We don't need this at root + if (container.children.length === 1) return; + + const main = document.getElementById("main"); + main?.prepend(container); +} \ No newline at end of file diff --git a/src/main/externallinks.ts b/src/main/externallinks.ts new file mode 100644 index 000000000..e411412f4 --- /dev/null +++ b/src/main/externallinks.ts @@ -0,0 +1,15 @@ +export default function addExternalLinks() { + const mapping = { + EGFtX8Uw96FQ: "https://github.com/zadam/trilium" + }; + + for (const id in mapping) { + const links = document.querySelectorAll(`a[href*="${id}"]`); + if (!links.length) {console.warn(`Could not find link to note id ${id}`); continue;} + for (const link of links) { + link.href = mapping[id as keyof typeof mapping]; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + } + } +} \ No newline at end of file diff --git a/src/main/fixactivelink.ts b/src/main/fixactivelink.ts new file mode 100644 index 000000000..c4797c401 --- /dev/null +++ b/src/main/fixactivelink.ts @@ -0,0 +1,11 @@ +export default function fixActiveLink() { + const active = document.querySelector("#menu strong"); + if (!active) return; // Something is really wrong + const link = document.createElement("a"); + link.className = "type-text active"; + link.href = ``; + link.textContent = active.textContent; + active.replaceWith(link); + const id = document.body.dataset.noteId; + link.href = `./${id}`; +} \ No newline at end of file diff --git a/src/main/fixtableheaders.ts b/src/main/fixtableheaders.ts new file mode 100644 index 000000000..0dbf61f5d --- /dev/null +++ b/src/main/fixtableheaders.ts @@ -0,0 +1,5 @@ +export default function fixTableHeaders() { + Array.from(document.querySelectorAll("th")).forEach(el => { + if (!el.textContent?.trim()) el.classList.add("empty"); + }); +} \ No newline at end of file diff --git a/src/main/highlight.ts b/src/main/highlight.ts new file mode 100644 index 000000000..38871f021 --- /dev/null +++ b/src/main/highlight.ts @@ -0,0 +1,31 @@ +import {HLJSApi} from "highlight.js"; + +declare const hljs: HLJSApi; + +export default function addHljs() { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = "api/notes/cVaK9ZJwx5Hs/download"; + document.head.append(link); + + const script = document.createElement("script"); + script.src = "api/notes/6PVElIem02b5/download"; + script.addEventListener("load", () => { + hljs.registerAliases(["application-javascript-env-frontend", "application-javascript-env-backend"], {languageName: "javascript"}); + hljs.addPlugin({ + "after:highlight": (result) => { + // Add api global + result.value = result.value.replaceAll(/([^A-Za-z0-9])api\./g, function(match, prefix) { + return `${prefix}api.`; + }); + + // Add jquery global + result.value = result.value.replaceAll(/([^A-Za-z0-9\.])\$\((.+)\)/g, function(match, prefix, variable) { + return `${prefix}$(${variable})`; + }); + } + }) + hljs.highlightAll(); + }); + document.head.append(script); +} \ No newline at end of file diff --git a/src/main/index.ts b/src/main/index.ts index 0abe7bf34..2e92bc03e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,312 +1,22 @@ +import fixActiveLink from "./fixactivelink"; +import fixTableHeaders from "./fixtableheaders"; +import highlight from "./highlight"; +import buildSidenav from "./sidenav"; +import buildBreadcrumbs from "./breadcrumbs"; +import fixSubMenu from "./submenu"; +import generateTOC from "./toc"; +import addExternalLinks from "./externallinks"; + // https://instance-name/api/notes/vW1cXaYNN7OM/download -function addHljs() { - const link = document.createElement("link"); - link.rel = "stylesheet"; - link.href = "api/notes/cVaK9ZJwx5Hs/download"; - document.head.append(link); - - const script = document.createElement("script"); - script.src = "api/notes/6PVElIem02b5/download"; - script.addEventListener("load", () => { - hljs.registerAliases(["application-javascript-env-frontend", "application-javascript-env-backend"], {languageName: "javascript"}); - hljs.addPlugin({ - "after:highlight": (result) => { - // Add api global - result.value = result.value.replaceAll(/([^A-Za-z0-9])api\./g, function(match, prefix) { - return `${prefix}api.`; - }); - - // Add jquery global - result.value = result.value.replaceAll(/([^A-Za-z0-9\.])\$\((.+)\)/g, function(match, prefix, variable) { - return `${prefix}$(${variable})`; - }); - } - }) - hljs.highlightAll(); - }); - document.head.append(script); -} - -function fixTableHeaders() { - Array.from(document.querySelectorAll("th")).forEach(el => { - if (!el.textContent.trim()) el.classList.add("empty"); - }); -} - -/*Array.from(document.querySelectorAll("pre code")).forEach(el => { - if (el.className.includes("javascript-env")) el.className = "language-javascript"; -});*/ - -function addLogo() { - const logo = document.createElement("img"); - logo.src = "https://raw.githubusercontent.com/zadam/trilium/master/images/icon-color.svg"; - logo.id = "logo"; - document.querySelector("#menu > p").append(logo); -} - - -function fixActiveLink() { - const active = document.querySelector("#menu strong"); - const link = document.createElement("a"); - link.className = "type-text active"; - link.href = ``; - link.textContent = active.textContent; - active.replaceWith(link); - const id = document.body.dataset.noteId; - link.href = `./${id}`; - - /*fetchNote().then(note => { - link.href = `./${note.noteId}`; - });*/ -} - -function addSideNav() { - const categories = document.querySelectorAll("#menu > ul > li > ul > li"); - for (const cat of categories) cat.classList.add("category"); - const active = document.querySelector("#menu .active"); - const treeToClone = active.closest(".category.submenu-item") ?? active.closest(".submenu-item.hidden"); - if (!treeToClone) return; // probably homepage - const layout = document.querySelector("#layout"); - const sidebar = document.createElement("ul"); - sidebar.id = "sidebar"; - const clone = treeToClone.cloneNode(true); - /*const title = document.createElement("div"); - title.className = "title"; - title.append(clone.querySelector("p > a")); - sidebar.append(title); - sidebar.append(clone.querySelector("ul"));*/ - sidebar.append(clone); - if (sidebar.querySelectorAll("li").length <= 1) return; - layout.prepend(sidebar); -} - - -async function buildBreadcrumbs() { - const main = document.getElementById("main"); - const placeholder = document.createElement("div"); - placeholder.id = "breadcrumbs"; - const pspan = document.createElement("span"); - const plink = document.createElement("a"); - pspan.append(plink); - plink.href = "#"; - plink.textContent = document.getElementById("title").textContent; - placeholder.append(pspan); - main.prepend(placeholder); - - const container = document.createElement("div"); - container.id = "breadcrumbs"; - - // const notePath = []; - - let currentNote, parentId; - do { - currentNote = await fetchNote(parentId); - const span = document.createElement("span"); - const link = document.createElement("a"); - link.className = "type-text"; - link.href = `./${currentNote.noteId}`; - link.textContent = currentNote.title; - // notePath.splice(0, 0, link); - span.append(link); - container.prepend(span) - parentId = currentNote.parentNoteIds[0]; - if (parentId === "_share") { - link.textContent = ""; - const logo = document.createElement("img"); - logo.src = "https://raw.githubusercontent.com/zadam/trilium/master/images/icon-black.svg"; - link.append(logo); - } - } while(parentId !== "_share") - - if (container.children.length === 1) return; - - placeholder.replaceWith(container); - /*let currentNote = await fetchNote(); - let parentId = currentNote.parentNoteIds[0]; - while (parentId !== "_share") { - notePath.splice(0, 0, currentNote.title); - parentId = currentNote.parentNoteIds[0]; - currentNote = await fetchNote(parentId); - }*/ - - // console.log(notePath); -} - - - -function buildBreadcrumbsFromNav() { - const container = document.createElement("ul"); - container.id = "breadcrumbs"; - - const current = document.querySelector("#menu .active"); - const wrap = document.createElement("li"); - wrap.append(current.cloneNode(true)); - container.prepend(wrap); - let next = current.closest("ul"); - while (next) { - const clone = next.previousElementSibling.querySelector("a").cloneNode(true); - const wrap = document.createElement("li"); - wrap.append(clone); - container.prepend(wrap); - next = next.parentElement.closest("ul"); - if (!next) { - clone.textContent = ""; - const logo = document.createElement("img"); - logo.src = "https://raw.githubusercontent.com/zadam/trilium/master/images/icon-black.svg"; - clone.append(logo); - } - } - - // We don't need this at root - if (container.children.length === 1) return; - - const main = document.getElementById("main"); - main.prepend(container); -} - -const submenuBlacklist = ["ZapIU17QNEyU"] -//if (item.innerHTML.includes(submenuBlacklist[0])) item.className += " hidden"; -/*function fixSubMenu() { - const items = document.querySelectorAll("#menu > ul > li"); - for (const item of items) { - const sublist = item.querySelector("ul"); - if (sublist) { - if (sublist.children.length) { - item.className = "submenu"; - } - else { - sublist.remove(); - } - } - } -}*/ - -function fixSubMenu() { - const items = document.querySelectorAll("#menu ul li"); - for (const item of items) { - const sublist = item.querySelector("ul"); - if (sublist) { - if (sublist.children.length) { - const ihtml = item.innerHTML; - for (const bl of submenuBlacklist) { - if (!ihtml.includes(bl)) continue; - item.classList.add("hidden"); - } - item.classList.add("submenu-item"); - sublist.classList.add("submenu"); - if (sublist.querySelector("ul")?.children.length) sublist.classList.add("hasSubmenu"); - } - else { - sublist.remove(); - } - } - } -} - - -function generateTOC() { - const slugify = text => text.toLowerCase().replace(/[^\w]/g, "-"); - const buildItem = (heading) => { - const slug = slugify(heading.textContent); - - const anchor = document.createElement("a"); - anchor.setAttribute("href", `#${slug}`); - anchor.setAttribute("name", slug); - anchor.setAttribute("id", slug); - anchor.textContent = "#"; - - const link = document.createElement("a"); - link.setAttribute("href", `#${slug}`); - link.textContent = heading.textContent; - - heading.append(anchor); - - const li = document.createElement("li"); - li.append(link); - return li; - }; - - const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")); - const items = headings.map(h => buildItem(h)); - if (headings.length <= 1) return; - - const getNum = el => parseInt(el.tagName.replace("H","").replace("h","")); - - const toc = document.createElement("ul"); - toc.id = "toc"; - const first = headings[1]; - const firstDepth = getNum(first); - - for (let h = 0; h < headings.length; h++) { - const current = headings[h]; - const currentDepth = getNum(current); - if (currentDepth === firstDepth) toc.append(items[h]); - - let nextIndex = h + 1; - if (nextIndex >= headings.length) continue; - - const children = []; - const childDepth = currentDepth + 1; - let nextDepth = getNum(headings[nextIndex]); - while (nextDepth > currentDepth) { - if (nextDepth === childDepth) children.push(nextIndex); - nextIndex++; - if (nextIndex < headings.length) nextDepth = getNum(headings[nextIndex]); - else nextDepth = currentDepth; - } - - if (children.length) { - const ul = document.createElement("ul"); - for (const c of children) ul.append(items[c]); - items[h].append(ul); - } - } - - const sections = headings.slice(1); - const links = toc.querySelectorAll("a"); - function changeLinkState() { - let index = sections.length; - - while(--index && window.scrollY + 50 < sections[index].offsetTop) {} - - links.forEach((link) => link.classList.remove('active')); - links[index].classList.add('active'); - } - - changeLinkState(); - window.addEventListener('scroll', changeLinkState); - - const layout = document.querySelector("#layout"); - layout.classList.add("toc"); - layout.append(toc) -} - -function addExternalLinks() { - const mapping = { - EGFtX8Uw96FQ: "https://github.com/zadam/trilium" - }; - - for (const id in mapping) { - const links = document.querySelectorAll(`a[href*="${id}"]`); - if (!links.length) {console.warn(`Could not find link to note id ${id}`); continue;} - for (const link of links) { - link.href = mapping[id]; - link.target = "_blank"; - link.rel = "noopener noreferrer"; - } - } -} - - try{fixActiveLink();} catch(e){console.error(e)} -try{addHljs();} catch(e){console.error(e)} +try{highlight();} catch(e){console.error(e)} try{fixTableHeaders();} catch(e){console.error(e)} // try{addLogo();} catch{} try{fixSubMenu();} catch(e){console.error(e)} -try{addSideNav();} catch(e){console.error(e)} -try{buildBreadcrumbsFromNav();} catch(e){console.error(e)} +try{buildSidenav();} catch(e){console.error(e)} +try{buildBreadcrumbs();} catch(e){console.error(e)} try{generateTOC();} catch(e){console.error(e)} try{addExternalLinks();} catch(e){console.error(e)} \ No newline at end of file diff --git a/src/main/sidenav.ts b/src/main/sidenav.ts new file mode 100644 index 000000000..c8d4c7af1 --- /dev/null +++ b/src/main/sidenav.ts @@ -0,0 +1,19 @@ +export default function addSideNav() { + const categories = document.querySelectorAll("#menu > ul > li > ul > li"); + for (const cat of categories) cat.classList.add("category"); + const active = document.querySelector("#menu .active"); + const treeToClone = active?.closest(".category.submenu-item") ?? active?.closest(".submenu-item.hidden"); + if (!treeToClone) return; // probably homepage + const layout = document.querySelector("#layout"); + const sidebar = document.createElement("ul"); + sidebar.id = "sidebar"; + const clone = treeToClone.cloneNode(true); + /*const title = document.createElement("div"); + title.className = "title"; + title.append(clone.querySelector("p > a")); + sidebar.append(title); + sidebar.append(clone.querySelector("ul"));*/ + sidebar.append(clone); + if (sidebar.querySelectorAll("li").length <= 1) return; + layout?.prepend(sidebar); +} \ No newline at end of file diff --git a/src/main/submenu.ts b/src/main/submenu.ts new file mode 100644 index 000000000..60d9998ca --- /dev/null +++ b/src/main/submenu.ts @@ -0,0 +1,38 @@ +const submenuBlacklist = ["ZapIU17QNEyU"] +//if (item.innerHTML.includes(submenuBlacklist[0])) item.className += " hidden"; +/*function fixSubMenu() { + const items = document.querySelectorAll("#menu > ul > li"); + for (const item of items) { + const sublist = item.querySelector("ul"); + if (sublist) { + if (sublist.children.length) { + item.className = "submenu"; + } + else { + sublist.remove(); + } + } + } +}*/ + +export default function fixSubMenu() { + const items = document.querySelectorAll("#menu ul li"); + for (const item of items) { + const sublist = item.querySelector("ul"); + if (sublist) { + if (sublist.children.length) { + const ihtml = item.innerHTML; + for (const bl of submenuBlacklist) { + if (!ihtml.includes(bl)) continue; + item.classList.add("hidden"); + } + item.classList.add("submenu-item"); + sublist.classList.add("submenu"); + if (sublist.querySelector("ul")?.children.length) sublist.classList.add("hasSubmenu"); + } + else { + sublist.remove(); + } + } + } +} \ No newline at end of file diff --git a/src/main/toc.ts b/src/main/toc.ts new file mode 100644 index 000000000..6b8b346e4 --- /dev/null +++ b/src/main/toc.ts @@ -0,0 +1,76 @@ +export default function generateTOC() { + const slugify = (text: string) => text.toLowerCase().replace(/[^\w]/g, "-"); + const buildItem = (heading: Element) => { + const slug = slugify(heading.textContent ?? ""); + + const anchor = document.createElement("a"); + anchor.setAttribute("href", `#${slug}`); + anchor.setAttribute("name", slug); + anchor.setAttribute("id", slug); + anchor.textContent = "#"; + + const link = document.createElement("a"); + link.setAttribute("href", `#${slug}`); + link.textContent = heading.textContent; + + heading.append(anchor); + + const li = document.createElement("li"); + li.append(link); + return li; + }; + + const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")); + const items = headings.map(h => buildItem(h)); + if (headings.length <= 1) return; + + const getNum = (el: Element) => parseInt(el.tagName.replace("H","").replace("h","")); + + const toc = document.createElement("ul"); + toc.id = "toc"; + const first = headings[1]; + const firstDepth = getNum(first); + + for (let h = 0; h < headings.length; h++) { + const current = headings[h]; + const currentDepth = getNum(current); + if (currentDepth === firstDepth) toc.append(items[h]); + + let nextIndex = h + 1; + if (nextIndex >= headings.length) continue; + + const children = []; + const childDepth = currentDepth + 1; + let nextDepth = getNum(headings[nextIndex]); + while (nextDepth > currentDepth) { + if (nextDepth === childDepth) children.push(nextIndex); + nextIndex++; + if (nextIndex < headings.length) nextDepth = getNum(headings[nextIndex]); + else nextDepth = currentDepth; + } + + if (children.length) { + const ul = document.createElement("ul"); + for (const c of children) ul.append(items[c]); + items[h].append(ul); + } + } + + const sections = headings.slice(1); + const links = toc.querySelectorAll("a"); + function changeLinkState() { + let index = sections.length; + + while(--index && window.scrollY + 50 < (sections[index] as HTMLElement).offsetTop) {} + + links.forEach((link) => link.classList.remove('active')); + links[index].classList.add('active'); + } + + changeLinkState(); + window.addEventListener('scroll', changeLinkState); + + const layout = document.querySelector("#layout"); + layout?.classList.add("toc"); + layout?.append(toc) +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 662aaadf4..e32c9959d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "declaration": true, "strictNullChecks": true, "moduleResolution": "Node16", - "target": "ES2020", + "target": "ES2021", "rootDir": "src", "outDir": "lib/cjs", "module": "Node16",