sommark 3.3.3 → 4.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 +98 -82
- package/assets/logo.json +28 -0
- package/assets/smark.logo.png +0 -0
- package/assets/smark.logo.svg +21 -0
- package/cli/cli.mjs +8 -16
- package/cli/commands/build.js +24 -4
- package/cli/commands/color.js +22 -26
- package/cli/commands/help.js +10 -10
- package/cli/commands/init.js +19 -42
- package/cli/commands/print.js +20 -12
- package/cli/commands/show.js +4 -0
- package/cli/commands/version.js +6 -0
- package/cli/constants.js +9 -5
- package/cli/helpers/config.js +11 -0
- package/cli/helpers/file.js +17 -6
- package/cli/helpers/transpile.js +7 -8
- package/core/errors.js +49 -25
- package/core/formats.js +7 -3
- package/core/formatter.js +215 -0
- package/core/helpers/config-loader.js +37 -56
- package/core/labels.js +21 -9
- package/core/lexer.js +491 -212
- package/core/modules.js +164 -0
- package/core/parser.js +516 -389
- package/core/tokenTypes.js +36 -1
- package/core/transpiler.js +237 -151
- package/core/validator.js +79 -0
- package/formatter/mark.js +203 -43
- package/formatter/tag.js +202 -32
- package/grammar.ebnf +57 -50
- package/helpers/colorize.js +26 -13
- package/helpers/escapeHTML.js +13 -6
- package/helpers/kebabize.js +6 -0
- package/helpers/peek.js +9 -0
- package/helpers/removeChar.js +26 -13
- package/helpers/safeDataParser.js +114 -0
- package/helpers/utils.js +140 -158
- package/index.js +198 -188
- package/mappers/languages/html.js +105 -213
- package/mappers/languages/json.js +122 -171
- package/mappers/languages/markdown.js +355 -108
- package/mappers/languages/mdx.js +76 -114
- package/mappers/languages/xml.js +114 -0
- package/mappers/mapper.js +152 -123
- package/mappers/shared/index.js +22 -0
- package/package.json +26 -6
- package/SOMMARK-SPEC.md +0 -481
- package/cli/commands/list.js +0 -124
- package/constants/html_tags.js +0 -146
- package/core/pluginManager.js +0 -149
- package/core/plugins/comment-remover.js +0 -47
- package/core/plugins/module-system.js +0 -176
- package/core/plugins/raw-content-plugin.js +0 -78
- package/core/plugins/rules-validation-plugin.js +0 -231
- package/core/plugins/sommark-format.js +0 -244
- package/coverage_test.js +0 -21
- package/debug.js +0 -15
- package/helpers/camelize.js +0 -2
- package/helpers/defaultTheme.js +0 -3
- package/test_format_fix.js +0 -42
- package/v3-todo.smark +0 -73
|
@@ -1,157 +1,404 @@
|
|
|
1
1
|
import Mapper from "../mapper.js";
|
|
2
2
|
import HTML from "./html.js";
|
|
3
|
-
import {
|
|
3
|
+
import { registerSharedOutputs } from "../shared/index.js";
|
|
4
|
+
import { BLOCK, TEXT} from "../../core/labels.js";
|
|
5
|
+
import transpiler from "../../core/transpiler.js";
|
|
6
|
+
import { VOID_ELEMENTS } from "../../constants/void_elements.js";
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The Markdown Mapper used for generating Markdown text.
|
|
10
|
+
*/
|
|
11
|
+
const MARKDOWN = Mapper.define({
|
|
12
|
+
options: {
|
|
13
|
+
trimAndWrapBlocks: false
|
|
14
|
+
},
|
|
15
|
+
/**
|
|
16
|
+
* Renders an HTML-style comment in Markdown output.
|
|
17
|
+
*/
|
|
9
18
|
comment(text) {
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
return `<!-- ${text} -->`;
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Formats a plain text node with Markdown escaping only.
|
|
23
|
+
*/
|
|
24
|
+
text(text, options) {
|
|
25
|
+
if (options?.escape === false) return text;
|
|
26
|
+
// Use smartEscaper to protect special characters like math
|
|
27
|
+
let out = text;
|
|
28
|
+
if (this.md && this.md.smartEscaper) out = this.md.smartEscaper(out);
|
|
29
|
+
return out;
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Formats inline content before rendering.
|
|
34
|
+
*/
|
|
35
|
+
inlineText(text, options) {
|
|
36
|
+
if (options?.escape !== false) {
|
|
37
|
+
// Use smartEscaper to protect special characters
|
|
38
|
+
let out = text;
|
|
39
|
+
if (this.md && this.md.smartEscaper) out = this.md.smartEscaper(out);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formats the literal content inside AtBlocks.
|
|
47
|
+
*/
|
|
48
|
+
atBlockBody(text, options) {
|
|
49
|
+
if (options?.escape === false) return text;
|
|
50
|
+
// Escaping with smartEscaper
|
|
51
|
+
let out = text;
|
|
52
|
+
if (this.md && this.md.smartEscaper) out = this.md.smartEscaper(out);
|
|
53
|
+
return out;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Provides a fallback for unknown tags by using the HTML mapper instead.
|
|
58
|
+
*/
|
|
59
|
+
getUnknownTag(node) {
|
|
60
|
+
const isBlock = node.type === BLOCK;
|
|
61
|
+
const id = node.id.toLowerCase();
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
render: async (ctx) => {
|
|
65
|
+
const { args, ast } = ctx;
|
|
66
|
+
const body = ast && ast.body ? ast.body : [];
|
|
67
|
+
const meaningful = body.filter(c => c.type !== TEXT || c.text.trim());
|
|
68
|
+
const childCount = meaningful.length;
|
|
69
|
+
const element = this.tag(id).smartAttributes(args, this.customProps);
|
|
70
|
+
|
|
71
|
+
// Use the transpiler to format the children if any, otherwise use direct content
|
|
72
|
+
let rawContent;
|
|
73
|
+
if (node.type === "AtBlock") {
|
|
74
|
+
rawContent = node.content || "";
|
|
75
|
+
rawContent = this.atBlockBody(rawContent, ctx);
|
|
76
|
+
} else if (node.type === "Inline") {
|
|
77
|
+
rawContent = node.value || "";
|
|
78
|
+
rawContent = this.inlineText(rawContent, ctx);
|
|
79
|
+
} else {
|
|
80
|
+
rawContent = (await transpiler({ ast: body, mapperFile: this })).trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (VOID_ELEMENTS.has(id)) {
|
|
84
|
+
return element.selfClose();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let finalContent;
|
|
88
|
+
if (childCount <= 1) {
|
|
89
|
+
// COMPACT PASS: Single child or empty
|
|
90
|
+
finalContent = rawContent;
|
|
91
|
+
} else {
|
|
92
|
+
// MULTILINE PASS: Enforce \n prefix/suffix for multiple children
|
|
93
|
+
finalContent = `\n${rawContent}\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return element.body(finalContent);
|
|
97
|
+
},
|
|
98
|
+
options: {
|
|
99
|
+
type: isBlock ? "Block" : (node.type === "AtBlock" ? "AtBlock" : "Inline"),
|
|
100
|
+
handleAst: true
|
|
28
101
|
}
|
|
29
|
-
|
|
30
|
-
return this.md.todo(todo(finalStatus), finalTask);
|
|
31
|
-
});
|
|
102
|
+
};
|
|
32
103
|
}
|
|
33
|
-
}
|
|
104
|
+
});
|
|
34
105
|
|
|
35
|
-
const MARKDOWN = new MarkdownMapper();
|
|
36
106
|
MARKDOWN.inherit(HTML);
|
|
37
107
|
const { md, safeArg } = MARKDOWN;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
MARKDOWN.register(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
108
|
+
registerSharedOutputs(MARKDOWN);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Quote - Renders blockquote content or GFM alerts.
|
|
112
|
+
*/
|
|
113
|
+
MARKDOWN.register("quote", ({ args, content }) => {
|
|
114
|
+
const type = safeArg({ args, index: 0, key: "type", fallBack: "" });
|
|
115
|
+
return md.quote(content, type);
|
|
116
|
+
}, { type: "Block", resolve: true });
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Unified heading renderer for Markdown and MDX mappers.
|
|
120
|
+
* @param {Object} options - Mapper context and args.
|
|
121
|
+
* @param {string} defaultFormat - Default format ("markdown" or "html").
|
|
122
|
+
* @returns {string} - Rendered heading.
|
|
123
|
+
*/
|
|
124
|
+
export function renderHeading({ args, content, ast }, defaultFormat = "markdown") {
|
|
125
|
+
const heading = ast.id;
|
|
126
|
+
const format = safeArg({ args, index: 0, key: "format", type: "string", fallBack: defaultFormat });
|
|
127
|
+
const lvl = heading[1] && !isNaN(Number(heading[1])) ? Number(heading[1]) : 1;
|
|
128
|
+
|
|
129
|
+
// Remove formatting arguments before checking for attributes
|
|
130
|
+
const cleanArgs = { ...args };
|
|
131
|
+
delete cleanArgs.format;
|
|
132
|
+
delete cleanArgs["0"]; // Clean positional 'format'
|
|
133
|
+
|
|
134
|
+
const hasAttributes = Object.keys(cleanArgs).length > 0;
|
|
135
|
+
|
|
136
|
+
// Hybrid Dispatch: Switch to HTML if format is requested OR if attributes are present
|
|
137
|
+
if (format === "html" || hasAttributes) {
|
|
138
|
+
let htmlTarget = HTML.get(heading);
|
|
139
|
+
if (!htmlTarget) {
|
|
140
|
+
htmlTarget = HTML.getUnknownTag(ast);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (htmlTarget) {
|
|
144
|
+
return htmlTarget.render.call(this, { args: cleanArgs, content, ast, nodeType: ast.type });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return md.heading(content, lvl);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Headings - Renders H1-H6 block headings.
|
|
153
|
+
*/
|
|
50
154
|
["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
|
|
51
|
-
MARKDOWN.register(heading, (
|
|
52
|
-
|
|
53
|
-
return md.heading(content, lvl);
|
|
155
|
+
MARKDOWN.register(heading, function (ctx) {
|
|
156
|
+
return renderHeading.call(this, ctx, "markdown");
|
|
54
157
|
}, { type: "Block" });
|
|
55
158
|
});
|
|
56
|
-
|
|
57
|
-
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Bold - Renders bold text (**text**).
|
|
162
|
+
*/
|
|
163
|
+
MARKDOWN.register(["bold", "b"], ({ content }) => {
|
|
58
164
|
return md.bold(content);
|
|
59
|
-
}, { type: "
|
|
60
|
-
|
|
61
|
-
|
|
165
|
+
}, { type: ["Block", "Inline"] });
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Italic - Renders italic text (*text*).
|
|
169
|
+
*/
|
|
170
|
+
MARKDOWN.register(["italic", "i"], ({ content }) => {
|
|
62
171
|
return md.italic(content);
|
|
63
|
-
}, { type: "
|
|
64
|
-
|
|
65
|
-
|
|
172
|
+
}, { type: ["Block", "Inline"] });
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Emphasis - Renders bold-italic text (***text***).
|
|
176
|
+
*/
|
|
177
|
+
MARKDOWN.register(["emphasis", "em"], ({ content }) => {
|
|
66
178
|
return md.emphasis(content);
|
|
67
|
-
}, { type: "
|
|
68
|
-
|
|
179
|
+
}, { type: ["Block", "Inline"] });
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Strike - Renders strikethrough text (~~text~~).
|
|
183
|
+
*/
|
|
184
|
+
MARKDOWN.register(["strike", "s"], ({ content }) => {
|
|
185
|
+
return md.strike(content);
|
|
186
|
+
}, { type: ["Block", "Inline"] });
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Code - Renders inline or fenced code blocks.
|
|
190
|
+
*/
|
|
69
191
|
MARKDOWN.register(
|
|
70
|
-
"Code",
|
|
71
|
-
({ args, content }) => {
|
|
72
|
-
|
|
192
|
+
["Code", "code"],
|
|
193
|
+
({ args, content, nodeType }) => {
|
|
194
|
+
if (nodeType === "Inline") {
|
|
195
|
+
return `\`${content}\``;
|
|
196
|
+
}
|
|
197
|
+
const lang = safeArg({ args, index: 0, key: "lang", fallBack: "text" });
|
|
73
198
|
return md.codeBlock(content, lang);
|
|
74
199
|
},
|
|
75
200
|
{
|
|
76
201
|
escape: false,
|
|
77
|
-
type: ["AtBlock", "
|
|
202
|
+
type: ["AtBlock", "Inline"]
|
|
78
203
|
}
|
|
79
204
|
);
|
|
80
|
-
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Link - Renders Markdown links [text](url).
|
|
208
|
+
*/
|
|
81
209
|
MARKDOWN.register(
|
|
82
210
|
"link",
|
|
83
|
-
({ args }) => {
|
|
84
|
-
const
|
|
85
|
-
const title = safeArg(args, 1, "title",
|
|
86
|
-
|
|
87
|
-
return md.url("link", text, url, title);
|
|
211
|
+
({ args, content }) => {
|
|
212
|
+
const src = safeArg({ args, index: 0, key: "src", fallBack: "" });
|
|
213
|
+
const title = safeArg({ args, index: 1, key: "title", fallBack: "" });
|
|
214
|
+
return md.url("link", content, src, title);
|
|
88
215
|
},
|
|
89
216
|
{
|
|
90
|
-
type: "Block"
|
|
217
|
+
type: ["Block", "Inline"],
|
|
218
|
+
rules: { is_self_closing: false }
|
|
91
219
|
}
|
|
92
220
|
);
|
|
93
|
-
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Image - Renders Markdown images .
|
|
224
|
+
*/
|
|
94
225
|
MARKDOWN.register(
|
|
95
226
|
"image",
|
|
96
227
|
({ args }) => {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
return md.url("image", alt,
|
|
228
|
+
const alt = safeArg({ args, index: 0, key: "alt", fallBack: "" });
|
|
229
|
+
const src = safeArg({ args, index: 1, key: "src", fallBack: "" });
|
|
230
|
+
const title = safeArg({ args, index: 2, key: "title", fallBack: "" });
|
|
231
|
+
return md.url("image", alt, src, title);
|
|
101
232
|
},
|
|
102
233
|
{
|
|
103
|
-
type: "Block"
|
|
234
|
+
type: "Block",
|
|
235
|
+
rules: { is_self_closing: true }
|
|
104
236
|
}
|
|
105
237
|
);
|
|
106
|
-
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* HR (Horizontal Rule) - Renders a thematic break (---).
|
|
241
|
+
*/
|
|
107
242
|
MARKDOWN.register(
|
|
108
243
|
"hr",
|
|
109
244
|
({ args }) => {
|
|
110
|
-
const fmt = safeArg(args, 0,
|
|
245
|
+
const fmt = safeArg({ args, index: 0, fallBack: "-" });
|
|
111
246
|
return md.horizontal(fmt);
|
|
112
247
|
},
|
|
113
248
|
{
|
|
114
|
-
type: "Block"
|
|
249
|
+
type: "Block",
|
|
250
|
+
rules: { is_self_closing: true }
|
|
115
251
|
}
|
|
116
252
|
);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Escape - Escapes special Markdown characters.
|
|
256
|
+
*/
|
|
257
|
+
MARKDOWN.register(["escape", "e"], function ({ content }) {
|
|
258
|
+
return this.md.escape(content);
|
|
259
|
+
}, { type: ["Block", "Inline"], resolve: true });
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Table - Authoritative Native AST Table resolution.
|
|
263
|
+
* Processes Header/Body sections with Row/Cell nesting.
|
|
264
|
+
*/
|
|
122
265
|
MARKDOWN.register(
|
|
123
266
|
"Table",
|
|
124
|
-
({
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
267
|
+
async function ({ ast }) {
|
|
268
|
+
const headers = [];
|
|
269
|
+
const rows = [];
|
|
270
|
+
|
|
271
|
+
const extractCells = async (node) => {
|
|
272
|
+
const cells = [];
|
|
273
|
+
if (!node || !node.body) return cells;
|
|
274
|
+
// Trim empty spaces while keeping line breaks
|
|
275
|
+
const cellAst = node.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
276
|
+
.filter(n => n.type !== TEXT || n.text);
|
|
277
|
+
|
|
278
|
+
for (const child of cellAst) {
|
|
279
|
+
if (child.type === BLOCK && (child.id.toLowerCase() === "cell" || child.id.toLowerCase() === "th" || child.id.toLowerCase() === "td")) {
|
|
280
|
+
const cellContent = await transpiler({ ast: child.body, mapperFile: this });
|
|
281
|
+
cells.push(cellContent.trim());
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return cells;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const extractRows = async (sectionNode) => {
|
|
288
|
+
if (!sectionNode || !sectionNode.body) return [];
|
|
289
|
+
const sectionRows = [];
|
|
290
|
+
// Trim empty spaces while keeping line breaks
|
|
291
|
+
const rowAst = sectionNode.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
292
|
+
.filter(n => n.type !== TEXT || n.text);
|
|
293
|
+
for (const rowNode of rowAst) {
|
|
294
|
+
if (rowNode.type === BLOCK && rowNode.id.toLowerCase() === "row") {
|
|
295
|
+
const rowData = await extractCells(rowNode);
|
|
296
|
+
if (rowData.length > 0) sectionRows.push(rowData);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return sectionRows;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const processTable = async () => {
|
|
303
|
+
// Remove empty text blocks
|
|
304
|
+
const tableNodes = ast.body.filter(n => n.type !== TEXT || n.text.trim());
|
|
305
|
+
for (const node of tableNodes) {
|
|
306
|
+
if (node.type !== BLOCK) continue;
|
|
307
|
+
|
|
308
|
+
const id = node.id.toLowerCase();
|
|
309
|
+
if (id === "header") {
|
|
310
|
+
const headerRows = await extractRows(node);
|
|
311
|
+
if (headerRows.length > 0) {
|
|
312
|
+
headers.push(...headerRows[0]);
|
|
313
|
+
}
|
|
314
|
+
} else if (id === "body") {
|
|
315
|
+
const bodyRows = await extractRows(node);
|
|
316
|
+
rows.push(...bodyRows);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return md.table(headers, rows);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return processTable();
|
|
141
323
|
},
|
|
142
|
-
{
|
|
324
|
+
{
|
|
325
|
+
escape: true,
|
|
326
|
+
type: "Block",
|
|
327
|
+
handleAst: true,
|
|
328
|
+
trimAndWrapBlocks: false
|
|
329
|
+
}
|
|
143
330
|
);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Table Helpers - Internal tags for table structural organization.
|
|
334
|
+
*/
|
|
335
|
+
MARKDOWN.register(["header", "body", "row", "cell"], ({ content }) => content);
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Lists - Authoritative Native AST List resolution.
|
|
339
|
+
* Supports Ordered (Number) and Unordered (Dotlex) lists with deep nesting.
|
|
340
|
+
*/
|
|
341
|
+
MARKDOWN.register(["list", "List"], async function ({ ast, args }) {
|
|
342
|
+
const items = [];
|
|
343
|
+
|
|
344
|
+
// Determine list type (dot/unordered vs number/ordered)
|
|
345
|
+
const indicator = safeArg({ args, index: 0, fallBack: "dot" });
|
|
346
|
+
const isOrdered = indicator === "number" || indicator === "ol";
|
|
347
|
+
let marker = "-";
|
|
348
|
+
|
|
349
|
+
if (!isOrdered) {
|
|
350
|
+
if (indicator === "dot") marker = "-";
|
|
351
|
+
else marker = indicator; // Custom symbol like "*" or "+"
|
|
149
352
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
353
|
+
|
|
354
|
+
// Remove empty spaces from the start and end of strings
|
|
355
|
+
const itemNodes = ast.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
356
|
+
.filter(n => n.type !== TEXT || n.text);
|
|
357
|
+
|
|
358
|
+
for (const node of itemNodes) {
|
|
359
|
+
const id = node.id?.toLowerCase();
|
|
360
|
+
if (node.type === BLOCK && (id === "item")) {
|
|
361
|
+
// Trim spaces inside the list item
|
|
362
|
+
const itemBody = node.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
363
|
+
.filter(n => n.type !== TEXT || n.text);
|
|
364
|
+
const itemContent = await transpiler({ ast: itemBody, mapperFile: this });
|
|
365
|
+
items.push(itemContent.trim());
|
|
366
|
+
} else if (node.type === BLOCK && (id === "list")) {
|
|
367
|
+
// Add nested lists to the latest item
|
|
368
|
+
if (items.length > 0) {
|
|
369
|
+
const listContent = await transpiler({ ast: [node], mapperFile: this });
|
|
370
|
+
items[items.length - 1] += "\n" + listContent;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const result = isOrdered
|
|
376
|
+
? md.orderedList(items, 0)
|
|
377
|
+
: md.unorderedList(items, 0, marker);
|
|
378
|
+
|
|
379
|
+
return result;
|
|
380
|
+
}, { type: "Block", handleAst: true, trimAndWrapBlocks: false });
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* List Helpers - Internal tags for list structural organization.
|
|
384
|
+
*/
|
|
385
|
+
MARKDOWN.register(["item", "Item"], async function ({ ast }) {
|
|
386
|
+
// Trim whitespace but keep line breaks
|
|
387
|
+
const bodyAst = ast.body.map(n => n.type === TEXT ? { ...n, text: n.text.replace(/^[ ]+|[ ]+$/gm, "") } : n)
|
|
388
|
+
.filter(n => n.type !== TEXT || n.text);
|
|
389
|
+
return await transpiler({ ast: bodyAst, mapperFile: this });
|
|
390
|
+
}, { type: "Block", handleAst: true, trimAndWrapBlocks: false });
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Todo - Renders task list items with status markers.
|
|
394
|
+
*/
|
|
395
|
+
MARKDOWN.register("todo", ({ args, content }) => {
|
|
396
|
+
const statusMarkers = ["done", "x", "-", ""].map(s => s.toLowerCase());
|
|
397
|
+
|
|
398
|
+
const isInlineStatus = statusMarkers.includes(content.toLowerCase());
|
|
399
|
+
const status = isInlineStatus ? content : (args[0] || "");
|
|
400
|
+
const task = isInlineStatus ? (args[0] || "") : content;
|
|
401
|
+
|
|
402
|
+
return md.todo(status, task);
|
|
403
|
+
}, { type: "Block", trimAndWrapBlocks: false });
|
|
157
404
|
export default MARKDOWN;
|