marked-frontmatter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # marked-frontmatter
2
+
3
+ Marked extension for rendering YAML/JSON frontmatter as HTML tree tables.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install marked-frontmatter json-to-frontmatter-html tree-to-html marked
9
+ ```
10
+
11
+ Or install from GitHub:
12
+
13
+ ```json
14
+ {
15
+ "dependencies": {
16
+ "marked": "^17.0.0",
17
+ "marked-frontmatter": "github:iafan/marked-frontmatter",
18
+ "json-to-frontmatter-html": "github:iafan/json-to-frontmatter-html",
19
+ "tree-to-html": "github:iafan/tree-to-html"
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```typescript
27
+ import { marked } from 'marked'
28
+ import { frontmatterExtension, renderFrontmatterBlocks } from 'marked-frontmatter'
29
+
30
+ // Register the extension
31
+ marked.use({ extensions: [frontmatterExtension] })
32
+
33
+ // Parse markdown
34
+ const markdown = `---
35
+ title: My Document
36
+ author:
37
+ name: John Doe
38
+ email: john@example.com
39
+ tags:
40
+ - typescript
41
+ - markdown
42
+ ---
43
+
44
+ # Content here
45
+ `
46
+
47
+ const html = marked.parse(markdown)
48
+ document.body.innerHTML = html
49
+
50
+ // Post-process to render frontmatter
51
+ renderFrontmatterBlocks()
52
+ ```
53
+
54
+ ## How It Works
55
+
56
+ 1. The extension detects YAML frontmatter at the start of a document (between `---` delimiters)
57
+ 2. During parsing, it outputs a placeholder `<div class="frontmatter-raw">...</div>`
58
+ 3. Call `renderFrontmatterBlocks()` after the DOM is ready to convert placeholders to tree tables
59
+
60
+ The frontmatter is rendered using [json-to-frontmatter-html](https://github.com/iafan/json-to-frontmatter-html), which uses [tree-to-html](https://github.com/iafan/tree-to-html) for consistent styling.
61
+
62
+ ## JSON Frontmatter
63
+
64
+ The extension also supports JSON frontmatter:
65
+
66
+ ```markdown
67
+ ---
68
+ {
69
+ "title": "My Document",
70
+ "version": "1.0.0"
71
+ }
72
+ ---
73
+ ```
74
+
75
+ If the frontmatter content starts with `{`, it's parsed as JSON; otherwise as YAML.
76
+
77
+ ## API
78
+
79
+ ### `frontmatterExtension`
80
+
81
+ Marked extension object. Register with `marked.use({ extensions: [frontmatterExtension] })`.
82
+
83
+ ### `renderFrontmatterBlocks(): void`
84
+
85
+ Post-process the DOM to convert frontmatter placeholders to rendered HTML.
86
+
87
+ ### `parseFrontmatter(content: string)`
88
+
89
+ Parse frontmatter content string as YAML or JSON. Returns a plain JavaScript object.
90
+
91
+ ## Output HTML Structure
92
+
93
+ ```html
94
+ <div class="frontmatter-container">
95
+ <div class="tree-container">
96
+ <table class="tree-table">
97
+ <!-- Key-value rows -->
98
+ </table>
99
+ </div>
100
+ </div>
101
+ ```
102
+
103
+ The outer `.frontmatter-container` allows styling frontmatter separately from regular tree blocks.
104
+
105
+ ## Styling
106
+
107
+ Uses tree-to-html classes for the tree structure. Add styles for `.frontmatter-container` to customize the frontmatter appearance:
108
+
109
+ ```css
110
+ .frontmatter-container {
111
+ margin-bottom: 24px;
112
+ padding: 16px;
113
+ background: #f8f9fa;
114
+ border-radius: 8px;
115
+ border-left: 4px solid #0066cc;
116
+ }
117
+
118
+ .frontmatter-container .tree-comment {
119
+ color: #333;
120
+ font-style: normal;
121
+ }
122
+ ```
123
+
124
+ ## License
125
+
126
+ This is free and unencumbered software released into the public domain (Unlicense).
package/dist/index.cjs ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ frontmatterExtension: () => frontmatterExtension,
24
+ parseFrontmatter: () => parseFrontmatter,
25
+ renderFrontmatterBlocks: () => renderFrontmatterBlocks
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var import_js_yaml = require("js-yaml");
29
+ var import_json_to_frontmatter_html = require("json-to-frontmatter-html");
30
+ function parseFrontmatter(content) {
31
+ const trimmed = content.trim();
32
+ if (trimmed.startsWith("{")) {
33
+ return JSON.parse(trimmed);
34
+ }
35
+ return (0, import_js_yaml.load)(trimmed);
36
+ }
37
+ function renderFrontmatterBlocks() {
38
+ const containers = document.querySelectorAll(".frontmatter-raw");
39
+ for (const container of containers) {
40
+ const content = container.textContent || "";
41
+ if (!content.trim()) continue;
42
+ try {
43
+ const data = parseFrontmatter(content);
44
+ const html = (0, import_json_to_frontmatter_html.renderJson)(data);
45
+ container.outerHTML = `<div class="frontmatter-container">${html}</div>`;
46
+ } catch (error) {
47
+ console.error("Failed to render frontmatter:", error);
48
+ }
49
+ }
50
+ }
51
+ var frontmatterExtension = {
52
+ name: "frontmatter",
53
+ level: "block",
54
+ start(src) {
55
+ if (src.match(/^---\r?\n/)) {
56
+ return 0;
57
+ }
58
+ return void 0;
59
+ },
60
+ tokenizer(src) {
61
+ const match = src.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
62
+ if (match) {
63
+ return {
64
+ type: "frontmatter",
65
+ raw: match[0],
66
+ text: match[1]
67
+ };
68
+ }
69
+ return void 0;
70
+ },
71
+ renderer(token) {
72
+ const escaped = token.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
73
+ return `<div class="frontmatter-raw">${escaped}</div>
74
+ `;
75
+ }
76
+ };
77
+ // Annotate the CommonJS export names for ESM import in node:
78
+ 0 && (module.exports = {
79
+ frontmatterExtension,
80
+ parseFrontmatter,
81
+ renderFrontmatterBlocks
82
+ });
83
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { TokenizerExtension, RendererExtension } from 'marked'\nimport { load as parseYaml } from 'js-yaml'\nimport { renderJson, JsonValue } from 'json-to-frontmatter-html'\n\n/**\n * Parse frontmatter content as YAML or JSON.\n * If content starts with '{', parse as JSON; otherwise parse as YAML.\n */\nexport function parseFrontmatter(content: string): JsonValue {\n const trimmed = content.trim()\n if (trimmed.startsWith('{')) {\n return JSON.parse(trimmed)\n }\n return parseYaml(trimmed) as JsonValue\n}\n\n/**\n * Post-process rendered HTML to convert frontmatter placeholders to actual HTML.\n * Call this after marked.parse() completes and DOM is ready.\n */\nexport function renderFrontmatterBlocks(): void {\n const containers = document.querySelectorAll<HTMLElement>('.frontmatter-raw')\n\n for (const container of containers) {\n const content = container.textContent || ''\n if (!content.trim()) continue\n\n try {\n const data = parseFrontmatter(content)\n const html = renderJson(data)\n // Wrap in frontmatter-specific container for styling\n container.outerHTML = `<div class=\"frontmatter-container\">${html}</div>`\n } catch (error) {\n console.error('Failed to render frontmatter:', error)\n }\n }\n}\n\n/**\n * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.\n * Use with: marked.use({ extensions: [frontmatterExtension] })\n */\nexport const frontmatterExtension: TokenizerExtension & RendererExtension = {\n name: 'frontmatter',\n level: 'block',\n start(src: string) {\n // Only match at the very start of the document\n if (src.match(/^---\\r?\\n/)) {\n return 0\n }\n return undefined\n },\n tokenizer(src: string) {\n // Match YAML frontmatter: ---\\n...\\n---\n const match = src.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/)\n if (match) {\n return {\n type: 'frontmatter',\n raw: match[0],\n text: match[1],\n }\n }\n return undefined\n },\n renderer(token) {\n // Escape HTML in the raw content\n const escaped = token.text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n return `<div class=\"frontmatter-raw\">${escaped}</div>\\n`\n },\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,qBAAkC;AAClC,sCAAsC;AAM/B,SAAS,iBAAiB,SAA4B;AAC3D,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B;AACA,aAAO,eAAAA,MAAU,OAAO;AAC1B;AAMO,SAAS,0BAAgC;AAC9C,QAAM,aAAa,SAAS,iBAA8B,kBAAkB;AAE5E,aAAW,aAAa,YAAY;AAClC,UAAM,UAAU,UAAU,eAAe;AACzC,QAAI,CAAC,QAAQ,KAAK,EAAG;AAErB,QAAI;AACF,YAAM,OAAO,iBAAiB,OAAO;AACrC,YAAM,WAAO,4CAAW,IAAI;AAE5B,gBAAU,YAAY,sCAAsC,IAAI;AAAA,IAClE,SAAS,OAAO;AACd,cAAQ,MAAM,iCAAiC,KAAK;AAAA,IACtD;AAAA,EACF;AACF;AAMO,IAAM,uBAA+D;AAAA,EAC1E,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM,KAAa;AAEjB,QAAI,IAAI,MAAM,WAAW,GAAG;AAC1B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EACA,UAAU,KAAa;AAErB,UAAM,QAAQ,IAAI,MAAM,wCAAwC;AAChE,QAAI,OAAO;AACT,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,MAAM,CAAC;AAAA,QACZ,MAAM,MAAM,CAAC;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,SAAS,OAAO;AAEd,UAAM,UAAU,MAAM,KACnB,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,WAAO,gCAAgC,OAAO;AAAA;AAAA,EAChD;AACF;","names":["parseYaml"]}
@@ -0,0 +1,20 @@
1
+ import { TokenizerExtension, RendererExtension } from 'marked';
2
+ import { JsonValue } from 'json-to-frontmatter-html';
3
+
4
+ /**
5
+ * Parse frontmatter content as YAML or JSON.
6
+ * If content starts with '{', parse as JSON; otherwise parse as YAML.
7
+ */
8
+ declare function parseFrontmatter(content: string): JsonValue;
9
+ /**
10
+ * Post-process rendered HTML to convert frontmatter placeholders to actual HTML.
11
+ * Call this after marked.parse() completes and DOM is ready.
12
+ */
13
+ declare function renderFrontmatterBlocks(): void;
14
+ /**
15
+ * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
16
+ * Use with: marked.use({ extensions: [frontmatterExtension] })
17
+ */
18
+ declare const frontmatterExtension: TokenizerExtension & RendererExtension;
19
+
20
+ export { frontmatterExtension, parseFrontmatter, renderFrontmatterBlocks };
@@ -0,0 +1,20 @@
1
+ import { TokenizerExtension, RendererExtension } from 'marked';
2
+ import { JsonValue } from 'json-to-frontmatter-html';
3
+
4
+ /**
5
+ * Parse frontmatter content as YAML or JSON.
6
+ * If content starts with '{', parse as JSON; otherwise parse as YAML.
7
+ */
8
+ declare function parseFrontmatter(content: string): JsonValue;
9
+ /**
10
+ * Post-process rendered HTML to convert frontmatter placeholders to actual HTML.
11
+ * Call this after marked.parse() completes and DOM is ready.
12
+ */
13
+ declare function renderFrontmatterBlocks(): void;
14
+ /**
15
+ * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
16
+ * Use with: marked.use({ extensions: [frontmatterExtension] })
17
+ */
18
+ declare const frontmatterExtension: TokenizerExtension & RendererExtension;
19
+
20
+ export { frontmatterExtension, parseFrontmatter, renderFrontmatterBlocks };
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ // src/index.ts
2
+ import { load as parseYaml } from "js-yaml";
3
+ import { renderJson } from "json-to-frontmatter-html";
4
+ function parseFrontmatter(content) {
5
+ const trimmed = content.trim();
6
+ if (trimmed.startsWith("{")) {
7
+ return JSON.parse(trimmed);
8
+ }
9
+ return parseYaml(trimmed);
10
+ }
11
+ function renderFrontmatterBlocks() {
12
+ const containers = document.querySelectorAll(".frontmatter-raw");
13
+ for (const container of containers) {
14
+ const content = container.textContent || "";
15
+ if (!content.trim()) continue;
16
+ try {
17
+ const data = parseFrontmatter(content);
18
+ const html = renderJson(data);
19
+ container.outerHTML = `<div class="frontmatter-container">${html}</div>`;
20
+ } catch (error) {
21
+ console.error("Failed to render frontmatter:", error);
22
+ }
23
+ }
24
+ }
25
+ var frontmatterExtension = {
26
+ name: "frontmatter",
27
+ level: "block",
28
+ start(src) {
29
+ if (src.match(/^---\r?\n/)) {
30
+ return 0;
31
+ }
32
+ return void 0;
33
+ },
34
+ tokenizer(src) {
35
+ const match = src.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
36
+ if (match) {
37
+ return {
38
+ type: "frontmatter",
39
+ raw: match[0],
40
+ text: match[1]
41
+ };
42
+ }
43
+ return void 0;
44
+ },
45
+ renderer(token) {
46
+ const escaped = token.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
47
+ return `<div class="frontmatter-raw">${escaped}</div>
48
+ `;
49
+ }
50
+ };
51
+ export {
52
+ frontmatterExtension,
53
+ parseFrontmatter,
54
+ renderFrontmatterBlocks
55
+ };
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { TokenizerExtension, RendererExtension } from 'marked'\nimport { load as parseYaml } from 'js-yaml'\nimport { renderJson, JsonValue } from 'json-to-frontmatter-html'\n\n/**\n * Parse frontmatter content as YAML or JSON.\n * If content starts with '{', parse as JSON; otherwise parse as YAML.\n */\nexport function parseFrontmatter(content: string): JsonValue {\n const trimmed = content.trim()\n if (trimmed.startsWith('{')) {\n return JSON.parse(trimmed)\n }\n return parseYaml(trimmed) as JsonValue\n}\n\n/**\n * Post-process rendered HTML to convert frontmatter placeholders to actual HTML.\n * Call this after marked.parse() completes and DOM is ready.\n */\nexport function renderFrontmatterBlocks(): void {\n const containers = document.querySelectorAll<HTMLElement>('.frontmatter-raw')\n\n for (const container of containers) {\n const content = container.textContent || ''\n if (!content.trim()) continue\n\n try {\n const data = parseFrontmatter(content)\n const html = renderJson(data)\n // Wrap in frontmatter-specific container for styling\n container.outerHTML = `<div class=\"frontmatter-container\">${html}</div>`\n } catch (error) {\n console.error('Failed to render frontmatter:', error)\n }\n }\n}\n\n/**\n * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.\n * Use with: marked.use({ extensions: [frontmatterExtension] })\n */\nexport const frontmatterExtension: TokenizerExtension & RendererExtension = {\n name: 'frontmatter',\n level: 'block',\n start(src: string) {\n // Only match at the very start of the document\n if (src.match(/^---\\r?\\n/)) {\n return 0\n }\n return undefined\n },\n tokenizer(src: string) {\n // Match YAML frontmatter: ---\\n...\\n---\n const match = src.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/)\n if (match) {\n return {\n type: 'frontmatter',\n raw: match[0],\n text: match[1],\n }\n }\n return undefined\n },\n renderer(token) {\n // Escape HTML in the raw content\n const escaped = token.text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n return `<div class=\"frontmatter-raw\">${escaped}</div>\\n`\n },\n}\n\n"],"mappings":";AACA,SAAS,QAAQ,iBAAiB;AAClC,SAAS,kBAA6B;AAM/B,SAAS,iBAAiB,SAA4B;AAC3D,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,QAAQ,WAAW,GAAG,GAAG;AAC3B,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B;AACA,SAAO,UAAU,OAAO;AAC1B;AAMO,SAAS,0BAAgC;AAC9C,QAAM,aAAa,SAAS,iBAA8B,kBAAkB;AAE5E,aAAW,aAAa,YAAY;AAClC,UAAM,UAAU,UAAU,eAAe;AACzC,QAAI,CAAC,QAAQ,KAAK,EAAG;AAErB,QAAI;AACF,YAAM,OAAO,iBAAiB,OAAO;AACrC,YAAM,OAAO,WAAW,IAAI;AAE5B,gBAAU,YAAY,sCAAsC,IAAI;AAAA,IAClE,SAAS,OAAO;AACd,cAAQ,MAAM,iCAAiC,KAAK;AAAA,IACtD;AAAA,EACF;AACF;AAMO,IAAM,uBAA+D;AAAA,EAC1E,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM,KAAa;AAEjB,QAAI,IAAI,MAAM,WAAW,GAAG;AAC1B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EACA,UAAU,KAAa;AAErB,UAAM,QAAQ,IAAI,MAAM,wCAAwC;AAChE,QAAI,OAAO;AACT,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,MAAM,CAAC;AAAA,QACZ,MAAM,MAAM,CAAC;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EACA,SAAS,OAAO;AAEd,UAAM,UAAU,MAAM,KACnB,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACvB,WAAO,gCAAgC,OAAO;AAAA;AAAA,EAChD;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "marked-frontmatter",
3
+ "version": "1.0.0",
4
+ "description": "Marked extension for rendering YAML/JSON frontmatter as HTML tree tables",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "keywords": [
26
+ "marked",
27
+ "frontmatter",
28
+ "yaml",
29
+ "json",
30
+ "html",
31
+ "extension"
32
+ ],
33
+ "author": "Igor Afanasyev",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/iafan/marked-frontmatter.git"
37
+ },
38
+ "license": "Unlicense",
39
+ "dependencies": {
40
+ "js-yaml": "^4.1.0"
41
+ },
42
+ "peerDependencies": {
43
+ "json-to-frontmatter-html": ">=1.0.0",
44
+ "marked": ">=4.0.0",
45
+ "tree-to-html": ">=1.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/js-yaml": "^4.0.9",
49
+ "json-to-frontmatter-html": "^1.0.0",
50
+ "marked": "^17.0.1",
51
+ "tree-to-html": "^1.0.0",
52
+ "tsup": "^8.5.0",
53
+ "typescript": "^5.9.3"
54
+ }
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { TokenizerExtension, RendererExtension } from 'marked'
2
+ import { load as parseYaml } from 'js-yaml'
3
+ import { renderJson, JsonValue } from 'json-to-frontmatter-html'
4
+
5
+ /**
6
+ * Parse frontmatter content as YAML or JSON.
7
+ * If content starts with '{', parse as JSON; otherwise parse as YAML.
8
+ */
9
+ export function parseFrontmatter(content: string): JsonValue {
10
+ const trimmed = content.trim()
11
+ if (trimmed.startsWith('{')) {
12
+ return JSON.parse(trimmed)
13
+ }
14
+ return parseYaml(trimmed) as JsonValue
15
+ }
16
+
17
+ /**
18
+ * Post-process rendered HTML to convert frontmatter placeholders to actual HTML.
19
+ * Call this after marked.parse() completes and DOM is ready.
20
+ */
21
+ export function renderFrontmatterBlocks(): void {
22
+ const containers = document.querySelectorAll<HTMLElement>('.frontmatter-raw')
23
+
24
+ for (const container of containers) {
25
+ const content = container.textContent || ''
26
+ if (!content.trim()) continue
27
+
28
+ try {
29
+ const data = parseFrontmatter(content)
30
+ const html = renderJson(data)
31
+ // Wrap in frontmatter-specific container for styling
32
+ container.outerHTML = `<div class="frontmatter-container">${html}</div>`
33
+ } catch (error) {
34
+ console.error('Failed to render frontmatter:', error)
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
41
+ * Use with: marked.use({ extensions: [frontmatterExtension] })
42
+ */
43
+ export const frontmatterExtension: TokenizerExtension & RendererExtension = {
44
+ name: 'frontmatter',
45
+ level: 'block',
46
+ start(src: string) {
47
+ // Only match at the very start of the document
48
+ if (src.match(/^---\r?\n/)) {
49
+ return 0
50
+ }
51
+ return undefined
52
+ },
53
+ tokenizer(src: string) {
54
+ // Match YAML frontmatter: ---\n...\n---
55
+ const match = src.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)
56
+ if (match) {
57
+ return {
58
+ type: 'frontmatter',
59
+ raw: match[0],
60
+ text: match[1],
61
+ }
62
+ }
63
+ return undefined
64
+ },
65
+ renderer(token) {
66
+ // Escape HTML in the raw content
67
+ const escaped = token.text
68
+ .replace(/&/g, '&amp;')
69
+ .replace(/</g, '&lt;')
70
+ .replace(/>/g, '&gt;')
71
+ return `<div class="frontmatter-raw">${escaped}</div>\n`
72
+ },
73
+ }
74
+