shelving 1.51.0 → 1.51.4
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/markup/index.d.ts +0 -1
- package/markup/index.js +0 -1
- package/markup/render.d.ts +3 -2
- package/markup/render.js +9 -9
- package/markup/rules.d.ts +5 -5
- package/markup/rules.js +45 -43
- package/markup/types.d.ts +6 -25
- package/package.json +5 -5
- package/react/useQuery.d.ts +2 -2
- package/util/index.d.ts +1 -0
- package/util/index.js +1 -0
- package/util/jsx.d.ts +35 -0
- package/util/jsx.js +35 -0
- package/util/search.d.ts +11 -7
- package/util/search.js +15 -11
- package/markup/helpers.d.ts +0 -25
- package/markup/helpers.js +0 -54
package/markup/index.d.ts
CHANGED
package/markup/index.js
CHANGED
package/markup/render.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { JSXNode } from "../util/index.js";
|
|
2
|
+
import type { MarkupOptions } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* Parse a text string as Markdownish syntax and render it as a JSX node.
|
|
4
5
|
* - Syntax is not defined by this code, but by the rules supplied to it.
|
|
@@ -32,4 +33,4 @@ import type { MarkupOptions, MarkupNode } from "./types.js";
|
|
|
32
33
|
*
|
|
33
34
|
* @returns ReactNode, i.e. either a complete `ReactElement`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
34
35
|
*/
|
|
35
|
-
export declare function renderMarkup(content: string, options?: Partial<MarkupOptions>):
|
|
36
|
+
export declare function renderMarkup(content: string, options?: Partial<MarkupOptions>): JSXNode;
|
package/markup/render.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
-
import { sanitizeLines } from "../index.js";
|
|
2
|
+
import { sanitizeLines } from "../util/index.js";
|
|
3
3
|
import { MARKUP_RULES } from "./rules.js";
|
|
4
4
|
/** Convert a string into an array of React nodes using a set of rules. */
|
|
5
5
|
function renderString(content, options) {
|
|
@@ -15,12 +15,12 @@ function renderString(content, options) {
|
|
|
15
15
|
let matchedRule = undefined;
|
|
16
16
|
let matchedResult = undefined;
|
|
17
17
|
for (const rule of options.rules) {
|
|
18
|
-
const { priority = 0, match, contexts } = rule;
|
|
18
|
+
const { priority = 0, match, regexp, contexts } = rule;
|
|
19
19
|
// Only apply this rule if both:
|
|
20
20
|
// 1. The priority is equal or higher to the current priority.
|
|
21
21
|
// 2. The rule is allowed in the current context.
|
|
22
22
|
if (priority >= matchedPriority && contexts.includes(options.context)) {
|
|
23
|
-
const result = match(content, options);
|
|
23
|
+
const result = match ? match(content, options) : regexp ? content.match(regexp) : null;
|
|
24
24
|
// If this matched and has an index (it might not if it's a `/g` global RegExp, which would be a mistake).
|
|
25
25
|
if (result && typeof result.index === "number") {
|
|
26
26
|
const index = result.index;
|
|
@@ -46,13 +46,12 @@ function renderString(content, options) {
|
|
|
46
46
|
// Call the rule's `render()` function to generate the node.
|
|
47
47
|
const childOptions = { ...options, context: matchedRule.childContext };
|
|
48
48
|
const element = matchedRule.render(matchedResult, childOptions);
|
|
49
|
-
element.rule = matchedRule;
|
|
50
49
|
appendNode(nodes, renderNode(element, childOptions));
|
|
51
50
|
// Decrement the content.
|
|
52
51
|
content = content.slice(matchedIndex + matchedResult[0].length);
|
|
53
52
|
}
|
|
54
53
|
else {
|
|
55
|
-
// If nothing else matched add the rest of the string as a node
|
|
54
|
+
// If nothing else matched add the rest of the string as a node.
|
|
56
55
|
// Don't need to push the string through `renderNode()` because we already know it doesn't match any rules in the current context.
|
|
57
56
|
nodes.push(content);
|
|
58
57
|
content = "";
|
|
@@ -103,10 +102,11 @@ function renderNode(node, options) {
|
|
|
103
102
|
if (node instanceof Array)
|
|
104
103
|
return node.map(n => renderNode(n, options));
|
|
105
104
|
if (typeof node === "object" && node) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
return {
|
|
106
|
+
...node,
|
|
107
|
+
$$typeof: REACT_SECURITY_SYMBOL,
|
|
108
|
+
props: node.props.children ? { ...node.props, children: renderNode(node.props.children, options) } : node.props,
|
|
109
|
+
};
|
|
110
110
|
}
|
|
111
111
|
return node;
|
|
112
112
|
}
|
package/markup/rules.d.ts
CHANGED
|
@@ -38,19 +38,19 @@ export declare const FENCED_CODE_RULE: MarkupRule;
|
|
|
38
38
|
export declare const PARAGRAPH_RULE: MarkupRule;
|
|
39
39
|
/**
|
|
40
40
|
* Markdown-style link.
|
|
41
|
-
* - Link in standard Markdown format, e.g. `[http://google.com/maps
|
|
41
|
+
* - Link in standard Markdown format, e.g. `[Google Maps](http://google.com/maps)`
|
|
42
42
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
43
43
|
* - Does not need space before/after the link.
|
|
44
44
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
45
45
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
46
46
|
*/
|
|
47
|
-
export declare const
|
|
47
|
+
export declare const LINK_RULE: MarkupRule;
|
|
48
48
|
/**
|
|
49
|
-
* Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
|
|
49
|
+
* Autolinked URL starts with `http:` or `https:` or `mailto:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
50
50
|
* - If followed by space and then text in `()` round or `[]` square brackets that will be used as the title, e.g. `http://google.com/maps (Google Maps)` or `http://google.com/maps [Google Maps]` (this syntax is from Todoist and maybe other things too).
|
|
51
51
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
52
52
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
53
|
-
* - For security only schemes that appear in
|
|
53
|
+
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
54
54
|
*/
|
|
55
55
|
export declare const AUTOLINK_RULE: MarkupRule;
|
|
56
56
|
/**
|
|
@@ -69,7 +69,7 @@ export declare const CODE_RULE: MarkupRule;
|
|
|
69
69
|
* - Closing characters must exactly match opening characters.
|
|
70
70
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
71
71
|
*/
|
|
72
|
-
export declare const
|
|
72
|
+
export declare const STRONG_RULE: MarkupRule;
|
|
73
73
|
/**
|
|
74
74
|
* Inline emphasis.
|
|
75
75
|
* - Inline text wrapped in one or more `_` underscore symbols.
|
package/markup/rules.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { formatUrl, toURL, getLineRegExp,
|
|
1
|
+
import { formatUrl, toURL, getLineRegExp, getBlockRegExp, MATCH_LINE, MATCH_BLOCK, getWrapRegExp } from "../util/index.js";
|
|
2
2
|
// Regular expression partials (`\` slashes must be escaped as `\\`).
|
|
3
3
|
const BULLETS = "-*•+"; // Anything that can be a bullet (used for unordered lists and horizontal rules).
|
|
4
4
|
// Regular expressions.
|
|
5
|
-
const
|
|
6
|
-
// Regular expression makers.
|
|
7
|
-
const getMatcher = regexp => content => content.match(regexp);
|
|
5
|
+
const MATCH_INDENT = /^ {1,2}/gm;
|
|
8
6
|
/**
|
|
9
7
|
* Headings are single line only (don't allow multiline).
|
|
10
8
|
* - 1-6 hashes then 1+ spaces, then the title.
|
|
@@ -12,7 +10,7 @@ const getMatcher = regexp => content => content.match(regexp);
|
|
|
12
10
|
* - Markdown's underline syntax is not supported (for simplification).
|
|
13
11
|
*/
|
|
14
12
|
export const HEADING_RULE = {
|
|
15
|
-
|
|
13
|
+
regexp: getLineRegExp(`(#{1,6}) +(${MATCH_LINE.source})`),
|
|
16
14
|
render: ([, prefix = "", children = ""]) => ({ type: `h${prefix.length}`, key: null, props: { children } }),
|
|
17
15
|
contexts: ["block"],
|
|
18
16
|
childContext: "inline",
|
|
@@ -25,7 +23,7 @@ export const HEADING_RULE = {
|
|
|
25
23
|
* - Might have infinite number of spaces between the characters.
|
|
26
24
|
*/
|
|
27
25
|
export const HORIZONTAL_RULE = {
|
|
28
|
-
|
|
26
|
+
regexp: getLineRegExp(`([${BULLETS}])(?: *\\1){2,}`),
|
|
29
27
|
render: () => ({ type: "hr", key: null, props: {} }),
|
|
30
28
|
contexts: ["block"],
|
|
31
29
|
};
|
|
@@ -38,7 +36,7 @@ export const HORIZONTAL_RULE = {
|
|
|
38
36
|
*/
|
|
39
37
|
const UNORDERED = `[${BULLETS}] +`; // Anything that can be a bullet (used for unordered lists and horizontal rules).
|
|
40
38
|
export const UNORDERED_LIST_RULE = {
|
|
41
|
-
|
|
39
|
+
regexp: getBlockRegExp(`${UNORDERED}(${MATCH_BLOCK.source})`),
|
|
42
40
|
render: ([, list = ""]) => {
|
|
43
41
|
const children = list.split(SPLIT_UL_ITEMS).map(mapUnorderedItem);
|
|
44
42
|
return { type: "ul", key: null, props: { children } };
|
|
@@ -48,7 +46,7 @@ export const UNORDERED_LIST_RULE = {
|
|
|
48
46
|
};
|
|
49
47
|
const SPLIT_UL_ITEMS = new RegExp(`\\n+${UNORDERED}`, "g");
|
|
50
48
|
const mapUnorderedItem = (item, key) => {
|
|
51
|
-
const children = item.replace(
|
|
49
|
+
const children = item.replace(MATCH_INDENT, "");
|
|
52
50
|
return { type: "li", key, props: { children } };
|
|
53
51
|
};
|
|
54
52
|
/**
|
|
@@ -58,7 +56,7 @@ const mapUnorderedItem = (item, key) => {
|
|
|
58
56
|
*/
|
|
59
57
|
const ORDERED = "[0-9]+[.):] +"; // Number for a numbered list (e.g. `1.` or `2)` or `3:`)
|
|
60
58
|
export const ORDERED_LIST_RULE = {
|
|
61
|
-
|
|
59
|
+
regexp: getBlockRegExp(`(${ORDERED}${MATCH_BLOCK.source})`),
|
|
62
60
|
render: ([, list = ""]) => {
|
|
63
61
|
const children = list.split(SPLIT_OL_ITEMS).map(mapOrderedItem);
|
|
64
62
|
return { type: "ol", key: null, props: { children } };
|
|
@@ -73,7 +71,7 @@ const mapOrderedItem = (item, key) => {
|
|
|
73
71
|
const children = item
|
|
74
72
|
.slice(firstSpace + 1)
|
|
75
73
|
.trimStart()
|
|
76
|
-
.replace(
|
|
74
|
+
.replace(MATCH_INDENT, "");
|
|
77
75
|
return { type: "li", key, props: { value, children } };
|
|
78
76
|
};
|
|
79
77
|
/**
|
|
@@ -83,7 +81,7 @@ const mapOrderedItem = (item, key) => {
|
|
|
83
81
|
* - Quote indent symbol can be followed by zero or more spaces.
|
|
84
82
|
*/
|
|
85
83
|
export const BLOCKQUOTE_RULE = {
|
|
86
|
-
|
|
84
|
+
regexp: getLineRegExp(`(>${MATCH_LINE.source}(?:\\n>${MATCH_LINE.source})*)`),
|
|
87
85
|
render: ([, quote = ""]) => ({
|
|
88
86
|
type: "blockquote",
|
|
89
87
|
key: null,
|
|
@@ -102,7 +100,7 @@ const BLOCKQUOTE_LINES = /^>/gm;
|
|
|
102
100
|
*/
|
|
103
101
|
export const FENCED_CODE_RULE = {
|
|
104
102
|
// Matcher has its own end that only stops when it reaches a matching closing fence or the end of the string.
|
|
105
|
-
|
|
103
|
+
regexp: getBlockRegExp(`(\`{3,}|~{3,}) *(${MATCH_LINE.source})\\n(${MATCH_BLOCK.source})`, `\\n\\1\\n+|\\n\\1$|$`),
|
|
106
104
|
render: ([, , file, children]) => ({
|
|
107
105
|
type: "pre",
|
|
108
106
|
key: null,
|
|
@@ -121,7 +119,7 @@ export const FENCED_CODE_RULE = {
|
|
|
121
119
|
* - When ordering rules, paragraph should go after other "block" context elements (because it has a very generous capture).
|
|
122
120
|
*/
|
|
123
121
|
export const PARAGRAPH_RULE = {
|
|
124
|
-
|
|
122
|
+
regexp: getBlockRegExp(` *(${MATCH_BLOCK.source})`),
|
|
125
123
|
render: ([, children]) => ({ type: `p`, key: null, props: { children } }),
|
|
126
124
|
contexts: ["block"],
|
|
127
125
|
childContext: "inline",
|
|
@@ -129,61 +127,65 @@ export const PARAGRAPH_RULE = {
|
|
|
129
127
|
};
|
|
130
128
|
/**
|
|
131
129
|
* Markdown-style link.
|
|
132
|
-
* - Link in standard Markdown format, e.g. `[http://google.com/maps
|
|
130
|
+
* - Link in standard Markdown format, e.g. `[Google Maps](http://google.com/maps)`
|
|
133
131
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
134
132
|
* - Does not need space before/after the link.
|
|
135
133
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
136
134
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
137
135
|
*/
|
|
138
|
-
export const
|
|
136
|
+
export const LINK_RULE = {
|
|
137
|
+
regexp: /\[([^\]]*?)\]\(([^)]*?)\)/,
|
|
139
138
|
// Custom matcher to check the URL against the allowed schemes.
|
|
140
139
|
match: (content, { schemes, url: base }) => {
|
|
141
|
-
const matches = content.match(
|
|
142
|
-
if (matches
|
|
140
|
+
const matches = content.match(LINK_RULE.regexp);
|
|
141
|
+
if (matches) {
|
|
143
142
|
const [, title = "", href = ""] = matches;
|
|
144
143
|
const url = toURL(href, base);
|
|
145
144
|
if (url && url.protocol && schemes.includes(url.protocol)) {
|
|
146
145
|
matches[1] = title.trim();
|
|
147
|
-
matches[2] = url.href;
|
|
146
|
+
matches[2] = url.href;
|
|
148
147
|
return matches;
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
},
|
|
152
|
-
render: ([, title
|
|
153
|
-
type:
|
|
151
|
+
render: ([, title, href = ""], { rel }) => ({
|
|
152
|
+
type: "a",
|
|
154
153
|
key: null,
|
|
155
|
-
props: { children: title || formatUrl(href), href
|
|
154
|
+
props: { children: title || formatUrl(href), href, rel },
|
|
156
155
|
}),
|
|
157
156
|
contexts: ["inline", "list"],
|
|
158
157
|
childContext: "link",
|
|
159
158
|
};
|
|
160
|
-
const MATCH_LINK = /\[([^\]]*?)\]\(([^)]*?)\)/;
|
|
161
159
|
/**
|
|
162
|
-
* Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
|
|
160
|
+
* Autolinked URL starts with `http:` or `https:` or `mailto:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
163
161
|
* - If followed by space and then text in `()` round or `[]` square brackets that will be used as the title, e.g. `http://google.com/maps (Google Maps)` or `http://google.com/maps [Google Maps]` (this syntax is from Todoist and maybe other things too).
|
|
164
162
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
165
163
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
166
|
-
* - For security only schemes that appear in
|
|
164
|
+
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
167
165
|
*/
|
|
168
166
|
export const AUTOLINK_RULE = {
|
|
167
|
+
regexp: /([a-z]+:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/,
|
|
169
168
|
// Custom matcher to check the URL against the allowed schemes.
|
|
170
169
|
match: (content, { schemes, url: base }) => {
|
|
171
|
-
const matches = content.match(
|
|
170
|
+
const matches = content.match(AUTOLINK_RULE.regexp);
|
|
172
171
|
if (matches && typeof matches.index === "number") {
|
|
173
|
-
const [, href = "",
|
|
172
|
+
const [, href = "", roundTitle = "", squareTitle = ""] = matches;
|
|
174
173
|
const url = toURL(href, base);
|
|
175
174
|
if (url && url.protocol && schemes.includes(url.protocol)) {
|
|
176
|
-
matches[1] =
|
|
177
|
-
matches[2] =
|
|
175
|
+
matches[1] = url.href;
|
|
176
|
+
matches[2] = (roundTitle || squareTitle).trim();
|
|
178
177
|
return matches;
|
|
179
178
|
}
|
|
180
179
|
}
|
|
181
180
|
},
|
|
182
|
-
render:
|
|
181
|
+
render: ([, href = "", title], { rel }) => ({
|
|
182
|
+
type: "a",
|
|
183
|
+
key: null,
|
|
184
|
+
props: { children: title || formatUrl(href), href, rel },
|
|
185
|
+
}),
|
|
183
186
|
contexts: ["inline", "list"],
|
|
184
187
|
childContext: "link",
|
|
185
188
|
};
|
|
186
|
-
const MATCH_AUTOLINK = /([a-z][a-z0-9-]*[a-z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/i;
|
|
187
189
|
/**
|
|
188
190
|
* Inline code.
|
|
189
191
|
* - Text surrounded by one or more "`" backtick tilde characters.
|
|
@@ -192,7 +194,7 @@ const MATCH_AUTOLINK = /([a-z][a-z0-9-]*[a-z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\
|
|
|
192
194
|
* - Same as Markdown syntax.
|
|
193
195
|
*/
|
|
194
196
|
export const CODE_RULE = {
|
|
195
|
-
|
|
197
|
+
regexp: getWrapRegExp("`+", MATCH_BLOCK.source),
|
|
196
198
|
render: ([, , children]) => ({ type: "code", key: null, props: { children } }),
|
|
197
199
|
contexts: ["inline", "list"],
|
|
198
200
|
priority: 10, // Higher priority than other inlines so it matches first before e.g. `strong` or `em` (from CommonMark spec: "Code span backticks have higher precedence than any other inline constructs except HTML tags and autolinks.")
|
|
@@ -205,8 +207,8 @@ export const CODE_RULE = {
|
|
|
205
207
|
* - Closing characters must exactly match opening characters.
|
|
206
208
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
207
209
|
*/
|
|
208
|
-
export const
|
|
209
|
-
|
|
210
|
+
export const STRONG_RULE = {
|
|
211
|
+
regexp: getWrapRegExp("\\*+"),
|
|
210
212
|
render: ([, , children]) => ({ type: "strong", key: null, props: { children } }),
|
|
211
213
|
contexts: ["inline", "list", "link"],
|
|
212
214
|
childContext: "inline",
|
|
@@ -220,7 +222,7 @@ export const STRONG_MARKUP = {
|
|
|
220
222
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
221
223
|
*/
|
|
222
224
|
export const EMPHASIS_RULE = {
|
|
223
|
-
|
|
225
|
+
regexp: getWrapRegExp("_+"),
|
|
224
226
|
render: ([, , children]) => ({ type: "em", key: null, props: { children } }),
|
|
225
227
|
contexts: ["inline", "list", "link"],
|
|
226
228
|
childContext: "inline",
|
|
@@ -234,7 +236,7 @@ export const EMPHASIS_RULE = {
|
|
|
234
236
|
* - Markdown doesn't have this.
|
|
235
237
|
*/
|
|
236
238
|
export const INSERT_RULE = {
|
|
237
|
-
|
|
239
|
+
regexp: getWrapRegExp("\\+\\++"),
|
|
238
240
|
render: ([, , children]) => ({ type: "ins", key: null, props: { children } }),
|
|
239
241
|
contexts: ["inline", "list", "link"],
|
|
240
242
|
childContext: "inline",
|
|
@@ -248,7 +250,7 @@ export const INSERT_RULE = {
|
|
|
248
250
|
* - Markdown doesn't have this.
|
|
249
251
|
*/
|
|
250
252
|
export const DELETE_RULE = {
|
|
251
|
-
|
|
253
|
+
regexp: getWrapRegExp("--+|~~+"),
|
|
252
254
|
render: ([, , children]) => ({ type: "del", key: null, props: { children } }),
|
|
253
255
|
contexts: ["inline", "list", "link"],
|
|
254
256
|
childContext: "inline",
|
|
@@ -262,7 +264,7 @@ export const DELETE_RULE = {
|
|
|
262
264
|
* - This works better with textareas that wrap text (since manually breaking up long lines is no longer necessary).
|
|
263
265
|
*/
|
|
264
266
|
export const LINEBREAK_RULE = {
|
|
265
|
-
|
|
267
|
+
regexp: /\n/,
|
|
266
268
|
render: () => ({ type: "br", key: null, props: {} }),
|
|
267
269
|
contexts: ["inline", "list", "link"],
|
|
268
270
|
childContext: "inline",
|
|
@@ -284,10 +286,10 @@ export const MARKUP_RULES = [
|
|
|
284
286
|
BLOCKQUOTE_RULE,
|
|
285
287
|
FENCED_CODE_RULE,
|
|
286
288
|
PARAGRAPH_RULE,
|
|
287
|
-
|
|
289
|
+
LINK_RULE,
|
|
288
290
|
AUTOLINK_RULE,
|
|
289
291
|
CODE_RULE,
|
|
290
|
-
|
|
292
|
+
STRONG_RULE,
|
|
291
293
|
EMPHASIS_RULE,
|
|
292
294
|
INSERT_RULE,
|
|
293
295
|
DELETE_RULE,
|
|
@@ -305,10 +307,10 @@ export const MARKUP_RULES_BLOCK = [
|
|
|
305
307
|
];
|
|
306
308
|
/** Subset of markup rules that work in an inline context. */
|
|
307
309
|
export const MARKUP_RULES_INLINE = [
|
|
308
|
-
|
|
310
|
+
LINK_RULE,
|
|
309
311
|
AUTOLINK_RULE,
|
|
310
312
|
CODE_RULE,
|
|
311
|
-
|
|
313
|
+
STRONG_RULE,
|
|
312
314
|
EMPHASIS_RULE,
|
|
313
315
|
INSERT_RULE,
|
|
314
316
|
DELETE_RULE,
|
|
@@ -319,10 +321,10 @@ export const MARKUP_RULES_SHORTFORM = [
|
|
|
319
321
|
UNORDERED_LIST_RULE,
|
|
320
322
|
ORDERED_LIST_RULE,
|
|
321
323
|
PARAGRAPH_RULE,
|
|
322
|
-
|
|
324
|
+
LINK_RULE,
|
|
323
325
|
AUTOLINK_RULE,
|
|
324
326
|
CODE_RULE,
|
|
325
|
-
|
|
327
|
+
STRONG_RULE,
|
|
326
328
|
EMPHASIS_RULE,
|
|
327
329
|
INSERT_RULE,
|
|
328
330
|
DELETE_RULE,
|
package/markup/types.d.ts
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
* JSX element.
|
|
3
|
-
* - Compatible with but _slightly_ more flexible than `React.ReactElement`
|
|
4
|
-
*/
|
|
5
|
-
export declare type MarkupElement = {
|
|
6
|
-
type: string;
|
|
7
|
-
key: string | number | null;
|
|
8
|
-
props: MarkupElementProps;
|
|
9
|
-
$$typeof?: symbol;
|
|
10
|
-
/** This specifies the rule that created this element. */
|
|
11
|
-
rule?: MarkupRule;
|
|
12
|
-
};
|
|
13
|
-
export declare type MarkupElementProps = {
|
|
14
|
-
[prop: string]: unknown;
|
|
15
|
-
children?: MarkupNode;
|
|
16
|
-
};
|
|
17
|
-
/** JSX node (compatible with but slightly more flexible than `React.ReactNode`) */
|
|
18
|
-
export declare type MarkupNode = undefined | null | string | MarkupElement | MarkupNode[];
|
|
1
|
+
import { JSXElement } from "../util/index.js";
|
|
19
2
|
/** A single markup parsing rule. */
|
|
20
3
|
export declare type MarkupRule = {
|
|
21
|
-
/** RegExp
|
|
22
|
-
readonly
|
|
4
|
+
/** RegExp for matching this rule. */
|
|
5
|
+
readonly regexp: RegExp;
|
|
6
|
+
/** Custom matching function for this rule (overrides `regexp` if present. */
|
|
7
|
+
readonly match?: (content: string, options: MarkupOptions) => RegExpMatchArray | null | undefined | void;
|
|
23
8
|
/**
|
|
24
9
|
* Return a corresponding JSX element for the match.
|
|
25
10
|
* @param ...matches The matches from `match` RegExp (without the `0` zeroeth "whole match").
|
|
@@ -27,7 +12,7 @@ export declare type MarkupRule = {
|
|
|
27
12
|
* - The `key` property is not required (will be set automatically).
|
|
28
13
|
* - e.g. `{ type: "a", props: { href: "/example.html", className: "strong", children: "Children *can* include _syntax_" } }`
|
|
29
14
|
*/
|
|
30
|
-
readonly render:
|
|
15
|
+
readonly render: (matches: RegExpMatchArray, options: MarkupOptions) => JSXElement;
|
|
31
16
|
/** Apply the rule only when in certain contexts, e.g. `["block", "inline", "list"]` */
|
|
32
17
|
readonly contexts: string[];
|
|
33
18
|
/** Context any string children returned from `render()` should be rendered with. */
|
|
@@ -42,8 +27,6 @@ export declare type MarkupRule = {
|
|
|
42
27
|
*/
|
|
43
28
|
readonly priority?: number;
|
|
44
29
|
};
|
|
45
|
-
export declare type MarkupRuleMatcher = (content: string, options: MarkupOptions) => RegExpMatchArray | null | undefined | void;
|
|
46
|
-
export declare type MarkupRuleRenderer = (matches: RegExpMatchArray, options: MarkupOptions) => MarkupElement;
|
|
47
30
|
/** A set of parse rules (as an object or array). */
|
|
48
31
|
export declare type MarkupRules = Iterable<MarkupRule>;
|
|
49
32
|
/** The current parsing options (represents the current state of the parsing). */
|
|
@@ -59,5 +42,3 @@ export declare type MarkupOptions = {
|
|
|
59
42
|
/** Valid URL schemes/protocols for links (including trailing commas), defaults to `[`http:`, `https:`]` */
|
|
60
43
|
readonly schemes: string[];
|
|
61
44
|
};
|
|
62
|
-
/** The create element function. */
|
|
63
|
-
export declare type MarkupElementCreator = (type: string, props?: MarkupElementProps | null) => MarkupElement;
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"state-management",
|
|
12
12
|
"query-builder"
|
|
13
13
|
],
|
|
14
|
-
"version": "1.51.
|
|
14
|
+
"version": "1.51.4",
|
|
15
15
|
"repository": "https://github.com/dhoulb/shelving",
|
|
16
16
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
17
17
|
"license": "0BSD",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"@types/jest": "^27.4.0",
|
|
64
64
|
"@types/react": "^17.0.39",
|
|
65
65
|
"@types/react-dom": "^17.0.11",
|
|
66
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
67
|
-
"@typescript-eslint/parser": "^5.
|
|
68
|
-
"eslint": "^8.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
|
67
|
+
"@typescript-eslint/parser": "^5.11.0",
|
|
68
|
+
"eslint": "^8.9.0",
|
|
69
69
|
"eslint-config-prettier": "^8.3.0",
|
|
70
70
|
"eslint-plugin-import": "^2.25.4",
|
|
71
71
|
"eslint-plugin-prettier": "^4.0.0",
|
|
72
72
|
"firebase": "^9.6.6",
|
|
73
|
-
"jest": "^27.5.
|
|
73
|
+
"jest": "^27.5.1",
|
|
74
74
|
"jest-ts-webcompat-resolver": "^1.0.0",
|
|
75
75
|
"prettier": "^2.5.1",
|
|
76
76
|
"react": "^17.0.2",
|
package/react/useQuery.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export declare function useAsyncQuery<T extends Data>(ref: DatabaseQuery<T> | un
|
|
|
34
34
|
export declare function useQuery<T extends Data>(ref: DatabaseQuery<T>, maxAge?: number | true): Results<T>;
|
|
35
35
|
export declare function useQuery<T extends Data>(ref: DatabaseQuery<T> | undefined, maxAge?: number | true): Results<T> | undefined;
|
|
36
36
|
/** Use the first result of a query or `undefined` if the query has no matching results (or a promise indicating the result is loading). */
|
|
37
|
-
export declare function useAsyncQueryResult<T extends Data>(ref: DatabaseQuery<T>, maxAge?: number | true): DocumentResult<T> |
|
|
38
|
-
export declare function useAsyncQueryResult<T extends Data>(ref: DatabaseQuery<T> | undefined, maxAge?: number | true): DocumentResult<T> |
|
|
37
|
+
export declare function useAsyncQueryResult<T extends Data>(ref: DatabaseQuery<T>, maxAge?: number | true): DocumentResult<T> | PromiseLike<DocumentResult<T>>;
|
|
38
|
+
export declare function useAsyncQueryResult<T extends Data>(ref: DatabaseQuery<T> | undefined, maxAge?: number | true): DocumentResult<T> | PromiseLike<DocumentResult<T>> | undefined;
|
|
39
39
|
/** Use the first result of a query or `undefined` if the query has no matching results */
|
|
40
40
|
export declare function useQueryResult<T extends Data>(ref: DatabaseQuery<T>, maxAge?: number | true): DocumentResult<T>;
|
|
41
41
|
export declare function useQueryResult<T extends Data>(ref: DatabaseQuery<T> | undefined, maxAge?: number | true): DocumentResult<T> | undefined;
|
package/util/index.d.ts
CHANGED
package/util/index.js
CHANGED
package/util/jsx.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Data } from "./data.js";
|
|
2
|
+
/** Function that creates a JSX element. */
|
|
3
|
+
export declare type JSXElementCreator<P extends Data = Data> = (props: P) => JSXElement | null;
|
|
4
|
+
/** Set of valid props for a JSX element. */
|
|
5
|
+
export declare type JSXProps = {
|
|
6
|
+
readonly [key: string]: unknown;
|
|
7
|
+
readonly children?: JSXNode;
|
|
8
|
+
};
|
|
9
|
+
/** JSX element (similar to `React.ReactElement`) */
|
|
10
|
+
export declare type JSXElement<P extends JSXProps = JSXProps, T extends string | JSXElementCreator<P> = string | JSXElementCreator<P>> = {
|
|
11
|
+
type: T;
|
|
12
|
+
props: P;
|
|
13
|
+
key: string | number | null;
|
|
14
|
+
$$typeof?: symbol;
|
|
15
|
+
};
|
|
16
|
+
/** JSX node (similar to `React.ReactNode`) */
|
|
17
|
+
export declare type JSXNode = undefined | null | string | JSXElement | JSXNode[];
|
|
18
|
+
/** Is an unknown value a JSX element? */
|
|
19
|
+
export declare const isElement: <T extends JSXNode>(el: unknown) => el is T;
|
|
20
|
+
/** Is an unknown value a JSX node? */
|
|
21
|
+
export declare const isNode: <T extends JSXNode>(node: unknown) => node is T;
|
|
22
|
+
/**
|
|
23
|
+
* Take a Markup JSX node and strip all tags from it to produce a plain text string.
|
|
24
|
+
*
|
|
25
|
+
* @param node A JsxNode, e.g. either a JSX element, a plain string, or null/undefined (or an array of those things).
|
|
26
|
+
* @returns The combined string made from the JSX node.
|
|
27
|
+
*
|
|
28
|
+
* @example `- Item with *strong*\n- Item with _em_` becomes `Item with strong Item with em`
|
|
29
|
+
*/
|
|
30
|
+
export declare function nodeToText(node?: JSXNode): string;
|
|
31
|
+
/**
|
|
32
|
+
* Iterate through all elements in a node.
|
|
33
|
+
* - This is useful if you, e.g. want to apply a `className` to all `<h1>` elements, or make a list of all URLs found in a Node.
|
|
34
|
+
*/
|
|
35
|
+
export declare function yieldElements(node: JSXNode): Generator<JSXElement, void>;
|
package/util/jsx.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Is an unknown value a JSX element? */
|
|
2
|
+
export const isElement = (el) => typeof el === "object" && el !== null && "type" in el;
|
|
3
|
+
/** Is an unknown value a JSX node? */
|
|
4
|
+
export const isNode = (node) => node === null || typeof node === "string" || isElement(node) || node instanceof Array;
|
|
5
|
+
/**
|
|
6
|
+
* Take a Markup JSX node and strip all tags from it to produce a plain text string.
|
|
7
|
+
*
|
|
8
|
+
* @param node A JsxNode, e.g. either a JSX element, a plain string, or null/undefined (or an array of those things).
|
|
9
|
+
* @returns The combined string made from the JSX node.
|
|
10
|
+
*
|
|
11
|
+
* @example `- Item with *strong*\n- Item with _em_` becomes `Item with strong Item with em`
|
|
12
|
+
*/
|
|
13
|
+
export function nodeToText(node) {
|
|
14
|
+
if (typeof node === "string")
|
|
15
|
+
return node;
|
|
16
|
+
if (node instanceof Array)
|
|
17
|
+
return node.map(nodeToText).join(" ");
|
|
18
|
+
if (typeof node === "object" && node)
|
|
19
|
+
return nodeToText(node.props.children);
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Iterate through all elements in a node.
|
|
24
|
+
* - This is useful if you, e.g. want to apply a `className` to all `<h1>` elements, or make a list of all URLs found in a Node.
|
|
25
|
+
*/
|
|
26
|
+
export function* yieldElements(node) {
|
|
27
|
+
if (node instanceof Array)
|
|
28
|
+
for (const n of node)
|
|
29
|
+
yield* yieldElements(n);
|
|
30
|
+
else if (typeof node === "object" && node) {
|
|
31
|
+
yield node;
|
|
32
|
+
if (isNode(node.props.children))
|
|
33
|
+
yield* yieldElements(node.props.children);
|
|
34
|
+
}
|
|
35
|
+
}
|
package/util/search.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { ImmutableArray } from "./array.js";
|
|
2
2
|
import { Matchable } from "./filter.js";
|
|
3
|
+
export declare const MATCH_SPACE: RegExp;
|
|
4
|
+
export declare const MATCH_SPACES: RegExp;
|
|
5
|
+
export declare const MATCH_LINEBREAK: RegExp;
|
|
6
|
+
export declare const MATCH_LINEBREAKS: RegExp;
|
|
7
|
+
export declare const MATCH_LINE: RegExp;
|
|
8
|
+
export declare const MATCH_LINE_START: RegExp;
|
|
9
|
+
export declare const MATCH_LINE_END: RegExp;
|
|
10
|
+
export declare const MATCH_BLOCK: RegExp;
|
|
11
|
+
export declare const MATCH_BLOCK_START: RegExp;
|
|
12
|
+
export declare const MATCH_BLOCK_END: RegExp;
|
|
13
|
+
export declare const MATCH_WORDS: RegExp;
|
|
3
14
|
/**
|
|
4
15
|
* Convert a string to a regular expression that matches that string.
|
|
5
16
|
*
|
|
@@ -9,13 +20,6 @@ import { Matchable } from "./filter.js";
|
|
|
9
20
|
export declare const toRegExp: (str: string, flags?: string) => RegExp;
|
|
10
21
|
/** Escape special characters in a string regular expression. */
|
|
11
22
|
export declare const escapeRegExp: (str: string) => string;
|
|
12
|
-
export declare const MATCH_LINE = "[^\\n]*";
|
|
13
|
-
export declare const MATCH_LINE_START = "^\\n*|\\n+";
|
|
14
|
-
export declare const MATCH_LINE_END = "\\n+|$";
|
|
15
|
-
export declare const MATCH_BLOCK = "[\\s\\S]*?";
|
|
16
|
-
export declare const MATCH_BLOCK_START = "^\\n*|\\n+";
|
|
17
|
-
export declare const MATCH_BLOCK_END = "\\n*$|\\n\\n+";
|
|
18
|
-
export declare const MATCH_WORDS = "\\S(?:[\\s\\S]*?\\S)?";
|
|
19
23
|
/** Create regular expression that matches a block of content. */
|
|
20
24
|
export declare const getBlockRegExp: (middle?: string, end?: string, start?: string) => RegExp;
|
|
21
25
|
/** Create regular expression that matches a line of content. */
|
package/util/search.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { toWords, normalizeString } from "./string.js";
|
|
2
|
+
// Regular expressions.
|
|
3
|
+
export const MATCH_SPACE = /\s+/; // Match the first run of one or more space characters.
|
|
4
|
+
export const MATCH_SPACES = /\s+/g; // Match all runs of one or more space characters.
|
|
5
|
+
export const MATCH_LINEBREAK = /\n+/; // Match the first run of one or more linebreak characters.
|
|
6
|
+
export const MATCH_LINEBREAKS = /\n+/; // Match all runs of one or more linebreak characters.
|
|
7
|
+
export const MATCH_LINE = /[^\n]*/; // Match line of content (anything that's not a newline).
|
|
8
|
+
export const MATCH_LINE_START = /^\n*|\n+/; // Starts at start of line (one or more linebreak or start of string).
|
|
9
|
+
export const MATCH_LINE_END = /\n+|$/; // Ends at end of line (one or more linebreak or end of string).
|
|
10
|
+
export const MATCH_BLOCK = /[\s\S]*?/; // Match block of content (including newlines so don't be greedy).
|
|
11
|
+
export const MATCH_BLOCK_START = /^\n*|\n+/; // Starts at start of a block (one or more linebreak or start of string).
|
|
12
|
+
export const MATCH_BLOCK_END = /\n*$|\n\n+/; // End of a block (two or more linebreaks or end of string).
|
|
13
|
+
export const MATCH_WORDS = /\S(?:[\s\S]*?\S)?/; // Run of text that starts and ends with non-space characters (possibly multi-line).
|
|
2
14
|
/**
|
|
3
15
|
* Convert a string to a regular expression that matches that string.
|
|
4
16
|
*
|
|
@@ -9,20 +21,12 @@ export const toRegExp = (str, flags = "") => new RegExp(escapeRegExp(str), flags
|
|
|
9
21
|
/** Escape special characters in a string regular expression. */
|
|
10
22
|
export const escapeRegExp = (str) => str.replace(REPLACE_ESCAPED, "\\$&");
|
|
11
23
|
const REPLACE_ESCAPED = /[-[\]/{}()*+?.\\^$|]/g;
|
|
12
|
-
// Regular expression partials (`\` slashes must be escaped as `\\`).
|
|
13
|
-
export const MATCH_LINE = "[^\\n]*"; // Match line of content (anything that's not a newline).
|
|
14
|
-
export const MATCH_LINE_START = "^\\n*|\\n+"; // Starts at start of line (one or more linebreak or start of string).
|
|
15
|
-
export const MATCH_LINE_END = "\\n+|$"; // Ends at end of line (one or more linebreak or end of string).
|
|
16
|
-
export const MATCH_BLOCK = "[\\s\\S]*?"; // Match block of content (including newlines so don't be greedy).
|
|
17
|
-
export const MATCH_BLOCK_START = "^\\n*|\\n+"; // Starts at start of a block (one or more linebreak or start of string).
|
|
18
|
-
export const MATCH_BLOCK_END = "\\n*$|\\n\\n+"; // End of a block (two or more linebreaks or end of string).
|
|
19
|
-
export const MATCH_WORDS = `\\S(?:[\\s\\S]*?\\S)?`; // Run of text that starts and ends with non-space characters (possibly multi-line).
|
|
20
24
|
/** Create regular expression that matches a block of content. */
|
|
21
|
-
export const getBlockRegExp = (middle = MATCH_BLOCK, end = MATCH_BLOCK_END, start = MATCH_BLOCK_START) => new RegExp(`(?:${start})${middle}(?:${end})`);
|
|
25
|
+
export const getBlockRegExp = (middle = MATCH_BLOCK.source, end = MATCH_BLOCK_END.source, start = MATCH_BLOCK_START.source) => new RegExp(`(?:${start})${middle}(?:${end})`);
|
|
22
26
|
/** Create regular expression that matches a line of content. */
|
|
23
|
-
export const getLineRegExp = (middle = MATCH_LINE, end = MATCH_LINE_END, start = MATCH_LINE_START) => new RegExp(`(?:${start})${middle}(?:${end})`);
|
|
27
|
+
export const getLineRegExp = (middle = MATCH_LINE.source, end = MATCH_LINE_END.source, start = MATCH_LINE_START.source) => new RegExp(`(?:${start})${middle}(?:${end})`);
|
|
24
28
|
/** Create regular expression that matches piece of text wrapped by a set of characters. */
|
|
25
|
-
export const getWrapRegExp = (chars, middle = MATCH_WORDS) => new RegExp(`(${chars})(${middle})\\1`);
|
|
29
|
+
export const getWrapRegExp = (chars, middle = MATCH_WORDS.source) => new RegExp(`(${chars})(${middle})\\1`);
|
|
26
30
|
/**
|
|
27
31
|
* Convert a string query to the corresponding set of case-insensitive regular expressions.
|
|
28
32
|
* - Splies the query into words (respecting "quoted phrases").
|
package/markup/helpers.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { MarkupElement, MarkupNode } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Take a Markup JSX node and strip all tags from it to produce a plain text string.
|
|
4
|
-
*
|
|
5
|
-
* @param node A JsxNode, e.g. either a JSX element, a plain string, or null/undefined (or an array of those things).
|
|
6
|
-
* @returns The combined string made from the JSX node.
|
|
7
|
-
*
|
|
8
|
-
* @example `- Item with *strong*\n- Item with _em_` becomes `Item with strong Item with em`
|
|
9
|
-
*/
|
|
10
|
-
export declare function nodeToText(node: MarkupNode): string;
|
|
11
|
-
/**
|
|
12
|
-
* Take a Markup JSX node and convert it to an HTML string.
|
|
13
|
-
*
|
|
14
|
-
* @param node Any `MarkupNode`, i.e. a string, `MarkupElement`, or array of those.
|
|
15
|
-
* - Any props in the node will be rendered if they are strings or numbers or `true`. All other props are skipped.
|
|
16
|
-
* @returns The HTML generated from the node.
|
|
17
|
-
*
|
|
18
|
-
* @example `- Item with *strong*\n- Item with _em_` becomes `<ul><li>Item with <strong>strong</strong></li><li>Item with <em>em</em></ul>`
|
|
19
|
-
*/
|
|
20
|
-
export declare function nodeToHtml(node: MarkupNode): string;
|
|
21
|
-
/**
|
|
22
|
-
* Iterate through all elements in a node.
|
|
23
|
-
* - This is useful if you, e.g. want to apply a `className` to all `<h1>` elements, or make a list of all URLs found in a Node.
|
|
24
|
-
*/
|
|
25
|
-
export declare function yieldElements(node: MarkupNode): Generator<MarkupElement, void>;
|
package/markup/helpers.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { serialise } from "../util/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Take a Markup JSX node and strip all tags from it to produce a plain text string.
|
|
4
|
-
*
|
|
5
|
-
* @param node A JsxNode, e.g. either a JSX element, a plain string, or null/undefined (or an array of those things).
|
|
6
|
-
* @returns The combined string made from the JSX node.
|
|
7
|
-
*
|
|
8
|
-
* @example `- Item with *strong*\n- Item with _em_` becomes `Item with strong Item with em`
|
|
9
|
-
*/
|
|
10
|
-
export function nodeToText(node) {
|
|
11
|
-
if (typeof node === "string")
|
|
12
|
-
return node;
|
|
13
|
-
if (node instanceof Array)
|
|
14
|
-
return node.map(nodeToText).join(" ");
|
|
15
|
-
if (typeof node === "object" && node)
|
|
16
|
-
return nodeToText(node.props.children);
|
|
17
|
-
return "";
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Take a Markup JSX node and convert it to an HTML string.
|
|
21
|
-
*
|
|
22
|
-
* @param node Any `MarkupNode`, i.e. a string, `MarkupElement`, or array of those.
|
|
23
|
-
* - Any props in the node will be rendered if they are strings or numbers or `true`. All other props are skipped.
|
|
24
|
-
* @returns The HTML generated from the node.
|
|
25
|
-
*
|
|
26
|
-
* @example `- Item with *strong*\n- Item with _em_` becomes `<ul><li>Item with <strong>strong</strong></li><li>Item with <em>em</em></ul>`
|
|
27
|
-
*/
|
|
28
|
-
export function nodeToHtml(node) {
|
|
29
|
-
if (typeof node === "string")
|
|
30
|
-
return node;
|
|
31
|
-
if (node instanceof Array)
|
|
32
|
-
return node.map(nodeToHtml).join("");
|
|
33
|
-
if (typeof node === "object" && node) {
|
|
34
|
-
const { type, props: { children, ...props }, } = node;
|
|
35
|
-
const strings = Object.entries(props).map(propToString).filter(Boolean);
|
|
36
|
-
return `<${type}${strings.length ? ` ${strings.join(" ")}` : ""}>${nodeToHtml(children)}</${type}>`;
|
|
37
|
-
}
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
40
|
-
const propToString = ([key, value]) => (value === true ? key : typeof value === "number" && Number.isFinite(value) ? `${key}="${value.toString()}"` : typeof value === "string" ? `${key}=${serialise(value)}` : "");
|
|
41
|
-
/**
|
|
42
|
-
* Iterate through all elements in a node.
|
|
43
|
-
* - This is useful if you, e.g. want to apply a `className` to all `<h1>` elements, or make a list of all URLs found in a Node.
|
|
44
|
-
*/
|
|
45
|
-
export function* yieldElements(node) {
|
|
46
|
-
if (node instanceof Array)
|
|
47
|
-
for (const n of node)
|
|
48
|
-
yield* yieldElements(n);
|
|
49
|
-
else if (typeof node === "object" && node) {
|
|
50
|
-
yield node;
|
|
51
|
-
if (node.props.children)
|
|
52
|
-
yield* yieldElements(node.props.children);
|
|
53
|
-
}
|
|
54
|
-
}
|