apexfile 1.1.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/LICENSE +94 -0
- package/README.md +415 -0
- package/bin/cli.js +334 -0
- package/package.json +59 -0
- package/src/ast/index.js +260 -0
- package/src/index.js +438 -0
- package/src/parser/index.js +594 -0
- package/src/renderer/html.js +983 -0
- package/src/resolver/index.js +442 -0
- package/src/tokenizer/index.js +518 -0
- package/src/tokenizer/tokens.js +75 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ApexDoc CLI
|
|
6
|
+
* Usage: apex <command> [options] <file>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const apex = require('./index');
|
|
12
|
+
|
|
13
|
+
const VERSION = '1.0.0';
|
|
14
|
+
|
|
15
|
+
// ── Arg Parsing ─────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const command = args[0];
|
|
19
|
+
const flags = parseFlags(args.slice(1));
|
|
20
|
+
|
|
21
|
+
function parseFlags(argv) {
|
|
22
|
+
const result = { _: [] };
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < argv.length) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg.startsWith('--')) {
|
|
27
|
+
const key = arg.slice(2);
|
|
28
|
+
const next = argv[i + 1];
|
|
29
|
+
if (next && !next.startsWith('--')) {
|
|
30
|
+
result[key] = next;
|
|
31
|
+
i += 2;
|
|
32
|
+
} else {
|
|
33
|
+
result[key] = true;
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
result._.push(arg);
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Command Router ──────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
switch (command) {
|
|
47
|
+
case 'build': cmdBuild(); break;
|
|
48
|
+
case 'export': cmdExport(); break;
|
|
49
|
+
case 'serve': cmdServe(); break;
|
|
50
|
+
case 'lint': cmdLint(); break;
|
|
51
|
+
case 'ast': cmdAst(); break;
|
|
52
|
+
case 'format': cmdFormat(); break;
|
|
53
|
+
case 'new': cmdNew(); break;
|
|
54
|
+
case 'plugin': cmdPlugin(); break;
|
|
55
|
+
case 'theme': cmdTheme(); break;
|
|
56
|
+
case '--version':
|
|
57
|
+
case '-v': console.log(`apexdoc v${VERSION}`); break;
|
|
58
|
+
case 'help':
|
|
59
|
+
case '--help':
|
|
60
|
+
case '-h': printHelp(); break;
|
|
61
|
+
default:
|
|
62
|
+
if (!command) printHelp();
|
|
63
|
+
else die(`Unknown command: "${command}". Run "apex help" for usage.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Commands ────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function cmdBuild() {
|
|
69
|
+
const file = flags._[0];
|
|
70
|
+
const target = flags.to || 'html';
|
|
71
|
+
const out = flags.out;
|
|
72
|
+
|
|
73
|
+
requireFile(file);
|
|
74
|
+
|
|
75
|
+
log(`Building ${file} → ${target.toUpperCase()}`);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const outPath = apex.export(file, target, out ? path.join(out, outputName(file, target)) : null);
|
|
79
|
+
ok(`Output → ${outPath}`);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
die(err.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cmdExport() {
|
|
86
|
+
// Alias for build with explicit --to
|
|
87
|
+
cmdBuild();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cmdServe() {
|
|
91
|
+
const file = flags._[0];
|
|
92
|
+
const port = parseInt(flags.port || flags.p || '3000');
|
|
93
|
+
|
|
94
|
+
requireFile(file);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const http = require('http');
|
|
98
|
+
|
|
99
|
+
const respond = (res) => {
|
|
100
|
+
try {
|
|
101
|
+
const html = apex.compile(file, 'html');
|
|
102
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
103
|
+
res.end(html);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
106
|
+
res.end('ApexDoc error:\n' + err.message);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const server = http.createServer((req, res) => {
|
|
111
|
+
if (req.url === '/favicon.ico') { res.end(); return; }
|
|
112
|
+
respond(res);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.listen(port, () => {
|
|
116
|
+
ok(`Serving ${file} at http://localhost:${port}`);
|
|
117
|
+
log('Press Ctrl+C to stop. File changes will reflect on refresh.');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
} catch (err) {
|
|
121
|
+
die('Could not start server: ' + err.message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function cmdLint() {
|
|
126
|
+
const file = flags._[0];
|
|
127
|
+
requireFile(file);
|
|
128
|
+
|
|
129
|
+
log(`Linting ${file}...`);
|
|
130
|
+
|
|
131
|
+
const result = apex.lint(file);
|
|
132
|
+
|
|
133
|
+
if (result.errors.length > 0) {
|
|
134
|
+
result.errors.forEach(e => {
|
|
135
|
+
console.error(` ${red('✗')} [${e.code}] Line ${e.line}: ${e.message}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (result.warnings.length > 0) {
|
|
140
|
+
result.warnings.forEach(w => {
|
|
141
|
+
console.warn(` ${yellow('⚠')} [${w.code}] Line ${w.line}: ${w.message}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (result.valid) {
|
|
146
|
+
ok(`No errors found.${result.warnings.length > 0 ? ` (${result.warnings.length} warning(s))` : ''}`);
|
|
147
|
+
} else {
|
|
148
|
+
die(`${result.errors.length} error(s) found.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function cmdAst() {
|
|
153
|
+
const file = flags._[0];
|
|
154
|
+
requireFile(file);
|
|
155
|
+
|
|
156
|
+
log(`Inspecting AST for ${file}...`);
|
|
157
|
+
const json = apex.inspect(file);
|
|
158
|
+
|
|
159
|
+
if (flags.out) {
|
|
160
|
+
fs.writeFileSync(flags.out, json, 'utf8');
|
|
161
|
+
ok(`AST written to ${flags.out}`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(json);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function cmdFormat() {
|
|
168
|
+
const file = flags._[0];
|
|
169
|
+
requireFile(file);
|
|
170
|
+
|
|
171
|
+
// Basic formatter: normalize indentation, trim trailing spaces
|
|
172
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
173
|
+
const lines = source.split('\n');
|
|
174
|
+
let depth = 0;
|
|
175
|
+
const indent = ' ';
|
|
176
|
+
|
|
177
|
+
const formatted = lines.map(line => {
|
|
178
|
+
const t = line.trim();
|
|
179
|
+
if (!t) return '';
|
|
180
|
+
|
|
181
|
+
// Dedent on %%end
|
|
182
|
+
if (t === '%%end') depth = Math.max(0, depth - 1);
|
|
183
|
+
|
|
184
|
+
const result = depth > 0 ? indent.repeat(depth) + t : t;
|
|
185
|
+
|
|
186
|
+
// Indent after section/block open
|
|
187
|
+
if (t.startsWith('%%') && !t.startsWith('%%end') && !t.includes('%%end')) {
|
|
188
|
+
depth++;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const output = formatted.join('\n');
|
|
195
|
+
|
|
196
|
+
if (flags.check) {
|
|
197
|
+
if (output !== source) {
|
|
198
|
+
console.log('File would be reformatted.');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
} else {
|
|
201
|
+
ok('File is already formatted.');
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
fs.writeFileSync(file, output, 'utf8');
|
|
205
|
+
ok(`Formatted ${file}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function cmdNew() {
|
|
210
|
+
const name = flags._[0] || 'document.apx';
|
|
211
|
+
const type = flags.template || flags.t || 'document';
|
|
212
|
+
const filePath = name.endsWith('.apx') ? name : name + '.apx';
|
|
213
|
+
|
|
214
|
+
if (fs.existsSync(filePath) && !flags.force) {
|
|
215
|
+
die(`File already exists: ${filePath}. Use --force to overwrite.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const content = apex.template(type);
|
|
219
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
220
|
+
ok(`Created ${filePath} (template: ${type})`);
|
|
221
|
+
log(`Edit it and run: apex serve ${filePath}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function cmdPlugin() {
|
|
225
|
+
const sub = flags._[0];
|
|
226
|
+
|
|
227
|
+
if (sub === 'list') {
|
|
228
|
+
log('Installed plugins: (none — plugin registry coming soon)');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (sub === 'install') {
|
|
233
|
+
const name = flags._[1];
|
|
234
|
+
if (!name) die('Usage: apex plugin install <name>');
|
|
235
|
+
log(`Installing plugin: ${name}...`);
|
|
236
|
+
log('Plugin registry coming in v1.1.0');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (sub === 'remove') {
|
|
241
|
+
const name = flags._[1];
|
|
242
|
+
if (!name) die('Usage: apex plugin remove <name>');
|
|
243
|
+
log(`Removing plugin: ${name}...`);
|
|
244
|
+
log('Plugin registry coming in v1.1.0');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
die('Usage: apex plugin <install|remove|list> [name]');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function cmdTheme() {
|
|
252
|
+
const sub = flags._[0];
|
|
253
|
+
|
|
254
|
+
const builtins = [
|
|
255
|
+
'default', 'dark-ocean', 'neon-city', 'sunset',
|
|
256
|
+
'minimal-light', 'forest', 'candy', 'terminal', 'academic', 'newspaper'
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
if (sub === 'list') {
|
|
260
|
+
log('Built-in themes:');
|
|
261
|
+
builtins.forEach(t => console.log(` • ${t}`));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (sub === 'preview') {
|
|
266
|
+
const name = flags._[1];
|
|
267
|
+
if (!name) die('Usage: apex theme preview <name>');
|
|
268
|
+
if (!builtins.includes(name)) die(`Unknown theme: ${name}`);
|
|
269
|
+
log(`Preview for "${name}" coming in v1.1.0`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
die('Usage: apex theme <list|preview> [name]');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Help ────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
function printHelp() {
|
|
279
|
+
console.log(`
|
|
280
|
+
${bold('apex')} — ApexDoc CLI v${VERSION}
|
|
281
|
+
|
|
282
|
+
${bold('USAGE')}
|
|
283
|
+
apex <command> [options] <file.apx>
|
|
284
|
+
|
|
285
|
+
${bold('COMMANDS')}
|
|
286
|
+
build <file> Build .apx to HTML (default)
|
|
287
|
+
export <file> --to <fmt> Export to: html, pdf, text, md, json
|
|
288
|
+
serve <file> [--port N] Serve with live preview
|
|
289
|
+
lint <file> Validate syntax and structure
|
|
290
|
+
ast <file> Inspect the parsed AST
|
|
291
|
+
format <file> Auto-format the source file
|
|
292
|
+
new <name> [--template] Create a new .apx file
|
|
293
|
+
plugin install|remove|list Manage plugins
|
|
294
|
+
theme list|preview Browse themes
|
|
295
|
+
|
|
296
|
+
${bold('OPTIONS')}
|
|
297
|
+
--to <target> Export target: html (default), pdf, text, md, json
|
|
298
|
+
--out <dir> Output directory
|
|
299
|
+
--port <n> Port for serve command (default: 3000)
|
|
300
|
+
--template <t> Template for new: document, presentation, report
|
|
301
|
+
--force Overwrite existing files
|
|
302
|
+
--version Show version
|
|
303
|
+
|
|
304
|
+
${bold('EXAMPLES')}
|
|
305
|
+
apex build report.apx
|
|
306
|
+
apex build report.apx --to pdf --out ./dist
|
|
307
|
+
apex serve slides.apx --port 4000
|
|
308
|
+
apex lint doc.apx
|
|
309
|
+
apex new mydoc.apx --template report
|
|
310
|
+
apex theme list
|
|
311
|
+
`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
function requireFile(file) {
|
|
317
|
+
if (!file) die('No file specified. Usage: apex <command> <file.apx>');
|
|
318
|
+
if (!fs.existsSync(file)) die(`File not found: ${file}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function outputName(file, target) {
|
|
322
|
+
const ext = { html: '.html', text: '.txt', json: '.json', md: '.md', pdf: '.pdf' };
|
|
323
|
+
return path.basename(file, '.apx') + (ext[target] || '.html');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function log(msg) { console.log(` ${cyan('›')} ${msg}`); }
|
|
327
|
+
function ok(msg) { console.log(` ${green('✓')} ${msg}`); }
|
|
328
|
+
function die(msg) { console.error(` ${red('✗')} ${msg}`); process.exit(1); }
|
|
329
|
+
|
|
330
|
+
function bold(s) { return `\x1b[1m${s}\x1b[0m`; }
|
|
331
|
+
function red(s) { return `\x1b[31m${s}\x1b[0m`; }
|
|
332
|
+
function green(s) { return `\x1b[32m${s}\x1b[0m`; }
|
|
333
|
+
function yellow(s) { return `\x1b[33m${s}\x1b[0m`; }
|
|
334
|
+
function cyan(s) { return `\x1b[36m${s}\x1b[0m`; }
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apexfile",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "The last document format you'll ever need. A unified format with Markdown, LaTeX, SCSS, animations, live data, charts, themes and more — all from a single .apx file.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apex": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test/index.js",
|
|
11
|
+
"build": "node bin/cli.js build",
|
|
12
|
+
"serve": "node bin/cli.js serve",
|
|
13
|
+
"lint": "node bin/cli.js lint",
|
|
14
|
+
"prepublishOnly": "node test/index.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"apexdoc",
|
|
18
|
+
"apx",
|
|
19
|
+
"document",
|
|
20
|
+
"format",
|
|
21
|
+
"parser",
|
|
22
|
+
"renderer",
|
|
23
|
+
"markdown",
|
|
24
|
+
"latex",
|
|
25
|
+
"template",
|
|
26
|
+
"themes",
|
|
27
|
+
"animations",
|
|
28
|
+
"charts",
|
|
29
|
+
"html",
|
|
30
|
+
"pdf",
|
|
31
|
+
"presentation",
|
|
32
|
+
"slides",
|
|
33
|
+
"documentation"
|
|
34
|
+
],
|
|
35
|
+
"author": "Boniface Njuguna (https://github.com/bonifacenjuguna)",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/bonifacenjuguna/apexdoc.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://apex.run",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/bonifacenjuguna/apexdoc/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=16.0.0"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"src/",
|
|
50
|
+
"bin/",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"devDependencies": {},
|
|
55
|
+
"directories": {
|
|
56
|
+
"test": "test"
|
|
57
|
+
},
|
|
58
|
+
"type": "commonjs"
|
|
59
|
+
}
|
package/src/ast/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ApexDoc AST Node Factories
|
|
5
|
+
* Every node in the AST is created via one of these functions.
|
|
6
|
+
* This ensures a consistent, predictable tree structure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const Node = {
|
|
10
|
+
|
|
11
|
+
// ── Document Root ───────────────────────────────────────────────
|
|
12
|
+
document: (meta, style, body) => ({
|
|
13
|
+
type: 'Document',
|
|
14
|
+
meta: meta || {},
|
|
15
|
+
style: style || {},
|
|
16
|
+
body: body || [],
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
// ── META ────────────────────────────────────────────────────────
|
|
20
|
+
meta: (fields) => ({
|
|
21
|
+
type: 'Meta',
|
|
22
|
+
fields: fields || {},
|
|
23
|
+
}),
|
|
24
|
+
|
|
25
|
+
// ── STYLE ───────────────────────────────────────────────────────
|
|
26
|
+
style: (variables, themes, directives) => ({
|
|
27
|
+
type: 'Style',
|
|
28
|
+
variables: variables || {}, // { '--primary': '#00f5d4' }
|
|
29
|
+
themes: themes || {}, // { 'dark-ocean': { '--bg': '...' } }
|
|
30
|
+
directives: directives || [], // @import, @media, @breakpoint
|
|
31
|
+
}),
|
|
32
|
+
|
|
33
|
+
theme: (name, variables) => ({
|
|
34
|
+
type: 'Theme',
|
|
35
|
+
name,
|
|
36
|
+
variables: variables || {},
|
|
37
|
+
}),
|
|
38
|
+
|
|
39
|
+
styleDirective: (directive, value) => ({
|
|
40
|
+
type: 'StyleDirective',
|
|
41
|
+
directive, // @import @media @breakpoint
|
|
42
|
+
value,
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
// ── HEADINGS ────────────────────────────────────────────────────
|
|
46
|
+
heading: (level, children, props, line) => ({
|
|
47
|
+
type: 'Heading',
|
|
48
|
+
level: level || 1,
|
|
49
|
+
children: children || [],
|
|
50
|
+
props: props || {},
|
|
51
|
+
line,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
// ── TEXT NODES ──────────────────────────────────────────────────
|
|
55
|
+
text: (value, line) => ({
|
|
56
|
+
type: 'Text',
|
|
57
|
+
value: value || '',
|
|
58
|
+
line,
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
bold: (children, line) => ({
|
|
62
|
+
type: 'Bold',
|
|
63
|
+
children: children || [],
|
|
64
|
+
line,
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
italic: (children, line) => ({
|
|
68
|
+
type: 'Italic',
|
|
69
|
+
children: children || [],
|
|
70
|
+
line,
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
underline: (children, line) => ({
|
|
74
|
+
type: 'Underline',
|
|
75
|
+
children: children || [],
|
|
76
|
+
line,
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
strikethrough: (children, line) => ({
|
|
80
|
+
type: 'Strikethrough',
|
|
81
|
+
children: children || [],
|
|
82
|
+
line,
|
|
83
|
+
}),
|
|
84
|
+
|
|
85
|
+
superscript: (children, line) => ({
|
|
86
|
+
type: 'Superscript',
|
|
87
|
+
children: children || [],
|
|
88
|
+
line,
|
|
89
|
+
}),
|
|
90
|
+
|
|
91
|
+
subscript: (children, line) => ({
|
|
92
|
+
type: 'Subscript',
|
|
93
|
+
children: children || [],
|
|
94
|
+
line,
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
codeInline: (value, line) => ({
|
|
98
|
+
type: 'CodeInline',
|
|
99
|
+
value: value || '',
|
|
100
|
+
line,
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
highlight: (children, line) => ({
|
|
104
|
+
type: 'Highlight',
|
|
105
|
+
children: children || [],
|
|
106
|
+
line,
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
inlineStyled: (children, props, line) => ({
|
|
110
|
+
type: 'InlineStyled',
|
|
111
|
+
children: children || [],
|
|
112
|
+
props: props || {},
|
|
113
|
+
line,
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
link: (children, href, line) => ({
|
|
117
|
+
type: 'Link',
|
|
118
|
+
children: children || [],
|
|
119
|
+
href: href || '',
|
|
120
|
+
line,
|
|
121
|
+
}),
|
|
122
|
+
|
|
123
|
+
// ── PARAGRAPH ───────────────────────────────────────────────────
|
|
124
|
+
paragraph: (children, line) => ({
|
|
125
|
+
type: 'Paragraph',
|
|
126
|
+
children: children || [],
|
|
127
|
+
line,
|
|
128
|
+
}),
|
|
129
|
+
|
|
130
|
+
// ── BLOCKQUOTE ──────────────────────────────────────────────────
|
|
131
|
+
blockquote: (children, level, line) => ({
|
|
132
|
+
type: 'Blockquote',
|
|
133
|
+
children: children || [],
|
|
134
|
+
level: level || 1,
|
|
135
|
+
line,
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
// ── HORIZONTAL RULE ─────────────────────────────────────────────
|
|
139
|
+
hr: (style, line) => ({
|
|
140
|
+
type: 'HR',
|
|
141
|
+
style: style || '---',
|
|
142
|
+
line,
|
|
143
|
+
}),
|
|
144
|
+
|
|
145
|
+
// ── LISTS ───────────────────────────────────────────────────────
|
|
146
|
+
list: (items, ordered, line) => ({
|
|
147
|
+
type: 'List',
|
|
148
|
+
ordered: ordered || false,
|
|
149
|
+
items: items || [],
|
|
150
|
+
line,
|
|
151
|
+
}),
|
|
152
|
+
|
|
153
|
+
listItem: (children, indent, line) => ({
|
|
154
|
+
type: 'ListItem',
|
|
155
|
+
children: children || [],
|
|
156
|
+
indent: indent || 0,
|
|
157
|
+
line,
|
|
158
|
+
}),
|
|
159
|
+
|
|
160
|
+
taskItem: (children, state, indent, line) => ({
|
|
161
|
+
type: 'TaskItem',
|
|
162
|
+
children: children || [],
|
|
163
|
+
state: state || 'todo', // 'todo' | 'done' | 'progress'
|
|
164
|
+
indent: indent || 0,
|
|
165
|
+
line,
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
// ── MATH ────────────────────────────────────────────────────────
|
|
169
|
+
mathInline: (value, line) => ({
|
|
170
|
+
type: 'MathInline',
|
|
171
|
+
value: value || '',
|
|
172
|
+
line,
|
|
173
|
+
}),
|
|
174
|
+
|
|
175
|
+
mathBlock: (value, props, line) => ({
|
|
176
|
+
type: 'MathBlock',
|
|
177
|
+
value: value || '',
|
|
178
|
+
props: props || {},
|
|
179
|
+
dialect: props?.dialect || 'latex',
|
|
180
|
+
line,
|
|
181
|
+
}),
|
|
182
|
+
|
|
183
|
+
// ── EXPRESSION / VARIABLE ───────────────────────────────────────
|
|
184
|
+
expression: (value, line) => ({
|
|
185
|
+
type: 'Expression',
|
|
186
|
+
value: value || '',
|
|
187
|
+
line,
|
|
188
|
+
}),
|
|
189
|
+
|
|
190
|
+
// ── FOOTNOTES ───────────────────────────────────────────────────
|
|
191
|
+
footnoteRef: (id, line) => ({
|
|
192
|
+
type: 'FootnoteRef',
|
|
193
|
+
id,
|
|
194
|
+
line,
|
|
195
|
+
}),
|
|
196
|
+
|
|
197
|
+
footnoteDef: (id, content, line) => ({
|
|
198
|
+
type: 'FootnoteDef',
|
|
199
|
+
id,
|
|
200
|
+
content: content || '',
|
|
201
|
+
line,
|
|
202
|
+
}),
|
|
203
|
+
|
|
204
|
+
// ── LOGIC ───────────────────────────────────────────────────────
|
|
205
|
+
setVar: (name, value, line) => ({
|
|
206
|
+
type: 'SetVar',
|
|
207
|
+
name,
|
|
208
|
+
value,
|
|
209
|
+
line,
|
|
210
|
+
}),
|
|
211
|
+
|
|
212
|
+
conditional: (condition, consequent, alternates, fallback, line) => ({
|
|
213
|
+
type: 'Conditional',
|
|
214
|
+
condition,
|
|
215
|
+
consequent: consequent || [], // if branch
|
|
216
|
+
alternates: alternates || [], // elseif branches [ { condition, body } ]
|
|
217
|
+
fallback: fallback || [], // else branch
|
|
218
|
+
line,
|
|
219
|
+
}),
|
|
220
|
+
|
|
221
|
+
loop: (variable, source, body, line) => ({
|
|
222
|
+
type: 'Loop',
|
|
223
|
+
variable, // iteration variable name
|
|
224
|
+
source, // source expression string
|
|
225
|
+
body: body || [],
|
|
226
|
+
line,
|
|
227
|
+
}),
|
|
228
|
+
|
|
229
|
+
keyframe: (name, steps, line) => ({
|
|
230
|
+
type: 'Keyframe',
|
|
231
|
+
name,
|
|
232
|
+
steps: steps || {},
|
|
233
|
+
line,
|
|
234
|
+
}),
|
|
235
|
+
|
|
236
|
+
// ── GENERIC BLOCK ───────────────────────────────────────────────
|
|
237
|
+
block: (name, props, children, line) => ({
|
|
238
|
+
type: 'Block',
|
|
239
|
+
name: name || 'unknown',
|
|
240
|
+
props: props || {},
|
|
241
|
+
children: children || [],
|
|
242
|
+
line,
|
|
243
|
+
}),
|
|
244
|
+
|
|
245
|
+
// ── SELF-CLOSING BLOCK ──────────────────────────────────────────
|
|
246
|
+
selfCloseBlock: (name, props, line) => ({
|
|
247
|
+
type: 'SelfCloseBlock',
|
|
248
|
+
name: name || 'unknown',
|
|
249
|
+
props: props || {},
|
|
250
|
+
line,
|
|
251
|
+
}),
|
|
252
|
+
|
|
253
|
+
// ── NEWLINE ─────────────────────────────────────────────────────
|
|
254
|
+
newline: (line) => ({
|
|
255
|
+
type: 'Newline',
|
|
256
|
+
line,
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
module.exports = Node;
|