@valbuild/core 0.18.0 → 0.19.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 (41) hide show
  1. package/dist/declarations/src/index.d.ts +4 -1
  2. package/dist/declarations/src/initSchema.d.ts +2 -2
  3. package/dist/declarations/src/patch/deref.d.ts +2 -1
  4. package/dist/declarations/src/patch/operation.d.ts +8 -0
  5. package/dist/declarations/src/schema/richtext.d.ts +7 -6
  6. package/dist/declarations/src/schema/string.d.ts +6 -3
  7. package/dist/declarations/src/selector/future/index.d.ts +3 -3
  8. package/dist/declarations/src/selector/index.d.ts +3 -3
  9. package/dist/declarations/src/source/future/remote.d.ts +2 -2
  10. package/dist/declarations/src/source/index.d.ts +3 -3
  11. package/dist/declarations/src/source/richtext.d.ts +70 -54
  12. package/dist/{index-4abf3a1f.esm.js → index-5d1ab97c.esm.js} +1 -1
  13. package/dist/{index-a9235737.esm.js → index-bccf1907.esm.js} +1 -1
  14. package/dist/{ops-f3015423.cjs.dev.js → ops-2d7e1742.cjs.dev.js} +106 -33
  15. package/dist/{ops-a2a295f8.esm.js → ops-7ef32b0a.esm.js} +107 -35
  16. package/dist/{ops-0d09f8ee.cjs.prod.js → ops-ae089ab2.cjs.prod.js} +106 -33
  17. package/dist/valbuild-core.cjs.dev.js +200 -104
  18. package/dist/valbuild-core.cjs.prod.js +200 -104
  19. package/dist/valbuild-core.esm.js +182 -106
  20. package/expr/dist/valbuild-core-expr.esm.js +2 -2
  21. package/package.json +4 -1
  22. package/patch/dist/valbuild-core-patch.cjs.dev.js +6 -1
  23. package/patch/dist/valbuild-core-patch.cjs.prod.js +6 -1
  24. package/patch/dist/valbuild-core-patch.esm.js +8 -3
  25. package/src/getSha256.ts +8 -0
  26. package/src/index.ts +21 -5
  27. package/src/module.ts +33 -2
  28. package/src/patch/deref.ts +14 -1
  29. package/src/patch/operation.ts +10 -0
  30. package/src/patch/parse.ts +1 -0
  31. package/src/patch/patch.ts +3 -0
  32. package/src/schema/richtext.ts +19 -73
  33. package/src/schema/string.ts +14 -4
  34. package/src/schema/validation.test.ts +2 -2
  35. package/src/selector/future/index.ts +8 -4
  36. package/src/selector/index.ts +4 -4
  37. package/src/source/future/remote.ts +2 -2
  38. package/src/source/index.ts +2 -2
  39. package/src/source/richtext.test.ts +178 -0
  40. package/src/source/richtext.ts +295 -89
  41. package/tsconfig.json +2 -1
@@ -1,116 +1,322 @@
1
+ import * as marked from "marked";
2
+ import { FileSource } from "./file";
1
3
  import { VAL_EXTENSION } from ".";
2
- import { SourcePath } from "../val";
4
+ import { convertFileSource } from "../schema/image";
3
5
 
4
- type Node = {
5
- version?: 1;
6
+ export type RichTextOptions = {
7
+ headings?: ("h1" | "h2" | "h3" | "h4" | "h5" | "h6")[];
8
+ img?: boolean;
9
+ ul?: boolean; // TODO: naming
10
+ ol?: boolean; // TODO: naming
11
+ lineThrough?: boolean;
12
+ bold?: boolean;
13
+ italic?: boolean;
14
+ // link?: boolean;
15
+ // fontFamily?: Record<string, string[]>;
16
+ // fontSize?: Record<string, string[]>;
17
+ // blockQuote?: boolean; // TODO: naming
6
18
  };
7
19
 
8
- type NodeType = Node & {
9
- format?: FormatType;
10
- direction?: DirectionType;
11
- indent?: number;
20
+ export type ParagraphNode<O extends RichTextOptions> = {
21
+ tag: "p";
22
+ children: (string | SpanNode<O> | ImageNode<O>)[];
23
+ // AnchorNode<O>
12
24
  };
13
25
 
14
- type FormatType =
15
- | "left"
16
- | "start"
17
- | "center"
18
- | "right"
19
- | "end"
20
- | "justify"
21
- | "";
22
- type DirectionType = "ltr" | "rtl" | null;
23
-
24
- export type TextNode = Node & {
25
- type: "text";
26
- format?: FormatType | number;
27
- detail?: number;
28
- mode?: "normal" | "code" | "quote";
29
- style?: string;
30
- text: string;
31
- direction?: DirectionType;
32
- indent?: number;
33
- };
26
+ export type LineThrough<O extends RichTextOptions> =
27
+ O["lineThrough"] extends true ? "line-through" : never;
28
+ export type Italic<O extends RichTextOptions> = O["italic"] extends true
29
+ ? "italic"
30
+ : never;
31
+ export type Bold<O extends RichTextOptions> = O["bold"] extends true
32
+ ? "bold"
33
+ : 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;
34
44
 
35
- export type ParagraphNode<VN = TextNode> = NodeType & {
36
- children: (TextNode | VN)[];
37
- type: "paragraph";
38
- };
45
+ export type Classes<O extends RichTextOptions> =
46
+ | LineThrough<O>
47
+ | Italic<O>
48
+ | Bold<O>;
49
+ // | FontFamily<O>
50
+ // | FontSize<O>;
39
51
 
40
- export type HeadingNode<HT extends HeadingTags = HeadingTags> = NodeType & {
41
- children: TextNode[];
42
- type: "heading";
43
- tag: HT;
52
+ export type SpanNode<O extends RichTextOptions> = {
53
+ tag: "span";
54
+ classes: Classes<O>[];
55
+ children: [string | SpanNode<O>];
44
56
  };
45
57
 
46
- export type ListItemNode<VN = TextNode> = NodeType & {
47
- children: (TextNode | VN)[];
48
- type: "listitem";
49
- value: number;
50
- checked?: boolean;
51
- };
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;
52
66
 
53
- export type ListNode<VN = TextNode> = NodeType & {
54
- children: ListItemNode<VN>[];
55
- type: "list";
56
- tag: "ol" | "ul";
57
- listType: "number" | "bullet" | "check";
58
- start?: number;
67
+ export type ImageNode<O extends RichTextOptions> = O["img"] extends true
68
+ ? {
69
+ tag: "img";
70
+ src: string;
71
+ height?: number;
72
+ width?: number;
73
+ }
74
+ : never;
75
+
76
+ export type ListItemNode<O extends RichTextOptions> = {
77
+ tag: "li";
78
+ children: (
79
+ | string
80
+ | SpanNode<O>
81
+ // | AnchorNode<O>
82
+ | ImageNode<O>
83
+ | UnorderedListNode<O>
84
+ | OrderedListNode<O>
85
+ )[];
59
86
  };
60
87
 
61
- type HeadingTags = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
62
- export type RootNode<HT extends HeadingTags, VN> = Node & {
63
- children: (HeadingNode<HT> | ParagraphNode<VN> | ListNode<VN>)[];
64
- type?: "root";
65
- format?: FormatType;
66
- direction?: DirectionType;
67
- indent?: number;
88
+ export type UnorderedListNode<O extends RichTextOptions> = O["ul"] extends true
89
+ ? {
90
+ tag: "ul";
91
+ dir?: "ltr" | "rtl";
92
+ children: ListItemNode<O>[];
93
+ }
94
+ : never;
95
+
96
+ export type OrderedListNode<O extends RichTextOptions> = O["ol"] extends true
97
+ ? {
98
+ tag: "ol";
99
+ dir?: "ltr" | "rtl";
100
+ children: ListItemNode<O>[];
101
+ }
102
+ : never;
103
+
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ export type HeadingNode<O extends RichTextOptions> = O["headings"] extends any[]
106
+ ? {
107
+ tag: O["headings"][number];
108
+ children: (string | SpanNode<O>)[];
109
+ // | AnchorNode<O>
110
+ }
111
+ : never;
112
+
113
+ // export type BlockQuoteNode<O extends RichTextOptions> =
114
+ // O["blockQuote"] extends true
115
+ // ? { tag: "blockquote"; children: [string] }
116
+ // : never;
117
+
118
+ type ImageSource = FileSource<{
119
+ width: number;
120
+ height: number;
121
+ sha256: string;
122
+ }>;
123
+
124
+ export type SourceNode<O extends RichTextOptions> = O["img"] extends true
125
+ ? ImageSource
126
+ : never;
127
+
128
+ export type AnyRichTextOptions = {
129
+ headings: ("h1" | "h2" | "h3" | "h4" | "h5" | "h6")[];
130
+ img: true;
131
+ ul: true;
132
+ ol: true;
133
+ lineThrough: true;
134
+ bold: true;
135
+ italic: true;
136
+ // blockQuote: true;
137
+ // fontFamily: Record<string, string[]>;
138
+ // fontSize: Record<string, string[]>;
68
139
  };
69
140
 
70
- const brand = Symbol("richtext");
71
- export type RichText<
72
- HT extends HeadingTags = HeadingTags,
73
- VN extends TextNode = TextNode
74
- > = RootNode<HT, VN> & {
75
- [brand]: "richtext";
76
- valPath: SourcePath;
141
+ export type RichTextSourceNode<O extends RichTextOptions> =
142
+ | Exclude<RichTextNode<O>, { tag: "img" }>
143
+ | ParagraphNode<O>
144
+ | ListItemNode<O>
145
+ | ImageNode<O>
146
+ | SourceNode<O>;
147
+
148
+ export type RichTextSource<O extends RichTextOptions> = {
149
+ [VAL_EXTENSION]: "richtext";
150
+ children: (
151
+ | HeadingNode<O>
152
+ | ParagraphNode<O>
153
+ | UnorderedListNode<O>
154
+ | OrderedListNode<O>
155
+ // | BlockQuoteNode<O>
156
+ | SourceNode<O>
157
+ )[];
77
158
  };
78
159
 
79
- export type RichTextSource = RichText & {
80
- readonly [VAL_EXTENSION]: "richtext";
160
+ export type RichTextNode<O extends RichTextOptions> =
161
+ | string
162
+ | HeadingNode<O>
163
+ | ParagraphNode<O>
164
+ | UnorderedListNode<O>
165
+ | OrderedListNode<O>
166
+ | ListItemNode<O>
167
+ | SpanNode<O>
168
+ // | BlockQuoteNode<O>
169
+ | ImageNode<O>;
170
+
171
+ export type RootNode<O extends RichTextOptions> =
172
+ | HeadingNode<O>
173
+ | ParagraphNode<O>
174
+ | UnorderedListNode<O>
175
+ | OrderedListNode<O>
176
+ // | BlockQuoteNode<O>
177
+ | ImageNode<O>;
178
+
179
+ // TODO: rename to RichTextSelector?
180
+ export type RichText<O extends RichTextOptions> = {
181
+ [VAL_EXTENSION]: "richtext";
182
+ children: RootNode<O>[];
81
183
  };
82
184
 
83
- export function richtext(
84
- data: RootNode<HeadingTags, TextNode> | string
85
- ): RichTextSource {
86
- if (typeof data === "string") {
87
- return {
88
- [VAL_EXTENSION]: "richtext",
89
- type: "root",
90
- children: [
185
+ function parseTokens<O extends RichTextOptions>(
186
+ tokens: marked.Token[]
187
+ ): RichTextSource<O>["children"] {
188
+ return tokens.flatMap((token) => {
189
+ if (token.type === "heading") {
190
+ return [
191
+ {
192
+ tag: `h${token.depth}`,
193
+ children: parseTokens(token.tokens ? token.tokens : []),
194
+ },
195
+ ];
196
+ }
197
+ if (token.type === "paragraph") {
198
+ return [
199
+ {
200
+ tag: "p",
201
+ children: parseTokens(token.tokens ? token.tokens : []),
202
+ },
203
+ ];
204
+ }
205
+ if (token.type === "strong") {
206
+ return [
207
+ {
208
+ tag: "span",
209
+ classes: ["bold"],
210
+ children: parseTokens(token.tokens ? token.tokens : []),
211
+ },
212
+ ];
213
+ }
214
+ if (token.type === "em") {
215
+ return [
91
216
  {
92
- type: "paragraph",
93
- children: [
94
- {
95
- type: "text",
96
- text: data,
97
- },
98
- ],
217
+ tag: "span",
218
+ classes: ["italic"],
219
+ children: parseTokens(token.tokens ? token.tokens : []),
99
220
  },
100
- ],
101
- } as RichTextSource;
221
+ ];
222
+ }
223
+ if (token.type === "del") {
224
+ return [
225
+ {
226
+ tag: "span",
227
+ classes: ["line-through"],
228
+ children: parseTokens(token.tokens ? token.tokens : []),
229
+ },
230
+ ];
231
+ }
232
+ if (token.type === "text") {
233
+ return [token.text];
234
+ }
235
+ if (token.type === "list") {
236
+ return [
237
+ {
238
+ tag: token.ordered ? "ol" : "ul",
239
+ children: parseTokens(token.items),
240
+ },
241
+ ];
242
+ }
243
+ if (token.type === "list_item") {
244
+ return [
245
+ {
246
+ tag: "li",
247
+ children: parseTokens(token.tokens ? token.tokens : []),
248
+ },
249
+ ];
250
+ }
251
+ if (token.type === "space") {
252
+ return [];
253
+ }
254
+
255
+ if (token.type === "code") {
256
+ return [
257
+ {
258
+ tag: "span",
259
+ classes: [],
260
+ children: [token.text],
261
+ },
262
+ ];
263
+ }
264
+ console.error(
265
+ `Could not parse markdown: unsupported token type: ${token.type}. Found: ${token.raw}`
266
+ );
267
+ return [token.raw];
268
+ });
269
+ }
270
+
271
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
272
+ function nodeToTag(node: any): any {
273
+ if (node[VAL_EXTENSION] === "file") {
274
+ return node;
102
275
  }
276
+ throw Error(`Unexpected node: ${JSON.stringify(node)}`);
277
+ }
278
+
279
+ function imgSrcToImgTag<O extends RichTextOptions>(
280
+ imageSrc: ImageSource
281
+ ): ImageNode<O> {
282
+ const converted = convertFileSource(imageSrc);
283
+ return {
284
+ tag: "img",
285
+ src: converted.url,
286
+ width: imageSrc.metadata?.width,
287
+ height: imageSrc.metadata?.height,
288
+ } as ImageNode<O>;
289
+ }
290
+
291
+ export function convertRichTextSource<O extends RichTextOptions>(
292
+ src: RichTextSource<O>
293
+ ): RichText<O> {
103
294
  return {
104
- ...data,
105
295
  [VAL_EXTENSION]: "richtext",
106
- } as RichTextSource;
296
+ children: src.children.map((source) => {
297
+ if (VAL_EXTENSION in source && source[VAL_EXTENSION] === "file") {
298
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
+ return imgSrcToImgTag(source as any);
300
+ }
301
+ return source;
302
+ }),
303
+ } as RichText<O>;
107
304
  }
108
305
 
109
- export function isRichText(obj: unknown): obj is RichTextSource {
110
- return (
111
- typeof obj === "object" &&
112
- obj !== null &&
113
- VAL_EXTENSION in obj &&
114
- obj[VAL_EXTENSION] === "richtext"
115
- );
306
+ export function richtext<
307
+ O extends RichTextOptions,
308
+ Nodes extends never | ImageSource
309
+ >(templateStrings: TemplateStringsArray, ...expr: Nodes[]): RichTextSource<O> {
310
+ return {
311
+ [VAL_EXTENSION]: "richtext",
312
+ children: templateStrings.flatMap((templateString, i) => {
313
+ const lex = marked.lexer(templateString, {
314
+ gfm: true,
315
+ });
316
+ if (expr[i]) {
317
+ return parseTokens(lex).concat(nodeToTag(expr[i]));
318
+ }
319
+ return parseTokens(lex);
320
+ }),
321
+ };
116
322
  }
package/tsconfig.json CHANGED
@@ -2,6 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "strict": true,
4
4
  "isolatedModules": true,
5
- "noEmit": true
5
+ "noEmit": true,
6
+ "skipLibCheck": true
6
7
  }
7
8
  }