sommark 4.5.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +314 -178
- package/cli/cli.mjs +1 -1
- package/cli/commands/color.js +36 -14
- package/cli/commands/help.js +3 -0
- package/cli/commands/init.js +0 -2
- package/cli/constants.js +5 -2
- package/constants/html_props.js +66 -1
- package/constants/svg_elements.js +31 -0
- package/core/errors.js +5 -4
- package/core/evaluator.js +1 -2
- package/core/formats.js +7 -1
- package/core/helpers/config-loader.js +1 -3
- package/core/helpers/lib.js +1 -1
- package/core/labels.js +2 -15
- package/core/lexer.js +197 -313
- package/core/modules.js +13 -13
- package/core/parser.js +226 -535
- package/core/tokenTypes.js +6 -15
- package/core/transpiler.js +129 -110
- package/core/validator.js +6 -26
- package/dist/sommark.browser.js +1939 -2223
- package/dist/sommark.browser.lite.js +1937 -2220
- package/dist/sommark.lexer.js +392 -544
- package/dist/sommark.parser.js +604 -1200
- package/formatter/mark.js +34 -0
- package/formatter/tag.js +7 -33
- package/helpers/utils.js +15 -16
- package/index.js +9 -1
- package/index.shared.js +22 -12
- package/mappers/languages/csv.js +62 -0
- package/mappers/languages/html.js +21 -69
- package/mappers/languages/json.js +74 -156
- package/mappers/languages/jsonc.js +21 -63
- package/mappers/languages/markdown.js +159 -276
- package/mappers/languages/mdx.js +7 -62
- package/mappers/languages/text.js +2 -19
- package/mappers/languages/toml.js +231 -0
- package/mappers/languages/xml.js +25 -25
- package/mappers/languages/yaml.js +323 -0
- package/mappers/mapper.js +1 -22
- package/mappers/shared/index.js +3 -16
- package/package.json +5 -2
|
@@ -1,68 +1,8 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
2
|
import HTML from "./html.js";
|
|
3
3
|
import { registerSharedOutputs } from "../shared/index.js";
|
|
4
|
-
import { BLOCK, TEXT,
|
|
4
|
+
import { BLOCK, TEXT, FOR_EACH } from "../../core/labels.js";
|
|
5
5
|
import { VOID_ELEMENTS } from "../../constants/void_elements.js";
|
|
6
|
-
import evaluator from "../../core/evaluator.js";
|
|
7
|
-
import { matchedValue } from "../../helpers/utils.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Helper to manually render AST children inside handleAst blocks,
|
|
11
|
-
* avoiding the need to call the core transpiler recursively.
|
|
12
|
-
*/
|
|
13
|
-
async function renderNodeAst(astArray, mapperFile) {
|
|
14
|
-
if (!astArray || !Array.isArray(astArray)) return "";
|
|
15
|
-
let result = "";
|
|
16
|
-
for (const node of astArray) {
|
|
17
|
-
if (node.type === TEXT) {
|
|
18
|
-
const text = String(node.text || "");
|
|
19
|
-
result += mapperFile.text(text);
|
|
20
|
-
} else if (node.type === INLINE) {
|
|
21
|
-
let target = matchedValue(mapperFile.outputs, node.id) || mapperFile.getUnknownTag(node);
|
|
22
|
-
if (target) {
|
|
23
|
-
let inlineValue = String(node.value || "").trim();
|
|
24
|
-
inlineValue = mapperFile.inlineText(inlineValue, target.options);
|
|
25
|
-
result += await target.render.call(mapperFile, {
|
|
26
|
-
nodeType: node.type,
|
|
27
|
-
args: node.args || {},
|
|
28
|
-
content: inlineValue,
|
|
29
|
-
ast: node
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
result += mapperFile.inlineText(node.value || "", {});
|
|
33
|
-
}
|
|
34
|
-
} else if (node.type === STATIC_LOGIC) {
|
|
35
|
-
try {
|
|
36
|
-
const val = await evaluator.execute(node.code);
|
|
37
|
-
if (val !== undefined && typeof val !== "object") {
|
|
38
|
-
result += mapperFile.text(String(val));
|
|
39
|
-
}
|
|
40
|
-
} catch (e) {
|
|
41
|
-
console.error(`\x1b[31mLogic Error in Markdown mapper:\x1b[0m ${e.message}`);
|
|
42
|
-
}
|
|
43
|
-
} else if (node.type === BLOCK) {
|
|
44
|
-
let target = matchedValue(mapperFile.outputs, node.id) || mapperFile.getUnknownTag(node);
|
|
45
|
-
if (target) {
|
|
46
|
-
const isSelfClosing = node.isSelfClosing || false;
|
|
47
|
-
let content = "";
|
|
48
|
-
evaluator.pushScope();
|
|
49
|
-
if (!target.options?.handleAst && node.body) {
|
|
50
|
-
content = await renderNodeAst(node.body, mapperFile);
|
|
51
|
-
}
|
|
52
|
-
const output = await target.render.call(mapperFile, {
|
|
53
|
-
nodeType: node.type,
|
|
54
|
-
args: node.args || {},
|
|
55
|
-
content,
|
|
56
|
-
ast: node,
|
|
57
|
-
isSelfClosing
|
|
58
|
-
});
|
|
59
|
-
await evaluator.popScope();
|
|
60
|
-
result += output;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
6
|
|
|
67
7
|
/**
|
|
68
8
|
* The Markdown Mapper used for generating Markdown text.
|
|
@@ -88,76 +28,29 @@ const MARKDOWN = Mapper.define({
|
|
|
88
28
|
return out;
|
|
89
29
|
},
|
|
90
30
|
|
|
91
|
-
/**
|
|
92
|
-
* Formats inline content before rendering.
|
|
93
|
-
*/
|
|
94
|
-
inlineText(text, options) {
|
|
95
|
-
if (options?.escape !== false) {
|
|
96
|
-
// Use smartEscaper to protect special characters
|
|
97
|
-
let out = text;
|
|
98
|
-
if (this.md && this.md.smartEscaper) out = this.md.smartEscaper(out);
|
|
99
|
-
return out;
|
|
100
|
-
}
|
|
101
|
-
return text;
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Formats the literal content inside AtBlocks.
|
|
106
|
-
*/
|
|
107
|
-
atBlockBody(text, options) {
|
|
108
|
-
if (options?.escape === false) return text;
|
|
109
|
-
// Escaping with smartEscaper
|
|
110
|
-
let out = text;
|
|
111
|
-
if (this.md && this.md.smartEscaper) out = this.md.smartEscaper(out);
|
|
112
|
-
return out;
|
|
113
|
-
},
|
|
114
|
-
|
|
115
31
|
/**
|
|
116
32
|
* Provides a fallback for unknown tags by using the HTML mapper instead.
|
|
117
33
|
*/
|
|
118
34
|
getUnknownTag(node) {
|
|
119
|
-
const isBlock = node.type === BLOCK;
|
|
120
35
|
const id = node.id.toLowerCase();
|
|
121
36
|
|
|
122
37
|
return {
|
|
123
|
-
render: async (
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
let rawContent;
|
|
132
|
-
if (node.type === "AtBlock") {
|
|
133
|
-
rawContent = node.content || "";
|
|
134
|
-
rawContent = this.atBlockBody(rawContent, ctx);
|
|
135
|
-
} else if (node.type === "Inline") {
|
|
136
|
-
rawContent = node.value || "";
|
|
137
|
-
rawContent = this.inlineText(rawContent, ctx);
|
|
138
|
-
} else {
|
|
139
|
-
rawContent = (await renderNodeAst(body, this)).trim();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (isSelfClosing || VOID_ELEMENTS.has(id)) {
|
|
143
|
-
return element.selfClose();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let finalContent;
|
|
147
|
-
if (childCount <= 1) {
|
|
148
|
-
// COMPACT PASS: Single child or empty
|
|
149
|
-
finalContent = rawContent;
|
|
150
|
-
} else {
|
|
151
|
-
// MULTILINE PASS: Enforce \n prefix/suffix for multiple children
|
|
152
|
-
finalContent = `\n${rawContent}\n`;
|
|
38
|
+
render: async ({ props, ast, isSelfClosing, renderChild }) => {
|
|
39
|
+
const element = this.tag(id).smartAttributes(props, this.customProps, this.options);
|
|
40
|
+
if (isSelfClosing || VOID_ELEMENTS.has(id)) return element.selfClose();
|
|
41
|
+
|
|
42
|
+
let rawContent = "";
|
|
43
|
+
for (const child of (ast.body || [])) {
|
|
44
|
+
if (child.type === TEXT) rawContent += this.text(child.text);
|
|
45
|
+
else if (child.type === BLOCK) rawContent += await renderChild(child);
|
|
153
46
|
}
|
|
47
|
+
rawContent = rawContent.trim();
|
|
154
48
|
|
|
49
|
+
const meaningful = (ast.body || []).filter(c => c.type !== TEXT || c.text.trim());
|
|
50
|
+
const finalContent = meaningful.length <= 1 ? rawContent : `\n${rawContent}\n`;
|
|
155
51
|
return element.body(finalContent);
|
|
156
52
|
},
|
|
157
|
-
options: {
|
|
158
|
-
type: isBlock ? "Block" : (node.type === "AtBlock" ? "AtBlock" : "Inline"),
|
|
159
|
-
handleAst: true
|
|
160
|
-
}
|
|
53
|
+
options: { handleAst: true }
|
|
161
54
|
};
|
|
162
55
|
}
|
|
163
56
|
});
|
|
@@ -169,105 +62,114 @@ registerSharedOutputs(MARKDOWN);
|
|
|
169
62
|
/**
|
|
170
63
|
* Quote - Renders blockquote content or GFM alerts.
|
|
171
64
|
*/
|
|
172
|
-
MARKDOWN.register("quote", ({
|
|
173
|
-
const type = safeArg({
|
|
65
|
+
MARKDOWN.register("quote", ({ props, content }) => {
|
|
66
|
+
const type = safeArg({ props, index: 0, key: "type", fallBack: "" });
|
|
174
67
|
return md.quote(content, type);
|
|
175
|
-
}, {
|
|
68
|
+
}, { resolve: true });
|
|
176
69
|
|
|
177
70
|
/**
|
|
178
71
|
* Headings - Renders H1-H6 block headings.
|
|
179
72
|
*/
|
|
180
73
|
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
|
|
181
|
-
MARKDOWN.register(heading, function ({
|
|
182
|
-
const format = safeArg({
|
|
74
|
+
MARKDOWN.register(heading, function ({ props, content, isSelfClosing }) {
|
|
75
|
+
const format = safeArg({ props, key: "format", type: "string", fallBack: "" });
|
|
183
76
|
const lvl = heading[1] && !isNaN(Number(heading[1])) ? Number(heading[1]) : 1;
|
|
184
77
|
if (format.toLowerCase() === "html") {
|
|
185
|
-
delete
|
|
186
|
-
const el = this.tag(heading).smartAttributes(
|
|
78
|
+
delete props.format;
|
|
79
|
+
const el = this.tag(heading).smartAttributes(props);
|
|
187
80
|
if (isSelfClosing) return el.selfClose();
|
|
188
81
|
return el.body(content);
|
|
189
82
|
}
|
|
190
83
|
return this.md.heading(content, lvl);
|
|
191
|
-
}
|
|
84
|
+
});
|
|
192
85
|
});
|
|
193
86
|
|
|
194
87
|
/**
|
|
195
88
|
* Bold - Renders bold text (**text**).
|
|
89
|
+
* Self-closing: [bold = "text" !] or [bold = text: "text" !]
|
|
196
90
|
*/
|
|
197
|
-
MARKDOWN.register(["bold", "b"], ({ content }) => {
|
|
198
|
-
|
|
199
|
-
|
|
91
|
+
MARKDOWN.register(["bold", "b"], ({ props, content, isSelfClosing }) => {
|
|
92
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
93
|
+
return md.bold(text);
|
|
94
|
+
});
|
|
200
95
|
|
|
201
96
|
/**
|
|
202
97
|
* Italic - Renders italic text (*text*).
|
|
98
|
+
* Self-closing: [italic = "text" !] or [italic = text: "text" !]
|
|
203
99
|
*/
|
|
204
|
-
MARKDOWN.register(["italic", "i"], ({ content }) => {
|
|
205
|
-
|
|
206
|
-
|
|
100
|
+
MARKDOWN.register(["italic", "i"], ({ props, content, isSelfClosing }) => {
|
|
101
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
102
|
+
return md.italic(text);
|
|
103
|
+
});
|
|
207
104
|
|
|
208
105
|
/**
|
|
209
106
|
* Emphasis - Renders bold-italic text (***text***).
|
|
107
|
+
* Self-closing: [emphasis = "text" !] or [emphasis = text: "text" !]
|
|
210
108
|
*/
|
|
211
|
-
MARKDOWN.register(["emphasis", "em"], ({ content }) => {
|
|
212
|
-
|
|
213
|
-
|
|
109
|
+
MARKDOWN.register(["emphasis", "em"], ({ props, content, isSelfClosing }) => {
|
|
110
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
111
|
+
return md.emphasis(text);
|
|
112
|
+
});
|
|
214
113
|
|
|
215
114
|
/**
|
|
216
115
|
* Strike - Renders strikethrough text (~~text~~).
|
|
116
|
+
* Self-closing: [strike = "text" !] or [strike = text: "text" !]
|
|
217
117
|
*/
|
|
218
|
-
MARKDOWN.register(["strike", "s"], ({ content }) => {
|
|
219
|
-
|
|
220
|
-
|
|
118
|
+
MARKDOWN.register(["strike", "s"], ({ props, content, isSelfClosing }) => {
|
|
119
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
120
|
+
return md.strike(text);
|
|
121
|
+
});
|
|
221
122
|
|
|
222
123
|
/**
|
|
223
124
|
* Code - Renders inline or fenced code blocks.
|
|
224
125
|
*/
|
|
225
126
|
MARKDOWN.register(
|
|
226
127
|
["Code", "code"],
|
|
227
|
-
({
|
|
228
|
-
if (
|
|
229
|
-
|
|
128
|
+
({ props, content, isSelfClosing }) => {
|
|
129
|
+
if (isSelfClosing) {
|
|
130
|
+
const text = safeArg({ props, index: 0, key: "text", fallBack: "" });
|
|
131
|
+
return `\`${text}\``;
|
|
230
132
|
}
|
|
231
|
-
const lang = safeArg({
|
|
133
|
+
const lang = safeArg({ props, index: 0, key: "lang", fallBack: "" });
|
|
232
134
|
return md.codeBlock(content, lang);
|
|
233
135
|
},
|
|
234
|
-
{
|
|
235
|
-
escape: false,
|
|
236
|
-
type: ["AtBlock", "Inline"]
|
|
237
|
-
}
|
|
136
|
+
{ escape: false }
|
|
238
137
|
);
|
|
239
138
|
|
|
240
139
|
/**
|
|
241
140
|
* Link - Renders Markdown links [text](url).
|
|
141
|
+
* Body form: [link = src: "url", title: "..."]text[end]
|
|
142
|
+
* Self-closing: [link = "text", "url" !] or [link = text: "...", src: "...", title: "..." !]
|
|
242
143
|
*/
|
|
243
144
|
MARKDOWN.register(
|
|
244
145
|
"link",
|
|
245
|
-
({
|
|
246
|
-
|
|
247
|
-
|
|
146
|
+
({ props, content, isSelfClosing }) => {
|
|
147
|
+
if (isSelfClosing) {
|
|
148
|
+
const text = safeArg({ props, index: 0, key: "text", fallBack: "" });
|
|
149
|
+
const src = safeArg({ props, index: 1, key: "src", fallBack: "" });
|
|
150
|
+
const title = safeArg({ props, index: 2, key: "title", fallBack: "" });
|
|
151
|
+
return md.url("link", text, src, title);
|
|
152
|
+
}
|
|
153
|
+
const src = safeArg({ props, index: 0, key: "src", fallBack: "" });
|
|
154
|
+
const title = safeArg({ props, index: 1, key: "title", fallBack: "" });
|
|
248
155
|
return md.url("link", content, src, title);
|
|
249
156
|
},
|
|
250
|
-
{
|
|
251
|
-
type: ["Block", "Inline"],
|
|
252
|
-
rules: { is_empty_body: false }
|
|
253
|
-
}
|
|
157
|
+
{ rules: { is_empty_body: false } }
|
|
254
158
|
);
|
|
255
159
|
|
|
256
160
|
/**
|
|
257
161
|
* Image - Renders Markdown images .
|
|
162
|
+
* [image = "alt", "src", "title" !] or [image = alt: "...", src: "...", title: "..." !]
|
|
258
163
|
*/
|
|
259
164
|
MARKDOWN.register(
|
|
260
165
|
"image",
|
|
261
|
-
({
|
|
262
|
-
const alt = safeArg({
|
|
263
|
-
const src = safeArg({
|
|
264
|
-
const title = safeArg({
|
|
166
|
+
({ props }) => {
|
|
167
|
+
const alt = safeArg({ props, index: 0, key: "alt", fallBack: "" });
|
|
168
|
+
const src = safeArg({ props, index: 1, key: "src", fallBack: "" });
|
|
169
|
+
const title = safeArg({ props, index: 2, key: "title", fallBack: "" });
|
|
265
170
|
return md.url("image", alt, src, title);
|
|
266
171
|
},
|
|
267
|
-
{
|
|
268
|
-
type: "Block",
|
|
269
|
-
rules: { is_empty_body: true }
|
|
270
|
-
}
|
|
172
|
+
{ rules: { is_empty_body: true } }
|
|
271
173
|
);
|
|
272
174
|
|
|
273
175
|
/**
|
|
@@ -275,170 +177,151 @@ MARKDOWN.register(
|
|
|
275
177
|
*/
|
|
276
178
|
MARKDOWN.register(
|
|
277
179
|
"hr",
|
|
278
|
-
({
|
|
279
|
-
const fmt = safeArg({
|
|
180
|
+
({ props }) => {
|
|
181
|
+
const fmt = safeArg({ props, index: 0, fallBack: "-" });
|
|
280
182
|
return md.horizontal(fmt);
|
|
281
183
|
},
|
|
282
|
-
{
|
|
283
|
-
type: "Block",
|
|
284
|
-
rules: { is_empty_body: true }
|
|
285
|
-
}
|
|
184
|
+
{ rules: { is_empty_body: true } }
|
|
286
185
|
);
|
|
287
186
|
|
|
288
187
|
/**
|
|
289
188
|
* Escape - Escapes special Markdown characters.
|
|
189
|
+
* Self-closing: [escape = "text" !] or [escape = text: "text" !]
|
|
290
190
|
*/
|
|
291
|
-
MARKDOWN.register(["escape", "e"], function ({ content }) {
|
|
292
|
-
|
|
293
|
-
|
|
191
|
+
MARKDOWN.register(["escape", "e"], function ({ props, content, isSelfClosing }) {
|
|
192
|
+
const text = isSelfClosing ? safeArg({ props, index: 0, key: "text", fallBack: "" }) : content;
|
|
193
|
+
return this.md.escape(text);
|
|
194
|
+
}, { resolve: true });
|
|
195
|
+
|
|
196
|
+
const ROW_SEP = "\x1E";
|
|
197
|
+
const CELL_SEP = "\x1F";
|
|
294
198
|
|
|
295
199
|
/**
|
|
296
200
|
* Table - Authoritative Native AST Table resolution.
|
|
297
201
|
* Processes Header/Body sections with Row/Cell nesting.
|
|
202
|
+
* Supports [for-each] inside [body] for dynamic rows.
|
|
298
203
|
*/
|
|
299
204
|
MARKDOWN.register(
|
|
300
205
|
"Table",
|
|
301
|
-
async function ({ ast }) {
|
|
206
|
+
async function ({ ast, renderChild }) {
|
|
302
207
|
const headers = [];
|
|
303
208
|
const rows = [];
|
|
304
209
|
|
|
305
|
-
const extractCells = async (node) => {
|
|
306
|
-
const cells = [];
|
|
307
|
-
if (!node || !node.body) return cells;
|
|
308
|
-
// Trim empty spaces while keeping line breaks
|
|
309
|
-
const cellAst = node.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
310
|
-
.filter(n => n.type !== TEXT || n.text);
|
|
311
|
-
|
|
312
|
-
for (const child of cellAst) {
|
|
313
|
-
if (child.type === BLOCK && (child.id.toLowerCase() === "cell" || child.id.toLowerCase() === "th" || child.id.toLowerCase() === "td")) {
|
|
314
|
-
const cellContent = await renderNodeAst(child.body, this);
|
|
315
|
-
cells.push(cellContent.trim());
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return cells;
|
|
319
|
-
};
|
|
320
|
-
|
|
321
210
|
const extractRows = async (sectionNode) => {
|
|
322
|
-
if (!sectionNode || !sectionNode.body) return [];
|
|
323
211
|
const sectionRows = [];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
212
|
+
for (const child of (sectionNode.body || [])) {
|
|
213
|
+
if (child.type === BLOCK && child.id?.toLowerCase() === "row") {
|
|
214
|
+
const rendered = await renderChild(child, { inTable: true });
|
|
215
|
+
const cells = rendered.split(ROW_SEP)[0]?.split(CELL_SEP).filter(c => c !== "") ?? [];
|
|
216
|
+
if (cells.length > 0) sectionRows.push(cells);
|
|
217
|
+
} else if (child.type === FOR_EACH) {
|
|
218
|
+
const rendered = await renderChild(child, { inTable: true });
|
|
219
|
+
for (const row of rendered.split(ROW_SEP)) {
|
|
220
|
+
const cells = row.split(CELL_SEP).filter(c => c !== "");
|
|
221
|
+
if (cells.length > 0) sectionRows.push(cells);
|
|
222
|
+
}
|
|
333
223
|
}
|
|
334
224
|
}
|
|
335
225
|
return sectionRows;
|
|
336
226
|
};
|
|
337
227
|
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (node.type !== BLOCK) continue;
|
|
347
|
-
|
|
348
|
-
const id = node.id.toLowerCase();
|
|
349
|
-
if (id === "header") {
|
|
350
|
-
const headerRows = await extractRows(node);
|
|
351
|
-
if (headerRows.length > 0) {
|
|
352
|
-
headers.push(...headerRows[0]);
|
|
353
|
-
}
|
|
354
|
-
} else if (id === "body") {
|
|
355
|
-
const bodyRows = await extractRows(node);
|
|
356
|
-
rows.push(...bodyRows);
|
|
357
|
-
}
|
|
228
|
+
for (const node of ast.body) {
|
|
229
|
+
if (node.type !== BLOCK) continue;
|
|
230
|
+
const id = node.id.toLowerCase();
|
|
231
|
+
if (id === "header") {
|
|
232
|
+
const headerRows = await extractRows(node);
|
|
233
|
+
if (headerRows.length > 0) headers.push(...headerRows[0]);
|
|
234
|
+
} else if (id === "body") {
|
|
235
|
+
rows.push(...(await extractRows(node)));
|
|
358
236
|
}
|
|
359
|
-
|
|
360
|
-
};
|
|
237
|
+
}
|
|
361
238
|
|
|
362
|
-
return
|
|
239
|
+
return md.table(headers, rows);
|
|
363
240
|
},
|
|
364
|
-
{
|
|
365
|
-
escape: true,
|
|
366
|
-
type: "Block",
|
|
367
|
-
handleAst: true,
|
|
368
|
-
trimAndWrapBlocks: false
|
|
369
|
-
}
|
|
241
|
+
{ escape: true, handleAst: true, trimAndWrapBlocks: false }
|
|
370
242
|
);
|
|
371
243
|
|
|
372
244
|
/**
|
|
373
245
|
* Table Helpers - Internal tags for table structural organization.
|
|
374
246
|
*/
|
|
375
|
-
MARKDOWN.register(["header", "body"
|
|
247
|
+
MARKDOWN.register(["header", "body"], ({ content }) => content);
|
|
248
|
+
|
|
249
|
+
MARKDOWN.register("row", async function ({ ast, renderChild, inTable }) {
|
|
250
|
+
if (!inTable) {
|
|
251
|
+
let result = "";
|
|
252
|
+
for (const child of ast.body) {
|
|
253
|
+
if (child.type === TEXT) result += this.text(child.text);
|
|
254
|
+
else if (child.type === BLOCK) result += await renderChild(child);
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
let cells = "";
|
|
259
|
+
for (const child of ast.body) {
|
|
260
|
+
if (child.type !== BLOCK) continue;
|
|
261
|
+
const id = child.id?.toLowerCase();
|
|
262
|
+
if (id === "cell" || id === "th" || id === "td") {
|
|
263
|
+
cells += await renderChild(child, { inTable: true });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return cells + ROW_SEP;
|
|
267
|
+
}, { handleAst: true });
|
|
268
|
+
|
|
269
|
+
MARKDOWN.register(["cell", "th", "td"], ({ content, inTable }) => {
|
|
270
|
+
return inTable ? content.trim() + CELL_SEP : content;
|
|
271
|
+
});
|
|
376
272
|
|
|
377
273
|
/**
|
|
378
274
|
* Lists - Authoritative Native AST List resolution.
|
|
379
275
|
* Supports Ordered (Number) and Unordered (Dotlex) lists with deep nesting.
|
|
380
276
|
*/
|
|
381
|
-
MARKDOWN.register(["list", "List"], async function ({ ast,
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
// Determine list type (dot/unordered vs number/ordered)
|
|
385
|
-
const indicator = safeArg({ args, index: 0, fallBack: "dot" });
|
|
277
|
+
MARKDOWN.register(["list", "List"], async function ({ ast, props, renderChild }) {
|
|
278
|
+
const indicator = safeArg({ props, index: 0, fallBack: "dot" });
|
|
386
279
|
const isOrdered = indicator === "number" || indicator === "ol";
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (!isOrdered) {
|
|
390
|
-
if (indicator === "dot") marker = "-";
|
|
391
|
-
else marker = indicator; // Custom symbol like "*" or "+"
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Remove empty spaces from the start and end of strings
|
|
395
|
-
const itemNodes = ast.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
396
|
-
.filter(n => n.type !== TEXT || n.text);
|
|
280
|
+
const marker = isOrdered ? "" : (indicator === "dot" ? "-" : indicator);
|
|
281
|
+
const items = [];
|
|
397
282
|
|
|
398
|
-
for (const node of
|
|
283
|
+
for (const node of ast.body) {
|
|
284
|
+
if (node.type !== BLOCK) continue;
|
|
399
285
|
const id = node.id?.toLowerCase();
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
const itemBody = node.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
403
|
-
.filter(n => n.type !== TEXT || n.text);
|
|
404
|
-
const itemContent = await renderNodeAst(itemBody, this);
|
|
405
|
-
items.push(itemContent.trim());
|
|
406
|
-
} else if (node.type === BLOCK && (id === "list")) {
|
|
407
|
-
// Add nested lists to the latest item
|
|
408
|
-
if (items.length > 0) {
|
|
409
|
-
const listContent = await renderNodeAst([node], this);
|
|
410
|
-
items[items.length - 1] += "\n" + listContent;
|
|
411
|
-
}
|
|
286
|
+
if (id === "item") {
|
|
287
|
+
items.push((await renderChild(node)).trim());
|
|
412
288
|
}
|
|
413
289
|
}
|
|
414
290
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
: md.unorderedList(items, 0, marker);
|
|
418
|
-
|
|
419
|
-
return result;
|
|
420
|
-
}, { type: "Block", handleAst: true, trimAndWrapBlocks: false });
|
|
291
|
+
return isOrdered ? md.orderedList(items, 0) : md.unorderedList(items, 0, marker);
|
|
292
|
+
}, { handleAst: true, trimAndWrapBlocks: false });
|
|
421
293
|
|
|
422
294
|
/**
|
|
423
295
|
* List Helpers - Internal tags for list structural organization.
|
|
424
296
|
*/
|
|
425
|
-
MARKDOWN.register(["item", "Item"], async function ({ ast }) {
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
297
|
+
MARKDOWN.register(["item", "Item"], async function ({ ast, renderChild }) {
|
|
298
|
+
let result = "";
|
|
299
|
+
for (const child of ast.body) {
|
|
300
|
+
if (child.type === TEXT) result += this.text(child.text);
|
|
301
|
+
else if (child.type === BLOCK) result += await renderChild(child);
|
|
302
|
+
}
|
|
303
|
+
return result.trim();
|
|
304
|
+
}, { handleAst: true, trimAndWrapBlocks: false });
|
|
431
305
|
|
|
432
306
|
/**
|
|
433
307
|
* Todo - Renders task list items with status markers.
|
|
308
|
+
*
|
|
309
|
+
* Supported forms:
|
|
310
|
+
* [todo = task: "Add feature", status: "x" !] named self-closing
|
|
311
|
+
* [todo = "Add feature", "x" !] positional self-closing (task, status)
|
|
312
|
+
* [todo = "x"]Add feature[end] status in prop, task in body
|
|
434
313
|
*/
|
|
435
|
-
MARKDOWN.register("todo", ({
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
314
|
+
MARKDOWN.register("todo", ({ props, content, isSelfClosing }) => {
|
|
315
|
+
let status, task;
|
|
316
|
+
|
|
317
|
+
if (isSelfClosing) {
|
|
318
|
+
task = safeArg({ props, index: 0, key: "task", fallBack: "" });
|
|
319
|
+
status = safeArg({ props, index: 1, key: "status", fallBack: "" });
|
|
320
|
+
} else {
|
|
321
|
+
status = safeArg({ props, index: 0, fallBack: "" });
|
|
322
|
+
task = content;
|
|
323
|
+
}
|
|
441
324
|
|
|
442
325
|
return md.todo(status, task);
|
|
443
|
-
}, {
|
|
326
|
+
}, { trimAndWrapBlocks: false });
|
|
444
327
|
export default MARKDOWN;
|