marked-frontmatter 1.0.0 → 1.0.1

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/dist/index.cjs CHANGED
@@ -52,21 +52,17 @@ var frontmatterExtension = {
52
52
  name: "frontmatter",
53
53
  level: "block",
54
54
  start(src) {
55
- if (src.match(/^---\r?\n/)) {
56
- return 0;
57
- }
58
- return void 0;
55
+ return src.startsWith("---") ? 0 : void 0;
59
56
  },
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;
57
+ tokenizer(src, tokens) {
58
+ if (tokens.length > 0) return void 0;
59
+ const match = src.match(/^---\s*\r?\n([\s\S]*?)\r?\n\s*---\s*(?:\r?\n|$)/);
60
+ if (!match) return void 0;
61
+ return {
62
+ type: "frontmatter",
63
+ raw: match[0],
64
+ text: match[1]
65
+ };
70
66
  },
71
67
  renderer(token) {
72
68
  const escaped = token.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1 +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"]}
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 * Frontmatter is only recognized when the entire document starts with \"---\";\n * only the first block (until the next \"---\") is extracted.\n * Use with: marked.use({ extensions: [frontmatterExtension] })\n *\n * Note: marked passes the remaining source as `src` and the tokens produced so far as `tokens`.\n * We only match when no tokens exist yet (document start) so that later \"---\" blocks are not treated as frontmatter.\n */\nexport const frontmatterExtension: TokenizerExtension & RendererExtension = {\n name: 'frontmatter',\n level: 'block',\n start(src: string) {\n return src.startsWith('---') ? 0 : undefined\n },\n tokenizer(src: string, tokens: unknown[]) {\n // Only extract frontmatter at document start (no tokens yet). Later \"---\" blocks are body content.\n if (tokens.length > 0) return undefined\n\n const match = src.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n\\s*---\\s*(?:\\r?\\n|$)/)\n if (!match) return undefined\n\n return {\n type: 'frontmatter',\n raw: match[0],\n text: match[1],\n }\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;AAWO,IAAM,uBAA+D;AAAA,EAC1E,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM,KAAa;AACjB,WAAO,IAAI,WAAW,KAAK,IAAI,IAAI;AAAA,EACrC;AAAA,EACA,UAAU,KAAa,QAAmB;AAExC,QAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,UAAM,QAAQ,IAAI,MAAM,iDAAiD;AACzE,QAAI,CAAC,MAAO,QAAO;AAEnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,MAAM,CAAC;AAAA,MACZ,MAAM,MAAM,CAAC;AAAA,IACf;AAAA,EACF;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"]}
package/dist/index.d.cts CHANGED
@@ -13,7 +13,12 @@ declare function parseFrontmatter(content: string): JsonValue;
13
13
  declare function renderFrontmatterBlocks(): void;
14
14
  /**
15
15
  * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
16
+ * Frontmatter is only recognized when the entire document starts with "---";
17
+ * only the first block (until the next "---") is extracted.
16
18
  * Use with: marked.use({ extensions: [frontmatterExtension] })
19
+ *
20
+ * Note: marked passes the remaining source as `src` and the tokens produced so far as `tokens`.
21
+ * We only match when no tokens exist yet (document start) so that later "---" blocks are not treated as frontmatter.
17
22
  */
18
23
  declare const frontmatterExtension: TokenizerExtension & RendererExtension;
19
24
 
package/dist/index.d.ts CHANGED
@@ -13,7 +13,12 @@ declare function parseFrontmatter(content: string): JsonValue;
13
13
  declare function renderFrontmatterBlocks(): void;
14
14
  /**
15
15
  * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
16
+ * Frontmatter is only recognized when the entire document starts with "---";
17
+ * only the first block (until the next "---") is extracted.
16
18
  * Use with: marked.use({ extensions: [frontmatterExtension] })
19
+ *
20
+ * Note: marked passes the remaining source as `src` and the tokens produced so far as `tokens`.
21
+ * We only match when no tokens exist yet (document start) so that later "---" blocks are not treated as frontmatter.
17
22
  */
18
23
  declare const frontmatterExtension: TokenizerExtension & RendererExtension;
19
24
 
package/dist/index.js CHANGED
@@ -26,21 +26,17 @@ var frontmatterExtension = {
26
26
  name: "frontmatter",
27
27
  level: "block",
28
28
  start(src) {
29
- if (src.match(/^---\r?\n/)) {
30
- return 0;
31
- }
32
- return void 0;
29
+ return src.startsWith("---") ? 0 : void 0;
33
30
  },
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;
31
+ tokenizer(src, tokens) {
32
+ if (tokens.length > 0) return void 0;
33
+ const match = src.match(/^---\s*\r?\n([\s\S]*?)\r?\n\s*---\s*(?:\r?\n|$)/);
34
+ if (!match) return void 0;
35
+ return {
36
+ type: "frontmatter",
37
+ raw: match[0],
38
+ text: match[1]
39
+ };
44
40
  },
45
41
  renderer(token) {
46
42
  const escaped = token.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
package/dist/index.js.map CHANGED
@@ -1 +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":[]}
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 * Frontmatter is only recognized when the entire document starts with \"---\";\n * only the first block (until the next \"---\") is extracted.\n * Use with: marked.use({ extensions: [frontmatterExtension] })\n *\n * Note: marked passes the remaining source as `src` and the tokens produced so far as `tokens`.\n * We only match when no tokens exist yet (document start) so that later \"---\" blocks are not treated as frontmatter.\n */\nexport const frontmatterExtension: TokenizerExtension & RendererExtension = {\n name: 'frontmatter',\n level: 'block',\n start(src: string) {\n return src.startsWith('---') ? 0 : undefined\n },\n tokenizer(src: string, tokens: unknown[]) {\n // Only extract frontmatter at document start (no tokens yet). Later \"---\" blocks are body content.\n if (tokens.length > 0) return undefined\n\n const match = src.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n\\s*---\\s*(?:\\r?\\n|$)/)\n if (!match) return undefined\n\n return {\n type: 'frontmatter',\n raw: match[0],\n text: match[1],\n }\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;AAWO,IAAM,uBAA+D;AAAA,EAC1E,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM,KAAa;AACjB,WAAO,IAAI,WAAW,KAAK,IAAI,IAAI;AAAA,EACrC;AAAA,EACA,UAAU,KAAa,QAAmB;AAExC,QAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,UAAM,QAAQ,IAAI,MAAM,iDAAiD;AACzE,QAAI,CAAC,MAAO,QAAO;AAEnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,KAAK,MAAM,CAAC;AAAA,MACZ,MAAM,MAAM,CAAC;AAAA,IACf;AAAA,EACF;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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marked-frontmatter",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Marked extension for rendering YAML/JSON frontmatter as HTML tree tables",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
package/src/index.ts CHANGED
@@ -38,29 +38,31 @@ export function renderFrontmatterBlocks(): void {
38
38
 
39
39
  /**
40
40
  * Marked extension that intercepts YAML/JSON frontmatter at the start of documents.
41
+ * Frontmatter is only recognized when the entire document starts with "---";
42
+ * only the first block (until the next "---") is extracted.
41
43
  * Use with: marked.use({ extensions: [frontmatterExtension] })
44
+ *
45
+ * Note: marked passes the remaining source as `src` and the tokens produced so far as `tokens`.
46
+ * We only match when no tokens exist yet (document start) so that later "---" blocks are not treated as frontmatter.
42
47
  */
43
48
  export const frontmatterExtension: TokenizerExtension & RendererExtension = {
44
49
  name: 'frontmatter',
45
50
  level: 'block',
46
51
  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
+ return src.startsWith('---') ? 0 : undefined
52
53
  },
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
- }
54
+ tokenizer(src: string, tokens: unknown[]) {
55
+ // Only extract frontmatter at document start (no tokens yet). Later "---" blocks are body content.
56
+ if (tokens.length > 0) return undefined
57
+
58
+ const match = src.match(/^---\s*\r?\n([\s\S]*?)\r?\n\s*---\s*(?:\r?\n|$)/)
59
+ if (!match) return undefined
60
+
61
+ return {
62
+ type: 'frontmatter',
63
+ raw: match[0],
64
+ text: match[1],
62
65
  }
63
- return undefined
64
66
  },
65
67
  renderer(token) {
66
68
  // Escape HTML in the raw content