@fuzdev/mdz 0.4.0 → 0.6.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 +10 -17
- package/README.md +2 -3
- package/dist/MdzNodeView.svelte +59 -23
- package/dist/MdzNodeView.svelte.d.ts.map +1 -1
- package/dist/MdzStreamNodeView.svelte +63 -23
- package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
- package/dist/mdz.d.ts +12 -1
- package/dist/mdz.d.ts.map +1 -1
- package/dist/mdz.js +1 -1
- package/dist/mdz_contexts.d.ts +33 -6
- package/dist/mdz_contexts.d.ts.map +1 -1
- package/dist/mdz_helpers.d.ts +31 -3
- package/dist/mdz_helpers.d.ts.map +1 -1
- package/dist/mdz_helpers.js +102 -10
- package/dist/mdz_lexer.d.ts +3 -1
- package/dist/mdz_lexer.d.ts.map +1 -1
- package/dist/mdz_lexer.js +89 -15
- package/dist/mdz_opcodes.d.ts +3 -1
- package/dist/mdz_opcodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
- package/dist/mdz_opcodes_to_nodes.js +3 -0
- package/dist/mdz_stream_parser_link.d.ts.map +1 -1
- package/dist/mdz_stream_parser_link.js +81 -9
- package/dist/mdz_stream_parser_table.js +8 -1
- package/dist/mdz_stream_state.svelte.d.ts +3 -1
- package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
- package/dist/mdz_stream_state.svelte.js +4 -0
- package/dist/mdz_to_svelte.d.ts +17 -4
- package/dist/mdz_to_svelte.d.ts.map +1 -1
- package/dist/mdz_to_svelte.js +22 -9
- package/dist/mdz_token_parser.d.ts.map +1 -1
- package/dist/mdz_token_parser.js +16 -2
- package/dist/svelte_preprocess_mdz.d.ts +5 -2
- package/dist/svelte_preprocess_mdz.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/lib/mdz.ts +13 -1
- package/src/lib/mdz_contexts.ts +36 -6
- package/src/lib/mdz_helpers.ts +129 -14
- package/src/lib/mdz_lexer.ts +98 -15
- package/src/lib/mdz_opcodes.ts +3 -1
- package/src/lib/mdz_opcodes_to_nodes.ts +5 -0
- package/src/lib/mdz_stream_parser_link.ts +77 -7
- package/src/lib/mdz_stream_parser_table.ts +8 -1
- package/src/lib/mdz_stream_state.svelte.ts +4 -1
- package/src/lib/mdz_to_svelte.ts +57 -13
- package/src/lib/mdz_token_parser.ts +16 -2
- package/src/lib/svelte_preprocess_mdz.ts +6 -4
package/dist/mdz_helpers.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { DEV } from 'esm-env';
|
|
9
10
|
// Character codes for performance
|
|
10
11
|
/** @nodocs */
|
|
11
12
|
export const BACKTICK = 96; // `
|
|
@@ -67,6 +68,8 @@ export const AMPERSAND = 38; // &
|
|
|
67
68
|
/** @nodocs */
|
|
68
69
|
export const APOSTROPHE = 39; // '
|
|
69
70
|
/** @nodocs */
|
|
71
|
+
export const DOUBLE_QUOTE = 34; // "
|
|
72
|
+
/** @nodocs */
|
|
70
73
|
export const PLUS = 43; // +
|
|
71
74
|
/** @nodocs */
|
|
72
75
|
export const EQUALS = 61; // =
|
|
@@ -713,6 +716,77 @@ const VOID_ELEMENTS = new Set([
|
|
|
713
716
|
// pair with children that renderers silently drop; consider treating void tags as
|
|
714
717
|
// implicitly self-closing at parse time, or rejecting the open/close form
|
|
715
718
|
export const mdz_is_void_element = (name) => VOID_ELEMENTS.has(name);
|
|
719
|
+
/**
|
|
720
|
+
* The closed, global set of attribute names allowed on rendered HTML elements.
|
|
721
|
+
* Every name is inert on any element (no URL, no script, no focus or DOM
|
|
722
|
+
* clobbering), so element safety is independent of which element names a
|
|
723
|
+
* consumer registers. Anything outside this set is dropped by
|
|
724
|
+
* `mdz_filter_element_attributes`.
|
|
725
|
+
*/
|
|
726
|
+
export const ALLOWED_ELEMENT_ATTRIBUTES = new Set([
|
|
727
|
+
'class',
|
|
728
|
+
'title',
|
|
729
|
+
'lang',
|
|
730
|
+
'dir',
|
|
731
|
+
'role',
|
|
732
|
+
'aria-label',
|
|
733
|
+
'aria-hidden',
|
|
734
|
+
'aria-describedby',
|
|
735
|
+
'aria-labelledby'
|
|
736
|
+
]);
|
|
737
|
+
// filters an element's parsed attributes against the allowlist, returning a
|
|
738
|
+
// name→value object ready to spread onto `<svelte:element {...}>`. Disallowed
|
|
739
|
+
// names are dropped (DEV-warned). Used identically by MdzNodeView,
|
|
740
|
+
// MdzStreamNodeView, and mdz_to_svelte, so precompiled and runtime output match.
|
|
741
|
+
export const mdz_filter_element_attributes = (element_name, attributes, allowlist) => {
|
|
742
|
+
const filtered = {};
|
|
743
|
+
for (const attr of attributes) {
|
|
744
|
+
if (allowlist.has(attr.name)) {
|
|
745
|
+
filtered[attr.name] = attr.value;
|
|
746
|
+
}
|
|
747
|
+
else if (DEV) {
|
|
748
|
+
console.warn(`mdz: dropped disallowed attribute \`${attr.name}\` on <${element_name}> — not in the element attribute allowlist`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return filtered;
|
|
752
|
+
};
|
|
753
|
+
// filters a component's parsed attributes against its registered `props`,
|
|
754
|
+
// returning a name→value props object spread onto `<Component {...props}>`.
|
|
755
|
+
// Omitted `props` passes nothing, `'all'` passes everything; dropped names are
|
|
756
|
+
// DEV-warned. Used identically by MdzNodeView, MdzStreamNodeView, and
|
|
757
|
+
// mdz_to_svelte, so precompiled and runtime output match.
|
|
758
|
+
export const mdz_filter_component_attributes = (component_name, attributes, props) => {
|
|
759
|
+
const filtered = {};
|
|
760
|
+
for (const attr of attributes) {
|
|
761
|
+
if (props === 'all' || props?.includes(attr.name)) {
|
|
762
|
+
filtered[attr.name] = attr.value;
|
|
763
|
+
}
|
|
764
|
+
else if (DEV) {
|
|
765
|
+
console.warn(`mdz: dropped attribute \`${attr.name}\` on <${component_name}> — not in its registered props`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return filtered;
|
|
769
|
+
};
|
|
770
|
+
// normalizes a component registry entry to its config form
|
|
771
|
+
export const mdz_component_config = (entry) => (typeof entry === 'function' ? { component: entry } : entry);
|
|
772
|
+
// reconstructs authored attribute text (` name="value"` / ` name`) for the
|
|
773
|
+
// unregistered-tag literal placeholder. The quote char is chosen so a value
|
|
774
|
+
// containing one quote type stays balanced — a parsed value holds at most one
|
|
775
|
+
// (a `"`-delimited value can't contain `"`, a `'`-delimited one can't contain
|
|
776
|
+
// `'`). The caller interpolates the result as Svelte text (auto-escaped).
|
|
777
|
+
export const mdz_format_unregistered_attributes = (attributes) => {
|
|
778
|
+
let text = '';
|
|
779
|
+
for (const attr of attributes) {
|
|
780
|
+
if (attr.value === true) {
|
|
781
|
+
text += ` ${attr.name}`;
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
const quote = attr.value.includes('"') ? "'" : '"';
|
|
785
|
+
text += ` ${attr.name}=${quote}${attr.value}${quote}`;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return text;
|
|
789
|
+
};
|
|
716
790
|
/**
|
|
717
791
|
* Resolves a relative path (`./` or `../`) against a base path.
|
|
718
792
|
* The base is treated as a directory regardless of trailing slash
|
|
@@ -747,9 +821,29 @@ export const mdz_resolve_relative_path = (reference, base) => {
|
|
|
747
821
|
segments.push('');
|
|
748
822
|
return segments.join('/');
|
|
749
823
|
};
|
|
824
|
+
// `/a` → `a`, `//a` → `a` — the pathname form `resolve()` takes; `\` counts as a
|
|
825
|
+
// slash too, and tab/LF/CR are dropped, since browsers read `/\host` and
|
|
826
|
+
// `/<tab>/host` as protocol-relative
|
|
827
|
+
const strip_leading_slashes = (path) => {
|
|
828
|
+
let i = 0;
|
|
829
|
+
for (;;) {
|
|
830
|
+
const c = path.charCodeAt(i);
|
|
831
|
+
if (c !== SLASH && c !== BACKSLASH && c !== TAB && c !== NEWLINE && c !== CARRIAGE_RETURN)
|
|
832
|
+
break;
|
|
833
|
+
i++;
|
|
834
|
+
}
|
|
835
|
+
return path.slice(i);
|
|
836
|
+
};
|
|
750
837
|
/**
|
|
751
838
|
* Classify a `Link` reference into how it should render. Pure — no rendering,
|
|
752
839
|
* no context; `base` is the resolved base path (or `undefined`).
|
|
840
|
+
*
|
|
841
|
+
* Absolute references are authored URLs, not SvelteKit route ids: they classify
|
|
842
|
+
* as `resolve` with the base-relative pathname, never a leading-slash route id,
|
|
843
|
+
* whose resolution would drop `(group)` and empty segments, decode `[x+nn]`
|
|
844
|
+
* escapes, and throw on `[param]` segments. A leading `/` followed by any `/`
|
|
845
|
+
* or `\` run collapses to one `/`, so `//host` and `/\host` stay under the base
|
|
846
|
+
* rather than becoming protocol-relative.
|
|
753
847
|
* @nodocs
|
|
754
848
|
*/
|
|
755
849
|
export const mdz_classify_link = (reference, link_type, base) => {
|
|
@@ -757,17 +851,15 @@ export const mdz_classify_link = (reference, link_type, base) => {
|
|
|
757
851
|
return { kind: 'unsafe' };
|
|
758
852
|
if (link_type === 'internal') {
|
|
759
853
|
if (reference.startsWith('.') && base) {
|
|
760
|
-
return {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (reference.startsWith('#') ||
|
|
765
|
-
reference.startsWith('?') ||
|
|
766
|
-
reference.startsWith('.') ||
|
|
767
|
-
!reference.startsWith('/')) {
|
|
768
|
-
return { kind: 'internal', href: reference };
|
|
854
|
+
return {
|
|
855
|
+
kind: 'resolve',
|
|
856
|
+
path: strip_leading_slashes(mdz_resolve_relative_path(reference, base))
|
|
857
|
+
};
|
|
769
858
|
}
|
|
770
|
-
|
|
859
|
+
// fragment/query/relative/bare — not under the base, so a raw `href`
|
|
860
|
+
if (!reference.startsWith('/'))
|
|
861
|
+
return { kind: 'internal', href: reference };
|
|
862
|
+
return { kind: 'resolve', path: strip_leading_slashes(reference) };
|
|
771
863
|
}
|
|
772
864
|
return { kind: 'external', href: reference };
|
|
773
865
|
};
|
package/dist/mdz_lexer.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { MdzTableAlign } from './mdz.js';
|
|
9
|
+
import type { MdzAttribute, MdzTableAlign } from './mdz.js';
|
|
10
10
|
export interface MdzTokenBase {
|
|
11
11
|
start: number;
|
|
12
12
|
end: number;
|
|
@@ -70,11 +70,13 @@ export interface MdzTokenTagOpen extends MdzTokenBase {
|
|
|
70
70
|
type: 'tag_open';
|
|
71
71
|
name: string;
|
|
72
72
|
is_component: boolean;
|
|
73
|
+
attributes: Array<MdzAttribute>;
|
|
73
74
|
}
|
|
74
75
|
export interface MdzTokenTagSelfClose extends MdzTokenBase {
|
|
75
76
|
type: 'tag_self_close';
|
|
76
77
|
name: string;
|
|
77
78
|
is_component: boolean;
|
|
79
|
+
attributes: Array<MdzAttribute>;
|
|
78
80
|
}
|
|
79
81
|
export interface MdzTokenTagClose extends MdzTokenBase {
|
|
80
82
|
type: 'tag_close';
|
package/dist/mdz_lexer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_lexer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_lexer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"mdz_lexer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_lexer.ts"],"names":[],"mappings":"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"}
|
package/dist/mdz_lexer.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { mdz_is_url, mdz_is_safe_reference, is_letter, is_tag_name_char, is_word_char, is_valid_path_char, mdz_trim_url_punctuation, BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, LEFT_ANGLE, RIGHT_ANGLE, SLASH, PERIOD, LEFT_BRACKET, LEFT_PAREN, RIGHT_BRACKET, A_UPPER, Z_UPPER, ZERO, ONE, NINE, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, MAX_INLINE_NESTING_DEPTH, MAX_LIST_NUMBER_DIGITS, match_url_prefix_case_insensitive, is_at_absolute_path, is_at_relative_path, is_line_whitespace, mdz_match_blockquote_prefix, mdz_blockquote_line_has_content, mdz_blockquote_strip_width, mdz_remap_segments, mdz_split_table_row, mdz_parse_table_delimiter, PIPE, TEXT_CLASS_BACKSLASH, TEXT_CLASS_NEWLINE, TEXT_CLASS_STOP, TEXT_CLASS_URL, TEXT_SCAN_CLASS } from './mdz_helpers.js';
|
|
9
|
+
import { mdz_is_url, mdz_is_safe_reference, is_letter, is_tag_name_char, is_word_char, is_valid_path_char, mdz_trim_url_punctuation, BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, LEFT_ANGLE, RIGHT_ANGLE, SLASH, EQUALS, APOSTROPHE, DOUBLE_QUOTE, PERIOD, LEFT_BRACKET, LEFT_PAREN, RIGHT_BRACKET, A_UPPER, Z_UPPER, ZERO, ONE, NINE, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, MAX_INLINE_NESTING_DEPTH, MAX_LIST_NUMBER_DIGITS, match_url_prefix_case_insensitive, is_at_absolute_path, is_at_relative_path, is_line_whitespace, mdz_match_blockquote_prefix, mdz_blockquote_line_has_content, mdz_blockquote_strip_width, mdz_remap_segments, mdz_split_table_row, mdz_parse_table_delimiter, PIPE, TEXT_CLASS_BACKSLASH, TEXT_CLASS_NEWLINE, TEXT_CLASS_STOP, TEXT_CLASS_URL, TEXT_SCAN_CLASS } from './mdz_helpers.js';
|
|
10
10
|
import { mdz_debug_work } from './mdz_debug_work.js';
|
|
11
11
|
//
|
|
12
12
|
// Lexer
|
|
@@ -1421,9 +1421,75 @@ export class MdzLexer {
|
|
|
1421
1421
|
const tag_name = this.#text.slice(tag_name_start, this.#index);
|
|
1422
1422
|
const first_char_code = tag_name.charCodeAt(0);
|
|
1423
1423
|
const is_component = first_char_code >= A_UPPER && first_char_code <= Z_UPPER;
|
|
1424
|
-
//
|
|
1425
|
-
|
|
1426
|
-
|
|
1424
|
+
// Attribute loop: consume space-separated attributes until `/>` or `>`.
|
|
1425
|
+
// Registry-blind — every syntactically valid attribute is parsed here;
|
|
1426
|
+
// policy (allowlist filtering) happens at render/build time. The scan is
|
|
1427
|
+
// a single forward pass bounded by the line: any newline (or a missing
|
|
1428
|
+
// separator, bad value, or duplicate name) rejects the whole tag to
|
|
1429
|
+
// literal text via the shared `#reject_tag` bailout.
|
|
1430
|
+
const attributes = [];
|
|
1431
|
+
const seen = new Set(); // O(1) duplicate-name detection
|
|
1432
|
+
for (;;) {
|
|
1433
|
+
// skip a run of literal spaces only (tabs/newlines are not SPACE, so
|
|
1434
|
+
// they fall through to the reject path — the single-line, space-only rule)
|
|
1435
|
+
let had_space = false;
|
|
1436
|
+
while (this.#index < this.#text.length && this.#text.charCodeAt(this.#index) === SPACE) {
|
|
1437
|
+
this.#index++;
|
|
1438
|
+
had_space = true;
|
|
1439
|
+
}
|
|
1440
|
+
const c = this.#index < this.#text.length ? this.#text.charCodeAt(this.#index) : -1;
|
|
1441
|
+
// `/>` or `>` ends the open tag — break to the existing self-close / commit
|
|
1442
|
+
if (c === SLASH || c === RIGHT_ANGLE)
|
|
1443
|
+
break;
|
|
1444
|
+
// otherwise an attribute name must follow, space-separated from what
|
|
1445
|
+
// precedes it (`<a x="1"y="2">` rejects — `y` abuts the closing quote)
|
|
1446
|
+
if (c === -1 || !had_space || !is_letter(c)) {
|
|
1447
|
+
this.#reject_tag(start, bound);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
// collect the attribute name (same grammar as tag names)
|
|
1451
|
+
const attr_start = this.#index;
|
|
1452
|
+
this.#index++; // first char already passed is_letter
|
|
1453
|
+
while (this.#index < this.#text.length &&
|
|
1454
|
+
is_tag_name_char(this.#text.charCodeAt(this.#index))) {
|
|
1455
|
+
this.#index++;
|
|
1456
|
+
}
|
|
1457
|
+
const name = this.#text.slice(attr_start, this.#index);
|
|
1458
|
+
// duplicate names are ambiguous (Svelte compile-errors) → literal tag
|
|
1459
|
+
if (seen.has(name)) {
|
|
1460
|
+
this.#reject_tag(start, bound);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
seen.add(name);
|
|
1464
|
+
let value = true; // bare name is boolean `true`
|
|
1465
|
+
if (this.#index < this.#text.length && this.#text.charCodeAt(this.#index) === EQUALS) {
|
|
1466
|
+
this.#index++; // consume =
|
|
1467
|
+
// the value must be immediately quoted — no space after `=`, no
|
|
1468
|
+
// `{` brace literal, no unquoted value (this one check covers all)
|
|
1469
|
+
const quote = this.#index < this.#text.length ? this.#text.charCodeAt(this.#index) : -1;
|
|
1470
|
+
if (quote !== DOUBLE_QUOTE && quote !== APOSTROPHE) {
|
|
1471
|
+
this.#reject_tag(start, bound);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
this.#index++; // consume opening quote
|
|
1475
|
+
const value_start = this.#index;
|
|
1476
|
+
// scan to the matching quote, bounded by newline / text end — NOT
|
|
1477
|
+
// by `#max_search_index` (the open-tag scan never is); a `>` inside
|
|
1478
|
+
// the value is content, consumed atomically before `>` is sought
|
|
1479
|
+
while (this.#index < this.#text.length &&
|
|
1480
|
+
this.#text.charCodeAt(this.#index) !== quote &&
|
|
1481
|
+
this.#text.charCodeAt(this.#index) !== NEWLINE) {
|
|
1482
|
+
this.#index++;
|
|
1483
|
+
}
|
|
1484
|
+
if (this.#index >= this.#text.length || this.#text.charCodeAt(this.#index) === NEWLINE) {
|
|
1485
|
+
// unterminated quote (or newline inside the value) → literal tag
|
|
1486
|
+
this.#reject_tag(start, bound);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
value = this.#text.slice(value_start, this.#index);
|
|
1490
|
+
this.#index++; // consume closing quote
|
|
1491
|
+
}
|
|
1492
|
+
attributes.push({ name, value, start: attr_start, end: this.#index });
|
|
1427
1493
|
}
|
|
1428
1494
|
// Check for self-closing />
|
|
1429
1495
|
if (this.#index + 1 < this.#text.length &&
|
|
@@ -1434,6 +1500,7 @@ export class MdzLexer {
|
|
|
1434
1500
|
type: 'tag_self_close',
|
|
1435
1501
|
name: tag_name,
|
|
1436
1502
|
is_component,
|
|
1503
|
+
attributes,
|
|
1437
1504
|
start,
|
|
1438
1505
|
end: this.#index
|
|
1439
1506
|
});
|
|
@@ -1441,9 +1508,7 @@ export class MdzLexer {
|
|
|
1441
1508
|
}
|
|
1442
1509
|
// Check for >
|
|
1443
1510
|
if (this.#index >= this.#text.length || this.#text.charCodeAt(this.#index) !== RIGHT_ANGLE) {
|
|
1444
|
-
this.#
|
|
1445
|
-
this.#index = start + 1;
|
|
1446
|
-
this.#emit_text('<', start);
|
|
1511
|
+
this.#reject_tag(start, bound);
|
|
1447
1512
|
return;
|
|
1448
1513
|
}
|
|
1449
1514
|
this.#index++; // consume >
|
|
@@ -1459,20 +1524,23 @@ export class MdzLexer {
|
|
|
1459
1524
|
const search_limit = Math.min(this.#max_search_index, this.#text.length);
|
|
1460
1525
|
const closing_tag_pos = this.#index_of(closing_tag, this.#index);
|
|
1461
1526
|
if (closing_tag_pos === -1 || closing_tag_pos > search_limit) {
|
|
1462
|
-
this.#
|
|
1463
|
-
this.#index = start + 1;
|
|
1464
|
-
this.#emit_text('<', start);
|
|
1527
|
+
this.#reject_tag(start, bound);
|
|
1465
1528
|
return;
|
|
1466
1529
|
}
|
|
1467
1530
|
if (this.#has_paragraph_break_between(this.#index, closing_tag_pos)) {
|
|
1468
|
-
this.#
|
|
1469
|
-
this.#index = start + 1;
|
|
1470
|
-
this.#emit_text('<', start);
|
|
1531
|
+
this.#reject_tag(start, bound);
|
|
1471
1532
|
return;
|
|
1472
1533
|
}
|
|
1473
1534
|
// Emit tag_open
|
|
1474
1535
|
const open_token_index = this.#tokens.length;
|
|
1475
|
-
this.#tokens.push({
|
|
1536
|
+
this.#tokens.push({
|
|
1537
|
+
type: 'tag_open',
|
|
1538
|
+
name: tag_name,
|
|
1539
|
+
is_component,
|
|
1540
|
+
attributes,
|
|
1541
|
+
start,
|
|
1542
|
+
end: this.#index
|
|
1543
|
+
});
|
|
1476
1544
|
// Tokenize children until the closing tag. Each iteration narrows
|
|
1477
1545
|
// `#max_search_index` to the next closer so a child's delimiter scan
|
|
1478
1546
|
// can never cross it (`` <b>`a</b>x` `` must not swallow `</b>` into
|
|
@@ -1509,8 +1577,14 @@ export class MdzLexer {
|
|
|
1509
1577
|
// (the `#revert_tokens_from_link_open` pattern; the old fallthrough
|
|
1510
1578
|
// left a dangling `tag_open` whose parser fallback discarded content)
|
|
1511
1579
|
this.#max_search_index = saved_max;
|
|
1512
|
-
this.#failed_tag.add(`${start}:${bound}`);
|
|
1513
1580
|
this.#tokens.splice(open_token_index);
|
|
1581
|
+
this.#reject_tag(start, bound);
|
|
1582
|
+
}
|
|
1583
|
+
// a tag open at `start` that can't commit: memoizes the failure for this
|
|
1584
|
+
// `bound` so re-entries bail fast, then emits the `<` as text and resumes
|
|
1585
|
+
// scanning after it — the rest re-parses as ordinary content
|
|
1586
|
+
#reject_tag(start, bound) {
|
|
1587
|
+
this.#failed_tag.add(`${start}:${bound}`);
|
|
1514
1588
|
this.#index = start + 1;
|
|
1515
1589
|
this.#emit_text('<', start);
|
|
1516
1590
|
}
|
package/dist/mdz_opcodes.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
-
import type { MdzTableAlign } from './mdz.js';
|
|
14
|
+
import type { MdzAttribute, MdzTableAlign } from './mdz.js';
|
|
15
15
|
/**
|
|
16
16
|
* Unique monotonic identifier for each node created by the parser.
|
|
17
17
|
* IDs are never reused within a parser instance.
|
|
@@ -44,6 +44,8 @@ export interface MdzOpcodeOpen {
|
|
|
44
44
|
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
45
45
|
/** Tag name. Present when `node_type` is `'Element'` or `'Component'`. */
|
|
46
46
|
name?: string;
|
|
47
|
+
/** Parsed attributes. Present (possibly empty) when `node_type` is `'Element'` or `'Component'`. */
|
|
48
|
+
attributes?: Array<MdzAttribute>;
|
|
47
49
|
/** Language hint. Present when `node_type` is `'Codeblock'`. */
|
|
48
50
|
lang?: string | null;
|
|
49
51
|
/** Whether the list is ordered. Present when `node_type` is `'List'`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_opcodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"mdz_opcodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5D;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAC7B,WAAW,GACX,MAAM,GACN,QAAQ,GACR,eAAe,GACf,MAAM,GACN,SAAS,GACT,MAAM,GACN,UAAU,GACV,YAAY,GACZ,SAAS,GACT,WAAW,GACX,WAAW,GACX,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,CAAC;AAEf,mDAAmD;AACnD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC;AAEnC,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,SAAS,CAAC;IACd,SAAS,EAAE,oBAAoB,CAAC;IAChC,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oGAAoG;IACpG,UAAU,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACjC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7B,+FAA+F;IAC/F,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,SAAS,CAAC;IACd,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACpC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,SAAS,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,aAAa,CAAC;IACpB,EAAE,EAAE,SAAS,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,SAAS,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;IAC3B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,sFAAsF;IACtF,gBAAgB,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,EAAE,EAAE,SAAS,CAAC;IACd,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,UAAU,GAAG,UAAU,CAAC;IACnC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,CAAC;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,eAAe,GAAG,eAAe,CAAC;AAEnF,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAClB,aAAa,GACb,cAAc,GACd,aAAa,GACb,mBAAmB,GACnB,iBAAiB,GACjB,aAAa,GACb,eAAe,GACf,aAAa,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_opcodes_to_nodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes_to_nodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"mdz_opcodes_to_nodes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_opcodes_to_nodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAEX,OAAO,EASP,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,kBAAkB,CAAC;AA0B7D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,KAAK,CAAC,SAAS,CAAC,KAAG,KAAK,CAAC,OAAO,CAiM7E,CAAC"}
|
|
@@ -39,6 +39,7 @@ export const mdz_opcodes_to_nodes = (opcodes) => {
|
|
|
39
39
|
start: op.start,
|
|
40
40
|
level: op.level,
|
|
41
41
|
name: op.name,
|
|
42
|
+
attributes: op.attributes,
|
|
42
43
|
lang: op.lang,
|
|
43
44
|
ordered: op.ordered,
|
|
44
45
|
start_number: op.start_number,
|
|
@@ -423,6 +424,7 @@ const build_node = (frame, frame_children) => {
|
|
|
423
424
|
return {
|
|
424
425
|
type: 'Element',
|
|
425
426
|
name: frame.name ?? '',
|
|
427
|
+
attributes: frame.attributes ?? [],
|
|
426
428
|
children: mdz_merge_adjacent_text(frame_children),
|
|
427
429
|
start: frame.start,
|
|
428
430
|
end: frame.end
|
|
@@ -431,6 +433,7 @@ const build_node = (frame, frame_children) => {
|
|
|
431
433
|
return {
|
|
432
434
|
type: 'Component',
|
|
433
435
|
name: frame.name ?? '',
|
|
436
|
+
attributes: frame.attributes ?? [],
|
|
434
437
|
children: mdz_merge_adjacent_text(frame_children),
|
|
435
438
|
start: frame.start,
|
|
436
439
|
end: frame.end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_stream_parser_link.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_link.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"mdz_stream_parser_link.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_parser_link.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH,OAAO,EACN,KAAK,oBAAoB,EACzB,KAAK,SAAS,EAYd,MAAM,8BAA8B,CAAC;AAMtC,cAAc;AACd,eAAO,MAAM,aAAa,GAAI,OAAO,oBAAoB,KAAG,OAoB3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAC9B,OAAO,oBAAoB,EAC3B,gBAAgB,MAAM,EACtB,aAAa,MAAM,KACjB,IAUF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,oBAAoB,EAAE,gBAAgB,MAAM,KAAG,OA4EvF,CAAC;AAIF,cAAc;AACd,eAAO,MAAM,YAAY,GAAI,OAAO,oBAAoB,KAAG,SAuJ1D,CAAC;AAEF,cAAc;AACd,eAAO,MAAM,aAAa,GAAI,OAAO,oBAAoB,KAAG,SAiD3D,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
|
-
import { A_UPPER, LEFT_BRACKET, LEFT_PAREN, MAX_INLINE_NESTING_DEPTH, RIGHT_ANGLE, RIGHT_BRACKET, RIGHT_PAREN, SLASH, SPACE, Z_UPPER, is_letter, is_tag_name_char, is_valid_path_char, mdz_is_safe_reference, mdz_is_url } from './mdz_helpers.js';
|
|
7
|
+
import { A_UPPER, APOSTROPHE, DOUBLE_QUOTE, EQUALS, LEFT_BRACKET, LEFT_PAREN, MAX_INLINE_NESTING_DEPTH, RIGHT_ANGLE, RIGHT_BRACKET, RIGHT_PAREN, SLASH, SPACE, Z_UPPER, is_letter, is_tag_name_char, is_valid_path_char, mdz_is_safe_reference, mdz_is_url } from './mdz_helpers.js';
|
|
8
8
|
import { accumulate_text, alloc_id, buffer_index_of, emit, ensure_paragraph, flush_text, is_run_boundary, offset, pop_stack_entry, push_stack_entry, revert_above } from './mdz_stream_parser_state.js';
|
|
9
9
|
import { consume_delimiter_as_text } from './mdz_stream_parser_text.js';
|
|
10
10
|
import { mdz_debug_work } from './mdz_debug_work.js';
|
|
@@ -161,12 +161,84 @@ export const try_tag_open = (state) => {
|
|
|
161
161
|
if (i >= state.buffer.length)
|
|
162
162
|
return 'need_more';
|
|
163
163
|
const name = state.buffer.slice(name_start, i);
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
// Attribute loop: consume space-separated attributes until `/>` or `>`.
|
|
165
|
+
// This widens the held-tag-open rescan surface (the TODO above) with the
|
|
166
|
+
// attribute/value text — still line-bounded, since a newline anywhere aborts
|
|
167
|
+
// the whole tag (`not_match`). Every buffer-end point holds (`need_more`) so
|
|
168
|
+
// the same tree is produced regardless of where feeds split. The quoted-value
|
|
169
|
+
// terminator search reuses `buffer_index_of` (memoized, append-aware) so a
|
|
170
|
+
// long streamed value doesn't rescan quadratically across held retries,
|
|
171
|
+
// matching `try_complete_link`.
|
|
172
|
+
const attributes = [];
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
for (;;) {
|
|
175
|
+
// skip a run of literal spaces only (tabs/newlines are not SPACE)
|
|
176
|
+
let had_space = false;
|
|
177
|
+
while (i < state.buffer.length && state.buffer.charCodeAt(i) === SPACE) {
|
|
178
|
+
i++;
|
|
179
|
+
had_space = true;
|
|
180
|
+
}
|
|
181
|
+
if (i >= state.buffer.length)
|
|
182
|
+
return 'need_more';
|
|
183
|
+
const c = state.buffer.charCodeAt(i);
|
|
184
|
+
// `/>` or `>` ends the open tag — break to the existing self-close / commit
|
|
185
|
+
if (c === SLASH || c === RIGHT_ANGLE)
|
|
186
|
+
break;
|
|
187
|
+
// otherwise an attribute name must follow, space-separated from what
|
|
188
|
+
// precedes it (`<a x="1"y="2">` — `y` abuts the closing quote — reverts)
|
|
189
|
+
if (!is_letter(c))
|
|
190
|
+
return 'not_match';
|
|
191
|
+
if (!had_space)
|
|
192
|
+
return 'not_match';
|
|
193
|
+
// collect the attribute name (same grammar as tag names)
|
|
194
|
+
const attr_start = i;
|
|
195
|
+
i++; // first char already passed is_letter
|
|
196
|
+
while (i < state.buffer.length && is_tag_name_char(state.buffer.charCodeAt(i))) {
|
|
197
|
+
i++;
|
|
198
|
+
}
|
|
199
|
+
if (i >= state.buffer.length)
|
|
200
|
+
return 'need_more'; // mid-name — could still grow
|
|
201
|
+
const attr_name = state.buffer.slice(attr_start, i);
|
|
202
|
+
// duplicate names are ambiguous (Svelte compile-errors) → literal tag
|
|
203
|
+
if (seen.has(attr_name))
|
|
204
|
+
return 'not_match';
|
|
205
|
+
seen.add(attr_name);
|
|
206
|
+
let value = true; // bare name is boolean `true`
|
|
207
|
+
if (state.buffer.charCodeAt(i) === EQUALS) {
|
|
208
|
+
i++; // consume =
|
|
209
|
+
if (i >= state.buffer.length)
|
|
210
|
+
return 'need_more'; // nothing after `=` yet
|
|
211
|
+
// the value must be immediately quoted — no space, no `{`, no unquoted
|
|
212
|
+
const quote_code = state.buffer.charCodeAt(i);
|
|
213
|
+
if (quote_code !== DOUBLE_QUOTE && quote_code !== APOSTROPHE)
|
|
214
|
+
return 'not_match';
|
|
215
|
+
i++; // consume opening quote
|
|
216
|
+
const value_start = i;
|
|
217
|
+
// find the matching quote and the next newline via the memoized,
|
|
218
|
+
// append-aware search; a `>` inside the value is content, consumed
|
|
219
|
+
// atomically here so a `</Name>`-shaped substring never reaches
|
|
220
|
+
// `try_close_tag`
|
|
221
|
+
const quote_str = quote_code === DOUBLE_QUOTE ? '"' : "'";
|
|
222
|
+
const q = buffer_index_of(state, quote_str, value_start);
|
|
223
|
+
const n = buffer_index_of(state, '\n', value_start);
|
|
224
|
+
if (q !== -1 && (n === -1 || q < n)) {
|
|
225
|
+
value = state.buffer.slice(value_start, q);
|
|
226
|
+
i = q + 1; // past the closing quote
|
|
227
|
+
}
|
|
228
|
+
else if (n !== -1 && (q === -1 || n < q)) {
|
|
229
|
+
return 'not_match'; // newline inside the value → literal tag
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
return 'need_more'; // neither found yet — unterminated so far, hold
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
attributes.push({
|
|
236
|
+
name: attr_name,
|
|
237
|
+
value,
|
|
238
|
+
start: offset(state, attr_start),
|
|
239
|
+
end: offset(state, i)
|
|
240
|
+
});
|
|
167
241
|
}
|
|
168
|
-
if (i >= state.buffer.length)
|
|
169
|
-
return 'need_more';
|
|
170
242
|
// check for self-closing />
|
|
171
243
|
if (state.buffer.charCodeAt(i) === SLASH && i + 1 >= state.buffer.length) {
|
|
172
244
|
return 'need_more'; // `/` at the buffer end can't classify yet (`/>` or junk)
|
|
@@ -179,7 +251,7 @@ export const try_tag_open = (state) => {
|
|
|
179
251
|
ensure_paragraph(state);
|
|
180
252
|
const id = alloc_id(state);
|
|
181
253
|
const tag_start = offset(state, start);
|
|
182
|
-
emit(state, { type: 'open', id, node_type, start: tag_start, name });
|
|
254
|
+
emit(state, { type: 'open', id, node_type, start: tag_start, name, attributes });
|
|
183
255
|
emit(state, { type: 'close', id, end: offset(state, i + 2) });
|
|
184
256
|
state.active_text_id = null;
|
|
185
257
|
state.pos = i + 2;
|
|
@@ -199,8 +271,8 @@ export const try_tag_open = (state) => {
|
|
|
199
271
|
ensure_paragraph(state);
|
|
200
272
|
const id = alloc_id(state);
|
|
201
273
|
const tag_start = offset(state, start);
|
|
202
|
-
const delimiter = state.buffer.slice(start, i); // e.g., "<Alert>"
|
|
203
|
-
emit(state, { type: 'open', id, node_type, start: tag_start, name });
|
|
274
|
+
const delimiter = state.buffer.slice(start, i); // e.g., "<Alert>" or "<Alert x="1">"
|
|
275
|
+
emit(state, { type: 'open', id, node_type, start: tag_start, name, attributes });
|
|
204
276
|
push_stack_entry(state, id, node_type, tag_start, delimiter, name);
|
|
205
277
|
state.active_text_id = null;
|
|
206
278
|
state.pos = i;
|
|
@@ -344,7 +344,14 @@ const emit_inline_nodes = (state, nodes) => {
|
|
|
344
344
|
case 'Component': {
|
|
345
345
|
const id = alloc_id(state);
|
|
346
346
|
const s = offset(state, node.start);
|
|
347
|
-
emit(state, {
|
|
347
|
+
emit(state, {
|
|
348
|
+
type: 'open',
|
|
349
|
+
id,
|
|
350
|
+
node_type: node.type,
|
|
351
|
+
start: s,
|
|
352
|
+
name: node.name,
|
|
353
|
+
attributes: node.attributes
|
|
354
|
+
});
|
|
348
355
|
push_stack_entry(state, id, node.type, s, '', node.name);
|
|
349
356
|
emit_inline_nodes(state, node.children);
|
|
350
357
|
pop_stack_entry(state);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import type { MdzNodeId, MdzNodeType, MdzOpcode } from './mdz_opcodes.js';
|
|
11
|
-
import type { MdzTableAlign } from './mdz.js';
|
|
11
|
+
import type { MdzAttribute, MdzTableAlign } from './mdz.js';
|
|
12
12
|
/**
|
|
13
13
|
* A reactive node in the stream renderer tree.
|
|
14
14
|
* Fields are `$state` so Svelte re-renders only what changes.
|
|
@@ -22,6 +22,8 @@ export declare class MdzStreamNode {
|
|
|
22
22
|
reference?: string;
|
|
23
23
|
link_type?: 'external' | 'internal';
|
|
24
24
|
name?: string;
|
|
25
|
+
/** Parsed tag attributes (Element/Component nodes); known at open, never mutated, so plain not `$state`. */
|
|
26
|
+
attributes?: Array<MdzAttribute>;
|
|
25
27
|
lang?: string | null;
|
|
26
28
|
heading_id?: string;
|
|
27
29
|
ordered?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdz_stream_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"mdz_stream_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_stream_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5D;;;GAGG;AACH,qBAAa,aAAa;IACzB,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC,aAAa,CAAC,CAAc;IAE5C,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAY;IAC9B,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,CAAY;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4GAA4G;IAC5G,UAAU,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAY;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7B,uDAAuD;IACvD,MAAM,CAAC,EAAE,OAAO,CAAC;gBAEL,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW;CAI5C;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;;IAC1B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAc;IAqCxC;;OAEG;IACH,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IA8R9B;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI;CAK5C"}
|
|
@@ -22,6 +22,8 @@ export class MdzStreamNode {
|
|
|
22
22
|
reference = $state();
|
|
23
23
|
link_type = $state();
|
|
24
24
|
name;
|
|
25
|
+
/** Parsed tag attributes (Element/Component nodes); known at open, never mutated, so plain not `$state`. */
|
|
26
|
+
attributes;
|
|
25
27
|
lang;
|
|
26
28
|
heading_id = $state();
|
|
27
29
|
ordered;
|
|
@@ -92,6 +94,8 @@ export class MdzStreamState {
|
|
|
92
94
|
node.level = opcode.level;
|
|
93
95
|
if (opcode.name !== undefined)
|
|
94
96
|
node.name = opcode.name;
|
|
97
|
+
if (opcode.attributes !== undefined)
|
|
98
|
+
node.attributes = opcode.attributes;
|
|
95
99
|
if (opcode.lang !== undefined)
|
|
96
100
|
node.lang = opcode.lang;
|
|
97
101
|
if (opcode.ordered !== undefined)
|
package/dist/mdz_to_svelte.d.ts
CHANGED
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import type { MdzNode } from './mdz.js';
|
|
11
|
+
import type { MdzComponentProps } from './mdz_contexts.js';
|
|
12
|
+
/**
|
|
13
|
+
* A build-time component registry entry: an import path, or an import path
|
|
14
|
+
* plus the attribute names passed as props (see `MdzComponentProps`).
|
|
15
|
+
*/
|
|
16
|
+
export type MdzComponentImport = string | {
|
|
17
|
+
path: string;
|
|
18
|
+
props?: MdzComponentProps;
|
|
19
|
+
};
|
|
11
20
|
/**
|
|
12
21
|
* Result of converting `MdzNode` arrays to Svelte markup.
|
|
13
22
|
*/
|
|
@@ -27,10 +36,13 @@ export interface MdzToSvelteResult {
|
|
|
27
36
|
*/
|
|
28
37
|
export interface MdzToSvelteOptions {
|
|
29
38
|
/**
|
|
30
|
-
* Component name to import
|
|
39
|
+
* Component name to import mapping, mirroring the runtime `MdzComponents`
|
|
40
|
+
* registry: a bare import path receives no attributes
|
|
41
|
+
* (`{Note: '#lib/Note.svelte'}`), and the config form names the attributes
|
|
42
|
+
* passed as props (`{Alert: {path: '#lib/Alert.svelte', props: ['status']}}`).
|
|
31
43
|
* If content references a component not in this map, `has_unconfigured_tags` is set.
|
|
32
44
|
*/
|
|
33
|
-
components: Record<string,
|
|
45
|
+
components: Record<string, MdzComponentImport>;
|
|
34
46
|
/**
|
|
35
47
|
* Allowed HTML elements (e.g., `{aside: true, details: true}`).
|
|
36
48
|
* Values are boolean placeholders for future per-element configuration,
|
|
@@ -40,8 +52,9 @@ export interface MdzToSvelteOptions {
|
|
|
40
52
|
elements: Record<string, boolean>;
|
|
41
53
|
/**
|
|
42
54
|
* Base path for resolving relative links (e.g., `'/docs/usage/'`).
|
|
43
|
-
* When provided, relative references (`./`, `../`) are resolved
|
|
44
|
-
*
|
|
55
|
+
* When provided, relative references (`./`, `../`) are resolved against it and,
|
|
56
|
+
* like absolute references, passed through `resolve()` in its pathname form.
|
|
57
|
+
* Trailing slash recommended.
|
|
45
58
|
*/
|
|
46
59
|
base?: string;
|
|
47
60
|
/**
|