@uniweb/content-reader 1.1.1 → 1.1.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/parser/block.js +7 -2
- package/src/parser/inline.js +14 -0
- package/tests/parser.test.js +139 -8
package/package.json
CHANGED
package/src/parser/block.js
CHANGED
|
@@ -111,8 +111,13 @@ function parseBlock(token, schema) {
|
|
|
111
111
|
let currentParagraph = null;
|
|
112
112
|
|
|
113
113
|
content.forEach((element) => {
|
|
114
|
-
if (
|
|
115
|
-
|
|
114
|
+
if (
|
|
115
|
+
(element.type === "image" && element.attrs?.role !== "icon") ||
|
|
116
|
+
element.type === "inline_child_ref"
|
|
117
|
+
) {
|
|
118
|
+
// Extract non-icon images to root level so they become
|
|
119
|
+
// block-level elements. Icons stay inline so the semantic
|
|
120
|
+
// parser can associate them with adjacent links.
|
|
116
121
|
if (currentParagraph) {
|
|
117
122
|
result.push({
|
|
118
123
|
type: "paragraph",
|
package/src/parser/inline.js
CHANGED
|
@@ -148,6 +148,20 @@ function parseInline(token, schema, removeNewLine = false) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (token.type === "image") {
|
|
151
|
+
// Check for @ component reference: {key=value}
|
|
152
|
+
if (token.href.startsWith('@') && token.href.length > 1) {
|
|
153
|
+
const component = token.href.slice(1)
|
|
154
|
+
const { role: _role, ...otherAttrs } = token.attrs || {}
|
|
155
|
+
return [{
|
|
156
|
+
type: "inline_child_ref",
|
|
157
|
+
attrs: {
|
|
158
|
+
component,
|
|
159
|
+
alt: text || null,
|
|
160
|
+
...otherAttrs,
|
|
161
|
+
},
|
|
162
|
+
}]
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
let role, src, iconLibrary, iconName;
|
|
152
166
|
|
|
153
167
|
// Supported icon families - friendly names and direct react-icons codes
|
package/tests/parser.test.js
CHANGED
|
@@ -200,7 +200,9 @@ describe("Extended Syntax", () => {
|
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
test("parses images with roles", () => {
|
|
203
|
-
//
|
|
203
|
+
// Icons stay inline (inside paragraphs) so the semantic parser can
|
|
204
|
+
// associate them with adjacent links. Non-icon images are extracted
|
|
205
|
+
// to root level.
|
|
204
206
|
const markdown = '';
|
|
205
207
|
const result = markdownToProseMirror(markdown);
|
|
206
208
|
|
|
@@ -208,13 +210,58 @@ describe("Extended Syntax", () => {
|
|
|
208
210
|
type: "doc",
|
|
209
211
|
content: [
|
|
210
212
|
{
|
|
211
|
-
type: "
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
type: "paragraph",
|
|
214
|
+
content: [
|
|
215
|
+
{
|
|
216
|
+
type: "image",
|
|
217
|
+
attrs: {
|
|
218
|
+
src: "path/to/image.svg",
|
|
219
|
+
caption: "Caption text",
|
|
220
|
+
alt: "Alt Text",
|
|
221
|
+
role: "icon",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("keeps icon inline with adjacent link for icon-link association", () => {
|
|
231
|
+
// Icons must stay inline so the semantic parser can associate them
|
|
232
|
+
// with adjacent links (iconBefore / iconAfter)
|
|
233
|
+
const markdown = " [Sports](/sports)";
|
|
234
|
+
const result = markdownToProseMirror(markdown);
|
|
235
|
+
|
|
236
|
+
expect(result).toEqual({
|
|
237
|
+
type: "doc",
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: "paragraph",
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: "image",
|
|
244
|
+
attrs: {
|
|
245
|
+
src: null,
|
|
246
|
+
caption: null,
|
|
247
|
+
alt: null,
|
|
248
|
+
role: "icon",
|
|
249
|
+
library: "lu",
|
|
250
|
+
name: "home",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{ type: "text", text: " " },
|
|
254
|
+
{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: "Sports",
|
|
257
|
+
marks: [
|
|
258
|
+
{
|
|
259
|
+
type: "link",
|
|
260
|
+
attrs: { href: "/sports", title: null },
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
218
265
|
},
|
|
219
266
|
],
|
|
220
267
|
});
|
|
@@ -652,3 +699,87 @@ describe("Bracketed Spans", () => {
|
|
|
652
699
|
expect(content[2].marks[0].attrs.class).toBe("muted");
|
|
653
700
|
});
|
|
654
701
|
});
|
|
702
|
+
|
|
703
|
+
describe("Component References (@)", () => {
|
|
704
|
+
test("parses bare @ComponentName", () => {
|
|
705
|
+
const markdown = "";
|
|
706
|
+
const result = markdownToProseMirror(markdown);
|
|
707
|
+
|
|
708
|
+
expect(result).toEqual({
|
|
709
|
+
type: "doc",
|
|
710
|
+
content: [
|
|
711
|
+
{
|
|
712
|
+
type: "inline_child_ref",
|
|
713
|
+
attrs: {
|
|
714
|
+
component: "Hero",
|
|
715
|
+
alt: null,
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("parses @ComponentName with alt text and params", () => {
|
|
723
|
+
const markdown = "{variant=compact size=lg}";
|
|
724
|
+
const result = markdownToProseMirror(markdown);
|
|
725
|
+
|
|
726
|
+
expect(result).toEqual({
|
|
727
|
+
type: "doc",
|
|
728
|
+
content: [
|
|
729
|
+
{
|
|
730
|
+
type: "inline_child_ref",
|
|
731
|
+
attrs: {
|
|
732
|
+
component: "NetworkDiagram",
|
|
733
|
+
alt: "Architecture diagram",
|
|
734
|
+
variant: "compact",
|
|
735
|
+
size: "lg",
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
],
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("bare @ is treated as regular image", () => {
|
|
743
|
+
const markdown = "";
|
|
744
|
+
const result = markdownToProseMirror(markdown);
|
|
745
|
+
|
|
746
|
+
// Bare @ doesn't match component ref (length check), falls through to image
|
|
747
|
+
expect(result.content[0].type).toBe("image");
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("@ ref is lifted to block level (not inline in paragraph)", () => {
|
|
751
|
+
const markdown = "Some text before  and after";
|
|
752
|
+
const result = markdownToProseMirror(markdown);
|
|
753
|
+
|
|
754
|
+
// inline_child_ref should be extracted from paragraph like images
|
|
755
|
+
const types = result.content.map((n) => n.type);
|
|
756
|
+
expect(types).toContain("inline_child_ref");
|
|
757
|
+
expect(types).toContain("paragraph");
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
test("icon syntax is not affected by @ detection", () => {
|
|
761
|
+
const markdown = "";
|
|
762
|
+
const result = markdownToProseMirror(markdown);
|
|
763
|
+
|
|
764
|
+
// Icons still work as before
|
|
765
|
+
expect(result.content[0].type).toBe("paragraph");
|
|
766
|
+
expect(result.content[0].content[0].attrs.role).toBe("icon");
|
|
767
|
+
expect(result.content[0].content[0].attrs.library).toBe("lu");
|
|
768
|
+
expect(result.content[0].content[0].attrs.name).toBe("house");
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("multiple @ refs in same section", () => {
|
|
772
|
+
const markdown = "\n\n";
|
|
773
|
+
const result = markdownToProseMirror(markdown);
|
|
774
|
+
|
|
775
|
+
expect(result.content).toHaveLength(2);
|
|
776
|
+
expect(result.content[0]).toEqual({
|
|
777
|
+
type: "inline_child_ref",
|
|
778
|
+
attrs: { component: "Widget", alt: null },
|
|
779
|
+
});
|
|
780
|
+
expect(result.content[1]).toEqual({
|
|
781
|
+
type: "inline_child_ref",
|
|
782
|
+
attrs: { component: "Chart", alt: null },
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
});
|