shelving 1.49.3 → 1.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/markup/render.d.ts +0 -5
- package/markup/render.js +1 -13
- package/markup/rules.d.ts +113 -2
- package/markup/rules.js +85 -49
- package/package.json +8 -8
- package/util/color.d.ts +2 -1
- package/util/color.js +8 -6
- package/util/iterate.d.ts +5 -0
- package/util/iterate.js +6 -0
- package/util/random.d.ts +2 -1
- package/util/random.js +4 -3
- package/util/search.d.ts +33 -24
- package/util/search.js +47 -36
- package/util/string.d.ts +2 -11
- package/util/string.js +7 -17
package/markup/render.d.ts
CHANGED
|
@@ -33,8 +33,3 @@ import type { MarkupOptions, MarkupNode } from "./types.js";
|
|
|
33
33
|
* @returns ReactNode, i.e. either a complete `ReactElement`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
34
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;
|
package/markup/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
2
|
import { sanitizeLines } from "../index.js";
|
|
3
|
-
import { MARKUP_RULES
|
|
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.
|
|
@@ -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_MARKUP: 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_MARKUP: 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
|
-
/**
|
|
13
|
-
export declare const
|
|
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,18 @@
|
|
|
1
|
-
import { formatUrl, toURL } from "../util/index.js";
|
|
1
|
+
import { formatUrl, toURL, getLineRegExp, MATCH_LINE, getBlockRegExp, 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
5
|
const REPLACE_INDENT = /^ {1,2}/gm;
|
|
13
6
|
// Regular expression makers.
|
|
14
7
|
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
|
-
};
|
|
21
8
|
/**
|
|
22
9
|
* Headings are single line only (don't allow multiline).
|
|
23
10
|
* - 1-6 hashes then 1+ spaces, then the title.
|
|
24
11
|
* - Same as Markdown syntax.
|
|
25
12
|
* - Markdown's underline syntax is not supported (for simplification).
|
|
26
13
|
*/
|
|
27
|
-
const
|
|
28
|
-
match:
|
|
14
|
+
export const HEADING_RULE = {
|
|
15
|
+
match: getMatcher(getLineRegExp(`(#{1,6}) +(${MATCH_LINE})`)),
|
|
29
16
|
render: ([, prefix = "", children = ""]) => ({ type: `h${prefix.length}`, key: null, props: { children } }),
|
|
30
17
|
contexts: ["block"],
|
|
31
18
|
childContext: "inline",
|
|
@@ -37,8 +24,8 @@ const HEADING = {
|
|
|
37
24
|
* - Character must be the same every time (can't mix)
|
|
38
25
|
* - Might have infinite number of spaces between the characters.
|
|
39
26
|
*/
|
|
40
|
-
const
|
|
41
|
-
match:
|
|
27
|
+
export const HORIZONTAL_RULE = {
|
|
28
|
+
match: getMatcher(getLineRegExp(`([${BULLETS}])(?: *\\1){2,}`)),
|
|
42
29
|
render: () => ({ type: "hr", key: null, props: {} }),
|
|
43
30
|
contexts: ["block"],
|
|
44
31
|
};
|
|
@@ -50,8 +37,8 @@ const HR = {
|
|
|
50
37
|
* - Second-level list can be indented with 1-2 spaces.
|
|
51
38
|
*/
|
|
52
39
|
const UNORDERED = `[${BULLETS}] +`; // Anything that can be a bullet (used for unordered lists and horizontal rules).
|
|
53
|
-
const
|
|
54
|
-
match:
|
|
40
|
+
export const UNORDERED_LIST_RULE = {
|
|
41
|
+
match: getMatcher(getBlockRegExp(`${UNORDERED}(${MATCH_BLOCK})`)),
|
|
55
42
|
render: ([, list = ""]) => {
|
|
56
43
|
const children = list.split(SPLIT_UL_ITEMS).map(mapUnorderedItem);
|
|
57
44
|
return { type: "ul", key: null, props: { children } };
|
|
@@ -70,8 +57,8 @@ const mapUnorderedItem = (item, key) => {
|
|
|
70
57
|
* - Second-level list can be indented with 1-3 spaces.
|
|
71
58
|
*/
|
|
72
59
|
const ORDERED = "[0-9]+[.):] +"; // Number for a numbered list (e.g. `1.` or `2)` or `3:`)
|
|
73
|
-
const
|
|
74
|
-
match:
|
|
60
|
+
export const ORDERED_LIST_RULE = {
|
|
61
|
+
match: getMatcher(getBlockRegExp(`(${ORDERED}${MATCH_BLOCK})`)),
|
|
75
62
|
render: ([, list = ""]) => {
|
|
76
63
|
const children = list.split(SPLIT_OL_ITEMS).map(mapOrderedItem);
|
|
77
64
|
return { type: "ol", key: null, props: { children } };
|
|
@@ -95,8 +82,8 @@ const mapOrderedItem = (item, key) => {
|
|
|
95
82
|
* - Block continues until it finds a line that doesn't start with `>`
|
|
96
83
|
* - Quote indent symbol can be followed by zero or more spaces.
|
|
97
84
|
*/
|
|
98
|
-
const
|
|
99
|
-
match:
|
|
85
|
+
export const BLOCKQUOTE_RULE = {
|
|
86
|
+
match: getMatcher(getLineRegExp(`(>${MATCH_LINE}(?:\\n>${MATCH_LINE})*)`)),
|
|
100
87
|
render: ([, quote = ""]) => ({
|
|
101
88
|
type: "blockquote",
|
|
102
89
|
key: null,
|
|
@@ -113,9 +100,9 @@ const BLOCKQUOTE_LINES = /^>/gm;
|
|
|
113
100
|
* - If there's no closing fence the code block will run to the end of the current string.
|
|
114
101
|
* - Markdown-style four-space indent syntax is not supported (only fenced code, since it's easier to use).
|
|
115
102
|
*/
|
|
116
|
-
const
|
|
103
|
+
export const FENCED_CODE_RULE = {
|
|
117
104
|
// Matcher has its own end that only stops when it reaches a matching closing fence or the end of the string.
|
|
118
|
-
match:
|
|
105
|
+
match: getMatcher(getBlockRegExp(`(\`{3,}|~{3,}) *(${MATCH_LINE})\\n(${MATCH_BLOCK})`, `\\n\\1\\n+|\\n\\1$|$`)),
|
|
119
106
|
render: ([, , file, children]) => ({
|
|
120
107
|
type: "pre",
|
|
121
108
|
key: null,
|
|
@@ -133,8 +120,8 @@ const FENCED = {
|
|
|
133
120
|
* Paragraph.
|
|
134
121
|
* - When ordering rules, paragraph should go after other "block" context elements (because it has a very generous capture).
|
|
135
122
|
*/
|
|
136
|
-
const
|
|
137
|
-
match:
|
|
123
|
+
export const PARAGRAPH_RULE = {
|
|
124
|
+
match: getMatcher(getBlockRegExp(` *(${MATCH_BLOCK})`)),
|
|
138
125
|
render: ([, children]) => ({ type: `p`, key: null, props: { children } }),
|
|
139
126
|
contexts: ["block"],
|
|
140
127
|
childContext: "inline",
|
|
@@ -148,7 +135,7 @@ const PARAGRAPH = {
|
|
|
148
135
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
149
136
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
150
137
|
*/
|
|
151
|
-
const
|
|
138
|
+
export const LINK_MARKUP = {
|
|
152
139
|
// Custom matcher to check the URL against the allowed schemes.
|
|
153
140
|
match: (content, { schemes, url: base }) => {
|
|
154
141
|
const matches = content.match(MATCH_LINK);
|
|
@@ -178,7 +165,7 @@ const MATCH_LINK = /\[([^\]]*?)\]\(([^)]*?)\)/;
|
|
|
178
165
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
179
166
|
* - For security only schemes that appear in the `options.schemes` will match (defaults to `http:` and `https:`).
|
|
180
167
|
*/
|
|
181
|
-
const
|
|
168
|
+
export const AUTOLINK_RULE = {
|
|
182
169
|
// Custom matcher to check the URL against the allowed schemes.
|
|
183
170
|
match: (content, { schemes, url: base }) => {
|
|
184
171
|
const matches = content.match(MATCH_AUTOLINK);
|
|
@@ -192,7 +179,7 @@ const AUTOLINK = {
|
|
|
192
179
|
}
|
|
193
180
|
}
|
|
194
181
|
},
|
|
195
|
-
render:
|
|
182
|
+
render: LINK_MARKUP.render,
|
|
196
183
|
contexts: ["inline", "list"],
|
|
197
184
|
childContext: "link",
|
|
198
185
|
};
|
|
@@ -204,8 +191,8 @@ const MATCH_AUTOLINK = /([a-z][a-z0-9-]*[a-z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\
|
|
|
204
191
|
* - Closing characters must exactly match opening characters.
|
|
205
192
|
* - Same as Markdown syntax.
|
|
206
193
|
*/
|
|
207
|
-
const
|
|
208
|
-
match:
|
|
194
|
+
export const CODE_RULE = {
|
|
195
|
+
match: getMatcher(getWrapRegExp("`+", MATCH_BLOCK)),
|
|
209
196
|
render: ([, , children]) => ({ type: "code", key: null, props: { children } }),
|
|
210
197
|
contexts: ["inline", "list"],
|
|
211
198
|
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 +205,8 @@ const CODE = {
|
|
|
218
205
|
* - Closing characters must exactly match opening characters.
|
|
219
206
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
220
207
|
*/
|
|
221
|
-
const
|
|
222
|
-
match:
|
|
208
|
+
export const STRONG_MARKUP = {
|
|
209
|
+
match: getMatcher(getWrapRegExp("\\*+")),
|
|
223
210
|
render: ([, , children]) => ({ type: "strong", key: null, props: { children } }),
|
|
224
211
|
contexts: ["inline", "list", "link"],
|
|
225
212
|
childContext: "inline",
|
|
@@ -232,36 +219,36 @@ const STRONG = {
|
|
|
232
219
|
* - Closing characters must exactly match opening characters.
|
|
233
220
|
* - Different to Markdown: strong is always surrounded by `*asterisks*` and emphasis is always surrounded by `_underscores_` (strong isn't 'double emphasis').
|
|
234
221
|
*/
|
|
235
|
-
const
|
|
236
|
-
match:
|
|
222
|
+
export const EMPHASIS_RULE = {
|
|
223
|
+
match: getMatcher(getWrapRegExp("_+")),
|
|
237
224
|
render: ([, , children]) => ({ type: "em", key: null, props: { children } }),
|
|
238
225
|
contexts: ["inline", "list", "link"],
|
|
239
226
|
childContext: "inline",
|
|
240
227
|
};
|
|
241
228
|
/**
|
|
242
229
|
* Inserted text (`<ins>` tag),
|
|
243
|
-
* - Inline text wrapped in
|
|
230
|
+
* - Inline text wrapped in two or more `++` pluses.
|
|
244
231
|
* - Works inside words (e.g. `magi+karp+carp`).
|
|
245
232
|
* - Whitespace cannot be the first or last character of the element (e.g. `+ abc +` will not work).
|
|
246
233
|
* - Closing characters must exactly match opening characters.
|
|
247
234
|
* - Markdown doesn't have this.
|
|
248
235
|
*/
|
|
249
|
-
const
|
|
250
|
-
match:
|
|
236
|
+
export const INSERT_RULE = {
|
|
237
|
+
match: getMatcher(getWrapRegExp("\\+\\++")),
|
|
251
238
|
render: ([, , children]) => ({ type: "ins", key: null, props: { children } }),
|
|
252
239
|
contexts: ["inline", "list", "link"],
|
|
253
240
|
childContext: "inline",
|
|
254
241
|
};
|
|
255
242
|
/**
|
|
256
243
|
* Deleted text (`<del>` tag),
|
|
257
|
-
* - Inline text wrapped in
|
|
258
|
-
* - Works inside words (e.g. `magi
|
|
259
|
-
* - Whitespace cannot be the first or last character of the element (e.g.
|
|
244
|
+
* - Inline text wrapped in two or more `--` hyphens or `~~` tildes.
|
|
245
|
+
* - Works inside words (e.g. `magi--karp--carp`).
|
|
246
|
+
* - Whitespace cannot be the first or last character of the element (e.g. `-- abc --` will not work).
|
|
260
247
|
* - Closing characters must exactly match opening characters.
|
|
261
248
|
* - Markdown doesn't have this.
|
|
262
249
|
*/
|
|
263
|
-
const
|
|
264
|
-
match:
|
|
250
|
+
export const DELETE_RULE = {
|
|
251
|
+
match: getMatcher(getWrapRegExp("--+|~~+")),
|
|
265
252
|
render: ([, , children]) => ({ type: "del", key: null, props: { children } }),
|
|
266
253
|
contexts: ["inline", "list", "link"],
|
|
267
254
|
childContext: "inline",
|
|
@@ -274,7 +261,7 @@ const DEL = {
|
|
|
274
261
|
* - This is more intuitive (a linebreak becomes a linebreak is isn't silently ignored).
|
|
275
262
|
* - This works better with textareas that wrap text (since manually breaking up long lines is no longer necessary).
|
|
276
263
|
*/
|
|
277
|
-
const
|
|
264
|
+
export const LINEBREAK_RULE = {
|
|
278
265
|
match: getMatcher(/\n/),
|
|
279
266
|
render: () => ({ type: "br", key: null, props: {} }),
|
|
280
267
|
contexts: ["inline", "list", "link"],
|
|
@@ -289,6 +276,55 @@ const BR = {
|
|
|
289
276
|
* 3. more aligned with smaller textboxes and editors that have line wrapping
|
|
290
277
|
* - HTML tags and character entities are never allowed (our use cases generally require a locked-down subset of syntax).
|
|
291
278
|
*/
|
|
292
|
-
export const MARKUP_RULES = [
|
|
293
|
-
|
|
294
|
-
|
|
279
|
+
export const MARKUP_RULES = [
|
|
280
|
+
HEADING_RULE,
|
|
281
|
+
HORIZONTAL_RULE,
|
|
282
|
+
UNORDERED_LIST_RULE,
|
|
283
|
+
ORDERED_LIST_RULE,
|
|
284
|
+
BLOCKQUOTE_RULE,
|
|
285
|
+
FENCED_CODE_RULE,
|
|
286
|
+
PARAGRAPH_RULE,
|
|
287
|
+
LINK_MARKUP,
|
|
288
|
+
AUTOLINK_RULE,
|
|
289
|
+
CODE_RULE,
|
|
290
|
+
STRONG_MARKUP,
|
|
291
|
+
EMPHASIS_RULE,
|
|
292
|
+
INSERT_RULE,
|
|
293
|
+
DELETE_RULE,
|
|
294
|
+
LINEBREAK_RULE,
|
|
295
|
+
];
|
|
296
|
+
/** Subset of markup rules that work in a block context. */
|
|
297
|
+
export const MARKUP_RULES_BLOCK = [
|
|
298
|
+
HEADING_RULE,
|
|
299
|
+
HORIZONTAL_RULE,
|
|
300
|
+
UNORDERED_LIST_RULE,
|
|
301
|
+
ORDERED_LIST_RULE,
|
|
302
|
+
BLOCKQUOTE_RULE,
|
|
303
|
+
FENCED_CODE_RULE,
|
|
304
|
+
PARAGRAPH_RULE,
|
|
305
|
+
];
|
|
306
|
+
/** Subset of markup rules that work in an inline context. */
|
|
307
|
+
export const MARKUP_RULES_INLINE = [
|
|
308
|
+
LINK_MARKUP,
|
|
309
|
+
AUTOLINK_RULE,
|
|
310
|
+
CODE_RULE,
|
|
311
|
+
STRONG_MARKUP,
|
|
312
|
+
EMPHASIS_RULE,
|
|
313
|
+
INSERT_RULE,
|
|
314
|
+
DELETE_RULE,
|
|
315
|
+
LINEBREAK_RULE,
|
|
316
|
+
];
|
|
317
|
+
/** Subset of markup rules that are relevant for collapsed shortform content. */
|
|
318
|
+
export const MARKUP_RULES_SHORTFORM = [
|
|
319
|
+
UNORDERED_LIST_RULE,
|
|
320
|
+
ORDERED_LIST_RULE,
|
|
321
|
+
PARAGRAPH_RULE,
|
|
322
|
+
LINK_MARKUP,
|
|
323
|
+
AUTOLINK_RULE,
|
|
324
|
+
CODE_RULE,
|
|
325
|
+
STRONG_MARKUP,
|
|
326
|
+
EMPHASIS_RULE,
|
|
327
|
+
INSERT_RULE,
|
|
328
|
+
DELETE_RULE,
|
|
329
|
+
LINEBREAK_RULE,
|
|
330
|
+
];
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"state-management",
|
|
12
12
|
"query-builder"
|
|
13
13
|
],
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.51.0",
|
|
15
15
|
"repository": "https://github.com/dhoulb/shelving",
|
|
16
16
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
17
17
|
"license": "0BSD",
|
|
@@ -61,22 +61,22 @@
|
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@google-cloud/firestore": "^4.15.1",
|
|
63
63
|
"@types/jest": "^27.4.0",
|
|
64
|
-
"@types/react": "^17.0.
|
|
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.10.2",
|
|
67
|
+
"@typescript-eslint/parser": "^5.10.2",
|
|
68
|
+
"eslint": "^8.8.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
|
-
"firebase": "^9.6.
|
|
73
|
-
"jest": "^27.
|
|
72
|
+
"firebase": "^9.6.6",
|
|
73
|
+
"jest": "^27.5.0",
|
|
74
74
|
"jest-ts-webcompat-resolver": "^1.0.0",
|
|
75
75
|
"prettier": "^2.5.1",
|
|
76
76
|
"react": "^17.0.2",
|
|
77
77
|
"react-dom": "^17.0.2",
|
|
78
78
|
"ts-jest": "^27.1.3",
|
|
79
|
-
"typescript": "^4.5.
|
|
79
|
+
"typescript": "^4.5.5"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"@google-cloud/firestore": ">=4.0.0",
|
package/util/color.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Things that can be converted to a `Color` instance. */
|
|
2
2
|
export declare type PossibleColor = Color | string;
|
|
3
|
+
export declare type PossibleOptionalColor = PossibleColor | null;
|
|
3
4
|
/** Represent a color. */
|
|
4
5
|
export declare class Color {
|
|
5
6
|
readonly r: number;
|
|
@@ -20,7 +21,7 @@ export declare class Color {
|
|
|
20
21
|
/** Convert a number or string to a color channel number that's within bounds (strings like `0a` or `ff` are parsed as hexadecimal). */
|
|
21
22
|
export declare function getColorChannel(channel: number | string): number;
|
|
22
23
|
/** Convert a possible color to a `Color` instance or `null` */
|
|
23
|
-
export declare function toColor(color:
|
|
24
|
+
export declare function toColor(color: unknown): Color | null;
|
|
24
25
|
/** Convert a possible color to a `Color` instance */
|
|
25
26
|
export declare function getColor(input: PossibleColor): Color;
|
|
26
27
|
/** Is a color light? */
|
package/util/color.js
CHANGED
|
@@ -49,12 +49,14 @@ export function getColorChannel(channel) {
|
|
|
49
49
|
export function toColor(color) {
|
|
50
50
|
if (color instanceof Color)
|
|
51
51
|
return color;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
if (typeof color === "string") {
|
|
53
|
+
const hex3 = color.match(HEX3);
|
|
54
|
+
if (hex3)
|
|
55
|
+
return new Color(hex3[1], hex3[2], hex3[3]);
|
|
56
|
+
const hex6 = color.match(HEX6);
|
|
57
|
+
if (hex6)
|
|
58
|
+
return new Color(hex6[1], hex6[2], hex6[3], hex6[4]);
|
|
59
|
+
}
|
|
58
60
|
return null;
|
|
59
61
|
}
|
|
60
62
|
/** Convert a possible color to a `Color` instance */
|
package/util/iterate.d.ts
CHANGED
|
@@ -52,6 +52,11 @@ export declare function yieldRange(start: number, end: number): Generator<number
|
|
|
52
52
|
* - Checks `items.size` or `items.length` first to see if the limit is necessary.
|
|
53
53
|
*/
|
|
54
54
|
export declare function limitItems<T>(items: Iterable<T>, limit: number): TypedIterable<T, void, void>;
|
|
55
|
+
/**
|
|
56
|
+
* Reduce an iterable set of items using a reducer function.
|
|
57
|
+
*/
|
|
58
|
+
export declare function reduceItems<T, R>(items: Iterable<T>, reducer: (previous: R, item: T) => R, initial: R): R;
|
|
59
|
+
export declare function reduceItems<T, R>(items: Iterable<T>, reducer: (previous: R | undefined, item: T) => R, initial?: R): R | undefined;
|
|
55
60
|
/** Yield items from a source iterable until we hit a maximum iteration count. */
|
|
56
61
|
export declare function yieldUntilLimit<T>(source: Iterable<T>, limit: number): Generator<T, void, void>;
|
|
57
62
|
/** Infinite iterator that yields the result of calling a function with a given set of arguments. */
|
package/util/iterate.js
CHANGED
|
@@ -70,6 +70,12 @@ export function limitItems(items, limit) {
|
|
|
70
70
|
const size = (_a = getKnownSize(items)) !== null && _a !== void 0 ? _a : Infinity;
|
|
71
71
|
return size <= limit ? items : yieldUntilLimit(items, limit);
|
|
72
72
|
}
|
|
73
|
+
export function reduceItems(items, reducer, initial) {
|
|
74
|
+
let current = initial;
|
|
75
|
+
for (const item of items)
|
|
76
|
+
current = reducer(current, item);
|
|
77
|
+
return current;
|
|
78
|
+
}
|
|
73
79
|
/** Yield items from a source iterable until we hit a maximum iteration count. */
|
|
74
80
|
export function* yieldUntilLimit(source, limit) {
|
|
75
81
|
const iterator = source[Symbol.iterator]();
|
package/util/random.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ import type { ImmutableArray } from "./array.js";
|
|
|
2
2
|
/** Generate a random integer between two numbers. */
|
|
3
3
|
export declare const getRandom: (min: number, max: number) => number;
|
|
4
4
|
/**
|
|
5
|
-
* Make a random key, e.g.
|
|
5
|
+
* Make a random key, e.g. `xs23r34hhsdx` or `e4m29klrugef`
|
|
6
6
|
* - Not designed to be cryptographically random!
|
|
7
7
|
* - Will probably clash — if you're making a random ID, check for existence of the record before saving.
|
|
8
|
+
* - Designed to be semi-readable, doesn't use capital letters or `i` or `o` or `l` or `u`
|
|
8
9
|
*/
|
|
9
10
|
export declare const getRandomKey: (length?: number) => string;
|
|
10
11
|
/** Get a random character from a string. */
|
package/util/random.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { yieldUntilLimit, yieldCall } from "./iterate.js";
|
|
2
|
-
import {
|
|
2
|
+
import { joinStrings } from "./string.js";
|
|
3
3
|
import { getDefined } from "./undefined.js";
|
|
4
4
|
/** Generate a random integer between two numbers. */
|
|
5
5
|
export const getRandom = (min, max) => Math.round(Math.random() * (max - min) + min);
|
|
6
6
|
/**
|
|
7
|
-
* Make a random key, e.g.
|
|
7
|
+
* Make a random key, e.g. `xs23r34hhsdx` or `e4m29klrugef`
|
|
8
8
|
* - Not designed to be cryptographically random!
|
|
9
9
|
* - Will probably clash — if you're making a random ID, check for existence of the record before saving.
|
|
10
|
+
* - Designed to be semi-readable, doesn't use capital letters or `i` or `o` or `l` or `u`
|
|
10
11
|
*/
|
|
11
|
-
export const getRandomKey = (length =
|
|
12
|
+
export const getRandomKey = (length = 12) => joinStrings(yieldUntilLimit(yieldCall(getRandomCharacter, KEY_CHARS), length));
|
|
12
13
|
const KEY_CHARS = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
13
14
|
/** Get a random character from a string. */
|
|
14
15
|
export const getRandomCharacter = (str) => str[getRandom(0, str.length - 1)];
|
package/util/search.d.ts
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import type { ImmutableArray } from "./array.js";
|
|
2
2
|
import { Matchable } from "./filter.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Convert a string to a regular expression that matches that string.
|
|
5
5
|
*
|
|
6
|
-
* @param
|
|
7
|
-
*
|
|
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
|
|
6
|
+
* @param str The input string.
|
|
7
|
+
* @param flags RegExp flags that are passed into the created RegExp.
|
|
13
8
|
*/
|
|
14
|
-
export declare
|
|
9
|
+
export declare const toRegExp: (str: string, flags?: string) => RegExp;
|
|
10
|
+
/** Escape special characters in a string regular expression. */
|
|
11
|
+
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
|
+
/** Create regular expression that matches a block of content. */
|
|
20
|
+
export declare const getBlockRegExp: (middle?: string, end?: string, start?: string) => RegExp;
|
|
21
|
+
/** Create regular expression that matches a line of content. */
|
|
22
|
+
export declare const getLineRegExp: (middle?: string, end?: string, start?: string) => RegExp;
|
|
23
|
+
/** Create regular expression that matches piece of text wrapped by a set of characters. */
|
|
24
|
+
export declare const getWrapRegExp: (chars: string, middle?: string) => RegExp;
|
|
15
25
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
26
|
+
* Convert a string query to the corresponding set of case-insensitive regular expressions.
|
|
27
|
+
* - Splies the query into words (respecting "quoted phrases").
|
|
28
|
+
* - Return a regex for each word or quoted phrase.
|
|
29
|
+
* - Unquoted words match partially (starting with a word boundary).
|
|
30
|
+
* - Quoted phrases match fully (starting and ending with a word boundary).
|
|
21
31
|
*/
|
|
22
|
-
export declare
|
|
32
|
+
export declare const toWordRegExps: (query: string) => ImmutableArray<RegExp>;
|
|
33
|
+
/** Convert a string to a regular expression matching the start of a word boundary. */
|
|
34
|
+
export declare const toWordRegExp: (word: string) => RegExp;
|
|
23
35
|
/**
|
|
24
36
|
* Match an item matching all words in a query.
|
|
25
37
|
*
|
|
@@ -27,17 +39,14 @@ export declare function MATCHES_ALL(item: unknown, regexps: ImmutableArray<RegEx
|
|
|
27
39
|
* @param query The query string to search for.
|
|
28
40
|
* - Supports `"compound queries"` with quotes.
|
|
29
41
|
*/
|
|
30
|
-
export declare function
|
|
42
|
+
export declare function matchesAll(item: unknown, regexps: Iterable<RegExp>): boolean;
|
|
31
43
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* - Quoted phrases match fully (starting and ending with a word boundary).
|
|
44
|
+
* Match an item any of several regular expressions.
|
|
45
|
+
*
|
|
46
|
+
* @param item The item to search for the query in.
|
|
47
|
+
* @param regexps An iterable set of regular expressions.
|
|
37
48
|
*/
|
|
38
|
-
export declare
|
|
39
|
-
/** Convert a string to a regular expression matching the start of a word boundary. */
|
|
40
|
-
export declare const toWordRegExp: (word: string) => RegExp;
|
|
49
|
+
export declare function matchesAny(item: unknown, regexps: Iterable<RegExp>): boolean;
|
|
41
50
|
/** Matcher that matches any words in a string. */
|
|
42
51
|
export declare class MatchAnyWord implements Matchable<unknown, void> {
|
|
43
52
|
private _regexps;
|
package/util/search.js
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
|
-
import { toWords,
|
|
1
|
+
import { toWords, normalizeString } from "./string.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Convert a string to a regular expression that matches that string.
|
|
4
4
|
*
|
|
5
|
-
* @param
|
|
6
|
-
*
|
|
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
|
|
5
|
+
* @param str The input string.
|
|
6
|
+
* @param flags RegExp flags that are passed into the created RegExp.
|
|
12
7
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
export const toRegExp = (str, flags = "") => new RegExp(escapeRegExp(str), flags);
|
|
9
|
+
/** Escape special characters in a string regular expression. */
|
|
10
|
+
export const escapeRegExp = (str) => str.replace(REPLACE_ESCAPED, "\\$&");
|
|
11
|
+
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
|
+
/** 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})`);
|
|
22
|
+
/** 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})`);
|
|
24
|
+
/** 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`);
|
|
26
|
+
/**
|
|
27
|
+
* Convert a string query to the corresponding set of case-insensitive regular expressions.
|
|
28
|
+
* - Splies the query into words (respecting "quoted phrases").
|
|
29
|
+
* - Return a regex for each word or quoted phrase.
|
|
30
|
+
* - Unquoted words match partially (starting with a word boundary).
|
|
31
|
+
* - Quoted phrases match fully (starting and ending with a word boundary).
|
|
32
|
+
*/
|
|
33
|
+
export const toWordRegExps = (query) => toWords(query).map(toWordRegExp);
|
|
34
|
+
/** Convert a string to a regular expression matching the start of a word boundary. */
|
|
35
|
+
export const toWordRegExp = (word) => new RegExp(`\\b${escapeRegExp(normalizeString(word))}`, "i");
|
|
16
36
|
/**
|
|
17
37
|
* Match an item matching all words in a query.
|
|
18
38
|
*
|
|
@@ -20,46 +40,37 @@ export function MATCHES(item, regexp) {
|
|
|
20
40
|
* @param query The query string to search for.
|
|
21
41
|
* - Supports `"compound queries"` with quotes.
|
|
22
42
|
*/
|
|
23
|
-
export function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
export function matchesAll(item, regexps) {
|
|
44
|
+
let count = 0;
|
|
45
|
+
if (typeof item === "string") {
|
|
46
|
+
for (const regexp of regexps) {
|
|
47
|
+
count++;
|
|
48
|
+
if (!regexp.test(item))
|
|
27
49
|
return false;
|
|
28
|
-
|
|
50
|
+
}
|
|
29
51
|
}
|
|
30
|
-
return false;
|
|
52
|
+
return count ? true : false;
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
33
|
-
* Match an item
|
|
55
|
+
* Match an item any of several regular expressions.
|
|
34
56
|
*
|
|
35
57
|
* @param item The item to search for the query in.
|
|
36
|
-
* @param
|
|
37
|
-
* - Supports `"compound queries"` with quotes.
|
|
58
|
+
* @param regexps An iterable set of regular expressions.
|
|
38
59
|
*/
|
|
39
|
-
export function
|
|
60
|
+
export function matchesAny(item, regexps) {
|
|
40
61
|
if (typeof item === "string")
|
|
41
62
|
for (const regexp of regexps)
|
|
42
|
-
if (
|
|
63
|
+
if (regexp.test(item))
|
|
43
64
|
return true;
|
|
44
65
|
return false;
|
|
45
66
|
}
|
|
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
67
|
/** Matcher that matches any words in a string. */
|
|
57
68
|
export class MatchAnyWord {
|
|
58
69
|
constructor(words) {
|
|
59
70
|
this._regexps = toWordRegExps(words);
|
|
60
71
|
}
|
|
61
72
|
match(value) {
|
|
62
|
-
return
|
|
73
|
+
return matchesAny(value, this._regexps);
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
/** Matcher that matches all words in a string. */
|
|
@@ -68,7 +79,7 @@ export class MatchAllWords {
|
|
|
68
79
|
this._regexps = toWordRegExps(words);
|
|
69
80
|
}
|
|
70
81
|
match(value) {
|
|
71
|
-
return
|
|
82
|
+
return matchesAll(value, this._regexps);
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
/** Matcher that matches an exact phrase. */
|
|
@@ -77,6 +88,6 @@ export class MatchWord {
|
|
|
77
88
|
this._regexp = toWordRegExp(phrase);
|
|
78
89
|
}
|
|
79
90
|
match(value) {
|
|
80
|
-
return
|
|
91
|
+
return this._regexp.test(value);
|
|
81
92
|
}
|
|
82
93
|
}
|
package/util/string.d.ts
CHANGED
|
@@ -16,8 +16,6 @@ export declare const isString: (v: unknown) => v is string;
|
|
|
16
16
|
* -
|
|
17
17
|
*/
|
|
18
18
|
export declare function toString(value: unknown): string;
|
|
19
|
-
/** Concatenate a set of potential strings together. */
|
|
20
|
-
export declare function concatStrings(values: Iterable<unknown>): string;
|
|
21
19
|
/**
|
|
22
20
|
* Convert an unknown value into a title string for user-facing use.
|
|
23
21
|
* - Strings return the string.
|
|
@@ -32,6 +30,8 @@ export declare function concatStrings(values: Iterable<unknown>): string;
|
|
|
32
30
|
* - Everything else returns `"Unknown"`
|
|
33
31
|
*/
|
|
34
32
|
export declare function toTitle(value: unknown): string;
|
|
33
|
+
/** Concatenate an iterable set of strings together. */
|
|
34
|
+
export declare function joinStrings(strs: Iterable<string>, joiner?: string): string;
|
|
35
35
|
/**
|
|
36
36
|
* Sanitize unexpected characters from a string by:
|
|
37
37
|
* - Stripping control characters.
|
|
@@ -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
|
@@ -32,13 +32,6 @@ export function toString(value) {
|
|
|
32
32
|
return value.name || "function";
|
|
33
33
|
return typeof value; // "symbol" etc.
|
|
34
34
|
}
|
|
35
|
-
/** Concatenate a set of potential strings together. */
|
|
36
|
-
export function concatStrings(values) {
|
|
37
|
-
let output = "";
|
|
38
|
-
for (const value of values)
|
|
39
|
-
output += toString(value);
|
|
40
|
-
return output;
|
|
41
|
-
}
|
|
42
35
|
/**
|
|
43
36
|
* Convert an unknown value into a title string for user-facing use.
|
|
44
37
|
* - Strings return the string.
|
|
@@ -73,6 +66,13 @@ export function toTitle(value) {
|
|
|
73
66
|
return "None";
|
|
74
67
|
return "Unknown";
|
|
75
68
|
}
|
|
69
|
+
/** Concatenate an iterable set of strings together. */
|
|
70
|
+
export function joinStrings(strs, joiner = "") {
|
|
71
|
+
let output = "";
|
|
72
|
+
for (const str of strs)
|
|
73
|
+
output += `${output.length ? joiner : ""}${str}`;
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
76
|
/**
|
|
77
77
|
* Sanitize unexpected characters from a string by:
|
|
78
78
|
* - Stripping control characters.
|
|
@@ -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? */
|