@uniweb/semantic-parser 1.2.1 → 1.2.3
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/package.json +1 -1
- package/src/index.js +6 -2
- package/src/processors/sequence.js +204 -45
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { processSequence } from "./processors/sequence.js";
|
|
1
|
+
import { processSequence, resolveAssetUrl, ASSET_SLOTS } from "./processors/sequence.js";
|
|
2
2
|
import { processGroups } from "./processors/groups.js";
|
|
3
3
|
import { buildDoc } from "./builders/doc.js";
|
|
4
4
|
|
|
@@ -46,4 +46,8 @@ function parseContent(doc, options = {}) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
// `resolveAssetUrl` is exported so a consumer that needs an asset URL outside a
|
|
50
|
+
// parse (the editor, a kit component) reaches THIS implementation rather than
|
|
51
|
+
// writing a second one. A duplicated selector is this repo's most-repeated
|
|
52
|
+
// defect; one exported function is the cheapest guard against it.
|
|
53
|
+
export { parseContent, buildDoc, resolveAssetUrl, ASSET_SLOTS };
|
|
@@ -106,7 +106,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
106
106
|
const attrs = node.attrs;
|
|
107
107
|
const content = node.content;
|
|
108
108
|
|
|
109
|
-
const linkVal = isLink(node);
|
|
109
|
+
const linkVal = isLink(node, options);
|
|
110
110
|
|
|
111
111
|
if (linkVal) {
|
|
112
112
|
return {
|
|
@@ -121,7 +121,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
121
121
|
return multipleLinks; // Returns array of link elements
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
const styledLink = isStyledLink(node);
|
|
124
|
+
const styledLink = isStyledLink(node, options);
|
|
125
125
|
|
|
126
126
|
if (styledLink) return styledLink;
|
|
127
127
|
|
|
@@ -131,7 +131,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
131
131
|
type: "heading",
|
|
132
132
|
level: node.attrs.level,
|
|
133
133
|
text: getTextContent(content, options),
|
|
134
|
-
children: processInlineElements(content),
|
|
134
|
+
children: processInlineElements(content, options),
|
|
135
135
|
attrs,
|
|
136
136
|
};
|
|
137
137
|
|
|
@@ -141,16 +141,14 @@ function createSequenceElement(node, options = {}) {
|
|
|
141
141
|
return {
|
|
142
142
|
type: "paragraph",
|
|
143
143
|
text: textContent,
|
|
144
|
-
children: processInlineElements(content),
|
|
144
|
+
children: processInlineElements(content, options),
|
|
145
145
|
attrs,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
case "blockquote":
|
|
149
149
|
return {
|
|
150
150
|
type: "blockquote",
|
|
151
|
-
children: processSequence({
|
|
152
|
-
content,
|
|
153
|
-
}),
|
|
151
|
+
children: processSequence({ content }, options),
|
|
154
152
|
attrs,
|
|
155
153
|
};
|
|
156
154
|
|
|
@@ -164,7 +162,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
164
162
|
type: "inset_block",
|
|
165
163
|
component,
|
|
166
164
|
params,
|
|
167
|
-
children: processSequence({ content }),
|
|
165
|
+
children: processSequence({ content }, options),
|
|
168
166
|
};
|
|
169
167
|
}
|
|
170
168
|
|
|
@@ -178,9 +176,11 @@ function createSequenceElement(node, options = {}) {
|
|
|
178
176
|
// put content-reader inside the runtime. The reader already built
|
|
179
177
|
// this node; the job here is only to WALK it.
|
|
180
178
|
//
|
|
181
|
-
// `options` rides along
|
|
182
|
-
//
|
|
183
|
-
//
|
|
179
|
+
// `options` rides along so nested content is sequenced under the
|
|
180
|
+
// same options as its parent. (`inset_block` and `blockquote` used
|
|
181
|
+
// to drop it and now do the same — harmless while nothing here read
|
|
182
|
+
// `options`, but an asset inside a blockquote would have lost its
|
|
183
|
+
// resolution pattern once one did.)
|
|
184
184
|
return {
|
|
185
185
|
type: "concept_block",
|
|
186
186
|
tag: attrs?.tag,
|
|
@@ -242,7 +242,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
242
242
|
case "ImageBlock":
|
|
243
243
|
return {
|
|
244
244
|
type: "image",
|
|
245
|
-
attrs: parseImgBlock(attrs),
|
|
245
|
+
attrs: parseImgBlock(attrs, options),
|
|
246
246
|
};
|
|
247
247
|
case "image":
|
|
248
248
|
// `role` decides which content array this lands in — the contract
|
|
@@ -260,7 +260,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
260
260
|
if (attrs?.role === "video") {
|
|
261
261
|
return {
|
|
262
262
|
type: "video",
|
|
263
|
-
attrs: parseMarkdownVideo(attrs),
|
|
263
|
+
attrs: parseMarkdownVideo(attrs, options),
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
266
|
// Standard ProseMirror image node - resolve attrs the same way as
|
|
@@ -269,12 +269,12 @@ function createSequenceElement(node, options = {}) {
|
|
|
269
269
|
// wouldn't be populated and components render an empty slot.
|
|
270
270
|
return {
|
|
271
271
|
type: "image",
|
|
272
|
-
attrs: parseImgBlock(attrs || {}),
|
|
272
|
+
attrs: parseImgBlock(attrs || {}, options),
|
|
273
273
|
};
|
|
274
274
|
case "Video":
|
|
275
275
|
return {
|
|
276
276
|
type: "video",
|
|
277
|
-
attrs: parseVideoBlock(attrs),
|
|
277
|
+
attrs: parseVideoBlock(attrs, options),
|
|
278
278
|
};
|
|
279
279
|
case "bulletList":
|
|
280
280
|
case "orderedList": {
|
|
@@ -391,7 +391,7 @@ function createSequenceElement(node, options = {}) {
|
|
|
391
391
|
return {
|
|
392
392
|
type: "button",
|
|
393
393
|
text: textContent,
|
|
394
|
-
children: processInlineElements(content),
|
|
394
|
+
children: processInlineElements(content, options),
|
|
395
395
|
attrs,
|
|
396
396
|
};
|
|
397
397
|
}
|
|
@@ -470,10 +470,23 @@ function getTextContent(content, options = {}) {
|
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
// Icons are emitted as positional markers that a renderer swaps for a real
|
|
474
|
+
// component (see renderInlineNode). The marker carries the icon's ordinal
|
|
475
|
+
// among the icons of THIS content array, which is the same order
|
|
476
|
+
// processInlineElements() puts them in `children` — so a consumer resolves
|
|
477
|
+
// one with `children.filter(c => c.type === 'icon')[index]`. Referencing by
|
|
478
|
+
// ordinal rather than serializing the attrs keeps an inline `svg` blob out
|
|
479
|
+
// of the text stream and leaves `children` the single source of truth.
|
|
480
|
+
const iconOrdinals = new Map();
|
|
481
|
+
let nextIcon = 0;
|
|
482
|
+
for (const node of content) {
|
|
483
|
+
if (isIconNode(node)) iconOrdinals.set(node, nextIcon++);
|
|
484
|
+
}
|
|
485
|
+
|
|
473
486
|
return groups
|
|
474
487
|
.reduce((prev, group) => {
|
|
475
488
|
const inner = group.nodes.reduce(
|
|
476
|
-
(acc, node) => acc + renderInlineNode(node),
|
|
489
|
+
(acc, node) => acc + renderInlineNode(node, iconOrdinals),
|
|
477
490
|
"",
|
|
478
491
|
);
|
|
479
492
|
if (!inner && !group.link) return prev;
|
|
@@ -486,7 +499,7 @@ function getTextContent(content, options = {}) {
|
|
|
486
499
|
* Render one inline node to HTML, applying every mark EXCEPT link — the
|
|
487
500
|
* anchor is applied per group by getTextContent, since it may span nodes.
|
|
488
501
|
*/
|
|
489
|
-
function renderInlineNode(curr) {
|
|
502
|
+
function renderInlineNode(curr, iconOrdinals) {
|
|
490
503
|
return [curr]
|
|
491
504
|
.reduce((prev, curr) => {
|
|
492
505
|
const { type, marks = [], text } = curr;
|
|
@@ -593,6 +606,21 @@ function renderInlineNode(curr) {
|
|
|
593
606
|
return prev + `<uniweb-inset data-ref-id="${refId}"></uniweb-inset>`;
|
|
594
607
|
} else if (type === "hardBreak") {
|
|
595
608
|
return prev + "<br>";
|
|
609
|
+
} else if (isIconNode(curr)) {
|
|
610
|
+
// An icon sitting IN the prose — `Click the  button`.
|
|
611
|
+
// It rides as a positional marker for the same reason an inline
|
|
612
|
+
// inset does: the paragraph's text is an HTML string, and an
|
|
613
|
+
// icon needs a component (kit's <Icon>, which resolves the
|
|
614
|
+
// library+name against the icon CDN at render time).
|
|
615
|
+
//
|
|
616
|
+
// Until 2026-08-12 this fell through to the catch-all below and
|
|
617
|
+
// the icon vanished — the text kept a gap where it had been, the
|
|
618
|
+
// entry stayed in `children` and in `content.icons`, and nothing
|
|
619
|
+
// put it back. Every other inline atom (math, insets) already
|
|
620
|
+
// had a marker; this was the one that did not.
|
|
621
|
+
const index = iconOrdinals?.get(curr);
|
|
622
|
+
if (index === undefined) return prev;
|
|
623
|
+
return prev + `<uniweb-icon data-index="${index}"></uniweb-icon>`;
|
|
596
624
|
} else {
|
|
597
625
|
// console.warn(`unhandled text content type: ${type}`, curr);
|
|
598
626
|
return prev;
|
|
@@ -600,7 +628,7 @@ function renderInlineNode(curr) {
|
|
|
600
628
|
}, "");
|
|
601
629
|
}
|
|
602
630
|
|
|
603
|
-
function processInlineElements(content) {
|
|
631
|
+
function processInlineElements(content, options = {}) {
|
|
604
632
|
if (!content) return [];
|
|
605
633
|
|
|
606
634
|
const items = [];
|
|
@@ -626,8 +654,8 @@ function processInlineElements(content) {
|
|
|
626
654
|
items.push({
|
|
627
655
|
type: isVideo ? "video" : "image",
|
|
628
656
|
attrs: isVideo
|
|
629
|
-
? parseMarkdownVideo(item.attrs)
|
|
630
|
-
: parseImgBlock(item.attrs || {}),
|
|
657
|
+
? parseMarkdownVideo(item.attrs, options)
|
|
658
|
+
: parseImgBlock(item.attrs || {}, options),
|
|
631
659
|
});
|
|
632
660
|
} else if (item.type === "math-inline" || item.type === "math_inline") {
|
|
633
661
|
items.push(item);
|
|
@@ -659,6 +687,99 @@ function processInlineElements(content) {
|
|
|
659
687
|
return items;
|
|
660
688
|
}
|
|
661
689
|
|
|
690
|
+
/**
|
|
691
|
+
* Resolve a STORE-HELD asset (`assetId` + `assetExt`) through the host's
|
|
692
|
+
* URL pattern — the current mechanism, and the one to reach for.
|
|
693
|
+
*
|
|
694
|
+
* The host declares `config.assets.url` on the published payload; the runtime
|
|
695
|
+
* hands it here as `options.assets.url`. We substitute `{id}` and `{ext}` and
|
|
696
|
+
* do nothing else: the pattern names a host and carries the store's whole path
|
|
697
|
+
* layout, so a framework package never composes a serve location or guesses an
|
|
698
|
+
* origin — a host may move its assets without a framework release.
|
|
699
|
+
*
|
|
700
|
+
* ⛔ Three ways this returns '' — "unresolved", never a guess:
|
|
701
|
+
* - **no pattern** (the host declared none) — absent ⇒ absent
|
|
702
|
+
* - **an unknown placeholder** — never emit a half-substituted URL
|
|
703
|
+
* - **`{ext}` with no `assetExt`** — `base.` is a broken URL that reads like
|
|
704
|
+
* a typo rather than a missing field
|
|
705
|
+
*
|
|
706
|
+
* Callers treat '' as "fall through to the next reference form", which is what
|
|
707
|
+
* makes an id-bearing node safe to write before any deployment emits a pattern.
|
|
708
|
+
*
|
|
709
|
+
* @param {string} assetId - the store's opaque id (backend mints a sha256)
|
|
710
|
+
* @param {string} assetExt - the extension, minted separately (never derived
|
|
711
|
+
* from the id, which carries no extension)
|
|
712
|
+
* @param {string} pattern - `config.assets.url`, e.g. `https://cdn/x/{id}/base.{ext}`
|
|
713
|
+
* @returns {string} the resolved URL, or '' when it cannot be resolved
|
|
714
|
+
*/
|
|
715
|
+
/**
|
|
716
|
+
* The asset-bearing slots on a media node — the ONE definition, exported so no
|
|
717
|
+
* consumer keeps its own.
|
|
718
|
+
*
|
|
719
|
+
* ⭐ A node carries more than one asset reference. An image has `src`; a video
|
|
720
|
+
* adds a `poster`; a document embed adds a `preview`. Identity therefore cannot
|
|
721
|
+
* be a single pair of attrs — it has to name WHICH reference it identifies, or a
|
|
722
|
+
* producer cannot stamp a poster without inventing vocabulary.
|
|
723
|
+
*
|
|
724
|
+
* The identity attrs are **flat and declared**, not a nested `assets: {…}` map,
|
|
725
|
+
* and that is deliberate: a nested map puts identity inside a value the schema
|
|
726
|
+
* sweep cannot see, which is exactly the class of defect that makes a reference
|
|
727
|
+
* buried in an opaque field unenumerable and un-re-pointable. Flat attrs stay
|
|
728
|
+
* visible to `getBaseSchema()`, to the parity sweep, and to anything generating
|
|
729
|
+
* an adapter from the declared schema.
|
|
730
|
+
*
|
|
731
|
+
* `assetId`/`assetExt` are unprefixed because `src` is the PRIMARY reference —
|
|
732
|
+
* the one every fallback path already resolves to. The convention is: unprefixed
|
|
733
|
+
* identifies the primary; a prefix names the attr it belongs to.
|
|
734
|
+
*
|
|
735
|
+
* `url` rides in the primary slot because the editor's `ImageBlock` uses it where
|
|
736
|
+
* markdown's `image` uses `src`; both mean the same reference.
|
|
737
|
+
*/
|
|
738
|
+
export const ASSET_SLOTS = [
|
|
739
|
+
{ urls: ["src", "url"], id: "assetId", ext: "assetExt" },
|
|
740
|
+
{ urls: ["poster"], id: "posterAssetId", ext: "posterAssetExt" },
|
|
741
|
+
{ urls: ["preview"], id: "previewAssetId", ext: "previewAssetExt" },
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
function resolveAssetUrl(assetId, assetExt, pattern) {
|
|
745
|
+
if (!assetId || typeof assetId !== "string") return "";
|
|
746
|
+
if (!pattern || typeof pattern !== "string") return "";
|
|
747
|
+
let resolvable = true;
|
|
748
|
+
const url = pattern.replace(/\{(\w+)\}/g, (match, name) => {
|
|
749
|
+
if (name === "id") return assetId;
|
|
750
|
+
if (name === "ext" && assetExt) return assetExt;
|
|
751
|
+
resolvable = false;
|
|
752
|
+
return match;
|
|
753
|
+
});
|
|
754
|
+
return resolvable ? url : "";
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/** `resolveAssetUrl` against a node's attrs + the parse options, for one slot. */
|
|
758
|
+
function assetUrlFromAttrs(attrs, options, slot = ASSET_SLOTS[0]) {
|
|
759
|
+
return resolveAssetUrl(attrs?.[slot.id], attrs?.[slot.ext], options?.assets?.url);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/** The slot whose identity attrs name a given url attr (`poster` → posterAsset*). */
|
|
763
|
+
const slotFor = (urlAttr) => ASSET_SLOTS.find((s) => s.urls.includes(urlAttr));
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Resolve a secondary reference (`poster`, `preview`) the same way the primary
|
|
767
|
+
* one resolves: identity wins when it resolves, else the stored URL stands.
|
|
768
|
+
* A poster is an asset like any other — it just is not the node's primary.
|
|
769
|
+
*/
|
|
770
|
+
function secondaryAssetUrl(attrs, options, urlAttr) {
|
|
771
|
+
const slot = slotFor(urlAttr);
|
|
772
|
+
const resolved = slot ? assetUrlFromAttrs(attrs, options, slot) : "";
|
|
773
|
+
return resolved || attrs?.[urlAttr] || "";
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ⛔ LEGACY BELOW — the PHP-estate identifier path. Do not extend it.
|
|
777
|
+
//
|
|
778
|
+
// `{version}/{filename}` identifiers have no current producer — the CLI emits
|
|
779
|
+
// `src`, and editors store the URL their host hands them. Only content authored
|
|
780
|
+
// against the original hosted service still carries one, and the hardcoded host
|
|
781
|
+
// below cannot be correct for any other deployment: the CLI can be pointed at
|
|
782
|
+
// any host. New references use `assetId`/`assetExt` above.
|
|
662
783
|
const ASSET_BASE_URL = "https://assets.uniweb.app/";
|
|
663
784
|
|
|
664
785
|
/**
|
|
@@ -667,6 +788,20 @@ const ASSET_BASE_URL = "https://assets.uniweb.app/";
|
|
|
667
788
|
*/
|
|
668
789
|
function resolveAssetIdentifier(identifier) {
|
|
669
790
|
if (!identifier || typeof identifier !== "string") return "";
|
|
791
|
+
// ⛔ An identifier that is ALREADY a URL or a rooted path is not an
|
|
792
|
+
// identifier — it is a serve URL sitting in the identifier slot, and it is
|
|
793
|
+
// usable exactly as it stands. Composing it against the legacy base
|
|
794
|
+
// destroys it: `split("/")` on `/gateway/asset/dist/{id}/base.png` takes
|
|
795
|
+
// `""` as the version and `"gateway"` as the filename, yielding
|
|
796
|
+
// `https://assets.uniweb.app/dist//base.gateway` — not a 404 and not a
|
|
797
|
+
// validation failure, a URL-shaped string that renders a broken image and
|
|
798
|
+
// reads like a missing file.
|
|
799
|
+
//
|
|
800
|
+
// The legacy grammar is `{version}/{filename}` with no leading slash, so
|
|
801
|
+
// nothing legitimate is caught here. Passing it through is not constructing
|
|
802
|
+
// a serve location: it is declining to construct one over a value that is
|
|
803
|
+
// already an address. (Measured 2026-08-17 against a real producer.)
|
|
804
|
+
if (/^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(identifier)) return identifier;
|
|
670
805
|
const [version, filename] = identifier.split("/");
|
|
671
806
|
if (!filename) return "";
|
|
672
807
|
const ext = filename.substring(filename.lastIndexOf(".") + 1);
|
|
@@ -787,7 +922,7 @@ function parseIconBlock(itemAttrs) {
|
|
|
787
922
|
return icon;
|
|
788
923
|
}
|
|
789
924
|
|
|
790
|
-
function parseImgBlock(itemAttrs) {
|
|
925
|
+
function parseImgBlock(itemAttrs, options) {
|
|
791
926
|
let {
|
|
792
927
|
info: imgInfo,
|
|
793
928
|
targetId,
|
|
@@ -834,12 +969,23 @@ function parseImgBlock(itemAttrs) {
|
|
|
834
969
|
|
|
835
970
|
caption = stripTags(caption);
|
|
836
971
|
|
|
837
|
-
// Standard ProseMirror `image` nodes use `src`; the
|
|
838
|
-
// `ImageBlock` node uses `url`. Honor either so callers don't have
|
|
839
|
-
//
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
|
|
972
|
+
// Reference precedence. Standard ProseMirror `image` nodes use `src`; the
|
|
973
|
+
// custom `ImageBlock` node uses `url`. Honor either so callers don't have to
|
|
974
|
+
// pre-normalize.
|
|
975
|
+
//
|
|
976
|
+
// ⭐ A store-held asset (`assetId`) wins WHEN IT RESOLVES — not merely when
|
|
977
|
+
// it is present. That distinction is the whole reason a producer can write
|
|
978
|
+
// `assetId` alongside `src` before any deployment declares
|
|
979
|
+
// `config.assets.url`: until one does, resolution yields '' and the node
|
|
980
|
+
// renders through `src` exactly as it does today. Content written now stays
|
|
981
|
+
// correct whichever order the halves arrive in.
|
|
982
|
+
//
|
|
983
|
+
// `identifier` is the LEGACY estate path and keeps its old precedence over a
|
|
984
|
+
// stale `src`.
|
|
985
|
+
const assetUrl = assetUrlFromAttrs(itemAttrs, options);
|
|
986
|
+
if (assetUrl) {
|
|
987
|
+
url = assetUrl;
|
|
988
|
+
} else if (identifier) {
|
|
843
989
|
url = makeAssetUrl(imgInfo);
|
|
844
990
|
} else if (!url && src) {
|
|
845
991
|
url = src;
|
|
@@ -873,7 +1019,11 @@ function parseImgBlock(itemAttrs) {
|
|
|
873
1019
|
...(loading && { loading }),
|
|
874
1020
|
...(fit && { fit }),
|
|
875
1021
|
...(position && { position }),
|
|
876
|
-
...(
|
|
1022
|
+
...((() => {
|
|
1023
|
+
// Same rule for a document's preview image.
|
|
1024
|
+
const p = secondaryAssetUrl(itemAttrs, options, "preview");
|
|
1025
|
+
return p ? { preview: p } : {};
|
|
1026
|
+
})()),
|
|
877
1027
|
...(author && { author }),
|
|
878
1028
|
...(description && { description }),
|
|
879
1029
|
};
|
|
@@ -887,7 +1037,7 @@ function parseImgBlock(itemAttrs) {
|
|
|
887
1037
|
// attribute set that docs/reference/content-structure.md publishes for the
|
|
888
1038
|
// video role, and the shape it documents: `{ src, href, target }` plus the
|
|
889
1039
|
// playback flags.
|
|
890
|
-
function parseMarkdownVideo(itemAttrs) {
|
|
1040
|
+
function parseMarkdownVideo(itemAttrs, options) {
|
|
891
1041
|
const {
|
|
892
1042
|
src,
|
|
893
1043
|
url,
|
|
@@ -905,11 +1055,18 @@ function parseMarkdownVideo(itemAttrs) {
|
|
|
905
1055
|
id, // {#fig-id} cross-reference label, same as parseImgBlock
|
|
906
1056
|
} = itemAttrs || {};
|
|
907
1057
|
|
|
1058
|
+
// Reference precedence, now genuinely identical to parseImgBlock: a
|
|
1059
|
+
// resolvable store-held asset, then the legacy identifier, then the authored
|
|
1060
|
+
// src/url.
|
|
1061
|
+
//
|
|
1062
|
+
// ⚠️ The comment here used to claim identifier-precedence "matching
|
|
1063
|
+
// parseImgBlock" while the code did the opposite — it passed `src` INTO
|
|
1064
|
+
// `makeAssetUrl`, which returns a src/url before it ever looks at
|
|
1065
|
+
// `identifier`. So image and video resolved differently for years and the
|
|
1066
|
+
// comment asserted they did not. Aligned to the documented intent (2026-08-17).
|
|
1067
|
+
const assetUrl = assetUrlFromAttrs(itemAttrs, options);
|
|
908
1068
|
const video = {
|
|
909
|
-
|
|
910
|
-
// parseImgBlock — an editor-deployed asset with a stale src must not
|
|
911
|
-
// shadow the CDN copy.
|
|
912
|
-
src: makeAssetUrl({ src: url || src, ...info }),
|
|
1069
|
+
src: assetUrl || makeAssetUrl(info) || url || src || "",
|
|
913
1070
|
alt,
|
|
914
1071
|
caption: stripTags(caption),
|
|
915
1072
|
role,
|
|
@@ -920,7 +1077,10 @@ function parseMarkdownVideo(itemAttrs) {
|
|
|
920
1077
|
// Playback attributes are omitted rather than defaulted: a component can
|
|
921
1078
|
// then tell "the author said nothing" from "the author said false", and
|
|
922
1079
|
// kit's <Media> supplies its own defaults (controls on, the rest off).
|
|
923
|
-
|
|
1080
|
+
// A poster is an asset like any other — identity resolves it when it can,
|
|
1081
|
+
// and the stored URL stands when it cannot. Same rule as the primary `src`.
|
|
1082
|
+
const posterUrl = secondaryAssetUrl(itemAttrs, options, "poster");
|
|
1083
|
+
if (posterUrl) video.poster = posterUrl;
|
|
924
1084
|
if (autoplay !== undefined) video.autoplay = autoplay;
|
|
925
1085
|
if (muted !== undefined) video.muted = muted;
|
|
926
1086
|
if (loop !== undefined) video.loop = loop;
|
|
@@ -930,7 +1090,7 @@ function parseMarkdownVideo(itemAttrs) {
|
|
|
930
1090
|
return video;
|
|
931
1091
|
}
|
|
932
1092
|
|
|
933
|
-
function parseVideoBlock(itemAttrs) {
|
|
1093
|
+
function parseVideoBlock(itemAttrs, options) {
|
|
934
1094
|
let {
|
|
935
1095
|
src,
|
|
936
1096
|
caption = "",
|
|
@@ -942,10 +1102,9 @@ function parseVideoBlock(itemAttrs) {
|
|
|
942
1102
|
target = "",
|
|
943
1103
|
} = itemAttrs;
|
|
944
1104
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
});
|
|
1105
|
+
// Same precedence as parseImgBlock / parseMarkdownVideo: a resolvable
|
|
1106
|
+
// store-held asset, then the legacy identifier, then the authored src.
|
|
1107
|
+
const video = assetUrlFromAttrs(itemAttrs, options) || makeAssetUrl(info) || src || "";
|
|
949
1108
|
|
|
950
1109
|
return {
|
|
951
1110
|
src: video,
|
|
@@ -981,7 +1140,7 @@ function stripTags(htmlString) {
|
|
|
981
1140
|
.replace(/&#x([0-9a-fA-F]+);/g, (_, n) => String.fromCharCode(parseInt(n, 16)));
|
|
982
1141
|
}
|
|
983
1142
|
|
|
984
|
-
function isLink(item) {
|
|
1143
|
+
function isLink(item, options = {}) {
|
|
985
1144
|
// Detect paragraphs/headings that are semantically "just a link"
|
|
986
1145
|
// (single link text, possibly with decorative icons)
|
|
987
1146
|
//
|
|
@@ -1039,7 +1198,7 @@ function isLink(item) {
|
|
|
1039
1198
|
iconBefore,
|
|
1040
1199
|
iconAfter,
|
|
1041
1200
|
// Preserve all inline elements for advanced rendering
|
|
1042
|
-
children: processInlineElements(originalContent),
|
|
1201
|
+
children: processInlineElements(originalContent, options),
|
|
1043
1202
|
};
|
|
1044
1203
|
}
|
|
1045
1204
|
}
|
|
@@ -1104,7 +1263,7 @@ function isOnlyLinks(item) {
|
|
|
1104
1263
|
// method to check if given item has multiple content parts and each of them has the same link attrs with different inline style (plain, em, strong, u)
|
|
1105
1264
|
// if so, it will return the link attrs and all the content parts whose link mark has been removed
|
|
1106
1265
|
// warning: This method will not work if the any of the content parts are not link marks
|
|
1107
|
-
function isStyledLink(item) {
|
|
1266
|
+
function isStyledLink(item, options = {}) {
|
|
1108
1267
|
if (!["paragraph", "heading"].includes(item.type)) return false;
|
|
1109
1268
|
|
|
1110
1269
|
let content = item?.content || [];
|
|
@@ -1154,10 +1313,10 @@ function isStyledLink(item) {
|
|
|
1154
1313
|
|
|
1155
1314
|
return {
|
|
1156
1315
|
type: "paragraph",
|
|
1157
|
-
children: processInlineElements(item.content),
|
|
1316
|
+
children: processInlineElements(item.content, options),
|
|
1158
1317
|
text: `<a target="${target}" href="${href}">${textContent}</a>`,
|
|
1159
1318
|
attrs: item.attrs,
|
|
1160
1319
|
};
|
|
1161
1320
|
}
|
|
1162
1321
|
|
|
1163
|
-
export { processSequence };
|
|
1322
|
+
export { processSequence, resolveAssetUrl };
|