@jxsuite/parser 1.4.0 → 1.5.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/dist/content-loader.js +267 -11
- package/dist/content.js +105 -6
- package/dist/markdown.js +129 -12
- package/dist/serialize.js +29 -4
- package/package.json +8 -8
- package/src/Markdown.class.json +1 -1
- package/src/content-loader.ts +200 -14
- package/src/content.ts +38 -7
- package/src/dates.ts +184 -0
- package/src/markdown.ts +13 -1
- package/src/md.ts +52 -4
- package/src/serialize.ts +28 -3
- package/src/transpile.ts +25 -6
package/src/serialize.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { unified } from "unified";
|
|
22
|
-
import { isRef } from "@jxsuite/schema/guards";
|
|
22
|
+
import { isRef, tagNameCandidates } from "@jxsuite/schema/guards";
|
|
23
23
|
import remarkStringify from "remark-stringify";
|
|
24
24
|
import remarkDirective from "remark-directive";
|
|
25
25
|
import remarkGfm from "remark-gfm";
|
|
@@ -494,7 +494,7 @@ function convertJxNode(
|
|
|
494
494
|
return null;
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
const tag = el.tagName
|
|
497
|
+
const tag = serializableTag(el.tagName) || "div";
|
|
498
498
|
|
|
499
499
|
// Not markdown-native or carries Jx-specific props → directive
|
|
500
500
|
if (!allowlist.has(tag) || hasJxProps(el)) {
|
|
@@ -690,6 +690,31 @@ const JX_DOLLAR_KEYS = new Set([
|
|
|
690
690
|
"$elements",
|
|
691
691
|
]);
|
|
692
692
|
|
|
693
|
+
/**
|
|
694
|
+
* The tag, for a serializer that must write ONE name.
|
|
695
|
+
*
|
|
696
|
+
* A markdown directive is named by a literal, so a tag chosen at creation cannot round-trip through
|
|
697
|
+
* this format: picking a candidate would silently rewrite the author's document into one branch of
|
|
698
|
+
* it, which is the failure the tag-name pattern was added to stop. Content documents do not carry
|
|
699
|
+
* chosen tags today — the construct is an element-document (component) feature — so this refuses
|
|
700
|
+
* rather than guesses, and says which candidates it saw.
|
|
701
|
+
*
|
|
702
|
+
* @param {unknown} tagName
|
|
703
|
+
* @returns {string} The literal tag, or "" when none is declared
|
|
704
|
+
*/
|
|
705
|
+
function serializableTag(tagName: unknown): string {
|
|
706
|
+
if (typeof tagName === "string") {
|
|
707
|
+
return tagName;
|
|
708
|
+
}
|
|
709
|
+
if (tagName == null) {
|
|
710
|
+
return "";
|
|
711
|
+
}
|
|
712
|
+
throw new Error(
|
|
713
|
+
`Markdown cannot express a tag chosen at creation (candidates: ` +
|
|
714
|
+
`${tagNameCandidates(tagName).join(", ")}). Keep this element in a JSON component.`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
693
718
|
/**
|
|
694
719
|
* `$prototype` element types that serialize as a directive named after the prototype (no tagName),
|
|
695
720
|
* e.g. `:::Array`. Mirrors the set in transpile.ts.
|
|
@@ -898,7 +923,7 @@ function nodeToMdast(
|
|
|
898
923
|
return expandArray(node, ctx, scope);
|
|
899
924
|
}
|
|
900
925
|
|
|
901
|
-
const tag = node.tagName
|
|
926
|
+
const tag = serializableTag(node.tagName) || "div";
|
|
902
927
|
const text = resolveText(node.textContent, ctx, scope);
|
|
903
928
|
|
|
904
929
|
// InnerHTML — convert HTML content to mdast
|
package/src/transpile.ts
CHANGED
|
@@ -725,18 +725,35 @@ export function transpileJxMarkdown(source: string) {
|
|
|
725
725
|
// ─── Heading anchors ─────────────────────────────────────────────────────────
|
|
726
726
|
|
|
727
727
|
/**
|
|
728
|
-
* Slugify a heading's text into an anchor id:
|
|
729
|
-
* single hyphens. The one algorithm behind rendered heading `id`s, `$toc` entries, and
|
|
730
|
-
* links (specs/parser.md).
|
|
728
|
+
* Slugify a heading's text into an anchor id: normalized, lowercased, punctuation stripped, spaces
|
|
729
|
+
* collapsed to single hyphens. The one algorithm behind rendered heading `id`s, `$toc` entries, and
|
|
730
|
+
* search deep links (specs/parser.md).
|
|
731
|
+
*
|
|
732
|
+
* **NFC first, and that is the whole point of the change** (UAX #15). "Café" typed on macOS is `e`
|
|
733
|
+
* + U+0301; the same word typed on Windows, or pasted from a web page, is U+00E9. They look
|
|
734
|
+
* identical, they compare unequal, and they produced two different anchors — so a link written
|
|
735
|
+
* against one heading silently stopped working when someone re-typed the heading. Normalizing puts
|
|
736
|
+
* both spellings in one form before anything else looks at them.
|
|
737
|
+
*
|
|
738
|
+
* **`toLowerCase`, never `toLocaleLowerCase`.** Locale-sensitive casing would make the same heading
|
|
739
|
+
* produce different anchors in different projects — Turkish maps `I` to `ı`, not `i` — and an
|
|
740
|
+
* anchor is a URL, which belongs to the document rather than to the reader's locale.
|
|
741
|
+
*
|
|
742
|
+
* **`_` stays in the character class deliberately.** The old class was `\w`, which is
|
|
743
|
+
* `[A-Za-z0-9_]`; `\p{L}\p{N}` is the Unicode-aware replacement and does **not** include `_`.
|
|
744
|
+
* Keeping it means this function is **byte-identical on every pure-ASCII heading**, so no existing
|
|
745
|
+
* anchor on an English site moves — `tests/transpile.test.ts` asserts exactly that against a
|
|
746
|
+
* corpus. What changes is only headings that were being mangled anyway.
|
|
731
747
|
*
|
|
732
748
|
* @param {string} text
|
|
733
749
|
* @returns {string}
|
|
734
750
|
*/
|
|
735
751
|
export function slugifyHeading(text: string) {
|
|
736
752
|
return text
|
|
753
|
+
.normalize("NFC")
|
|
737
754
|
.toLowerCase()
|
|
738
|
-
.replaceAll(/[^\
|
|
739
|
-
.replaceAll(/\s+/
|
|
755
|
+
.replaceAll(/[^\p{L}\p{N}_\s-]/gu, "")
|
|
756
|
+
.replaceAll(/\s+/gu, "-")
|
|
740
757
|
.replaceAll(/-+/g, "-")
|
|
741
758
|
.replaceAll(/^-|-$/g, "");
|
|
742
759
|
}
|
|
@@ -780,7 +797,9 @@ export function assignHeadingIds(children?: (JxElement | string)[]): TocEntry[]
|
|
|
780
797
|
if (typeof node !== "object" || node === null) {
|
|
781
798
|
continue;
|
|
782
799
|
}
|
|
783
|
-
|
|
800
|
+
// Heading extraction reads a literal tag; a chosen one is never a heading (every candidate
|
|
801
|
+
// Would have to be, and the construct exists to vary a WRAPPER), so it is simply not one.
|
|
802
|
+
const tag = typeof node.tagName === "string" ? node.tagName : "";
|
|
784
803
|
if (tag && HEADING_TAGS.has(tag)) {
|
|
785
804
|
const text = jxNodeText(node).trim();
|
|
786
805
|
let id = typeof node.id === "string" && node.id.length > 0 ? node.id : "";
|