@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/content-reader",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Markdown to ProseMirror document structure converter",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -111,8 +111,13 @@ function parseBlock(token, schema) {
111
111
  let currentParagraph = null;
112
112
 
113
113
  content.forEach((element) => {
114
- if (element.type === "image") {
115
- // If there's an open paragraph, push it to the result before the image
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",
@@ -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: ![alt](@ComponentName){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
@@ -200,7 +200,9 @@ describe("Extended Syntax", () => {
200
200
  });
201
201
 
202
202
  test("parses images with roles", () => {
203
- // Images are extracted from paragraphs to root level for component rendering
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 = '![Alt Text](icon:path/to/image.svg "Caption text")';
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: "image",
212
- attrs: {
213
- src: "path/to/image.svg",
214
- caption: "Caption text",
215
- alt: "Alt Text",
216
- role: "icon",
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 = "![](lu-home) [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 = "![](@Hero)";
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 = "![Architecture diagram](@NetworkDiagram){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 ![](@Widget) 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 = "![](lu-house)";
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 = "![](@Widget)\n\n![](@Chart)";
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
+ });