Wrap headings in section

This commit is contained in:
thiloho
2024-08-24 19:37:00 +02:00
parent 7191530d9b
commit e34a2ac87f
4 changed files with 103 additions and 3 deletions

View File

@@ -25,7 +25,7 @@
api = pkgs.mkShell { api = pkgs.mkShell {
packages = with pkgs; [ postgresql_16 ]; packages = with pkgs; [ postgresql_16 ];
shellHook = '' 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" alias formatsql="${pkgs.pgformatter}/bin/pg_format -s 2 -f 2 -U 2 -i db/migrations/*.sql"
''; '';
}; };

View File

@@ -8,6 +8,7 @@
"name": "web-app", "name": "web-app",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"github-slugger": "2.0.0",
"highlight.js": "11.10.0", "highlight.js": "11.10.0",
"marked": "14.0.0", "marked": "14.0.0",
"marked-highlight": "2.1.4" "marked-highlight": "2.1.4"
@@ -1429,6 +1430,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/glob": {
"version": "10.4.5", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",

View File

@@ -26,6 +26,7 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"github-slugger": "2.0.0",
"highlight.js": "11.10.0", "highlight.js": "11.10.0",
"marked": "14.0.0", "marked": "14.0.0",
"marked-highlight": "2.1.4" "marked-highlight": "2.1.4"

View File

@@ -1,6 +1,8 @@
import { Marked } from "marked"; import { Marked } from "marked";
import type { Renderer, Token } from "marked";
import { markedHighlight } from "marked-highlight"; import { markedHighlight } from "marked-highlight";
import hljs from "highlight.js"; import hljs from "highlight.js";
import GithubSlugger from "github-slugger";
export const sortOptions = [ export const sortOptions = [
{ value: "creation-time", text: "Creation time" }, { 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 += "</section>";
}
// Open a new section for this heading
sectionStack.push({ level, id });
const openingSection = `<section id="${id}">`;
return `
${closingSections}
${openingSection}
<h${level}>
<a href="#${id}">${text}</a>
</h${level}>
`;
}
},
hooks: {
preprocess(src: string) {
headings = [];
sectionStack = [];
slugger = new GithubSlugger();
return src;
},
postprocess(html: string) {
// Close any remaining open sections
const closingRemainingSection = "</section>".repeat(sectionStack.length);
// Generate table of contents
const tableOfContents =
headings.length > 0
? `<details>
<summary>Table of contents</summary>
<ul>
${headings
.map(
({ id, text, level }) => `
<li><a href="#${id}" class="h${level}">${text}</a></li>`
)
.join("")}
</ul>
</details>`
: "";
return `
${tableOfContents}
${html}
${closingRemainingSection}
`;
}
}
};
}
marked.use(gfmHeadingId());
return marked; return marked;
}; };
@@ -42,8 +136,6 @@ export const md = async (markdownContent: string) => {
return html; return html;
}; };
// test
export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => { export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => {
const clipboardItems = Array.from(event.clipboardData?.items || []); const clipboardItems = Array.from(event.clipboardData?.items || []);
const file = clipboardItems.find((item) => item.type.startsWith("image/")); const file = clipboardItems.find((item) => item.type.startsWith("image/"));