From e34a2ac87f891abb45e9a0202877c9038a87957c Mon Sep 17 00:00:00 2001 From: thiloho <123883702+thiloho@users.noreply.github.com> Date: Sat, 24 Aug 2024 19:37:00 +0200 Subject: [PATCH] Wrap headings in section --- flake.nix | 2 +- web-app/package-lock.json | 7 +++ web-app/package.json | 1 + web-app/src/lib/utils.ts | 96 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index d80c69f..e12cdc8 100644 --- a/flake.nix +++ b/flake.nix @@ -25,7 +25,7 @@ api = pkgs.mkShell { packages = with pkgs; [ postgresql_16 ]; shellHook = '' - alias dbmate="${pkgs.dbmate}/bin/dbmate --url postgres://postgres@localhost:15432/archtika?sslmode=disable" + alias dbmate="${pkgs.dbmate}/bin/dbmate --no-dump-schema --url postgres://postgres@localhost:15432/archtika?sslmode=disable" alias formatsql="${pkgs.pgformatter}/bin/pg_format -s 2 -f 2 -U 2 -i db/migrations/*.sql" ''; }; diff --git a/web-app/package-lock.json b/web-app/package-lock.json index 1927efe..33a77dc 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -8,6 +8,7 @@ "name": "web-app", "version": "0.0.1", "dependencies": { + "github-slugger": "2.0.0", "highlight.js": "11.10.0", "marked": "14.0.0", "marked-highlight": "2.1.4" @@ -1429,6 +1430,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", diff --git a/web-app/package.json b/web-app/package.json index 19335d7..6965d7b 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -26,6 +26,7 @@ }, "type": "module", "dependencies": { + "github-slugger": "2.0.0", "highlight.js": "11.10.0", "marked": "14.0.0", "marked-highlight": "2.1.4" diff --git a/web-app/src/lib/utils.ts b/web-app/src/lib/utils.ts index 507a067..3a297d2 100644 --- a/web-app/src/lib/utils.ts +++ b/web-app/src/lib/utils.ts @@ -1,6 +1,8 @@ import { Marked } from "marked"; +import type { Renderer, Token } from "marked"; import { markedHighlight } from "marked-highlight"; import hljs from "highlight.js"; +import GithubSlugger from "github-slugger"; export const sortOptions = [ { value: "creation-time", text: "Creation time" }, @@ -31,6 +33,98 @@ const createMarkdownParser = () => { }) ); + const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi; + + function unescape(html: string) { + return html.replace(unescapeTest, (_, n) => { + n = n.toLowerCase(); + if (n === "colon") return ":"; + if (n.charAt(0) === "#") { + return n.charAt(1) === "x" + ? String.fromCharCode(parseInt(n.substring(2), 16)) + : String.fromCharCode(+n.substring(1)); + } + return ""; + }); + } + + let slugger = new GithubSlugger(); + let headings: { text: string; raw: string; level: number; id: string }[] = []; + let sectionStack: { level: number; id: string }[] = []; + + function gfmHeadingId({ prefix = "" } = {}) { + return { + renderer: { + heading(this: Renderer, { tokens, depth }: { tokens: Token[]; depth: number }) { + const text = this.parser.parseInline(tokens); + const raw = unescape(this.parser.parseInline(tokens, this.parser.textRenderer)) + .trim() + .replace(/<[!\/a-z].*?>/gi, ""); + const level = depth; + const id = `${prefix}${slugger.slug(raw.toLowerCase())}`; + const heading = { level, text, id, raw }; + headings.push(heading); + + // Close any sections that are at a higher level than the current heading + let closingSections = ""; + while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].level >= level) { + sectionStack.pop(); + closingSections += ""; + } + + // Open a new section for this heading + sectionStack.push({ level, id }); + const openingSection = `
`; + + return ` + ${closingSections} + ${openingSection} + + ${text} + + `; + } + }, + hooks: { + preprocess(src: string) { + headings = []; + sectionStack = []; + slugger = new GithubSlugger(); + + return src; + }, + postprocess(html: string) { + // Close any remaining open sections + const closingRemainingSection = "
".repeat(sectionStack.length); + + // Generate table of contents + const tableOfContents = + headings.length > 0 + ? `
+ Table of contents + +
` + : ""; + + return ` + ${tableOfContents} + ${html} + ${closingRemainingSection} + `; + } + } + }; + } + + marked.use(gfmHeadingId()); + return marked; }; @@ -42,8 +136,6 @@ export const md = async (markdownContent: string) => { return html; }; -// test - export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => { const clipboardItems = Array.from(event.clipboardData?.items || []); const file = clipboardItems.find((item) => item.type.startsWith("image/"));