Modularize proof of concept

This commit is contained in:
Zack Rauen 2023-09-21 03:18:11 -04:00
parent a584a5c296
commit 568ea271a4
13 changed files with 255 additions and 303 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules/ node_modules/
legacy/
dist/
.env .env

10
package-lock.json generated
View File

@ -15,6 +15,7 @@
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
"eslint": "^8.49.0", "eslint": "^8.49.0",
"highlight.js": "^11.8.0",
"trilium-etapi": "^0.1.2", "trilium-etapi": "^0.1.2",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
@ -1811,6 +1812,15 @@
"node": ">=8" "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": { "node_modules/ignore": {
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",

View File

@ -4,6 +4,9 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "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" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "author": "",
@ -15,6 +18,7 @@
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
"eslint": "^8.49.0", "eslint": "^8.49.0",
"highlight.js": "^11.8.0",
"trilium-etapi": "^0.1.2", "trilium-etapi": "^0.1.2",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }

31
src/main/breadcrumbs.ts Normal file
View File

@ -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);
}

15
src/main/externallinks.ts Normal file
View File

@ -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<HTMLAnchorElement>(`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";
}
}
}

11
src/main/fixactivelink.ts Normal file
View File

@ -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}`;
}

View File

@ -0,0 +1,5 @@
export default function fixTableHeaders() {
Array.from(document.querySelectorAll("th")).forEach(el => {
if (!el.textContent?.trim()) el.classList.add("empty");
});
}

31
src/main/highlight.ts Normal file
View File

@ -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}<span class="hljs-variable language_">api</span>.`;
});
// Add jquery global
result.value = result.value.replaceAll(/([^A-Za-z0-9\.])\$\((.+)\)/g, function(match, prefix, variable) {
return `${prefix}<span class="hljs-variable language_">$(</span>${variable}<span class="hljs-variable language_">)</span>`;
});
}
})
hljs.highlightAll();
});
document.head.append(script);
}

View File

@ -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 // 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}<span class="hljs-variable language_">api</span>.`;
});
// Add jquery global
result.value = result.value.replaceAll(/([^A-Za-z0-9\.])\$\((.+)\)/g, function(match, prefix, variable) {
return `${prefix}<span class="hljs-variable language_">$(</span>${variable}<span class="hljs-variable language_">)</span>`;
});
}
})
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{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{fixTableHeaders();} catch(e){console.error(e)}
// try{addLogo();} catch{} // try{addLogo();} catch{}
try{fixSubMenu();} catch(e){console.error(e)} try{fixSubMenu();} catch(e){console.error(e)}
try{addSideNav();} catch(e){console.error(e)} try{buildSidenav();} catch(e){console.error(e)}
try{buildBreadcrumbsFromNav();} catch(e){console.error(e)} try{buildBreadcrumbs();} catch(e){console.error(e)}
try{generateTOC();} catch(e){console.error(e)} try{generateTOC();} catch(e){console.error(e)}
try{addExternalLinks();} catch(e){console.error(e)} try{addExternalLinks();} catch(e){console.error(e)}

19
src/main/sidenav.ts Normal file
View File

@ -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);
}

38
src/main/submenu.ts Normal file
View File

@ -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();
}
}
}
}

76
src/main/toc.ts Normal file
View File

@ -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)
}

View File

@ -8,7 +8,7 @@
"declaration": true, "declaration": true,
"strictNullChecks": true, "strictNullChecks": true,
"moduleResolution": "Node16", "moduleResolution": "Node16",
"target": "ES2020", "target": "ES2021",
"rootDir": "src", "rootDir": "src",
"outDir": "lib/cjs", "outDir": "lib/cjs",
"module": "Node16", "module": "Node16",