@raspberrypifoundation/rpf-markdown-core 0.1.1 → 0.1.2

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.
Files changed (3) hide show
  1. package/dist/index.cjs +287 -237
  2. package/dist/index.js +288 -238
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -45,12 +45,16 @@ function escapeHtml(value) {
45
45
  }
46
46
 
47
47
  // src/block_renderers.ts
48
+ var parseMarkdown = (input) => import_marked.marked.parse(input);
49
+ function setMarkdownParser(parse) {
50
+ parseMarkdown = parse;
51
+ }
48
52
  var CALLOUT_HEADINGS = {
49
53
  debug: "Debugging",
50
54
  tip: "Tip"
51
55
  };
52
56
  function renderMarkdown(input) {
53
- return import_marked.marked.parse(input);
57
+ return parseMarkdown(input);
54
58
  }
55
59
  function renderAccordion({
56
60
  modifier,
@@ -408,7 +412,8 @@ function getOrCreateScratchblocksRenderer() {
408
412
  return rendererCache;
409
413
  }
410
414
  function parseScratchblocksBlock(fencedBlock) {
411
- const fenceMatch = fencedBlock.match(SCRATCHBLOCKS_FENCED_BLOCK_REGEX);
415
+ const normalizedBlock = fencedBlock.replace(/\r\n/g, "\n").trimEnd();
416
+ const fenceMatch = normalizedBlock.match(SCRATCHBLOCKS_FENCED_BLOCK_REGEX);
412
417
  if (!fenceMatch) {
413
418
  throw new Error(SCRATCHBLOCKS_PARSE_ERROR);
414
419
  }
@@ -416,14 +421,14 @@ function parseScratchblocksBlock(fencedBlock) {
416
421
  const code = fenceMatch[3] ?? "";
417
422
  const style = mapFenceInfoToStyle(infoString);
418
423
  try {
419
- const renderer = getOrCreateScratchblocksRenderer();
420
- const doc = renderer.api.parse(code, { languages: ["en"] });
421
- const svgElement = renderer.api.render(doc, {
424
+ const renderer2 = getOrCreateScratchblocksRenderer();
425
+ const doc = renderer2.api.parse(code, { languages: ["en"] });
426
+ const svgElement = renderer2.api.render(doc, {
422
427
  style,
423
428
  languages: ["en"],
424
429
  scale: 1
425
430
  });
426
- const svg = embedStylesIntoSvg(svgElement.outerHTML, renderer.stylesHtml);
431
+ const svg = embedStylesIntoSvg(svgElement.outerHTML, renderer2.stylesHtml);
427
432
  const html = `<div class="c-project-scratchblocks" data-style="${style}">${svg}</div>`;
428
433
  return { html, style, svg };
429
434
  } catch (error) {
@@ -976,268 +981,313 @@ function createLegacyBlockExtension(name, blockName, render, appendNewline = tru
976
981
  }
977
982
  };
978
983
  }
979
- function canUseHtmlRenderer(content) {
980
- return /^\s*(?:<!doctype\b|<!--|<(?:article|aside|blockquote|br|code|details|div|dl|figure|footer|h[1-6]|header|hr|main|nav|ol|p|pre|section|table|ul)\b)/i.test(
981
- content
982
- );
983
- }
984
- function serializeHtmlRendererNode(node) {
985
- if ("outerHTML" in node && typeof node.outerHTML === "string") {
986
- return node.outerHTML;
984
+ var RAW_HTML_BLOCK_TAGS = "article|aside|blockquote|body|details|dialog|div|dl|fieldset|figure|footer|form|h[1-6]|head|header|hr|html|iframe|main|nav|ol|p|pre|script|section|style|svg|table|ul|video";
985
+ var RAW_HTML_VOID_TAGS = /* @__PURE__ */ new Set(["hr", "br", "img", "input", "wbr", "col"]);
986
+ var RAW_HTML_BLOCK_AT_START = new RegExp(
987
+ `^[ \\t]*(?:<!--|<!doctype\\b|<(?:${RAW_HTML_BLOCK_TAGS})(?=[\\s/>]))`,
988
+ "i"
989
+ );
990
+ var RAW_HTML_BLOCK_SEARCH = new RegExp(RAW_HTML_BLOCK_AT_START.source, "im");
991
+ function consumeTrailingNewline(src, end) {
992
+ const after = /^[ \t]*\r?\n/.exec(src.slice(end));
993
+ return src.slice(0, end + (after?.[0].length ?? 0));
994
+ }
995
+ function matchRawHtmlBlock(src) {
996
+ if (!RAW_HTML_BLOCK_AT_START.test(src)) return void 0;
997
+ const leading = /^[ \t]*/.exec(src)?.[0].length ?? 0;
998
+ const rest = src.slice(leading);
999
+ if (rest.startsWith("<!--")) {
1000
+ const end = rest.indexOf("-->");
1001
+ if (end === -1) return void 0;
1002
+ return consumeTrailingNewline(src, leading + end + 3);
987
1003
  }
988
- if (node.nodeType === 8) {
989
- return `<!--${node.textContent ?? ""}-->`;
1004
+ const doctype = /^<!doctype\b[^>]*>/i.exec(rest);
1005
+ if (doctype) {
1006
+ return consumeTrailingNewline(src, leading + doctype[0].length);
990
1007
  }
991
- return node.textContent ?? "";
992
- }
993
- function serializeHtmlRendererOutput(fragment) {
994
- return Array.from(fragment.childNodes).map(serializeHtmlRendererNode).join("");
995
- }
996
- function processHtmlContent(content) {
997
- if (typeof document === "undefined") {
998
- return applyInlineCodeClasses(content);
1008
+ const open = /^<([a-z][a-z0-9]*)(?=[\s/>])[^>]*>/i.exec(rest);
1009
+ if (!open) return void 0;
1010
+ const tag = (open[1] ?? "").toLowerCase();
1011
+ if (open[0].endsWith("/>") || RAW_HTML_VOID_TAGS.has(tag)) {
1012
+ return consumeTrailingNewline(src, leading + open[0].length);
999
1013
  }
1000
- const template = document.createElement("template");
1001
- template.innerHTML = content;
1002
- return applyInlineCodeClasses(serializeHtmlRendererOutput(template.content));
1003
- }
1004
- function processEditorProject(content) {
1005
- if (canUseHtmlRenderer(content)) {
1006
- return processHtmlContent(content);
1014
+ const tagRe = new RegExp(`<(/?)${tag}(?=[\\s/>])[^>]*>`, "ig");
1015
+ tagRe.lastIndex = leading;
1016
+ let depth = 0;
1017
+ let match;
1018
+ while ((match = tagRe.exec(src)) !== null) {
1019
+ if (match[1] === "/") {
1020
+ depth--;
1021
+ } else if (!match[0].endsWith("/>")) {
1022
+ depth++;
1023
+ }
1024
+ if (depth === 0) {
1025
+ return consumeTrailingNewline(src, tagRe.lastIndex);
1026
+ }
1007
1027
  }
1008
- import_marked2.marked.use(markedSmartypantsLite());
1009
- import_marked2.marked.use({
1010
- extensions: [
1011
- {
1012
- name: "htmlLineBreak",
1013
- level: "block",
1014
- start(src) {
1015
- return src.search(/^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/m);
1016
- },
1017
- tokenizer(src) {
1018
- const match = /^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/.exec(src);
1019
- if (match) {
1020
- return { type: "htmlLineBreak", raw: match[0] };
1021
- }
1022
- },
1023
- renderer() {
1024
- return "<div class=page-break></div>\n";
1028
+ return void 0;
1029
+ }
1030
+ var renderer = new import_marked2.Marked();
1031
+ renderer.use(markedSmartypantsLite());
1032
+ renderer.use({
1033
+ extensions: [
1034
+ // Generic raw HTML passthrough. marked registers extension tokenizers by
1035
+ // unshifting, so dispatch order is the reverse of this array — placing
1036
+ // this first means it is tried LAST, letting the specific HTML
1037
+ // transformers below (callouts, output, wrapped code) take precedence.
1038
+ // Any other top-level HTML element is captured whole and emitted verbatim
1039
+ // so its interior is never re-parsed as markdown.
1040
+ {
1041
+ name: "rawHtmlBlock",
1042
+ level: "block",
1043
+ start(src) {
1044
+ const index = src.search(RAW_HTML_BLOCK_SEARCH);
1045
+ return index < 0 ? void 0 : index;
1046
+ },
1047
+ tokenizer(src) {
1048
+ const raw = matchRawHtmlBlock(src);
1049
+ if (raw) {
1050
+ return { type: "rawHtmlBlock", raw, text: raw };
1025
1051
  }
1026
1052
  },
1027
- {
1028
- name: "preservedFence",
1029
- level: "block",
1030
- start(src) {
1031
- return src.search(/^[ \t]*(`{3,}|~{3,})/m);
1032
- },
1033
- tokenizer(src) {
1034
- const match = src.match(
1035
- /^[ \t]*(`{3,}|~{3,})([^\r\n]*)\r?\n([\s\S]*?)\r?\n[ \t]*\1[ \t]*(?:\n|$)/
1036
- );
1037
- if (!match) return;
1038
- return {
1039
- type: "preservedFence",
1040
- raw: match[0],
1041
- text: match[3] ?? "",
1042
- info: (match[2] ?? "").trim()
1043
- };
1044
- },
1045
- renderer(token) {
1046
- return renderMarkedCodeToken(token, token.raw);
1053
+ renderer(token) {
1054
+ return token.text;
1055
+ }
1056
+ },
1057
+ {
1058
+ name: "htmlLineBreak",
1059
+ level: "block",
1060
+ start(src) {
1061
+ return src.search(/^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/m);
1062
+ },
1063
+ tokenizer(src) {
1064
+ const match = /^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/.exec(src);
1065
+ if (match) {
1066
+ return { type: "htmlLineBreak", raw: match[0] };
1047
1067
  }
1048
1068
  },
1049
- // RFM alert blocks: > [!TASK], > [!ACCORDION], etc.
1050
- {
1051
- name: "rfmBlock",
1052
- level: "block",
1053
- start(src) {
1054
- return src.search(/^[ \t]*>+[ \t]?\[!/m);
1055
- },
1056
- tokenizer(src) {
1057
- const raw = matchRfmBlock(src);
1058
- if (raw !== void 0) {
1059
- return { type: "rfmBlock", raw, text: raw };
1060
- }
1061
- },
1062
- renderer(token) {
1063
- const raw = token.text;
1064
- const hintBlocks = splitTopLevelRfmHintBlocks(raw);
1065
- if (hintBlocks.length > 1) {
1066
- return `${renderHintsPanel(hintBlocks.map(getRfmHintBody))}
1067
- `;
1068
- }
1069
- return `${parseRfmBlock(raw).html}
1070
- `;
1069
+ renderer() {
1070
+ return "<div class=page-break></div>\n";
1071
+ }
1072
+ },
1073
+ {
1074
+ name: "preservedFence",
1075
+ level: "block",
1076
+ start(src) {
1077
+ return src.search(/^[ \t]*(`{3,}|~{3,})/m);
1078
+ },
1079
+ tokenizer(src) {
1080
+ const match = src.match(
1081
+ /^[ \t]*(`{3,}|~{3,})([^\r\n]*)\r?\n([\s\S]*?)\r?\n[ \t]*\1[ \t]*(?:\n|$)/
1082
+ );
1083
+ if (!match) return;
1084
+ return {
1085
+ type: "preservedFence",
1086
+ raw: match[0],
1087
+ text: match[3] ?? "",
1088
+ info: (match[2] ?? "").trim()
1089
+ };
1090
+ },
1091
+ renderer(token) {
1092
+ return renderMarkedCodeToken(token, token.raw);
1093
+ }
1094
+ },
1095
+ // RFM alert blocks: > [!TASK], > [!ACCORDION], etc.
1096
+ {
1097
+ name: "rfmBlock",
1098
+ level: "block",
1099
+ start(src) {
1100
+ return src.search(/^[ \t]*>+[ \t]?\[!/m);
1101
+ },
1102
+ tokenizer(src) {
1103
+ const raw = matchRfmBlock(src);
1104
+ if (raw !== void 0) {
1105
+ return { type: "rfmBlock", raw, text: raw };
1071
1106
  }
1072
1107
  },
1073
- // RPF callout blocks: <div class="c-project-callout c-project-callout--{type}">
1074
- {
1075
- name: "rpfCallout",
1076
- level: "block",
1077
- start(src) {
1078
- return src.search(
1079
- /<div\s+class="c-project-callout\s+c-project-callout--/
1080
- );
1081
- },
1082
- tokenizer(src) {
1083
- const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
1084
- src
1085
- );
1086
- if (match) {
1087
- return { type: "rpfCallout", raw: match[0], text: match[0] };
1088
- }
1089
- },
1090
- renderer(token) {
1091
- return `${parseCalloutBlock(token.text).html}
1108
+ renderer(token) {
1109
+ const raw = token.text;
1110
+ const hintBlocks = splitTopLevelRfmHintBlocks(raw);
1111
+ if (hintBlocks.length > 1) {
1112
+ return `${renderHintsPanel(hintBlocks.map(getRfmHintBody))}
1092
1113
  `;
1093
1114
  }
1094
- },
1095
- // RPF task blocks: --- task --- ... --- /task ---
1096
- {
1097
- name: "rpfTask",
1098
- level: "block",
1099
- start(src) {
1100
- return src.search(/^--- task ---$/m);
1101
- },
1102
- tokenizer(src) {
1103
- const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
1104
- if (match) {
1105
- return { type: "rpfTask", raw: match[0], text: match[0] };
1106
- }
1107
- },
1108
- renderer(token) {
1109
- return `${parseTaskBlock(token.text).html}
1115
+ return `${parseRfmBlock(raw).html}
1110
1116
  `;
1117
+ }
1118
+ },
1119
+ // RPF callout blocks: <div class="c-project-callout c-project-callout--{type}">
1120
+ {
1121
+ name: "rpfCallout",
1122
+ level: "block",
1123
+ start(src) {
1124
+ return src.search(
1125
+ /<div\s+class="c-project-callout\s+c-project-callout--/
1126
+ );
1127
+ },
1128
+ tokenizer(src) {
1129
+ const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
1130
+ src
1131
+ );
1132
+ if (match) {
1133
+ return { type: "rpfCallout", raw: match[0], text: match[0] };
1111
1134
  }
1112
1135
  },
1113
- // RPF collapse blocks: --- collapse --- ... --- /collapse ---
1114
- createLegacyBlockExtension("rpfCollapse", "collapse", parseCollapseBlock),
1115
- // RPF hints blocks: --- hints --- ... --- /hints ---
1116
- createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
1117
- // RPF hint blocks: --- hint --- ... --- /hint ---
1118
- createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
1119
- // RPF challenge blocks: --- challenge --- ... --- /challenge ---
1120
- createLegacyBlockExtension(
1121
- "rpfChallenge",
1122
- "challenge",
1123
- parseChallengeBlock,
1124
- false
1125
- ),
1126
- // RPF print visibility blocks.
1127
- createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
1128
- createLegacyBlockExtension(
1129
- "rpfPrintOnly",
1130
- "print-only",
1131
- parsePrintOnlyBlock
1132
- ),
1133
- // RPF save block: --- save ---
1134
- {
1135
- name: "rpfSave",
1136
- level: "block",
1137
- start(src) {
1138
- return src.search(/^[ \t]*--- save ---$/m);
1139
- },
1140
- tokenizer(src) {
1141
- const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
1142
- if (match) {
1143
- return { type: "rpfSave", raw: match[0], text: match[0] };
1144
- }
1145
- },
1146
- renderer() {
1147
- return `${parseSaveBlock()}
1136
+ renderer(token) {
1137
+ return `${parseCalloutBlock(token.text).html}
1148
1138
  `;
1139
+ }
1140
+ },
1141
+ // RPF task blocks: --- task --- ... --- /task ---
1142
+ {
1143
+ name: "rpfTask",
1144
+ level: "block",
1145
+ start(src) {
1146
+ return src.search(/^--- task ---$/m);
1147
+ },
1148
+ tokenizer(src) {
1149
+ const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
1150
+ if (match) {
1151
+ return { type: "rpfTask", raw: match[0], text: match[0] };
1149
1152
  }
1150
1153
  },
1151
- // RPF code blocks: --- code --- ... --- /code ---
1152
- // Also handles the wrapped form: <div class="c-project-code">\n--- code ---\n...\n--- /code ---\n</div>
1153
- {
1154
- name: "rpfCode",
1155
- level: "block",
1156
- start(src) {
1157
- const bare = src.search(/^--- code ---$/m);
1158
- const wrapped = src.search(/^<div class="c-project-code">/m);
1159
- if (bare === -1) return wrapped;
1160
- if (wrapped === -1) return bare;
1161
- return Math.min(bare, wrapped);
1162
- },
1163
- tokenizer(src) {
1164
- const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
1165
- src
1166
- );
1167
- if (wrappedMatch) {
1168
- return {
1169
- type: "rpfCode",
1170
- raw: wrappedMatch[0],
1171
- text: wrappedMatch[1]
1172
- };
1173
- }
1174
- const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
1175
- if (match) {
1176
- return { type: "rpfCode", raw: match[0], text: match[0] };
1177
- }
1178
- },
1179
- renderer(token) {
1180
- return `${parseRpfCodeBlock(token.text).html}
1154
+ renderer(token) {
1155
+ return `${parseTaskBlock(token.text).html}
1181
1156
  `;
1157
+ }
1158
+ },
1159
+ // RPF collapse blocks: --- collapse --- ... --- /collapse ---
1160
+ createLegacyBlockExtension("rpfCollapse", "collapse", parseCollapseBlock),
1161
+ // RPF hints blocks: --- hints --- ... --- /hints ---
1162
+ createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
1163
+ // RPF hint blocks: --- hint --- ... --- /hint ---
1164
+ createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
1165
+ // RPF challenge blocks: --- challenge --- ... --- /challenge ---
1166
+ createLegacyBlockExtension(
1167
+ "rpfChallenge",
1168
+ "challenge",
1169
+ parseChallengeBlock,
1170
+ false
1171
+ ),
1172
+ // RPF print visibility blocks.
1173
+ createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
1174
+ createLegacyBlockExtension(
1175
+ "rpfPrintOnly",
1176
+ "print-only",
1177
+ parsePrintOnlyBlock
1178
+ ),
1179
+ // RPF save block: --- save ---
1180
+ {
1181
+ name: "rpfSave",
1182
+ level: "block",
1183
+ start(src) {
1184
+ return src.search(/^[ \t]*--- save ---$/m);
1185
+ },
1186
+ tokenizer(src) {
1187
+ const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
1188
+ if (match) {
1189
+ return { type: "rpfSave", raw: match[0], text: match[0] };
1182
1190
  }
1183
1191
  },
1184
- // RPF output blocks: <div class="c-project-output"> ... </div>
1185
- {
1186
- name: "rpfOutput",
1187
- level: "block",
1188
- start(src) {
1189
- return src.search(/^<div\s+class="c-project-output">\s*$/m);
1190
- },
1191
- tokenizer(src) {
1192
- const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(src);
1193
- if (match) {
1194
- return { type: "rpfOutput", raw: match[0], text: match[0] };
1195
- }
1196
- },
1197
- renderer(token) {
1198
- return `${parseOutputBlock(token.text)}
1192
+ renderer() {
1193
+ return `${parseSaveBlock()}
1199
1194
  `;
1195
+ }
1196
+ },
1197
+ // RPF code blocks: --- code --- ... --- /code ---
1198
+ // Also handles the wrapped form: <div class="c-project-code">\n--- code ---\n...\n--- /code ---\n</div>
1199
+ {
1200
+ name: "rpfCode",
1201
+ level: "block",
1202
+ start(src) {
1203
+ const bare = src.search(/^--- code ---$/m);
1204
+ const wrapped = src.search(/^<div class="c-project-code">/m);
1205
+ if (bare === -1) return wrapped;
1206
+ if (wrapped === -1) return bare;
1207
+ return Math.min(bare, wrapped);
1208
+ },
1209
+ tokenizer(src) {
1210
+ const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
1211
+ src
1212
+ );
1213
+ if (wrappedMatch) {
1214
+ return {
1215
+ type: "rpfCode",
1216
+ raw: wrappedMatch[0],
1217
+ text: wrappedMatch[1]
1218
+ };
1219
+ }
1220
+ const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
1221
+ if (match) {
1222
+ return { type: "rpfCode", raw: match[0], text: match[0] };
1200
1223
  }
1224
+ },
1225
+ renderer(token) {
1226
+ return `${parseRpfCodeBlock(token.text).html}
1227
+ `;
1201
1228
  }
1202
- ],
1203
- renderer: {
1204
- // Override fenced code blocks to use Prism with our custom options.
1205
- code(token) {
1206
- return renderMarkedCodeToken(token);
1229
+ },
1230
+ // RPF output blocks: <div class="c-project-output"> ... </div>
1231
+ {
1232
+ name: "rpfOutput",
1233
+ level: "block",
1234
+ start(src) {
1235
+ return src.search(/^<div\s+class="c-project-output">\s*$/m);
1207
1236
  },
1208
- listitem(item) {
1209
- const itemTokens = Array.isArray(item.tokens) ? item.tokens : [];
1210
- const raw = typeof item.raw === "string" ? item.raw : "";
1211
- const rawFenceBlocks = extractListItemFenceBlocks(raw);
1212
- let rawFenceIndex = 0;
1213
- const firstNewline = raw.indexOf("\n");
1214
- const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
1215
- let body = "";
1216
- for (let index = 0; index < itemTokens.length; index++) {
1217
- const token = itemTokens[index];
1218
- if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
1219
- body += this.parser.parseInline(token.tokens ?? []);
1220
- continue;
1221
- }
1222
- if (token?.type === "code" || token?.type === "preservedFence") {
1223
- body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
1224
- rawFenceIndex++;
1225
- continue;
1226
- }
1227
- body += this.parser.parse([token]).trimStart();
1237
+ tokenizer(src) {
1238
+ const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(
1239
+ src
1240
+ );
1241
+ if (match) {
1242
+ return { type: "rpfOutput", raw: match[0], text: match[0] };
1228
1243
  }
1229
- return `<li>${body}</li>
1244
+ },
1245
+ renderer(token) {
1246
+ return `${parseOutputBlock(token.text)}
1230
1247
  `;
1231
1248
  }
1249
+ }
1250
+ ],
1251
+ renderer: {
1252
+ // Override fenced code blocks to use Prism with our custom options.
1253
+ code(token) {
1254
+ return renderMarkedCodeToken(token);
1232
1255
  },
1233
- hooks: {
1234
- postprocess(html) {
1235
- return applyInlineCodeClasses(html);
1256
+ listitem(item) {
1257
+ const itemTokens = Array.isArray(item.tokens) ? item.tokens : [];
1258
+ const raw = typeof item.raw === "string" ? item.raw : "";
1259
+ const rawFenceBlocks = extractListItemFenceBlocks(raw);
1260
+ let rawFenceIndex = 0;
1261
+ const firstNewline = raw.indexOf("\n");
1262
+ const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
1263
+ let body = "";
1264
+ for (let index = 0; index < itemTokens.length; index++) {
1265
+ const token = itemTokens[index];
1266
+ if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
1267
+ body += this.parser.parseInline(token.tokens ?? []);
1268
+ continue;
1269
+ }
1270
+ if (token?.type === "code" || token?.type === "preservedFence") {
1271
+ body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
1272
+ rawFenceIndex++;
1273
+ continue;
1274
+ }
1275
+ body += this.parser.parse([token]).trimStart();
1236
1276
  }
1277
+ return `<li>${body}</li>
1278
+ `;
1237
1279
  }
1238
- });
1239
- import_marked2.marked.use((0, import_marked_gfm_heading_id.gfmHeadingId)({ prefix: "" }));
1240
- return import_marked2.marked.parse(content);
1280
+ },
1281
+ hooks: {
1282
+ postprocess(html) {
1283
+ return applyInlineCodeClasses(html);
1284
+ }
1285
+ }
1286
+ });
1287
+ renderer.use((0, import_marked_gfm_heading_id.gfmHeadingId)({ prefix: "" }));
1288
+ setMarkdownParser((input) => renderer.parse(input));
1289
+ function processEditorProject(content) {
1290
+ return renderer.parse(content);
1241
1291
  }
1242
1292
  // Annotate the CommonJS export names for ESM import in node:
1243
1293
  0 && (module.exports = {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { marked as marked2 } from "marked";
2
+ import { Marked } from "marked";
3
3
  import { gfmHeadingId } from "marked-gfm-heading-id";
4
4
 
5
5
  // src/block_renderers.ts
@@ -11,12 +11,16 @@ function escapeHtml(value) {
11
11
  }
12
12
 
13
13
  // src/block_renderers.ts
14
+ var parseMarkdown = (input) => marked.parse(input);
15
+ function setMarkdownParser(parse) {
16
+ parseMarkdown = parse;
17
+ }
14
18
  var CALLOUT_HEADINGS = {
15
19
  debug: "Debugging",
16
20
  tip: "Tip"
17
21
  };
18
22
  function renderMarkdown(input) {
19
- return marked.parse(input);
23
+ return parseMarkdown(input);
20
24
  }
21
25
  function renderAccordion({
22
26
  modifier,
@@ -373,7 +377,8 @@ function getOrCreateScratchblocksRenderer() {
373
377
  return rendererCache;
374
378
  }
375
379
  function parseScratchblocksBlock(fencedBlock) {
376
- const fenceMatch = fencedBlock.match(SCRATCHBLOCKS_FENCED_BLOCK_REGEX);
380
+ const normalizedBlock = fencedBlock.replace(/\r\n/g, "\n").trimEnd();
381
+ const fenceMatch = normalizedBlock.match(SCRATCHBLOCKS_FENCED_BLOCK_REGEX);
377
382
  if (!fenceMatch) {
378
383
  throw new Error(SCRATCHBLOCKS_PARSE_ERROR);
379
384
  }
@@ -381,14 +386,14 @@ function parseScratchblocksBlock(fencedBlock) {
381
386
  const code = fenceMatch[3] ?? "";
382
387
  const style = mapFenceInfoToStyle(infoString);
383
388
  try {
384
- const renderer = getOrCreateScratchblocksRenderer();
385
- const doc = renderer.api.parse(code, { languages: ["en"] });
386
- const svgElement = renderer.api.render(doc, {
389
+ const renderer2 = getOrCreateScratchblocksRenderer();
390
+ const doc = renderer2.api.parse(code, { languages: ["en"] });
391
+ const svgElement = renderer2.api.render(doc, {
387
392
  style,
388
393
  languages: ["en"],
389
394
  scale: 1
390
395
  });
391
- const svg = embedStylesIntoSvg(svgElement.outerHTML, renderer.stylesHtml);
396
+ const svg = embedStylesIntoSvg(svgElement.outerHTML, renderer2.stylesHtml);
392
397
  const html = `<div class="c-project-scratchblocks" data-style="${style}">${svg}</div>`;
393
398
  return { html, style, svg };
394
399
  } catch (error) {
@@ -941,268 +946,313 @@ function createLegacyBlockExtension(name, blockName, render, appendNewline = tru
941
946
  }
942
947
  };
943
948
  }
944
- function canUseHtmlRenderer(content) {
945
- return /^\s*(?:<!doctype\b|<!--|<(?:article|aside|blockquote|br|code|details|div|dl|figure|footer|h[1-6]|header|hr|main|nav|ol|p|pre|section|table|ul)\b)/i.test(
946
- content
947
- );
948
- }
949
- function serializeHtmlRendererNode(node) {
950
- if ("outerHTML" in node && typeof node.outerHTML === "string") {
951
- return node.outerHTML;
949
+ var RAW_HTML_BLOCK_TAGS = "article|aside|blockquote|body|details|dialog|div|dl|fieldset|figure|footer|form|h[1-6]|head|header|hr|html|iframe|main|nav|ol|p|pre|script|section|style|svg|table|ul|video";
950
+ var RAW_HTML_VOID_TAGS = /* @__PURE__ */ new Set(["hr", "br", "img", "input", "wbr", "col"]);
951
+ var RAW_HTML_BLOCK_AT_START = new RegExp(
952
+ `^[ \\t]*(?:<!--|<!doctype\\b|<(?:${RAW_HTML_BLOCK_TAGS})(?=[\\s/>]))`,
953
+ "i"
954
+ );
955
+ var RAW_HTML_BLOCK_SEARCH = new RegExp(RAW_HTML_BLOCK_AT_START.source, "im");
956
+ function consumeTrailingNewline(src, end) {
957
+ const after = /^[ \t]*\r?\n/.exec(src.slice(end));
958
+ return src.slice(0, end + (after?.[0].length ?? 0));
959
+ }
960
+ function matchRawHtmlBlock(src) {
961
+ if (!RAW_HTML_BLOCK_AT_START.test(src)) return void 0;
962
+ const leading = /^[ \t]*/.exec(src)?.[0].length ?? 0;
963
+ const rest = src.slice(leading);
964
+ if (rest.startsWith("<!--")) {
965
+ const end = rest.indexOf("-->");
966
+ if (end === -1) return void 0;
967
+ return consumeTrailingNewline(src, leading + end + 3);
952
968
  }
953
- if (node.nodeType === 8) {
954
- return `<!--${node.textContent ?? ""}-->`;
969
+ const doctype = /^<!doctype\b[^>]*>/i.exec(rest);
970
+ if (doctype) {
971
+ return consumeTrailingNewline(src, leading + doctype[0].length);
955
972
  }
956
- return node.textContent ?? "";
957
- }
958
- function serializeHtmlRendererOutput(fragment) {
959
- return Array.from(fragment.childNodes).map(serializeHtmlRendererNode).join("");
960
- }
961
- function processHtmlContent(content) {
962
- if (typeof document === "undefined") {
963
- return applyInlineCodeClasses(content);
973
+ const open = /^<([a-z][a-z0-9]*)(?=[\s/>])[^>]*>/i.exec(rest);
974
+ if (!open) return void 0;
975
+ const tag = (open[1] ?? "").toLowerCase();
976
+ if (open[0].endsWith("/>") || RAW_HTML_VOID_TAGS.has(tag)) {
977
+ return consumeTrailingNewline(src, leading + open[0].length);
964
978
  }
965
- const template = document.createElement("template");
966
- template.innerHTML = content;
967
- return applyInlineCodeClasses(serializeHtmlRendererOutput(template.content));
968
- }
969
- function processEditorProject(content) {
970
- if (canUseHtmlRenderer(content)) {
971
- return processHtmlContent(content);
979
+ const tagRe = new RegExp(`<(/?)${tag}(?=[\\s/>])[^>]*>`, "ig");
980
+ tagRe.lastIndex = leading;
981
+ let depth = 0;
982
+ let match;
983
+ while ((match = tagRe.exec(src)) !== null) {
984
+ if (match[1] === "/") {
985
+ depth--;
986
+ } else if (!match[0].endsWith("/>")) {
987
+ depth++;
988
+ }
989
+ if (depth === 0) {
990
+ return consumeTrailingNewline(src, tagRe.lastIndex);
991
+ }
972
992
  }
973
- marked2.use(markedSmartypantsLite());
974
- marked2.use({
975
- extensions: [
976
- {
977
- name: "htmlLineBreak",
978
- level: "block",
979
- start(src) {
980
- return src.search(/^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/m);
981
- },
982
- tokenizer(src) {
983
- const match = /^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/.exec(src);
984
- if (match) {
985
- return { type: "htmlLineBreak", raw: match[0] };
986
- }
987
- },
988
- renderer() {
989
- return "<div class=page-break></div>\n";
993
+ return void 0;
994
+ }
995
+ var renderer = new Marked();
996
+ renderer.use(markedSmartypantsLite());
997
+ renderer.use({
998
+ extensions: [
999
+ // Generic raw HTML passthrough. marked registers extension tokenizers by
1000
+ // unshifting, so dispatch order is the reverse of this array — placing
1001
+ // this first means it is tried LAST, letting the specific HTML
1002
+ // transformers below (callouts, output, wrapped code) take precedence.
1003
+ // Any other top-level HTML element is captured whole and emitted verbatim
1004
+ // so its interior is never re-parsed as markdown.
1005
+ {
1006
+ name: "rawHtmlBlock",
1007
+ level: "block",
1008
+ start(src) {
1009
+ const index = src.search(RAW_HTML_BLOCK_SEARCH);
1010
+ return index < 0 ? void 0 : index;
1011
+ },
1012
+ tokenizer(src) {
1013
+ const raw = matchRawHtmlBlock(src);
1014
+ if (raw) {
1015
+ return { type: "rawHtmlBlock", raw, text: raw };
990
1016
  }
991
1017
  },
992
- {
993
- name: "preservedFence",
994
- level: "block",
995
- start(src) {
996
- return src.search(/^[ \t]*(`{3,}|~{3,})/m);
997
- },
998
- tokenizer(src) {
999
- const match = src.match(
1000
- /^[ \t]*(`{3,}|~{3,})([^\r\n]*)\r?\n([\s\S]*?)\r?\n[ \t]*\1[ \t]*(?:\n|$)/
1001
- );
1002
- if (!match) return;
1003
- return {
1004
- type: "preservedFence",
1005
- raw: match[0],
1006
- text: match[3] ?? "",
1007
- info: (match[2] ?? "").trim()
1008
- };
1009
- },
1010
- renderer(token) {
1011
- return renderMarkedCodeToken(token, token.raw);
1018
+ renderer(token) {
1019
+ return token.text;
1020
+ }
1021
+ },
1022
+ {
1023
+ name: "htmlLineBreak",
1024
+ level: "block",
1025
+ start(src) {
1026
+ return src.search(/^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/m);
1027
+ },
1028
+ tokenizer(src) {
1029
+ const match = /^[ \t]*\{\.page-break\}[ \t]*(?:\n|$)/.exec(src);
1030
+ if (match) {
1031
+ return { type: "htmlLineBreak", raw: match[0] };
1012
1032
  }
1013
1033
  },
1014
- // RFM alert blocks: > [!TASK], > [!ACCORDION], etc.
1015
- {
1016
- name: "rfmBlock",
1017
- level: "block",
1018
- start(src) {
1019
- return src.search(/^[ \t]*>+[ \t]?\[!/m);
1020
- },
1021
- tokenizer(src) {
1022
- const raw = matchRfmBlock(src);
1023
- if (raw !== void 0) {
1024
- return { type: "rfmBlock", raw, text: raw };
1025
- }
1026
- },
1027
- renderer(token) {
1028
- const raw = token.text;
1029
- const hintBlocks = splitTopLevelRfmHintBlocks(raw);
1030
- if (hintBlocks.length > 1) {
1031
- return `${renderHintsPanel(hintBlocks.map(getRfmHintBody))}
1032
- `;
1033
- }
1034
- return `${parseRfmBlock(raw).html}
1035
- `;
1034
+ renderer() {
1035
+ return "<div class=page-break></div>\n";
1036
+ }
1037
+ },
1038
+ {
1039
+ name: "preservedFence",
1040
+ level: "block",
1041
+ start(src) {
1042
+ return src.search(/^[ \t]*(`{3,}|~{3,})/m);
1043
+ },
1044
+ tokenizer(src) {
1045
+ const match = src.match(
1046
+ /^[ \t]*(`{3,}|~{3,})([^\r\n]*)\r?\n([\s\S]*?)\r?\n[ \t]*\1[ \t]*(?:\n|$)/
1047
+ );
1048
+ if (!match) return;
1049
+ return {
1050
+ type: "preservedFence",
1051
+ raw: match[0],
1052
+ text: match[3] ?? "",
1053
+ info: (match[2] ?? "").trim()
1054
+ };
1055
+ },
1056
+ renderer(token) {
1057
+ return renderMarkedCodeToken(token, token.raw);
1058
+ }
1059
+ },
1060
+ // RFM alert blocks: > [!TASK], > [!ACCORDION], etc.
1061
+ {
1062
+ name: "rfmBlock",
1063
+ level: "block",
1064
+ start(src) {
1065
+ return src.search(/^[ \t]*>+[ \t]?\[!/m);
1066
+ },
1067
+ tokenizer(src) {
1068
+ const raw = matchRfmBlock(src);
1069
+ if (raw !== void 0) {
1070
+ return { type: "rfmBlock", raw, text: raw };
1036
1071
  }
1037
1072
  },
1038
- // RPF callout blocks: <div class="c-project-callout c-project-callout--{type}">
1039
- {
1040
- name: "rpfCallout",
1041
- level: "block",
1042
- start(src) {
1043
- return src.search(
1044
- /<div\s+class="c-project-callout\s+c-project-callout--/
1045
- );
1046
- },
1047
- tokenizer(src) {
1048
- const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
1049
- src
1050
- );
1051
- if (match) {
1052
- return { type: "rpfCallout", raw: match[0], text: match[0] };
1053
- }
1054
- },
1055
- renderer(token) {
1056
- return `${parseCalloutBlock(token.text).html}
1073
+ renderer(token) {
1074
+ const raw = token.text;
1075
+ const hintBlocks = splitTopLevelRfmHintBlocks(raw);
1076
+ if (hintBlocks.length > 1) {
1077
+ return `${renderHintsPanel(hintBlocks.map(getRfmHintBody))}
1057
1078
  `;
1058
1079
  }
1059
- },
1060
- // RPF task blocks: --- task --- ... --- /task ---
1061
- {
1062
- name: "rpfTask",
1063
- level: "block",
1064
- start(src) {
1065
- return src.search(/^--- task ---$/m);
1066
- },
1067
- tokenizer(src) {
1068
- const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
1069
- if (match) {
1070
- return { type: "rpfTask", raw: match[0], text: match[0] };
1071
- }
1072
- },
1073
- renderer(token) {
1074
- return `${parseTaskBlock(token.text).html}
1080
+ return `${parseRfmBlock(raw).html}
1075
1081
  `;
1082
+ }
1083
+ },
1084
+ // RPF callout blocks: <div class="c-project-callout c-project-callout--{type}">
1085
+ {
1086
+ name: "rpfCallout",
1087
+ level: "block",
1088
+ start(src) {
1089
+ return src.search(
1090
+ /<div\s+class="c-project-callout\s+c-project-callout--/
1091
+ );
1092
+ },
1093
+ tokenizer(src) {
1094
+ const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
1095
+ src
1096
+ );
1097
+ if (match) {
1098
+ return { type: "rpfCallout", raw: match[0], text: match[0] };
1076
1099
  }
1077
1100
  },
1078
- // RPF collapse blocks: --- collapse --- ... --- /collapse ---
1079
- createLegacyBlockExtension("rpfCollapse", "collapse", parseCollapseBlock),
1080
- // RPF hints blocks: --- hints --- ... --- /hints ---
1081
- createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
1082
- // RPF hint blocks: --- hint --- ... --- /hint ---
1083
- createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
1084
- // RPF challenge blocks: --- challenge --- ... --- /challenge ---
1085
- createLegacyBlockExtension(
1086
- "rpfChallenge",
1087
- "challenge",
1088
- parseChallengeBlock,
1089
- false
1090
- ),
1091
- // RPF print visibility blocks.
1092
- createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
1093
- createLegacyBlockExtension(
1094
- "rpfPrintOnly",
1095
- "print-only",
1096
- parsePrintOnlyBlock
1097
- ),
1098
- // RPF save block: --- save ---
1099
- {
1100
- name: "rpfSave",
1101
- level: "block",
1102
- start(src) {
1103
- return src.search(/^[ \t]*--- save ---$/m);
1104
- },
1105
- tokenizer(src) {
1106
- const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
1107
- if (match) {
1108
- return { type: "rpfSave", raw: match[0], text: match[0] };
1109
- }
1110
- },
1111
- renderer() {
1112
- return `${parseSaveBlock()}
1101
+ renderer(token) {
1102
+ return `${parseCalloutBlock(token.text).html}
1113
1103
  `;
1104
+ }
1105
+ },
1106
+ // RPF task blocks: --- task --- ... --- /task ---
1107
+ {
1108
+ name: "rpfTask",
1109
+ level: "block",
1110
+ start(src) {
1111
+ return src.search(/^--- task ---$/m);
1112
+ },
1113
+ tokenizer(src) {
1114
+ const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
1115
+ if (match) {
1116
+ return { type: "rpfTask", raw: match[0], text: match[0] };
1114
1117
  }
1115
1118
  },
1116
- // RPF code blocks: --- code --- ... --- /code ---
1117
- // Also handles the wrapped form: <div class="c-project-code">\n--- code ---\n...\n--- /code ---\n</div>
1118
- {
1119
- name: "rpfCode",
1120
- level: "block",
1121
- start(src) {
1122
- const bare = src.search(/^--- code ---$/m);
1123
- const wrapped = src.search(/^<div class="c-project-code">/m);
1124
- if (bare === -1) return wrapped;
1125
- if (wrapped === -1) return bare;
1126
- return Math.min(bare, wrapped);
1127
- },
1128
- tokenizer(src) {
1129
- const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
1130
- src
1131
- );
1132
- if (wrappedMatch) {
1133
- return {
1134
- type: "rpfCode",
1135
- raw: wrappedMatch[0],
1136
- text: wrappedMatch[1]
1137
- };
1138
- }
1139
- const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
1140
- if (match) {
1141
- return { type: "rpfCode", raw: match[0], text: match[0] };
1142
- }
1143
- },
1144
- renderer(token) {
1145
- return `${parseRpfCodeBlock(token.text).html}
1119
+ renderer(token) {
1120
+ return `${parseTaskBlock(token.text).html}
1146
1121
  `;
1122
+ }
1123
+ },
1124
+ // RPF collapse blocks: --- collapse --- ... --- /collapse ---
1125
+ createLegacyBlockExtension("rpfCollapse", "collapse", parseCollapseBlock),
1126
+ // RPF hints blocks: --- hints --- ... --- /hints ---
1127
+ createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
1128
+ // RPF hint blocks: --- hint --- ... --- /hint ---
1129
+ createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
1130
+ // RPF challenge blocks: --- challenge --- ... --- /challenge ---
1131
+ createLegacyBlockExtension(
1132
+ "rpfChallenge",
1133
+ "challenge",
1134
+ parseChallengeBlock,
1135
+ false
1136
+ ),
1137
+ // RPF print visibility blocks.
1138
+ createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
1139
+ createLegacyBlockExtension(
1140
+ "rpfPrintOnly",
1141
+ "print-only",
1142
+ parsePrintOnlyBlock
1143
+ ),
1144
+ // RPF save block: --- save ---
1145
+ {
1146
+ name: "rpfSave",
1147
+ level: "block",
1148
+ start(src) {
1149
+ return src.search(/^[ \t]*--- save ---$/m);
1150
+ },
1151
+ tokenizer(src) {
1152
+ const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
1153
+ if (match) {
1154
+ return { type: "rpfSave", raw: match[0], text: match[0] };
1147
1155
  }
1148
1156
  },
1149
- // RPF output blocks: <div class="c-project-output"> ... </div>
1150
- {
1151
- name: "rpfOutput",
1152
- level: "block",
1153
- start(src) {
1154
- return src.search(/^<div\s+class="c-project-output">\s*$/m);
1155
- },
1156
- tokenizer(src) {
1157
- const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(src);
1158
- if (match) {
1159
- return { type: "rpfOutput", raw: match[0], text: match[0] };
1160
- }
1161
- },
1162
- renderer(token) {
1163
- return `${parseOutputBlock(token.text)}
1157
+ renderer() {
1158
+ return `${parseSaveBlock()}
1164
1159
  `;
1160
+ }
1161
+ },
1162
+ // RPF code blocks: --- code --- ... --- /code ---
1163
+ // Also handles the wrapped form: <div class="c-project-code">\n--- code ---\n...\n--- /code ---\n</div>
1164
+ {
1165
+ name: "rpfCode",
1166
+ level: "block",
1167
+ start(src) {
1168
+ const bare = src.search(/^--- code ---$/m);
1169
+ const wrapped = src.search(/^<div class="c-project-code">/m);
1170
+ if (bare === -1) return wrapped;
1171
+ if (wrapped === -1) return bare;
1172
+ return Math.min(bare, wrapped);
1173
+ },
1174
+ tokenizer(src) {
1175
+ const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
1176
+ src
1177
+ );
1178
+ if (wrappedMatch) {
1179
+ return {
1180
+ type: "rpfCode",
1181
+ raw: wrappedMatch[0],
1182
+ text: wrappedMatch[1]
1183
+ };
1165
1184
  }
1185
+ const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
1186
+ if (match) {
1187
+ return { type: "rpfCode", raw: match[0], text: match[0] };
1188
+ }
1189
+ },
1190
+ renderer(token) {
1191
+ return `${parseRpfCodeBlock(token.text).html}
1192
+ `;
1166
1193
  }
1167
- ],
1168
- renderer: {
1169
- // Override fenced code blocks to use Prism with our custom options.
1170
- code(token) {
1171
- return renderMarkedCodeToken(token);
1194
+ },
1195
+ // RPF output blocks: <div class="c-project-output"> ... </div>
1196
+ {
1197
+ name: "rpfOutput",
1198
+ level: "block",
1199
+ start(src) {
1200
+ return src.search(/^<div\s+class="c-project-output">\s*$/m);
1172
1201
  },
1173
- listitem(item) {
1174
- const itemTokens = Array.isArray(item.tokens) ? item.tokens : [];
1175
- const raw = typeof item.raw === "string" ? item.raw : "";
1176
- const rawFenceBlocks = extractListItemFenceBlocks(raw);
1177
- let rawFenceIndex = 0;
1178
- const firstNewline = raw.indexOf("\n");
1179
- const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
1180
- let body = "";
1181
- for (let index = 0; index < itemTokens.length; index++) {
1182
- const token = itemTokens[index];
1183
- if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
1184
- body += this.parser.parseInline(token.tokens ?? []);
1185
- continue;
1186
- }
1187
- if (token?.type === "code" || token?.type === "preservedFence") {
1188
- body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
1189
- rawFenceIndex++;
1190
- continue;
1191
- }
1192
- body += this.parser.parse([token]).trimStart();
1202
+ tokenizer(src) {
1203
+ const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(
1204
+ src
1205
+ );
1206
+ if (match) {
1207
+ return { type: "rpfOutput", raw: match[0], text: match[0] };
1193
1208
  }
1194
- return `<li>${body}</li>
1209
+ },
1210
+ renderer(token) {
1211
+ return `${parseOutputBlock(token.text)}
1195
1212
  `;
1196
1213
  }
1214
+ }
1215
+ ],
1216
+ renderer: {
1217
+ // Override fenced code blocks to use Prism with our custom options.
1218
+ code(token) {
1219
+ return renderMarkedCodeToken(token);
1197
1220
  },
1198
- hooks: {
1199
- postprocess(html) {
1200
- return applyInlineCodeClasses(html);
1221
+ listitem(item) {
1222
+ const itemTokens = Array.isArray(item.tokens) ? item.tokens : [];
1223
+ const raw = typeof item.raw === "string" ? item.raw : "";
1224
+ const rawFenceBlocks = extractListItemFenceBlocks(raw);
1225
+ let rawFenceIndex = 0;
1226
+ const firstNewline = raw.indexOf("\n");
1227
+ const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
1228
+ let body = "";
1229
+ for (let index = 0; index < itemTokens.length; index++) {
1230
+ const token = itemTokens[index];
1231
+ if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
1232
+ body += this.parser.parseInline(token.tokens ?? []);
1233
+ continue;
1234
+ }
1235
+ if (token?.type === "code" || token?.type === "preservedFence") {
1236
+ body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
1237
+ rawFenceIndex++;
1238
+ continue;
1239
+ }
1240
+ body += this.parser.parse([token]).trimStart();
1201
1241
  }
1242
+ return `<li>${body}</li>
1243
+ `;
1202
1244
  }
1203
- });
1204
- marked2.use(gfmHeadingId({ prefix: "" }));
1205
- return marked2.parse(content);
1245
+ },
1246
+ hooks: {
1247
+ postprocess(html) {
1248
+ return applyInlineCodeClasses(html);
1249
+ }
1250
+ }
1251
+ });
1252
+ renderer.use(gfmHeadingId({ prefix: "" }));
1253
+ setMarkdownParser((input) => renderer.parse(input));
1254
+ function processEditorProject(content) {
1255
+ return renderer.parse(content);
1206
1256
  }
1207
1257
  export {
1208
1258
  processEditorProject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raspberrypifoundation/rpf-markdown-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",