@raspberrypifoundation/rpf-markdown-core 0.1.0 → 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.
- package/dist/index.cjs +291 -213
- package/dist/index.js +292 -214
- package/package.json +4 -3
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
|
|
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
|
|
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
|
|
420
|
-
const doc =
|
|
421
|
-
const svgElement =
|
|
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,
|
|
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,240 +981,313 @@ function createLegacyBlockExtension(name, blockName, render, appendNewline = tru
|
|
|
976
981
|
}
|
|
977
982
|
};
|
|
978
983
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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);
|
|
1003
|
+
}
|
|
1004
|
+
const doctype = /^<!doctype\b[^>]*>/i.exec(rest);
|
|
1005
|
+
if (doctype) {
|
|
1006
|
+
return consumeTrailingNewline(src, leading + doctype[0].length);
|
|
1007
|
+
}
|
|
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);
|
|
1013
|
+
}
|
|
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
|
+
}
|
|
1027
|
+
}
|
|
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 };
|
|
997
1051
|
}
|
|
998
1052
|
},
|
|
999
|
-
{
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
text: match[3] ?? "",
|
|
1014
|
-
info: (match[2] ?? "").trim()
|
|
1015
|
-
};
|
|
1016
|
-
},
|
|
1017
|
-
renderer(token) {
|
|
1018
|
-
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] };
|
|
1019
1067
|
}
|
|
1020
1068
|
},
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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 };
|
|
1043
1106
|
}
|
|
1044
1107
|
},
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
return src.search(
|
|
1051
|
-
/<div\s+class="c-project-callout\s+c-project-callout--/
|
|
1052
|
-
);
|
|
1053
|
-
},
|
|
1054
|
-
tokenizer(src) {
|
|
1055
|
-
const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
|
|
1056
|
-
src
|
|
1057
|
-
);
|
|
1058
|
-
if (match) {
|
|
1059
|
-
return { type: "rpfCallout", raw: match[0], text: match[0] };
|
|
1060
|
-
}
|
|
1061
|
-
},
|
|
1062
|
-
renderer(token) {
|
|
1063
|
-
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))}
|
|
1064
1113
|
`;
|
|
1065
1114
|
}
|
|
1066
|
-
|
|
1067
|
-
// RPF task blocks: --- task --- ... --- /task ---
|
|
1068
|
-
{
|
|
1069
|
-
name: "rpfTask",
|
|
1070
|
-
level: "block",
|
|
1071
|
-
start(src) {
|
|
1072
|
-
return src.search(/^--- task ---$/m);
|
|
1073
|
-
},
|
|
1074
|
-
tokenizer(src) {
|
|
1075
|
-
const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
|
|
1076
|
-
if (match) {
|
|
1077
|
-
return { type: "rpfTask", raw: match[0], text: match[0] };
|
|
1078
|
-
}
|
|
1079
|
-
},
|
|
1080
|
-
renderer(token) {
|
|
1081
|
-
return `${parseTaskBlock(token.text).html}
|
|
1115
|
+
return `${parseRfmBlock(raw).html}
|
|
1082
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] };
|
|
1083
1134
|
}
|
|
1084
1135
|
},
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
// RPF hints blocks: --- hints --- ... --- /hints ---
|
|
1088
|
-
createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
|
|
1089
|
-
// RPF hint blocks: --- hint --- ... --- /hint ---
|
|
1090
|
-
createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
|
|
1091
|
-
// RPF challenge blocks: --- challenge --- ... --- /challenge ---
|
|
1092
|
-
createLegacyBlockExtension(
|
|
1093
|
-
"rpfChallenge",
|
|
1094
|
-
"challenge",
|
|
1095
|
-
parseChallengeBlock,
|
|
1096
|
-
false
|
|
1097
|
-
),
|
|
1098
|
-
// RPF print visibility blocks.
|
|
1099
|
-
createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
|
|
1100
|
-
createLegacyBlockExtension(
|
|
1101
|
-
"rpfPrintOnly",
|
|
1102
|
-
"print-only",
|
|
1103
|
-
parsePrintOnlyBlock
|
|
1104
|
-
),
|
|
1105
|
-
// RPF save block: --- save ---
|
|
1106
|
-
{
|
|
1107
|
-
name: "rpfSave",
|
|
1108
|
-
level: "block",
|
|
1109
|
-
start(src) {
|
|
1110
|
-
return src.search(/^[ \t]*--- save ---$/m);
|
|
1111
|
-
},
|
|
1112
|
-
tokenizer(src) {
|
|
1113
|
-
const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
|
|
1114
|
-
if (match) {
|
|
1115
|
-
return { type: "rpfSave", raw: match[0], text: match[0] };
|
|
1116
|
-
}
|
|
1117
|
-
},
|
|
1118
|
-
renderer() {
|
|
1119
|
-
return `${parseSaveBlock()}
|
|
1136
|
+
renderer(token) {
|
|
1137
|
+
return `${parseCalloutBlock(token.text).html}
|
|
1120
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] };
|
|
1121
1152
|
}
|
|
1122
1153
|
},
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
{
|
|
1126
|
-
name: "rpfCode",
|
|
1127
|
-
level: "block",
|
|
1128
|
-
start(src) {
|
|
1129
|
-
const bare = src.search(/^--- code ---$/m);
|
|
1130
|
-
const wrapped = src.search(/^<div class="c-project-code">/m);
|
|
1131
|
-
if (bare === -1) return wrapped;
|
|
1132
|
-
if (wrapped === -1) return bare;
|
|
1133
|
-
return Math.min(bare, wrapped);
|
|
1134
|
-
},
|
|
1135
|
-
tokenizer(src) {
|
|
1136
|
-
const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
|
|
1137
|
-
src
|
|
1138
|
-
);
|
|
1139
|
-
if (wrappedMatch) {
|
|
1140
|
-
return {
|
|
1141
|
-
type: "rpfCode",
|
|
1142
|
-
raw: wrappedMatch[0],
|
|
1143
|
-
text: wrappedMatch[1]
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
|
|
1147
|
-
if (match) {
|
|
1148
|
-
return { type: "rpfCode", raw: match[0], text: match[0] };
|
|
1149
|
-
}
|
|
1150
|
-
},
|
|
1151
|
-
renderer(token) {
|
|
1152
|
-
return `${parseRpfCodeBlock(token.text).html}
|
|
1154
|
+
renderer(token) {
|
|
1155
|
+
return `${parseTaskBlock(token.text).html}
|
|
1153
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] };
|
|
1154
1190
|
}
|
|
1155
1191
|
},
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
name: "rpfOutput",
|
|
1159
|
-
level: "block",
|
|
1160
|
-
start(src) {
|
|
1161
|
-
return src.search(/^<div\s+class="c-project-output">\s*$/m);
|
|
1162
|
-
},
|
|
1163
|
-
tokenizer(src) {
|
|
1164
|
-
const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(src);
|
|
1165
|
-
if (match) {
|
|
1166
|
-
return { type: "rpfOutput", raw: match[0], text: match[0] };
|
|
1167
|
-
}
|
|
1168
|
-
},
|
|
1169
|
-
renderer(token) {
|
|
1170
|
-
return `${parseOutputBlock(token.text)}
|
|
1192
|
+
renderer() {
|
|
1193
|
+
return `${parseSaveBlock()}
|
|
1171
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] };
|
|
1172
1223
|
}
|
|
1224
|
+
},
|
|
1225
|
+
renderer(token) {
|
|
1226
|
+
return `${parseRpfCodeBlock(token.text).html}
|
|
1227
|
+
`;
|
|
1173
1228
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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);
|
|
1179
1236
|
},
|
|
1180
|
-
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
|
|
1187
|
-
let body = "";
|
|
1188
|
-
for (let index = 0; index < itemTokens.length; index++) {
|
|
1189
|
-
const token = itemTokens[index];
|
|
1190
|
-
if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
|
|
1191
|
-
body += this.parser.parseInline(token.tokens ?? []);
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
if (token?.type === "code" || token?.type === "preservedFence") {
|
|
1195
|
-
body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
|
|
1196
|
-
rawFenceIndex++;
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
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] };
|
|
1200
1243
|
}
|
|
1201
|
-
|
|
1244
|
+
},
|
|
1245
|
+
renderer(token) {
|
|
1246
|
+
return `${parseOutputBlock(token.text)}
|
|
1202
1247
|
`;
|
|
1203
1248
|
}
|
|
1249
|
+
}
|
|
1250
|
+
],
|
|
1251
|
+
renderer: {
|
|
1252
|
+
// Override fenced code blocks to use Prism with our custom options.
|
|
1253
|
+
code(token) {
|
|
1254
|
+
return renderMarkedCodeToken(token);
|
|
1204
1255
|
},
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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();
|
|
1208
1276
|
}
|
|
1277
|
+
return `<li>${body}</li>
|
|
1278
|
+
`;
|
|
1209
1279
|
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
|
|
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);
|
|
1213
1291
|
}
|
|
1214
1292
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1215
1293
|
0 && (module.exports = {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
385
|
-
const doc =
|
|
386
|
-
const svgElement =
|
|
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,
|
|
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,240 +946,313 @@ function createLegacyBlockExtension(name, blockName, render, appendNewline = tru
|
|
|
941
946
|
}
|
|
942
947
|
};
|
|
943
948
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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);
|
|
968
|
+
}
|
|
969
|
+
const doctype = /^<!doctype\b[^>]*>/i.exec(rest);
|
|
970
|
+
if (doctype) {
|
|
971
|
+
return consumeTrailingNewline(src, leading + doctype[0].length);
|
|
972
|
+
}
|
|
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);
|
|
978
|
+
}
|
|
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
|
+
}
|
|
992
|
+
}
|
|
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 };
|
|
962
1016
|
}
|
|
963
1017
|
},
|
|
964
|
-
{
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
text: match[3] ?? "",
|
|
979
|
-
info: (match[2] ?? "").trim()
|
|
980
|
-
};
|
|
981
|
-
},
|
|
982
|
-
renderer(token) {
|
|
983
|
-
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] };
|
|
984
1032
|
}
|
|
985
1033
|
},
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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 };
|
|
1008
1071
|
}
|
|
1009
1072
|
},
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
return src.search(
|
|
1016
|
-
/<div\s+class="c-project-callout\s+c-project-callout--/
|
|
1017
|
-
);
|
|
1018
|
-
},
|
|
1019
|
-
tokenizer(src) {
|
|
1020
|
-
const match = /^<div\s+class="c-project-callout\s+c-project-callout--[a-z0-9-]+">[\s\S]*?<\/div>/.exec(
|
|
1021
|
-
src
|
|
1022
|
-
);
|
|
1023
|
-
if (match) {
|
|
1024
|
-
return { type: "rpfCallout", raw: match[0], text: match[0] };
|
|
1025
|
-
}
|
|
1026
|
-
},
|
|
1027
|
-
renderer(token) {
|
|
1028
|
-
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))}
|
|
1029
1078
|
`;
|
|
1030
1079
|
}
|
|
1031
|
-
|
|
1032
|
-
// RPF task blocks: --- task --- ... --- /task ---
|
|
1033
|
-
{
|
|
1034
|
-
name: "rpfTask",
|
|
1035
|
-
level: "block",
|
|
1036
|
-
start(src) {
|
|
1037
|
-
return src.search(/^--- task ---$/m);
|
|
1038
|
-
},
|
|
1039
|
-
tokenizer(src) {
|
|
1040
|
-
const match = /^--- task ---\n[\s\S]*?\n--- \/task ---/.exec(src);
|
|
1041
|
-
if (match) {
|
|
1042
|
-
return { type: "rpfTask", raw: match[0], text: match[0] };
|
|
1043
|
-
}
|
|
1044
|
-
},
|
|
1045
|
-
renderer(token) {
|
|
1046
|
-
return `${parseTaskBlock(token.text).html}
|
|
1080
|
+
return `${parseRfmBlock(raw).html}
|
|
1047
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] };
|
|
1048
1099
|
}
|
|
1049
1100
|
},
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
// RPF hints blocks: --- hints --- ... --- /hints ---
|
|
1053
|
-
createLegacyBlockExtension("rpfHints", "hints", parseHintsBlock),
|
|
1054
|
-
// RPF hint blocks: --- hint --- ... --- /hint ---
|
|
1055
|
-
createLegacyBlockExtension("rpfHint", "hint", parseHintBlock),
|
|
1056
|
-
// RPF challenge blocks: --- challenge --- ... --- /challenge ---
|
|
1057
|
-
createLegacyBlockExtension(
|
|
1058
|
-
"rpfChallenge",
|
|
1059
|
-
"challenge",
|
|
1060
|
-
parseChallengeBlock,
|
|
1061
|
-
false
|
|
1062
|
-
),
|
|
1063
|
-
// RPF print visibility blocks.
|
|
1064
|
-
createLegacyBlockExtension("rpfNoPrint", "no-print", parseNoPrintBlock),
|
|
1065
|
-
createLegacyBlockExtension(
|
|
1066
|
-
"rpfPrintOnly",
|
|
1067
|
-
"print-only",
|
|
1068
|
-
parsePrintOnlyBlock
|
|
1069
|
-
),
|
|
1070
|
-
// RPF save block: --- save ---
|
|
1071
|
-
{
|
|
1072
|
-
name: "rpfSave",
|
|
1073
|
-
level: "block",
|
|
1074
|
-
start(src) {
|
|
1075
|
-
return src.search(/^[ \t]*--- save ---$/m);
|
|
1076
|
-
},
|
|
1077
|
-
tokenizer(src) {
|
|
1078
|
-
const match = /^[ \t]*---\s+save\s+---[ \t]*(?:\n|$)/.exec(src);
|
|
1079
|
-
if (match) {
|
|
1080
|
-
return { type: "rpfSave", raw: match[0], text: match[0] };
|
|
1081
|
-
}
|
|
1082
|
-
},
|
|
1083
|
-
renderer() {
|
|
1084
|
-
return `${parseSaveBlock()}
|
|
1101
|
+
renderer(token) {
|
|
1102
|
+
return `${parseCalloutBlock(token.text).html}
|
|
1085
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] };
|
|
1086
1117
|
}
|
|
1087
1118
|
},
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
{
|
|
1091
|
-
name: "rpfCode",
|
|
1092
|
-
level: "block",
|
|
1093
|
-
start(src) {
|
|
1094
|
-
const bare = src.search(/^--- code ---$/m);
|
|
1095
|
-
const wrapped = src.search(/^<div class="c-project-code">/m);
|
|
1096
|
-
if (bare === -1) return wrapped;
|
|
1097
|
-
if (wrapped === -1) return bare;
|
|
1098
|
-
return Math.min(bare, wrapped);
|
|
1099
|
-
},
|
|
1100
|
-
tokenizer(src) {
|
|
1101
|
-
const wrappedMatch = /^<div class="c-project-code">\n(--- code ---\n[\s\S]*?\n--- \/code ---)\n\s*<\/div>/.exec(
|
|
1102
|
-
src
|
|
1103
|
-
);
|
|
1104
|
-
if (wrappedMatch) {
|
|
1105
|
-
return {
|
|
1106
|
-
type: "rpfCode",
|
|
1107
|
-
raw: wrappedMatch[0],
|
|
1108
|
-
text: wrappedMatch[1]
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
const match = /^--- code ---\n[\s\S]*?\n--- \/code ---/.exec(src);
|
|
1112
|
-
if (match) {
|
|
1113
|
-
return { type: "rpfCode", raw: match[0], text: match[0] };
|
|
1114
|
-
}
|
|
1115
|
-
},
|
|
1116
|
-
renderer(token) {
|
|
1117
|
-
return `${parseRpfCodeBlock(token.text).html}
|
|
1119
|
+
renderer(token) {
|
|
1120
|
+
return `${parseTaskBlock(token.text).html}
|
|
1118
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] };
|
|
1119
1155
|
}
|
|
1120
1156
|
},
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
name: "rpfOutput",
|
|
1124
|
-
level: "block",
|
|
1125
|
-
start(src) {
|
|
1126
|
-
return src.search(/^<div\s+class="c-project-output">\s*$/m);
|
|
1127
|
-
},
|
|
1128
|
-
tokenizer(src) {
|
|
1129
|
-
const match = /^<div\s+class="c-project-output">\s[\s\S]*?<\/div>/.exec(src);
|
|
1130
|
-
if (match) {
|
|
1131
|
-
return { type: "rpfOutput", raw: match[0], text: match[0] };
|
|
1132
|
-
}
|
|
1133
|
-
},
|
|
1134
|
-
renderer(token) {
|
|
1135
|
-
return `${parseOutputBlock(token.text)}
|
|
1157
|
+
renderer() {
|
|
1158
|
+
return `${parseSaveBlock()}
|
|
1136
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
|
+
};
|
|
1137
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
|
+
`;
|
|
1138
1193
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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);
|
|
1144
1201
|
},
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const hasBlankLineAfterMarker = firstNewline !== -1 && /^\n[ \t]*\n/.test(raw.slice(firstNewline));
|
|
1152
|
-
let body = "";
|
|
1153
|
-
for (let index = 0; index < itemTokens.length; index++) {
|
|
1154
|
-
const token = itemTokens[index];
|
|
1155
|
-
if (index === 0 && !item.task && !hasBlankLineAfterMarker && itemTokens.length > 1 && token?.type === "paragraph") {
|
|
1156
|
-
body += this.parser.parseInline(token.tokens ?? []);
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
if (token?.type === "code" || token?.type === "preservedFence") {
|
|
1160
|
-
body += renderMarkedCodeToken(token, rawFenceBlocks[rawFenceIndex]);
|
|
1161
|
-
rawFenceIndex++;
|
|
1162
|
-
continue;
|
|
1163
|
-
}
|
|
1164
|
-
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] };
|
|
1165
1208
|
}
|
|
1166
|
-
|
|
1209
|
+
},
|
|
1210
|
+
renderer(token) {
|
|
1211
|
+
return `${parseOutputBlock(token.text)}
|
|
1167
1212
|
`;
|
|
1168
1213
|
}
|
|
1214
|
+
}
|
|
1215
|
+
],
|
|
1216
|
+
renderer: {
|
|
1217
|
+
// Override fenced code blocks to use Prism with our custom options.
|
|
1218
|
+
code(token) {
|
|
1219
|
+
return renderMarkedCodeToken(token);
|
|
1169
1220
|
},
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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();
|
|
1173
1241
|
}
|
|
1242
|
+
return `<li>${body}</li>
|
|
1243
|
+
`;
|
|
1174
1244
|
}
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
|
|
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);
|
|
1178
1256
|
}
|
|
1179
1257
|
export {
|
|
1180
1258
|
processEditorProject
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raspberrypifoundation/rpf-markdown-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"registry": "https://npm.pkg.github.com"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
|
-
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
26
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
27
27
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"prepack": "npm run build"
|
|
@@ -46,5 +46,6 @@
|
|
|
46
46
|
"tsx": "^4.21.0",
|
|
47
47
|
"typescript": "^6.0.2",
|
|
48
48
|
"vitest": "^4.1.2"
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8"
|
|
50
51
|
}
|