shelving 1.50.1 → 1.51.2

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 CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from "./types.js";
2
- export * from "./helpers.js";
3
2
  export * from "./rules.js";
4
3
  export * from "./render.js";
package/markup/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from "./types.js";
2
- export * from "./helpers.js";
3
2
  export * from "./rules.js";
4
3
  export * from "./render.js";
@@ -1,4 +1,5 @@
1
- import type { MarkupOptions, MarkupNode } from "./types.js";
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,9 +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>): MarkupNode;
36
- /**
37
- * Parse a text string as user-generated markup.
38
- * - Like `renderMarkup()` but only enables a subset of rules and applies `rel="nofollow ugc"` to all links.
39
- */
40
- export declare function renderUgcMarkup(content: string, options?: Partial<MarkupOptions>): MarkupNode;
36
+ export declare function renderMarkup(content: string, options?: Partial<MarkupOptions>): JSXNode;
package/markup/render.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { sanitizeLines } from "../index.js";
3
- import { MARKUP_RULES, MARKUP_RULES_UGC } from "./rules.js";
2
+ import { sanitizeLines } from "../util/index.js";
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) {
6
6
  // If there's no context return the unmodified string.
@@ -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 (presumably it doesn't have any punctuation).
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
- node.$$typeof = REACT_SECURITY_SYMBOL; // Inject React security type. See https://github.com/facebook/react/pull/4832
107
- if (node.props.children)
108
- node.props.children = renderNode(node.props.children, options);
109
- return node;
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
  }
@@ -154,15 +154,3 @@ const defaults = {
154
154
  rel: undefined,
155
155
  schemes: ["http:", "https:"],
156
156
  };
157
- /**
158
- * Parse a text string as user-generated markup.
159
- * - Like `renderMarkup()` but only enables a subset of rules and applies `rel="nofollow ugc"` to all links.
160
- */
161
- export function renderUgcMarkup(content, options) {
162
- return renderString(sanitizeLines(content), { ...defaultsUgc, ...options });
163
- }
164
- const defaultsUgc = {
165
- ...defaults,
166
- rules: MARKUP_RULES_UGC,
167
- rel: "nofollow ugc",
168
- };
package/markup/rules.d.ts CHANGED
@@ -1,4 +1,111 @@
1
1
  import type { MarkupRule } from "./types.js";
2
+ /**
3
+ * Headings are single line only (don't allow multiline).
4
+ * - 1-6 hashes then 1+ spaces, then the title.
5
+ * - Same as Markdown syntax.
6
+ * - Markdown's underline syntax is not supported (for simplification).
7
+ */
8
+ export declare const HEADING_RULE: MarkupRule;
9
+ /**
10
+ * Horizontal rules
11
+ * - Same as Markdown syntax but also allows `•` bullet character (in addition to `-` dash, `+` plus, `*` asterisk, and `_` underscore).
12
+ * - Character must be repeated three (or more) times.
13
+ * - Character must be the same every time (can't mix)
14
+ * - Might have infinite number of spaces between the characters.
15
+ */
16
+ export declare const HORIZONTAL_RULE: MarkupRule;
17
+ export declare const UNORDERED_LIST_RULE: MarkupRule;
18
+ export declare const ORDERED_LIST_RULE: MarkupRule;
19
+ /**
20
+ * Blockquote block.
21
+ * - Same as Markdown's syntax.
22
+ * - Block continues until it finds a line that doesn't start with `>`
23
+ * - Quote indent symbol can be followed by zero or more spaces.
24
+ */
25
+ export declare const BLOCKQUOTE_RULE: MarkupRule;
26
+ /**
27
+ * Fenced code blocks
28
+ * - Same as Markdown syntax.
29
+ * - Closing fence must be exactly the same as the opening fence, and can be made of at least three "```" backticks or three `~~~` tildes.
30
+ * - If there's no closing fence the code block will run to the end of the current string.
31
+ * - Markdown-style four-space indent syntax is not supported (only fenced code, since it's easier to use).
32
+ */
33
+ export declare const FENCED_CODE_RULE: MarkupRule;
34
+ /**
35
+ * Paragraph.
36
+ * - When ordering rules, paragraph should go after other "block" context elements (because it has a very generous capture).
37
+ */
38
+ export declare const PARAGRAPH_RULE: MarkupRule;
39
+ /**
40
+ * Markdown-style link.
41
+ * - Link in standard Markdown format, e.g. `[http://google.com/maps](Google Maps)`
42
+ * - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
43
+ * - Does not need space before/after the link.
44
+ * - If link is not valid (using `new URL(url)` then unparsed text will be returned.
45
+ * - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
46
+ */
47
+ export declare const LINK_RULE: MarkupRule;
48
+ /**
49
+ * Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
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
+ * - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
52
+ * - If link is not valid (using `new URL(url)` then unparsed text will be returned.
53
+ * - For security only schemes that appear in the `options.schemes` will match (defaults to `http:` and `https:`).
54
+ */
55
+ export declare const AUTOLINK_RULE: MarkupRule;
56
+ /**
57
+ * Inline code.
58
+ * - Text surrounded by one or more "`" backtick tilde characters.
59
+ * - Unlike strong/emphasis first or last character of the element can be space, (e.g. `- abc -` will not work).
60
+ * - Closing characters must exactly match opening characters.
61
+ * - Same as Markdown syntax.
62
+ */
63
+ export declare const CODE_RULE: MarkupRule;
64
+ /**
65
+ * Inline strong.
66
+ * - Inline text wrapped in one or more `*` asterisks.
67
+ * - Must be surrounded by space (e.g. ` *abc* `) — so formatting cannot be applied inside a word (e.g. `a*b*c`).
68
+ * - Whitespace cannot be the first or last character of the element (e.g. `* abc *` will not work).
69
+ * - Closing characters must exactly match opening characters.
70
+ * - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
71
+ */
72
+ export declare const STRONG_RULE: MarkupRule;
73
+ /**
74
+ * Inline emphasis.
75
+ * - Inline text wrapped in one or more `_` underscore symbols.
76
+ * - Works inside words (e.g. `magi_carp_carp`).
77
+ * - Whitespace cannot be the first or last character of the element (e.g. `_ abc _` will not work).
78
+ * - Closing characters must exactly match opening characters.
79
+ * - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
80
+ */
81
+ export declare const EMPHASIS_RULE: MarkupRule;
82
+ /**
83
+ * Inserted text (`<ins>` tag),
84
+ * - Inline text wrapped in two or more `++` pluses.
85
+ * - Works inside words (e.g. `magi+karp+carp`).
86
+ * - Whitespace cannot be the first or last character of the element (e.g. `+ abc +` will not work).
87
+ * - Closing characters must exactly match opening characters.
88
+ * - Markdown doesn't have this.
89
+ */
90
+ export declare const INSERT_RULE: MarkupRule;
91
+ /**
92
+ * Deleted text (`<del>` tag),
93
+ * - Inline text wrapped in two or more `--` hyphens or `~~` tildes.
94
+ * - Works inside words (e.g. `magi--karp--carp`).
95
+ * - Whitespace cannot be the first or last character of the element (e.g. `-- abc --` will not work).
96
+ * - Closing characters must exactly match opening characters.
97
+ * - Markdown doesn't have this.
98
+ */
99
+ export declare const DELETE_RULE: MarkupRule;
100
+ /**
101
+ * Hard linebreak (`<br />` tag).
102
+ * - Any line break in a paragraph will become a hard `<br />` tag.
103
+ * - Different to Markdown:
104
+ * - Markdown needs two spaces at the end of a line, any line break in a paragraph will be a `<br />` tag (lines without two spaces are not joined together).
105
+ * - This is more intuitive (a linebreak becomes a linebreak is isn't silently ignored).
106
+ * - This works better with textareas that wrap text (since manually breaking up long lines is no longer necessary).
107
+ */
108
+ export declare const LINEBREAK_RULE: MarkupRule;
2
109
  /**
3
110
  * All markup rules.
4
111
  * - Syntax parsed by `renderMarkup()` is defined entirely by the list of rules (i.e. not by code).
@@ -9,5 +116,9 @@ import type { MarkupRule } from "./types.js";
9
116
  * - HTML tags and character entities are never allowed (our use cases generally require a locked-down subset of syntax).
10
117
  */
11
118
  export declare const MARKUP_RULES: MarkupRule[];
12
- /** Default markup rules for user-generated-content rendering. */
13
- export declare const MARKUP_RULES_UGC: MarkupRule[];
119
+ /** Subset of markup rules that work in a block context. */
120
+ export declare const MARKUP_RULES_BLOCK: MarkupRule[];
121
+ /** Subset of markup rules that work in an inline context. */
122
+ export declare const MARKUP_RULES_INLINE: MarkupRule[];
123
+ /** Subset of markup rules that are relevant for collapsed shortform content. */
124
+ export declare const MARKUP_RULES_SHORTFORM: MarkupRule[];
package/markup/rules.js CHANGED
@@ -1,31 +1,16 @@
1
- import { formatUrl, toURL } from "../util/index.js";
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
- const LINE = "[^\\n]*"; // Match line of content (anything that's not a newline).
4
- const LINE_START = "^\\n*|\\n+"; // Starts at start of line (one or more linebreak or start of string).
5
- const LINE_END = "\\n+|$"; // Ends at end of line (one or more linebreak or end of string).
6
- const BLOCK = "[\\s\\S]*?"; // Match block of content (including newlines so don't be greedy).
7
- const BLOCK_START = "^\\n*|\\n+"; // Starts at start of a block (one or more linebreak or start of string).
8
- const BLOCK_END = "\\n*$|\\n\\n+"; // End of a block (two or more linebreaks or end of string).
9
3
  const BULLETS = "-*•+"; // Anything that can be a bullet (used for unordered lists and horizontal rules).
10
- const WORDS = `\\S(?:[\\s\\S]*?\\S)?`; // Run of text that starts and ends with non-space characters (possibly multi-line).
11
4
  // Regular expressions.
12
- const REPLACE_INDENT = /^ {1,2}/gm;
13
- // Regular expression makers.
14
- const getMatcher = regexp => content => content.match(regexp);
15
- const getBlockMatcher = (middle = BLOCK, end = BLOCK_END, start = BLOCK_START) => getMatcher(new RegExp(`(?:${start})${middle}(?:${end})`));
16
- const getLineMatcher = (middle = LINE, end = LINE_END, start = LINE_START) => getMatcher(new RegExp(`(?:${start})${middle}(?:${end})`));
17
- const getWrapMatcher = (chars, middle = WORDS) => {
18
- const regexp = new RegExp(`(${chars})(${middle})\\1`);
19
- return content => content.match(regexp);
20
- };
5
+ const MATCH_INDENT = /^ {1,2}/gm;
21
6
  /**
22
7
  * Headings are single line only (don't allow multiline).
23
8
  * - 1-6 hashes then 1+ spaces, then the title.
24
9
  * - Same as Markdown syntax.
25
10
  * - Markdown's underline syntax is not supported (for simplification).
26
11
  */
27
- const HEADING = {
28
- match: getLineMatcher(`(#{1,6}) +(${LINE})`),
12
+ export const HEADING_RULE = {
13
+ regexp: getLineRegExp(`(#{1,6}) +(${MATCH_LINE.source})`),
29
14
  render: ([, prefix = "", children = ""]) => ({ type: `h${prefix.length}`, key: null, props: { children } }),
30
15
  contexts: ["block"],
31
16
  childContext: "inline",
@@ -37,8 +22,8 @@ const HEADING = {
37
22
  * - Character must be the same every time (can't mix)
38
23
  * - Might have infinite number of spaces between the characters.
39
24
  */
40
- const HR = {
41
- match: getLineMatcher(`([${BULLETS}])(?: *\\1){2,}`),
25
+ export const HORIZONTAL_RULE = {
26
+ regexp: getLineRegExp(`([${BULLETS}])(?: *\\1){2,}`),
42
27
  render: () => ({ type: "hr", key: null, props: {} }),
43
28
  contexts: ["block"],
44
29
  };
@@ -50,8 +35,8 @@ const HR = {
50
35
  * - Second-level list can be indented with 1-2 spaces.
51
36
  */
52
37
  const UNORDERED = `[${BULLETS}] +`; // Anything that can be a bullet (used for unordered lists and horizontal rules).
53
- const UL = {
54
- match: getBlockMatcher(`${UNORDERED}(${BLOCK})`),
38
+ export const UNORDERED_LIST_RULE = {
39
+ regexp: getBlockRegExp(`${UNORDERED}(${MATCH_BLOCK.source})`),
55
40
  render: ([, list = ""]) => {
56
41
  const children = list.split(SPLIT_UL_ITEMS).map(mapUnorderedItem);
57
42
  return { type: "ul", key: null, props: { children } };
@@ -61,7 +46,7 @@ const UL = {
61
46
  };
62
47
  const SPLIT_UL_ITEMS = new RegExp(`\\n+${UNORDERED}`, "g");
63
48
  const mapUnorderedItem = (item, key) => {
64
- const children = item.replace(REPLACE_INDENT, "");
49
+ const children = item.replace(MATCH_INDENT, "");
65
50
  return { type: "li", key, props: { children } };
66
51
  };
67
52
  /**
@@ -70,8 +55,8 @@ const mapUnorderedItem = (item, key) => {
70
55
  * - Second-level list can be indented with 1-3 spaces.
71
56
  */
72
57
  const ORDERED = "[0-9]+[.):] +"; // Number for a numbered list (e.g. `1.` or `2)` or `3:`)
73
- const OL = {
74
- match: getBlockMatcher(`(${ORDERED}${BLOCK})`),
58
+ export const ORDERED_LIST_RULE = {
59
+ regexp: getBlockRegExp(`(${ORDERED}${MATCH_BLOCK.source})`),
75
60
  render: ([, list = ""]) => {
76
61
  const children = list.split(SPLIT_OL_ITEMS).map(mapOrderedItem);
77
62
  return { type: "ol", key: null, props: { children } };
@@ -86,7 +71,7 @@ const mapOrderedItem = (item, key) => {
86
71
  const children = item
87
72
  .slice(firstSpace + 1)
88
73
  .trimStart()
89
- .replace(REPLACE_INDENT, "");
74
+ .replace(MATCH_INDENT, "");
90
75
  return { type: "li", key, props: { value, children } };
91
76
  };
92
77
  /**
@@ -95,8 +80,8 @@ const mapOrderedItem = (item, key) => {
95
80
  * - Block continues until it finds a line that doesn't start with `>`
96
81
  * - Quote indent symbol can be followed by zero or more spaces.
97
82
  */
98
- const BLOCKQUOTE = {
99
- match: getLineMatcher(`(>${LINE}(?:\\n>${LINE})*)`),
83
+ export const BLOCKQUOTE_RULE = {
84
+ regexp: getLineRegExp(`(>${MATCH_LINE.source}(?:\\n>${MATCH_LINE.source})*)`),
100
85
  render: ([, quote = ""]) => ({
101
86
  type: "blockquote",
102
87
  key: null,
@@ -113,9 +98,9 @@ const BLOCKQUOTE_LINES = /^>/gm;
113
98
  * - If there's no closing fence the code block will run to the end of the current string.
114
99
  * - Markdown-style four-space indent syntax is not supported (only fenced code, since it's easier to use).
115
100
  */
116
- const FENCED = {
101
+ export const FENCED_CODE_RULE = {
117
102
  // Matcher has its own end that only stops when it reaches a matching closing fence or the end of the string.
118
- match: getBlockMatcher(`(\`{3,}|~{3,}) *(${LINE})\\n(${BLOCK})`, `\\n\\1\\n+|\\n\\1$|$`),
103
+ regexp: getBlockRegExp(`(\`{3,}|~{3,}) *(${MATCH_LINE.source})\\n(${MATCH_BLOCK.source})`, `\\n\\1\\n+|\\n\\1$|$`),
119
104
  render: ([, , file, children]) => ({
120
105
  type: "pre",
121
106
  key: null,
@@ -133,8 +118,8 @@ const FENCED = {
133
118
  * Paragraph.
134
119
  * - When ordering rules, paragraph should go after other "block" context elements (because it has a very generous capture).
135
120
  */
136
- const PARAGRAPH = {
137
- match: getBlockMatcher(` *(${BLOCK})`),
121
+ export const PARAGRAPH_RULE = {
122
+ regexp: getBlockRegExp(` *(${MATCH_BLOCK.source})`),
138
123
  render: ([, children]) => ({ type: `p`, key: null, props: { children } }),
139
124
  contexts: ["block"],
140
125
  childContext: "inline",
@@ -148,10 +133,11 @@ const PARAGRAPH = {
148
133
  * - If link is not valid (using `new URL(url)` then unparsed text will be returned.
149
134
  * - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
150
135
  */
151
- const LINK = {
136
+ export const LINK_RULE = {
152
137
  // Custom matcher to check the URL against the allowed schemes.
138
+ regexp: /\[([^\]]*?)\]\(([^)]*?)\)/,
153
139
  match: (content, { schemes, url: base }) => {
154
- const matches = content.match(MATCH_LINK);
140
+ const matches = content.match(LINK_RULE.regexp);
155
141
  if (matches && typeof matches.index === "number") {
156
142
  const [, title = "", href = ""] = matches;
157
143
  const url = toURL(href, base);
@@ -170,7 +156,6 @@ const LINK = {
170
156
  contexts: ["inline", "list"],
171
157
  childContext: "link",
172
158
  };
173
- const MATCH_LINK = /\[([^\]]*?)\]\(([^)]*?)\)/;
174
159
  /**
175
160
  * Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
176
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).
@@ -178,10 +163,11 @@ const MATCH_LINK = /\[([^\]]*?)\]\(([^)]*?)\)/;
178
163
  * - If link is not valid (using `new URL(url)` then unparsed text will be returned.
179
164
  * - For security only schemes that appear in the `options.schemes` will match (defaults to `http:` and `https:`).
180
165
  */
181
- const AUTOLINK = {
166
+ export const AUTOLINK_RULE = {
182
167
  // Custom matcher to check the URL against the allowed schemes.
168
+ regexp: /([a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/,
183
169
  match: (content, { schemes, url: base }) => {
184
- const matches = content.match(MATCH_AUTOLINK);
170
+ const matches = content.match(AUTOLINK_RULE.regexp);
185
171
  if (matches && typeof matches.index === "number") {
186
172
  const [, href = "", title1 = "", title2 = ""] = matches;
187
173
  const url = toURL(href, base);
@@ -192,11 +178,10 @@ const AUTOLINK = {
192
178
  }
193
179
  }
194
180
  },
195
- render: LINK.render,
181
+ render: LINK_RULE.render,
196
182
  contexts: ["inline", "list"],
197
183
  childContext: "link",
198
184
  };
199
- const MATCH_AUTOLINK = /([a-z][a-z0-9-]*[a-z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/i;
200
185
  /**
201
186
  * Inline code.
202
187
  * - Text surrounded by one or more "`" backtick tilde characters.
@@ -204,8 +189,8 @@ const MATCH_AUTOLINK = /([a-z][a-z0-9-]*[a-z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\
204
189
  * - Closing characters must exactly match opening characters.
205
190
  * - Same as Markdown syntax.
206
191
  */
207
- const CODE = {
208
- match: getWrapMatcher("`+", BLOCK),
192
+ export const CODE_RULE = {
193
+ regexp: getWrapRegExp("`+", MATCH_BLOCK.source),
209
194
  render: ([, , children]) => ({ type: "code", key: null, props: { children } }),
210
195
  contexts: ["inline", "list"],
211
196
  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.")
@@ -218,8 +203,8 @@ const CODE = {
218
203
  * - Closing characters must exactly match opening characters.
219
204
  * - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
220
205
  */
221
- const STRONG = {
222
- match: getWrapMatcher("\\*+"),
206
+ export const STRONG_RULE = {
207
+ regexp: getWrapRegExp("\\*+"),
223
208
  render: ([, , children]) => ({ type: "strong", key: null, props: { children } }),
224
209
  contexts: ["inline", "list", "link"],
225
210
  childContext: "inline",
@@ -232,36 +217,36 @@ const STRONG = {
232
217
  * - Closing characters must exactly match opening characters.
233
218
  * - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
234
219
  */
235
- const EM = {
236
- match: getWrapMatcher("_+"),
220
+ export const EMPHASIS_RULE = {
221
+ regexp: getWrapRegExp("_+"),
237
222
  render: ([, , children]) => ({ type: "em", key: null, props: { children } }),
238
223
  contexts: ["inline", "list", "link"],
239
224
  childContext: "inline",
240
225
  };
241
226
  /**
242
227
  * Inserted text (`<ins>` tag),
243
- * - Inline text wrapped in one or more `+` pluses.
228
+ * - Inline text wrapped in two or more `++` pluses.
244
229
  * - Works inside words (e.g. `magi+karp+carp`).
245
230
  * - Whitespace cannot be the first or last character of the element (e.g. `+ abc +` will not work).
246
231
  * - Closing characters must exactly match opening characters.
247
232
  * - Markdown doesn't have this.
248
233
  */
249
- const INS = {
250
- match: getWrapMatcher("\\++"),
234
+ export const INSERT_RULE = {
235
+ regexp: getWrapRegExp("\\+\\++"),
251
236
  render: ([, , children]) => ({ type: "ins", key: null, props: { children } }),
252
237
  contexts: ["inline", "list", "link"],
253
238
  childContext: "inline",
254
239
  };
255
240
  /**
256
241
  * Deleted text (`<del>` tag),
257
- * - Inline text wrapped in one or more `~` tildes.
258
- * - Works inside words (e.g. `magi~karp~carp`).
259
- * - Whitespace cannot be the first or last character of the element (e.g. `~ abc ~` will not work).
242
+ * - Inline text wrapped in two or more `--` hyphens or `~~` tildes.
243
+ * - Works inside words (e.g. `magi--karp--carp`).
244
+ * - Whitespace cannot be the first or last character of the element (e.g. `-- abc --` will not work).
260
245
  * - Closing characters must exactly match opening characters.
261
246
  * - Markdown doesn't have this.
262
247
  */
263
- const DEL = {
264
- match: getWrapMatcher("~+"),
248
+ export const DELETE_RULE = {
249
+ regexp: getWrapRegExp("--+|~~+"),
265
250
  render: ([, , children]) => ({ type: "del", key: null, props: { children } }),
266
251
  contexts: ["inline", "list", "link"],
267
252
  childContext: "inline",
@@ -274,8 +259,8 @@ const DEL = {
274
259
  * - This is more intuitive (a linebreak becomes a linebreak is isn't silently ignored).
275
260
  * - This works better with textareas that wrap text (since manually breaking up long lines is no longer necessary).
276
261
  */
277
- const BR = {
278
- match: getMatcher(/\n/),
262
+ export const LINEBREAK_RULE = {
263
+ regexp: /\n/,
279
264
  render: () => ({ type: "br", key: null, props: {} }),
280
265
  contexts: ["inline", "list", "link"],
281
266
  childContext: "inline",
@@ -289,6 +274,55 @@ const BR = {
289
274
  * 3. more aligned with smaller textboxes and editors that have line wrapping
290
275
  * - HTML tags and character entities are never allowed (our use cases generally require a locked-down subset of syntax).
291
276
  */
292
- export const MARKUP_RULES = [HEADING, HR, UL, OL, BLOCKQUOTE, FENCED, PARAGRAPH, LINK, AUTOLINK, CODE, STRONG, EM, INS, DEL, BR];
293
- /** Default markup rules for user-generated-content rendering. */
294
- export const MARKUP_RULES_UGC = [UL, OL, PARAGRAPH, LINK, AUTOLINK, CODE, STRONG, EM, INS, DEL, BR];
277
+ export const MARKUP_RULES = [
278
+ HEADING_RULE,
279
+ HORIZONTAL_RULE,
280
+ UNORDERED_LIST_RULE,
281
+ ORDERED_LIST_RULE,
282
+ BLOCKQUOTE_RULE,
283
+ FENCED_CODE_RULE,
284
+ PARAGRAPH_RULE,
285
+ LINK_RULE,
286
+ AUTOLINK_RULE,
287
+ CODE_RULE,
288
+ STRONG_RULE,
289
+ EMPHASIS_RULE,
290
+ INSERT_RULE,
291
+ DELETE_RULE,
292
+ LINEBREAK_RULE,
293
+ ];
294
+ /** Subset of markup rules that work in a block context. */
295
+ export const MARKUP_RULES_BLOCK = [
296
+ HEADING_RULE,
297
+ HORIZONTAL_RULE,
298
+ UNORDERED_LIST_RULE,
299
+ ORDERED_LIST_RULE,
300
+ BLOCKQUOTE_RULE,
301
+ FENCED_CODE_RULE,
302
+ PARAGRAPH_RULE,
303
+ ];
304
+ /** Subset of markup rules that work in an inline context. */
305
+ export const MARKUP_RULES_INLINE = [
306
+ LINK_RULE,
307
+ AUTOLINK_RULE,
308
+ CODE_RULE,
309
+ STRONG_RULE,
310
+ EMPHASIS_RULE,
311
+ INSERT_RULE,
312
+ DELETE_RULE,
313
+ LINEBREAK_RULE,
314
+ ];
315
+ /** Subset of markup rules that are relevant for collapsed shortform content. */
316
+ export const MARKUP_RULES_SHORTFORM = [
317
+ UNORDERED_LIST_RULE,
318
+ ORDERED_LIST_RULE,
319
+ PARAGRAPH_RULE,
320
+ LINK_RULE,
321
+ AUTOLINK_RULE,
322
+ CODE_RULE,
323
+ STRONG_RULE,
324
+ EMPHASIS_RULE,
325
+ INSERT_RULE,
326
+ DELETE_RULE,
327
+ LINEBREAK_RULE,
328
+ ];
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 that matches this rule. */
22
- readonly match: MarkupRuleMatcher;
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: MarkupRuleRenderer;
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.50.1",
14
+ "version": "1.51.2",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
package/util/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export * from "./filter.js";
17
17
  export * from "./function.js";
18
18
  export * from "./hydrate.js";
19
19
  export * from "./iterate.js";
20
+ export * from "./jsx.js";
20
21
  export * from "./lazy.js";
21
22
  export * from "./map.js";
22
23
  export * from "./merge.js";
package/util/index.js CHANGED
@@ -17,6 +17,7 @@ export * from "./filter.js";
17
17
  export * from "./function.js";
18
18
  export * from "./hydrate.js";
19
19
  export * from "./iterate.js";
20
+ export * from "./jsx.js";
20
21
  export * from "./lazy.js";
21
22
  export * from "./map.js";
22
23
  export * from "./merge.js";
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,25 +1,41 @@
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
- * Match a string against a regular expression.
15
+ * Convert a string to a regular expression that matches that string.
5
16
  *
6
- * @param item The item to search for the regexp in.
7
- * - Item is an array: recurse into each item of the array to look for strings that match.
8
- * - Item is an object: recurse into each property of the object to look for strings that match.
9
- * - Item is string: match the string against the regular expression.
10
- * - Item is anything else: return false (can't be matched).
11
- *
12
- * @param regexp The `RegExp` instance to match the
17
+ * @param str The input string.
18
+ * @param flags RegExp flags that are passed into the created RegExp.
13
19
  */
14
- export declare function MATCHES(item: unknown, regexp: RegExp): boolean;
20
+ export declare const toRegExp: (str: string, flags?: string) => RegExp;
21
+ /** Escape special characters in a string regular expression. */
22
+ export declare const escapeRegExp: (str: string) => string;
23
+ /** Create regular expression that matches a block of content. */
24
+ export declare const getBlockRegExp: (middle?: string, end?: string, start?: string) => RegExp;
25
+ /** Create regular expression that matches a line of content. */
26
+ export declare const getLineRegExp: (middle?: string, end?: string, start?: string) => RegExp;
27
+ /** Create regular expression that matches piece of text wrapped by a set of characters. */
28
+ export declare const getWrapRegExp: (chars: string, middle?: string) => RegExp;
15
29
  /**
16
- * Match an item matching all words in a query.
17
- *
18
- * @param item The item to search for the query in.
19
- * @param query The query string to search for.
20
- * - Supports `"compound queries"` with quotes.
30
+ * Convert a string query to the corresponding set of case-insensitive regular expressions.
31
+ * - Splies the query into words (respecting "quoted phrases").
32
+ * - Return a regex for each word or quoted phrase.
33
+ * - Unquoted words match partially (starting with a word boundary).
34
+ * - Quoted phrases match fully (starting and ending with a word boundary).
21
35
  */
22
- export declare function MATCHES_ALL(item: unknown, regexps: ImmutableArray<RegExp>): boolean;
36
+ export declare const toWordRegExps: (query: string) => ImmutableArray<RegExp>;
37
+ /** Convert a string to a regular expression matching the start of a word boundary. */
38
+ export declare const toWordRegExp: (word: string) => RegExp;
23
39
  /**
24
40
  * Match an item matching all words in a query.
25
41
  *
@@ -27,17 +43,14 @@ export declare function MATCHES_ALL(item: unknown, regexps: ImmutableArray<RegEx
27
43
  * @param query The query string to search for.
28
44
  * - Supports `"compound queries"` with quotes.
29
45
  */
30
- export declare function MATCHES_ANY(item: unknown, regexps: ImmutableArray<RegExp>): boolean;
46
+ export declare function matchesAll(item: unknown, regexps: Iterable<RegExp>): boolean;
31
47
  /**
32
- * Convert a string query to the corresponding set of case-insensitive regular expressions.
33
- * - Splies the query into words (respecting "quoted phrases").
34
- * - Return a regex for each word or quoted phrase.
35
- * - Unquoted words match partially (starting with a word boundary).
36
- * - Quoted phrases match fully (starting and ending with a word boundary).
48
+ * Match an item any of several regular expressions.
49
+ *
50
+ * @param item The item to search for the query in.
51
+ * @param regexps An iterable set of regular expressions.
37
52
  */
38
- export declare const toWordRegExps: (query: string) => ImmutableArray<RegExp>;
39
- /** Convert a string to a regular expression matching the start of a word boundary. */
40
- export declare const toWordRegExp: (word: string) => RegExp;
53
+ export declare function matchesAny(item: unknown, regexps: Iterable<RegExp>): boolean;
41
54
  /** Matcher that matches any words in a string. */
42
55
  export declare class MatchAnyWord implements Matchable<unknown, void> {
43
56
  private _regexps;
package/util/search.js CHANGED
@@ -1,18 +1,42 @@
1
- import { toWords, escapeRegExp, normalizeString } from "./string.js";
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
- * Match a string against a regular expression.
15
+ * Convert a string to a regular expression that matches that string.
4
16
  *
5
- * @param item The item to search for the regexp in.
6
- * - Item is an array: recurse into each item of the array to look for strings that match.
7
- * - Item is an object: recurse into each property of the object to look for strings that match.
8
- * - Item is string: match the string against the regular expression.
9
- * - Item is anything else: return false (can't be matched).
10
- *
11
- * @param regexp The `RegExp` instance to match the
17
+ * @param str The input string.
18
+ * @param flags RegExp flags that are passed into the created RegExp.
12
19
  */
13
- export function MATCHES(item, regexp) {
14
- return typeof item === "string" && !!item.match(regexp);
15
- }
20
+ export const toRegExp = (str, flags = "") => new RegExp(escapeRegExp(str), flags);
21
+ /** Escape special characters in a string regular expression. */
22
+ export const escapeRegExp = (str) => str.replace(REPLACE_ESCAPED, "\\$&");
23
+ const REPLACE_ESCAPED = /[-[\]/{}()*+?.\\^$|]/g;
24
+ /** Create regular expression that matches a block of content. */
25
+ export const getBlockRegExp = (middle = MATCH_BLOCK.source, end = MATCH_BLOCK_END.source, start = MATCH_BLOCK_START.source) => new RegExp(`(?:${start})${middle}(?:${end})`);
26
+ /** Create regular expression that matches a line of content. */
27
+ export const getLineRegExp = (middle = MATCH_LINE.source, end = MATCH_LINE_END.source, start = MATCH_LINE_START.source) => new RegExp(`(?:${start})${middle}(?:${end})`);
28
+ /** Create regular expression that matches piece of text wrapped by a set of characters. */
29
+ export const getWrapRegExp = (chars, middle = MATCH_WORDS.source) => new RegExp(`(${chars})(${middle})\\1`);
30
+ /**
31
+ * Convert a string query to the corresponding set of case-insensitive regular expressions.
32
+ * - Splies the query into words (respecting "quoted phrases").
33
+ * - Return a regex for each word or quoted phrase.
34
+ * - Unquoted words match partially (starting with a word boundary).
35
+ * - Quoted phrases match fully (starting and ending with a word boundary).
36
+ */
37
+ export const toWordRegExps = (query) => toWords(query).map(toWordRegExp);
38
+ /** Convert a string to a regular expression matching the start of a word boundary. */
39
+ export const toWordRegExp = (word) => new RegExp(`\\b${escapeRegExp(normalizeString(word))}`, "i");
16
40
  /**
17
41
  * Match an item matching all words in a query.
18
42
  *
@@ -20,46 +44,37 @@ export function MATCHES(item, regexp) {
20
44
  * @param query The query string to search for.
21
45
  * - Supports `"compound queries"` with quotes.
22
46
  */
23
- export function MATCHES_ALL(item, regexps) {
24
- if (typeof item === "string" && regexps.length) {
25
- for (const regexp of regexps)
26
- if (!item.match(regexp))
47
+ export function matchesAll(item, regexps) {
48
+ let count = 0;
49
+ if (typeof item === "string") {
50
+ for (const regexp of regexps) {
51
+ count++;
52
+ if (!regexp.test(item))
27
53
  return false;
28
- return true;
54
+ }
29
55
  }
30
- return false;
56
+ return count ? true : false;
31
57
  }
32
58
  /**
33
- * Match an item matching all words in a query.
59
+ * Match an item any of several regular expressions.
34
60
  *
35
61
  * @param item The item to search for the query in.
36
- * @param query The query string to search for.
37
- * - Supports `"compound queries"` with quotes.
62
+ * @param regexps An iterable set of regular expressions.
38
63
  */
39
- export function MATCHES_ANY(item, regexps) {
64
+ export function matchesAny(item, regexps) {
40
65
  if (typeof item === "string")
41
66
  for (const regexp of regexps)
42
- if (MATCHES(item, regexp))
67
+ if (regexp.test(item))
43
68
  return true;
44
69
  return false;
45
70
  }
46
- /**
47
- * Convert a string query to the corresponding set of case-insensitive regular expressions.
48
- * - Splies the query into words (respecting "quoted phrases").
49
- * - Return a regex for each word or quoted phrase.
50
- * - Unquoted words match partially (starting with a word boundary).
51
- * - Quoted phrases match fully (starting and ending with a word boundary).
52
- */
53
- export const toWordRegExps = (query) => toWords(query).map(toWordRegExp);
54
- /** Convert a string to a regular expression matching the start of a word boundary. */
55
- export const toWordRegExp = (word) => new RegExp(`\\b${escapeRegExp(normalizeString(word))}`, "i");
56
71
  /** Matcher that matches any words in a string. */
57
72
  export class MatchAnyWord {
58
73
  constructor(words) {
59
74
  this._regexps = toWordRegExps(words);
60
75
  }
61
76
  match(value) {
62
- return MATCHES_ANY(value, this._regexps);
77
+ return matchesAny(value, this._regexps);
63
78
  }
64
79
  }
65
80
  /** Matcher that matches all words in a string. */
@@ -68,7 +83,7 @@ export class MatchAllWords {
68
83
  this._regexps = toWordRegExps(words);
69
84
  }
70
85
  match(value) {
71
- return MATCHES_ALL(value, this._regexps);
86
+ return matchesAll(value, this._regexps);
72
87
  }
73
88
  }
74
89
  /** Matcher that matches an exact phrase. */
@@ -77,6 +92,6 @@ export class MatchWord {
77
92
  this._regexp = toWordRegExp(phrase);
78
93
  }
79
94
  match(value) {
80
- return MATCHES(value, this._regexp);
95
+ return this._regexp.test(value);
81
96
  }
82
97
  }
package/util/string.d.ts CHANGED
@@ -77,15 +77,6 @@ export declare const toSlug: (str: string) => string;
77
77
  export declare const toWords: (str: string) => ImmutableArray<string>;
78
78
  /** Find and iterate over the words in a string. */
79
79
  export declare function yieldWords(value: string): Generator<string, void, void>;
80
- /**
81
- * Convert a string to a regular expression that matches that string.
82
- *
83
- * @param str The input string.
84
- * @param flags RegExp flags that are passed into the created RegExp.
85
- */
86
- export declare const toRegExp: (str: string, flags?: string) => RegExp;
87
- /** Escape special characters in a string regular expression. */
88
- export declare const escapeRegExp: (str: string) => string;
89
80
  /** Is the first character of a string an uppercase letter? */
90
81
  export declare const isUppercaseLetter: (str: string) => boolean;
91
82
  /** Is the first character of a string a lowercase letter? */
package/util/string.js CHANGED
@@ -135,16 +135,6 @@ export function* yieldWords(value) {
135
135
  }
136
136
  }
137
137
  const MATCH_WORD = /[^\s"]+|"([^"]*)"/g; // Runs of characters without spaces, or "quoted phrases"
138
- /**
139
- * Convert a string to a regular expression that matches that string.
140
- *
141
- * @param str The input string.
142
- * @param flags RegExp flags that are passed into the created RegExp.
143
- */
144
- export const toRegExp = (str, flags = "") => new RegExp(escapeRegExp(str), flags);
145
- /** Escape special characters in a string regular expression. */
146
- export const escapeRegExp = (str) => str.replace(REPLACE_ESCAPED, "\\$&");
147
- const REPLACE_ESCAPED = /[-[\]/{}()*+?.\\^$|]/g;
148
138
  /** Is the first character of a string an uppercase letter? */
149
139
  export const isUppercaseLetter = (str) => isBetween(str.charCodeAt(0), 65, 90);
150
140
  /** Is the first character of a string a lowercase letter? */
@@ -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
- }