@valbuild/core 0.20.2 → 0.21.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.
Files changed (29) hide show
  1. package/dist/declarations/src/index.d.ts +3 -3
  2. package/dist/declarations/src/initVal.d.ts +2 -0
  3. package/dist/declarations/src/source/link.d.ts +9 -0
  4. package/dist/declarations/src/source/richtext.d.ts +44 -30
  5. package/dist/{index-601a7d73.cjs.prod.js → index-067cff4a.cjs.prod.js} +1 -1
  6. package/dist/{index-a6e642dd.cjs.dev.js → index-31991dd7.cjs.dev.js} +3 -0
  7. package/dist/{index-bccf1907.esm.js → index-4bb14a92.esm.js} +4 -1
  8. package/dist/{index-8706c87e.cjs.prod.js → index-75b79c89.cjs.prod.js} +3 -0
  9. package/dist/{index-5d1ab97c.esm.js → index-870205b5.esm.js} +1 -1
  10. package/dist/{index-486c7fbf.cjs.dev.js → index-d17f9503.cjs.dev.js} +1 -1
  11. package/dist/{ops-b0a33248.cjs.dev.js → ops-0f7617a0.cjs.dev.js} +1 -1
  12. package/dist/{ops-def81fc3.cjs.prod.js → ops-451ffb3f.cjs.prod.js} +1 -1
  13. package/dist/{ops-22b624eb.esm.js → ops-9b396073.esm.js} +1 -1
  14. package/dist/valbuild-core.cjs.dev.js +39 -159
  15. package/dist/valbuild-core.cjs.prod.js +39 -159
  16. package/dist/valbuild-core.esm.js +40 -140
  17. package/expr/dist/valbuild-core-expr.cjs.dev.js +2 -2
  18. package/expr/dist/valbuild-core-expr.cjs.prod.js +2 -2
  19. package/expr/dist/valbuild-core-expr.esm.js +2 -2
  20. package/package.json +2 -4
  21. package/patch/dist/valbuild-core-patch.cjs.dev.js +2 -2
  22. package/patch/dist/valbuild-core-patch.cjs.prod.js +2 -2
  23. package/patch/dist/valbuild-core-patch.esm.js +3 -3
  24. package/src/index.ts +4 -4
  25. package/src/initVal.ts +3 -0
  26. package/src/schema/image.ts +5 -0
  27. package/src/source/link.ts +14 -0
  28. package/src/source/richtext.ts +91 -234
  29. package/src/source/richtext.test.ts +0 -178
@@ -1,84 +1,95 @@
1
- import * as marked from "marked";
2
1
  import { FileSource } from "./file";
3
2
  import { VAL_EXTENSION } from ".";
4
- import { convertFileSource } from "../schema/image";
3
+ import { LinkSource } from "./link";
4
+ import { ImageMetadata } from "../schema/image";
5
5
 
6
6
  export type RichTextOptions = {
7
7
  headings?: ("h1" | "h2" | "h3" | "h4" | "h5" | "h6")[];
8
8
  img?: boolean;
9
- ul?: boolean; // TODO: naming
10
- ol?: boolean; // TODO: naming
9
+ a?: boolean;
10
+ ul?: boolean;
11
+ ol?: boolean;
11
12
  lineThrough?: boolean;
12
13
  bold?: boolean;
13
14
  italic?: boolean;
14
- // link?: boolean;
15
- // fontFamily?: Record<string, string[]>;
16
- // fontSize?: Record<string, string[]>;
17
- // blockQuote?: boolean; // TODO: naming
18
15
  };
19
-
20
- export type ParagraphNode<O extends RichTextOptions> = {
21
- tag: "p";
22
- children: (string | SpanNode<O>)[];
23
- // AnchorNode<O>
16
+ export type AnyRichTextOptions = {
17
+ headings: ("h1" | "h2" | "h3" | "h4" | "h5" | "h6")[];
18
+ img: true;
19
+ a: true;
20
+ ul: true;
21
+ ol: true;
22
+ lineThrough: true;
23
+ bold: true;
24
+ italic: true;
24
25
  };
25
26
 
27
+ // Classes
26
28
  export type LineThrough<O extends RichTextOptions> =
27
29
  O["lineThrough"] extends true ? "line-through" : never;
30
+
28
31
  export type Italic<O extends RichTextOptions> = O["italic"] extends true
29
32
  ? "italic"
30
33
  : never;
34
+
31
35
  export type Bold<O extends RichTextOptions> = O["bold"] extends true
32
36
  ? "bold"
33
37
  : never;
34
- // export type FontFamily<O extends RichTextOptions> =
35
- // O["fontFamily"] extends Record<string, unknown>
36
- // ? `font-${keyof O["fontFamily"] & string}`
37
- // : never;
38
- // export type FontSize<O extends RichTextOptions> = O["fontSize"] extends Record<
39
- // string,
40
- // unknown
41
- // >
42
- // ? `text-${keyof O["fontSize"] & string}`
43
- // : never;
44
38
 
45
39
  export type Classes<O extends RichTextOptions> =
46
40
  | LineThrough<O>
47
41
  | Italic<O>
48
42
  | Bold<O>;
49
- // | FontFamily<O>
50
- // | FontSize<O>;
51
43
 
44
+ /// Paragraph
45
+ export type ParagraphNode<O extends RichTextOptions> = {
46
+ tag: "p";
47
+ children: (string | SpanNode<O> | BrNode | LinkNode<O> | ImageNode<O>)[];
48
+ };
49
+
50
+ /// Break
51
+ export type BrNode = {
52
+ tag: "br";
53
+ children: [];
54
+ };
55
+
56
+ /// Span
52
57
  export type SpanNode<O extends RichTextOptions> = {
53
58
  tag: "span";
54
59
  classes: Classes<O>[];
55
60
  children: [string | SpanNode<O>];
56
61
  };
57
62
 
58
- // export type AnchorNode<O extends RichTextOptions> = never; // TODO:
59
- // O["link"] extends true
60
- // ? {
61
- // tag: "a";
62
- // href: string;
63
- // children: [string];
64
- // }
65
- // : never;
66
-
63
+ /// Image
64
+ type ImageTagNode = {
65
+ tag: "img";
66
+ src: string;
67
+ height?: number;
68
+ width?: number;
69
+ children: [];
70
+ };
67
71
  export type ImageNode<O extends RichTextOptions> = O["img"] extends true
68
- ? {
69
- tag: "img";
70
- src: string;
71
- height?: number;
72
- width?: number;
73
- }
72
+ ? ImageTagNode
73
+ : never;
74
+
75
+ /// Link
76
+ type LinkTagNode<O extends RichTextOptions> = {
77
+ tag: "a";
78
+ href: string;
79
+ children: (string | SpanNode<O>)[];
80
+ };
81
+ export type LinkNode<O extends RichTextOptions> = O["a"] extends true
82
+ ? LinkTagNode<O>
74
83
  : never;
75
84
 
85
+ /// List
76
86
  export type ListItemNode<O extends RichTextOptions> = {
77
87
  tag: "li";
78
88
  children: (
79
89
  | string
80
90
  | SpanNode<O>
81
- // | AnchorNode<O>
91
+ | LinkNode<O>
92
+ | BrNode
82
93
  | UnorderedListNode<O>
83
94
  | OrderedListNode<O>
84
95
  )[];
@@ -100,222 +111,68 @@ export type OrderedListNode<O extends RichTextOptions> = O["ol"] extends true
100
111
  }
101
112
  : never;
102
113
 
103
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
- export type HeadingNode<O extends RichTextOptions> = O["headings"] extends any[]
114
+ /// Heading
115
+ export type HeadingNode<O extends RichTextOptions> = O["headings"] extends (
116
+ | "h1"
117
+ | "h2"
118
+ | "h3"
119
+ | "h4"
120
+ | "h5"
121
+ | "h6"
122
+ )[]
105
123
  ? {
106
124
  tag: O["headings"][number];
107
125
  children: (string | SpanNode<O>)[];
108
- // | AnchorNode<O>
109
126
  }
110
127
  : never;
111
128
 
112
- // export type BlockQuoteNode<O extends RichTextOptions> =
113
- // O["blockQuote"] extends true
114
- // ? { tag: "blockquote"; children: [string] }
115
- // : never;
116
-
117
- type ImageSource = FileSource<{
118
- width: number;
119
- height: number;
120
- sha256: string;
121
- }>;
122
-
123
- export type SourceNode<O extends RichTextOptions> = O["img"] extends true
124
- ? ImageSource
125
- : never;
126
-
127
- export type AnyRichTextOptions = {
128
- headings: ("h1" | "h2" | "h3" | "h4" | "h5" | "h6")[];
129
- img: true;
130
- ul: true;
131
- ol: true;
132
- lineThrough: true;
133
- bold: true;
134
- italic: true;
135
- // blockQuote: true;
136
- // fontFamily: Record<string, string[]>;
137
- // fontSize: Record<string, string[]>;
138
- };
139
-
140
- export type RichTextSourceNode<O extends RichTextOptions> =
141
- | Exclude<RichTextNode<O>, { tag: "img" }>
142
- | SourceNode<O>;
143
-
144
- export type RichTextSource<O extends RichTextOptions> = {
145
- [VAL_EXTENSION]: "richtext";
146
- children: (
147
- | HeadingNode<O>
148
- | ParagraphNode<O>
149
- | UnorderedListNode<O>
150
- | OrderedListNode<O>
151
- // | BlockQuoteNode<O>
152
- | SourceNode<O>
153
- )[];
154
- };
155
-
129
+ /// Root and nodes
156
130
  export type RichTextNode<O extends RichTextOptions> =
157
131
  | string
158
- | HeadingNode<O>
159
- | ParagraphNode<O>
160
- | UnorderedListNode<O>
161
- | OrderedListNode<O>
132
+ | RootNode<O>
162
133
  | ListItemNode<O>
134
+ | BrNode
163
135
  | SpanNode<O>
164
- // | BlockQuoteNode<O>
136
+ | LinkNode<O>
165
137
  | ImageNode<O>;
166
138
 
167
139
  export type RootNode<O extends RichTextOptions> =
168
140
  | HeadingNode<O>
169
141
  | ParagraphNode<O>
142
+ | BrNode
170
143
  | UnorderedListNode<O>
171
- | OrderedListNode<O>
172
- // | BlockQuoteNode<O>
173
- | ImageNode<O>;
144
+ | OrderedListNode<O>;
174
145
 
175
- // TODO: rename to RichTextSelector?
146
+ /// Main types
147
+
148
+ /**
149
+ * RichTextSource is defined in ValModules
150
+ **/
151
+ export type RichTextSource<O extends RichTextOptions> = {
152
+ [VAL_EXTENSION]: "richtext";
153
+ templateStrings: string[];
154
+ exprs: (
155
+ | (O["img"] extends true ? FileSource<ImageMetadata> : never)
156
+ | (O["a"] extends true ? LinkSource : never)
157
+ )[];
158
+ };
159
+ /**
160
+ * RichText is accessible by users (after conversion via useVal / fetchVal)
161
+ * Internally it is a Selector
162
+ **/
176
163
  export type RichText<O extends RichTextOptions> = {
177
164
  [VAL_EXTENSION]: "richtext";
178
165
  children: RootNode<O>[];
179
166
  };
180
167
 
181
- function parseTokens<O extends RichTextOptions>(
182
- tokens: marked.Token[]
183
- ): RichTextSource<O>["children"] {
184
- return tokens.flatMap((token) => {
185
- if (token.type === "heading") {
186
- return [
187
- {
188
- tag: `h${token.depth}`,
189
- children: parseTokens(token.tokens ? token.tokens : []),
190
- },
191
- ];
192
- }
193
- if (token.type === "paragraph") {
194
- return [
195
- {
196
- tag: "p",
197
- children: parseTokens(token.tokens ? token.tokens : []),
198
- },
199
- ];
200
- }
201
- if (token.type === "strong") {
202
- return [
203
- {
204
- tag: "span",
205
- classes: ["bold"],
206
- children: parseTokens(token.tokens ? token.tokens : []),
207
- },
208
- ];
209
- }
210
- if (token.type === "em") {
211
- return [
212
- {
213
- tag: "span",
214
- classes: ["italic"],
215
- children: parseTokens(token.tokens ? token.tokens : []),
216
- },
217
- ];
218
- }
219
- if (token.type === "del") {
220
- return [
221
- {
222
- tag: "span",
223
- classes: ["line-through"],
224
- children: parseTokens(token.tokens ? token.tokens : []),
225
- },
226
- ];
227
- }
228
- if (token.type === "text") {
229
- if ("tokens" in token && Array.isArray(token.tokens)) {
230
- return parseTokens(token.tokens);
231
- }
232
- return [token.text];
233
- }
234
- if (token.type === "list") {
235
- return [
236
- {
237
- tag: token.ordered ? "ol" : "ul",
238
- children: parseTokens(token.items),
239
- },
240
- ];
241
- }
242
- if (token.type === "list_item") {
243
- return [
244
- {
245
- tag: "li",
246
- children: parseTokens(token.tokens ? token.tokens : []),
247
- },
248
- ];
249
- }
250
- if (token.type === "space") {
251
- return [];
252
- }
253
-
254
- if (token.type === "code") {
255
- return [
256
- {
257
- tag: "span",
258
- classes: [],
259
- children: [token.text],
260
- },
261
- ];
262
- }
263
- // console.error(
264
- // `Could not parse markdown: unsupported token type: ${token.type}. Found: ${token.raw}`
265
- // );
266
- return [token.raw];
267
- });
268
- }
269
-
270
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
271
- function nodeToTag(node: any): any {
272
- if (node[VAL_EXTENSION] === "file") {
273
- return node;
274
- }
275
- throw Error(`Unexpected node: ${JSON.stringify(node)}`);
276
- }
277
-
278
- function imgSrcToImgTag<O extends RichTextOptions>(
279
- imageSrc: ImageSource
280
- ): ImageNode<O> {
281
- const converted = convertFileSource(imageSrc);
282
- return {
283
- tag: "img",
284
- src: converted.url,
285
- width: imageSrc.metadata?.width,
286
- height: imageSrc.metadata?.height,
287
- } as ImageNode<O>;
288
- }
289
-
290
- export function convertRichTextSource<O extends RichTextOptions>(
291
- src: RichTextSource<O>
292
- ): RichText<O> {
293
- return {
294
- [VAL_EXTENSION]: "richtext",
295
- children: src.children.map((source) => {
296
- if (VAL_EXTENSION in source && source[VAL_EXTENSION] === "file") {
297
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
298
- return imgSrcToImgTag(source as any);
299
- }
300
- return source;
301
- }),
302
- } as RichText<O>;
303
- }
304
-
305
- export function richtext<
306
- O extends RichTextOptions,
307
- Nodes extends never | ImageSource
308
- >(templateStrings: TemplateStringsArray, ...expr: Nodes[]): RichTextSource<O> {
168
+ export function richtext<O extends RichTextOptions>(
169
+ templateStrings: TemplateStringsArray,
170
+ ...nodes: (FileSource<ImageMetadata> | LinkSource)[]
171
+ ): RichTextSource<O> {
309
172
  return {
310
173
  [VAL_EXTENSION]: "richtext",
311
- children: templateStrings.flatMap((templateString, i) => {
312
- const lex = marked.lexer(templateString, {
313
- gfm: true,
314
- });
315
- if (expr[i]) {
316
- return parseTokens(lex).concat(nodeToTag(expr[i]));
317
- }
318
- return parseTokens(lex);
319
- }),
174
+ templateStrings: templateStrings as unknown as string[],
175
+ exprs:
176
+ nodes as RichTextSource<AnyRichTextOptions>["exprs"] as RichTextSource<O>["exprs"],
320
177
  };
321
178
  }
@@ -1,178 +0,0 @@
1
- import { file } from "../source/file";
2
- import { richtext } from "./richtext";
3
-
4
- //MD to HTML
5
- describe("richtext", () => {
6
- test("basic h1", () => {
7
- const r = richtext`# Title 1`;
8
- expect(r.children).toStrictEqual([{ tag: "h1", children: ["Title 1"] }]);
9
- });
10
-
11
- test("basic complete", () => {
12
- const r = richtext`# Title 1
13
- ## Title 2
14
-
15
- Paragraph 1 2 3 4 5. Words *italic* **bold**
16
- `;
17
- expect(r.children).toStrictEqual([
18
- { tag: "h1", children: ["Title 1"] },
19
- { tag: "h2", children: ["Title 2"] },
20
- {
21
- tag: "p",
22
- children: [
23
- "Paragraph 1 2 3 4 5. Words ",
24
- { tag: "span", classes: ["italic"], children: ["italic"] },
25
- " ",
26
- { tag: "span", classes: ["bold"], children: ["bold"] },
27
- ],
28
- },
29
- ]);
30
- });
31
-
32
- test.skip("strong and emphasis merged spans", () => {
33
- // TODO: currently we do not merge
34
- const r = richtext`Which classes?
35
- ***All of them!***
36
- `;
37
- expect(r.children).toStrictEqual([
38
- {
39
- tag: "p",
40
- children: [
41
- "Which classes?\n",
42
- {
43
- tag: "span",
44
- classes: ["italic", "bold"],
45
- children: ["All of them!"],
46
- },
47
- ],
48
- },
49
- ]);
50
- });
51
-
52
- test("line through", () => {
53
- // TODO: currently we do not merge
54
- const r = richtext`~~line through~~`;
55
- expect(r.children).toStrictEqual([
56
- {
57
- tag: "p",
58
- children: [
59
- {
60
- tag: "span",
61
- classes: ["line-through"],
62
- children: ["line through"],
63
- },
64
- ],
65
- },
66
- ]);
67
- });
68
-
69
- test("2 paragraphs", () => {
70
- const r = richtext`# Title 1
71
-
72
- First paragraph
73
-
74
- Second paragraph
75
- `;
76
- expect(r.children).toStrictEqual([
77
- { tag: "h1", children: ["Title 1"] },
78
- { tag: "p", children: ["First paragraph"] },
79
- { tag: "p", children: ["Second paragraph"] },
80
- ]);
81
- });
82
-
83
- test("lists", () => {
84
- const r = richtext`# Title 1
85
-
86
- A paragraph
87
-
88
- A bullet list:
89
- - bullet 1
90
- - bullet 2
91
-
92
- A numbered list:
93
- 1. number 1
94
- 2. number 2
95
-
96
- A nested list:
97
- - bullet 1:
98
- 1. number 1.1
99
- 2. number 1.2
100
- - bullet 2:
101
- - bullet 2.1
102
- - bullet 2.2
103
- `;
104
- expect(r.children).toStrictEqual([
105
- { tag: "h1", children: ["Title 1"] },
106
- { tag: "p", children: ["A paragraph"] },
107
- { tag: "p", children: ["A bullet list:"] },
108
- {
109
- tag: "ul",
110
- children: [
111
- { tag: "li", children: ["bullet 1"] },
112
- { tag: "li", children: ["bullet 2"] },
113
- ],
114
- },
115
- { tag: "p", children: ["A numbered list:"] },
116
- {
117
- tag: "ol",
118
- children: [
119
- { tag: "li", children: ["number 1"] },
120
- { tag: "li", children: ["number 2"] },
121
- ],
122
- },
123
- { tag: "p", children: ["A nested list:"] },
124
- {
125
- tag: "ul",
126
- children: [
127
- {
128
- tag: "li",
129
- children: [
130
- "bullet 1:",
131
- {
132
- tag: "ol",
133
- children: [
134
- { tag: "li", children: ["number 1.1"] },
135
- { tag: "li", children: ["number 1.2"] },
136
- ],
137
- },
138
- ],
139
- },
140
- {
141
- tag: "li",
142
- children: [
143
- "bullet 2:",
144
- {
145
- tag: "ul",
146
- children: [
147
- { tag: "li", children: ["bullet 2.1"] },
148
- { tag: "li", children: ["bullet 2.2"] },
149
- ],
150
- },
151
- ],
152
- },
153
- ],
154
- },
155
- ]);
156
- });
157
-
158
- test("image", () => {
159
- const r = richtext`# Title 1
160
-
161
- Below we have an image block:
162
-
163
- ${file("/public/foo.png", {
164
- width: 100,
165
- height: 100,
166
- sha256: "123",
167
- })}`;
168
- expect(r.children).toStrictEqual([
169
- { tag: "h1", children: ["Title 1"] },
170
- { tag: "p", children: ["Below we have an image block:"] },
171
- {
172
- _ref: "/public/foo.png",
173
- _type: "file",
174
- metadata: { width: 100, height: 100, sha256: "123" },
175
- },
176
- ]);
177
- });
178
- });