@naturalcycles/js-lib 14.107.0 → 14.108.1

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.
@@ -36,4 +36,4 @@ export declare function _isErrorObject(o: any): o is ErrorObject;
36
36
  * })
37
37
  * }
38
38
  */
39
- export declare function _errorDataAppend(err: Error, data: ErrorData): void;
39
+ export declare function _errorDataAppend(err: any, data: ErrorData): void;
@@ -121,7 +121,6 @@ exports._isErrorObject = _isErrorObject;
121
121
  * }
122
122
  */
123
123
  function _errorDataAppend(err, data) {
124
- ;
125
124
  err.data = {
126
125
  ...err.data,
127
126
  ...data,
package/dist/index.d.ts CHANGED
@@ -49,6 +49,9 @@ import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout';
49
49
  export * from './string/case';
50
50
  export * from './string/json.util';
51
51
  export * from './string/string.util';
52
+ export * from './string/readingTime';
53
+ export * from './string/escape';
54
+ export * from './string/pupa';
52
55
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny';
53
56
  export * from './time/time.util';
54
57
  export * from './is.util';
package/dist/index.js CHANGED
@@ -63,6 +63,9 @@ Object.defineProperty(exports, "pTimeoutFn", { enumerable: true, get: function (
63
63
  tslib_1.__exportStar(require("./string/case"), exports);
64
64
  tslib_1.__exportStar(require("./string/json.util"), exports);
65
65
  tslib_1.__exportStar(require("./string/string.util"), exports);
66
+ tslib_1.__exportStar(require("./string/readingTime"), exports);
67
+ tslib_1.__exportStar(require("./string/escape"), exports);
68
+ tslib_1.__exportStar(require("./string/pupa"), exports);
66
69
  const stringifyAny_1 = require("./string/stringifyAny");
67
70
  Object.defineProperty(exports, "_stringifyAny", { enumerable: true, get: function () { return stringifyAny_1._stringifyAny; } });
68
71
  tslib_1.__exportStar(require("./time/time.util"), exports);
@@ -0,0 +1,2 @@
1
+ export declare function htmlEscape(strings: string | TemplateStringsArray, ...values: any[]): string;
2
+ export declare function htmlUnescape(strings: string | TemplateStringsArray, ...values: any[]): string;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored:
5
+ https://github.com/sindresorhus/escape-goat
6
+
7
+ (c) Sindre Sorhus
8
+
9
+ Reasons:
10
+ 1. Stable enough to be included in "core" js-lib
11
+ 2. ESM-only
12
+
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.htmlUnescape = exports.htmlEscape = void 0;
16
+ // Multiple `.replace()` calls are actually faster than using replacer functions
17
+ function _htmlEscape(s) {
18
+ return s
19
+ .replace(/&/g, '&') // Must happen first or else it will escape other just-escaped characters.
20
+ .replace(/"/g, '"')
21
+ .replace(/'/g, ''')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;');
24
+ }
25
+ function _htmlUnescape(html) {
26
+ return html
27
+ .replace(/&gt;/g, '>')
28
+ .replace(/&lt;/g, '<')
29
+ .replace(/&#0?39;/g, "'")
30
+ .replace(/&quot;/g, '"')
31
+ .replace(/&amp;/g, '&'); // Must happen last or else it will unescape other characters in the wrong order.
32
+ }
33
+ function htmlEscape(strings, ...values) {
34
+ if (typeof strings === 'string') {
35
+ return _htmlEscape(strings);
36
+ }
37
+ let output = strings[0];
38
+ for (const [index, value] of values.entries()) {
39
+ output = output + _htmlEscape(String(value)) + strings[index + 1];
40
+ }
41
+ return output;
42
+ }
43
+ exports.htmlEscape = htmlEscape;
44
+ function htmlUnescape(strings, ...values) {
45
+ if (typeof strings === 'string') {
46
+ return _htmlUnescape(strings);
47
+ }
48
+ let output = strings[0];
49
+ for (const [index, value] of values.entries()) {
50
+ output = output + _htmlUnescape(String(value)) + strings[index + 1];
51
+ }
52
+ return output;
53
+ }
54
+ exports.htmlUnescape = htmlUnescape;
@@ -0,0 +1,22 @@
1
+ import type { AnyObject } from '../types';
2
+ export declare class MissingValueError extends Error {
3
+ key: any;
4
+ constructor(key: any);
5
+ }
6
+ export interface PupaOptions {
7
+ /**
8
+ * By default, Pupa throws a `MissingValueError` when a placeholder resolves to `undefined`. With this option set to `true`, it simply ignores it and leaves the placeholder as is.
9
+ */
10
+ ignoreMissing?: boolean;
11
+ /**
12
+ * Performs arbitrary operation for each interpolation. If the returned value was `undefined`, it behaves differently depending on the `ignoreMissing` option. Otherwise, the returned value will be interpolated into a string (and escaped when double-braced) and embedded into the template.
13
+ */
14
+ transform?: (data: {
15
+ value: any;
16
+ key: string;
17
+ }) => unknown;
18
+ }
19
+ /**
20
+ * API: https://github.com/sindresorhus/pupa
21
+ */
22
+ export declare function pupa(template: string, data: any[] | AnyObject, opt?: PupaOptions): string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored:
5
+ https://github.com/sindresorhus/pupa
6
+
7
+ (c) Sindre Sorhus
8
+
9
+ Reasons:
10
+ 1. Stable enough to be included in "core" js-lib
11
+ 2. ESM-only
12
+
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.pupa = exports.MissingValueError = void 0;
16
+ const escape_1 = require("./escape");
17
+ class MissingValueError extends Error {
18
+ constructor(key) {
19
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`);
20
+ this.key = key;
21
+ this.name = 'MissingValueError';
22
+ this.key = key;
23
+ }
24
+ }
25
+ exports.MissingValueError = MissingValueError;
26
+ /**
27
+ * API: https://github.com/sindresorhus/pupa
28
+ */
29
+ function pupa(template, data, opt = {}) {
30
+ if (typeof template !== 'string') {
31
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``);
32
+ }
33
+ if (typeof data !== 'object') {
34
+ throw new TypeError(`Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``);
35
+ }
36
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt;
37
+ const replace = (placeholder, key) => {
38
+ let value = data;
39
+ for (const property of key.split('.')) {
40
+ value = value ? value[property] : undefined;
41
+ }
42
+ const transformedValue = transform({ value, key });
43
+ if (transformedValue === undefined) {
44
+ if (ignoreMissing) {
45
+ return placeholder;
46
+ }
47
+ throw new MissingValueError(key);
48
+ }
49
+ return String(transformedValue);
50
+ };
51
+ const composeHtmlEscape = (replacer) => (...args) => (0, escape_1.htmlEscape)(replacer(...args));
52
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
53
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi;
54
+ if (doubleBraceRegex.test(template)) {
55
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace));
56
+ }
57
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi;
58
+ return template.replace(braceRegex, replace);
59
+ }
60
+ exports.pupa = pupa;
@@ -0,0 +1,29 @@
1
+ import type { Integer } from '../types';
2
+ export interface ReadingTimeOptions {
3
+ /**
4
+ * A function that returns a boolean value depending on if a character is considered as a word bound.
5
+ * Default: spaces, new lines and tabs
6
+ */
7
+ wordBound?: (char: string) => boolean;
8
+ /**
9
+ * Default 200
10
+ */
11
+ wordsPerMinute?: number;
12
+ }
13
+ export interface ReadingTimeStats {
14
+ /**
15
+ * Number of milliseconds.
16
+ */
17
+ time: Integer;
18
+ minutes: Integer;
19
+ }
20
+ export interface WordCountStats {
21
+ total: number;
22
+ }
23
+ export interface ReadingTimeResult extends ReadingTimeStats {
24
+ words: WordCountStats;
25
+ }
26
+ /**
27
+ * API: https://github.com/ngryman/reading-time
28
+ */
29
+ export declare function readingTime(text: string, options?: ReadingTimeOptions): ReadingTimeResult;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored from https://github.com/ngryman/reading-time
5
+ (c) Nicolas Gryman <ngryman@gmail.com>
6
+
7
+ Reasons:
8
+ 1. latest (1.5.0) version had a typescript import bug.
9
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
10
+
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.readingTime = void 0;
14
+ function codeIsInRanges(num, arrayOfRanges) {
15
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= num && num <= upperBound);
16
+ }
17
+ const isCJK = c => {
18
+ const charCode = c.codePointAt(0);
19
+ // Help wanted!
20
+ // This should be good for most cases, but if you find it unsatisfactory
21
+ // (e.g. some other language where each character should be standalone words),
22
+ // contributions welcome!
23
+ return codeIsInRanges(charCode, [
24
+ // Hiragana (Katakana not included on purpose,
25
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
26
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
27
+ [0x3040, 0x309f],
28
+ // CJK Unified ideographs
29
+ [0x4e00, 0x9fff],
30
+ // Hangul
31
+ [0xac00, 0xd7a3],
32
+ // CJK extensions
33
+ [0x20000, 0x2ebe0],
34
+ ]);
35
+ };
36
+ const isAnsiWordBound = c => {
37
+ return ' \n\r\t'.includes(c);
38
+ };
39
+ const isPunctuation = c => {
40
+ const charCode = c.codePointAt(0);
41
+ return codeIsInRanges(charCode, [
42
+ [0x21, 0x2f],
43
+ [0x3a, 0x40],
44
+ [0x5b, 0x60],
45
+ [0x7b, 0x7e],
46
+ // CJK Symbols and Punctuation
47
+ [0x3000, 0x303f],
48
+ // Full-width ASCII punctuation variants
49
+ [0xff00, 0xffef],
50
+ ]);
51
+ };
52
+ function countWords(text, options = {}) {
53
+ let words = 0;
54
+ let start = 0;
55
+ let end = text.length - 1;
56
+ const isWordBound = options.wordBound || isAnsiWordBound;
57
+ // fetch bounds
58
+ while (isWordBound(text[start]))
59
+ start++;
60
+ while (isWordBound(text[end]))
61
+ end--;
62
+ // Add a trailing word bound to make handling edges more convenient
63
+ const normalizedText = `${text}\n`;
64
+ // calculate the number of words
65
+ for (let i = start; i <= end; i++) {
66
+ // A CJK character is a always word;
67
+ // A non-word bound followed by a word bound / CJK is the end of a word.
68
+ if (isCJK(normalizedText[i]) ||
69
+ (!isWordBound(normalizedText[i]) &&
70
+ (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1])))) {
71
+ words++;
72
+ }
73
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
74
+ if (isCJK(normalizedText[i])) {
75
+ while (i <= end &&
76
+ (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) {
77
+ i++;
78
+ }
79
+ }
80
+ }
81
+ return { total: words };
82
+ }
83
+ function readingTimeWithCount(words, options = {}) {
84
+ const { wordsPerMinute = 200 } = options;
85
+ // reading time stats
86
+ const minutes = words.total / wordsPerMinute;
87
+ // Math.round used to resolve floating point funkiness
88
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
89
+ const time = Math.round(minutes * 60 * 1000);
90
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)));
91
+ return {
92
+ minutes: displayed,
93
+ time,
94
+ };
95
+ }
96
+ /**
97
+ * API: https://github.com/ngryman/reading-time
98
+ */
99
+ function readingTime(text, options = {}) {
100
+ const words = countWords(text, options);
101
+ return {
102
+ ...readingTimeWithCount(words, options),
103
+ words,
104
+ };
105
+ }
106
+ exports.readingTime = readingTime;
@@ -109,6 +109,5 @@ export function _isErrorObject(o) {
109
109
  * }
110
110
  */
111
111
  export function _errorDataAppend(err, data) {
112
- ;
113
112
  err.data = Object.assign(Object.assign({}, err.data), data);
114
113
  }
package/dist-esm/index.js CHANGED
@@ -46,6 +46,9 @@ import { pTimeout, pTimeoutFn } from './promise/pTimeout';
46
46
  export * from './string/case';
47
47
  export * from './string/json.util';
48
48
  export * from './string/string.util';
49
+ export * from './string/readingTime';
50
+ export * from './string/escape';
51
+ export * from './string/pupa';
49
52
  import { _stringifyAny } from './string/stringifyAny';
50
53
  export * from './time/time.util';
51
54
  export * from './is.util';
@@ -0,0 +1,49 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/escape-goat
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+ // Multiple `.replace()` calls are actually faster than using replacer functions
14
+ function _htmlEscape(s) {
15
+ return s
16
+ .replace(/&/g, '&amp;') // Must happen first or else it will escape other just-escaped characters.
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/'/g, '&#39;')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;');
21
+ }
22
+ function _htmlUnescape(html) {
23
+ return html
24
+ .replace(/&gt;/g, '>')
25
+ .replace(/&lt;/g, '<')
26
+ .replace(/&#0?39;/g, "'")
27
+ .replace(/&quot;/g, '"')
28
+ .replace(/&amp;/g, '&'); // Must happen last or else it will unescape other characters in the wrong order.
29
+ }
30
+ export function htmlEscape(strings, ...values) {
31
+ if (typeof strings === 'string') {
32
+ return _htmlEscape(strings);
33
+ }
34
+ let output = strings[0];
35
+ for (const [index, value] of values.entries()) {
36
+ output = output + _htmlEscape(String(value)) + strings[index + 1];
37
+ }
38
+ return output;
39
+ }
40
+ export function htmlUnescape(strings, ...values) {
41
+ if (typeof strings === 'string') {
42
+ return _htmlUnescape(strings);
43
+ }
44
+ let output = strings[0];
45
+ for (const [index, value] of values.entries()) {
46
+ output = output + _htmlUnescape(String(value)) + strings[index + 1];
47
+ }
48
+ return output;
49
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/pupa
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+ import { htmlEscape } from './escape';
14
+ export class MissingValueError extends Error {
15
+ constructor(key) {
16
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`);
17
+ this.key = key;
18
+ this.name = 'MissingValueError';
19
+ this.key = key;
20
+ }
21
+ }
22
+ /**
23
+ * API: https://github.com/sindresorhus/pupa
24
+ */
25
+ export function pupa(template, data, opt = {}) {
26
+ if (typeof template !== 'string') {
27
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``);
28
+ }
29
+ if (typeof data !== 'object') {
30
+ throw new TypeError(`Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``);
31
+ }
32
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt;
33
+ const replace = (placeholder, key) => {
34
+ let value = data;
35
+ for (const property of key.split('.')) {
36
+ value = value ? value[property] : undefined;
37
+ }
38
+ const transformedValue = transform({ value, key });
39
+ if (transformedValue === undefined) {
40
+ if (ignoreMissing) {
41
+ return placeholder;
42
+ }
43
+ throw new MissingValueError(key);
44
+ }
45
+ return String(transformedValue);
46
+ };
47
+ const composeHtmlEscape = (replacer) => (...args) => htmlEscape(replacer(...args));
48
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
49
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi;
50
+ if (doubleBraceRegex.test(template)) {
51
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace));
52
+ }
53
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi;
54
+ return template.replace(braceRegex, replace);
55
+ }
@@ -0,0 +1,99 @@
1
+ /*
2
+
3
+ Vendored from https://github.com/ngryman/reading-time
4
+ (c) Nicolas Gryman <ngryman@gmail.com>
5
+
6
+ Reasons:
7
+ 1. latest (1.5.0) version had a typescript import bug.
8
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
9
+
10
+ */
11
+ function codeIsInRanges(num, arrayOfRanges) {
12
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= num && num <= upperBound);
13
+ }
14
+ const isCJK = c => {
15
+ const charCode = c.codePointAt(0);
16
+ // Help wanted!
17
+ // This should be good for most cases, but if you find it unsatisfactory
18
+ // (e.g. some other language where each character should be standalone words),
19
+ // contributions welcome!
20
+ return codeIsInRanges(charCode, [
21
+ // Hiragana (Katakana not included on purpose,
22
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
23
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
24
+ [0x3040, 0x309f],
25
+ // CJK Unified ideographs
26
+ [0x4e00, 0x9fff],
27
+ // Hangul
28
+ [0xac00, 0xd7a3],
29
+ // CJK extensions
30
+ [0x20000, 0x2ebe0],
31
+ ]);
32
+ };
33
+ const isAnsiWordBound = c => {
34
+ return ' \n\r\t'.includes(c);
35
+ };
36
+ const isPunctuation = c => {
37
+ const charCode = c.codePointAt(0);
38
+ return codeIsInRanges(charCode, [
39
+ [0x21, 0x2f],
40
+ [0x3a, 0x40],
41
+ [0x5b, 0x60],
42
+ [0x7b, 0x7e],
43
+ // CJK Symbols and Punctuation
44
+ [0x3000, 0x303f],
45
+ // Full-width ASCII punctuation variants
46
+ [0xff00, 0xffef],
47
+ ]);
48
+ };
49
+ function countWords(text, options = {}) {
50
+ let words = 0;
51
+ let start = 0;
52
+ let end = text.length - 1;
53
+ const isWordBound = options.wordBound || isAnsiWordBound;
54
+ // fetch bounds
55
+ while (isWordBound(text[start]))
56
+ start++;
57
+ while (isWordBound(text[end]))
58
+ end--;
59
+ // Add a trailing word bound to make handling edges more convenient
60
+ const normalizedText = `${text}\n`;
61
+ // calculate the number of words
62
+ for (let i = start; i <= end; i++) {
63
+ // A CJK character is a always word;
64
+ // A non-word bound followed by a word bound / CJK is the end of a word.
65
+ if (isCJK(normalizedText[i]) ||
66
+ (!isWordBound(normalizedText[i]) &&
67
+ (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1])))) {
68
+ words++;
69
+ }
70
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
71
+ if (isCJK(normalizedText[i])) {
72
+ while (i <= end &&
73
+ (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) {
74
+ i++;
75
+ }
76
+ }
77
+ }
78
+ return { total: words };
79
+ }
80
+ function readingTimeWithCount(words, options = {}) {
81
+ const { wordsPerMinute = 200 } = options;
82
+ // reading time stats
83
+ const minutes = words.total / wordsPerMinute;
84
+ // Math.round used to resolve floating point funkiness
85
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
86
+ const time = Math.round(minutes * 60 * 1000);
87
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)));
88
+ return {
89
+ minutes: displayed,
90
+ time,
91
+ };
92
+ }
93
+ /**
94
+ * API: https://github.com/ngryman/reading-time
95
+ */
96
+ export function readingTime(text, options = {}) {
97
+ const words = countWords(text, options);
98
+ return Object.assign(Object.assign({}, readingTimeWithCount(words, options)), { words });
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.107.0",
3
+ "version": "14.108.1",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -158,9 +158,9 @@ export function _isErrorObject(o: any): o is ErrorObject {
158
158
  * })
159
159
  * }
160
160
  */
161
- export function _errorDataAppend(err: Error, data: ErrorData): void {
162
- ;(err as any).data = {
163
- ...(err as any).data,
161
+ export function _errorDataAppend(err: any, data: ErrorData): void {
162
+ err.data = {
163
+ ...err.data,
164
164
  ...data,
165
165
  }
166
166
  }
package/src/index.ts CHANGED
@@ -82,6 +82,9 @@ import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout'
82
82
  export * from './string/case'
83
83
  export * from './string/json.util'
84
84
  export * from './string/string.util'
85
+ export * from './string/readingTime'
86
+ export * from './string/escape'
87
+ export * from './string/pupa'
85
88
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny'
86
89
  export * from './time/time.util'
87
90
  export * from './is.util'
@@ -0,0 +1,57 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/escape-goat
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+
14
+ // Multiple `.replace()` calls are actually faster than using replacer functions
15
+ function _htmlEscape(s: string): string {
16
+ return s
17
+ .replace(/&/g, '&amp;') // Must happen first or else it will escape other just-escaped characters.
18
+ .replace(/"/g, '&quot;')
19
+ .replace(/'/g, '&#39;')
20
+ .replace(/</g, '&lt;')
21
+ .replace(/>/g, '&gt;')
22
+ }
23
+
24
+ function _htmlUnescape(html: string): string {
25
+ return html
26
+ .replace(/&gt;/g, '>')
27
+ .replace(/&lt;/g, '<')
28
+ .replace(/&#0?39;/g, "'")
29
+ .replace(/&quot;/g, '"')
30
+ .replace(/&amp;/g, '&') // Must happen last or else it will unescape other characters in the wrong order.
31
+ }
32
+
33
+ export function htmlEscape(strings: string | TemplateStringsArray, ...values: any[]): string {
34
+ if (typeof strings === 'string') {
35
+ return _htmlEscape(strings)
36
+ }
37
+
38
+ let output = strings[0]!
39
+ for (const [index, value] of values.entries()) {
40
+ output = output + _htmlEscape(String(value)) + strings[index + 1]
41
+ }
42
+
43
+ return output
44
+ }
45
+
46
+ export function htmlUnescape(strings: string | TemplateStringsArray, ...values: any[]): string {
47
+ if (typeof strings === 'string') {
48
+ return _htmlUnescape(strings)
49
+ }
50
+
51
+ let output = strings[0]!
52
+ for (const [index, value] of values.entries()) {
53
+ output = output + _htmlUnescape(String(value)) + strings[index + 1]
54
+ }
55
+
56
+ return output
57
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/pupa
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+
14
+ import type { AnyObject } from '../types'
15
+ import { htmlEscape } from './escape'
16
+
17
+ export class MissingValueError extends Error {
18
+ constructor(public key: any) {
19
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`)
20
+ this.name = 'MissingValueError'
21
+ this.key = key
22
+ }
23
+ }
24
+
25
+ export interface PupaOptions {
26
+ /**
27
+ * By default, Pupa throws a `MissingValueError` when a placeholder resolves to `undefined`. With this option set to `true`, it simply ignores it and leaves the placeholder as is.
28
+ */
29
+ ignoreMissing?: boolean
30
+
31
+ /**
32
+ * Performs arbitrary operation for each interpolation. If the returned value was `undefined`, it behaves differently depending on the `ignoreMissing` option. Otherwise, the returned value will be interpolated into a string (and escaped when double-braced) and embedded into the template.
33
+ */
34
+ transform?: (data: { value: any; key: string }) => unknown
35
+ }
36
+
37
+ /**
38
+ * API: https://github.com/sindresorhus/pupa
39
+ */
40
+ export function pupa(template: string, data: any[] | AnyObject, opt: PupaOptions = {}): string {
41
+ if (typeof template !== 'string') {
42
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``)
43
+ }
44
+
45
+ if (typeof data !== 'object') {
46
+ throw new TypeError(
47
+ `Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``,
48
+ )
49
+ }
50
+
51
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt
52
+
53
+ const replace = (placeholder: string, key: string): string => {
54
+ let value = data
55
+ for (const property of key.split('.')) {
56
+ value = value ? (value as AnyObject)[property] : undefined
57
+ }
58
+
59
+ const transformedValue = transform({ value, key })
60
+ if (transformedValue === undefined) {
61
+ if (ignoreMissing) {
62
+ return placeholder
63
+ }
64
+
65
+ throw new MissingValueError(key)
66
+ }
67
+
68
+ return String(transformedValue)
69
+ }
70
+
71
+ const composeHtmlEscape =
72
+ (replacer: any) =>
73
+ (...args: any[]) =>
74
+ htmlEscape(replacer(...args))
75
+
76
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
77
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi
78
+
79
+ if (doubleBraceRegex.test(template)) {
80
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace))
81
+ }
82
+
83
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi
84
+
85
+ return template.replace(braceRegex, replace)
86
+ }
@@ -0,0 +1,150 @@
1
+ /*
2
+
3
+ Vendored from https://github.com/ngryman/reading-time
4
+ (c) Nicolas Gryman <ngryman@gmail.com>
5
+
6
+ Reasons:
7
+ 1. latest (1.5.0) version had a typescript import bug.
8
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
9
+
10
+ */
11
+
12
+ import type { Integer } from '../types'
13
+
14
+ export interface ReadingTimeOptions {
15
+ /**
16
+ * A function that returns a boolean value depending on if a character is considered as a word bound.
17
+ * Default: spaces, new lines and tabs
18
+ */
19
+ wordBound?: (char: string) => boolean
20
+ /**
21
+ * Default 200
22
+ */
23
+ wordsPerMinute?: number
24
+ }
25
+
26
+ export interface ReadingTimeStats {
27
+ /**
28
+ * Number of milliseconds.
29
+ */
30
+ time: Integer
31
+ minutes: Integer
32
+ }
33
+
34
+ export interface WordCountStats {
35
+ total: number
36
+ }
37
+
38
+ export interface ReadingTimeResult extends ReadingTimeStats {
39
+ words: WordCountStats
40
+ }
41
+
42
+ type WordBoundFunction = (char: string) => boolean
43
+
44
+ function codeIsInRanges(num: number, arrayOfRanges: number[][]) {
45
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound! <= num && num <= upperBound!)
46
+ }
47
+
48
+ const isCJK: WordBoundFunction = c => {
49
+ const charCode = c.codePointAt(0)!
50
+ // Help wanted!
51
+ // This should be good for most cases, but if you find it unsatisfactory
52
+ // (e.g. some other language where each character should be standalone words),
53
+ // contributions welcome!
54
+ return codeIsInRanges(charCode, [
55
+ // Hiragana (Katakana not included on purpose,
56
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
57
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
58
+ [0x3040, 0x309f],
59
+ // CJK Unified ideographs
60
+ [0x4e00, 0x9fff],
61
+ // Hangul
62
+ [0xac00, 0xd7a3],
63
+ // CJK extensions
64
+ [0x20000, 0x2ebe0],
65
+ ])
66
+ }
67
+
68
+ const isAnsiWordBound: WordBoundFunction = c => {
69
+ return ' \n\r\t'.includes(c)
70
+ }
71
+
72
+ const isPunctuation: WordBoundFunction = c => {
73
+ const charCode = c.codePointAt(0)!
74
+ return codeIsInRanges(charCode, [
75
+ [0x21, 0x2f],
76
+ [0x3a, 0x40],
77
+ [0x5b, 0x60],
78
+ [0x7b, 0x7e],
79
+ // CJK Symbols and Punctuation
80
+ [0x3000, 0x303f],
81
+ // Full-width ASCII punctuation variants
82
+ [0xff00, 0xffef],
83
+ ])
84
+ }
85
+
86
+ function countWords(text: string, options: ReadingTimeOptions = {}): WordCountStats {
87
+ let words = 0
88
+ let start = 0
89
+ let end = text.length - 1
90
+ const isWordBound = options.wordBound || isAnsiWordBound
91
+
92
+ // fetch bounds
93
+ while (isWordBound(text[start]!)) start++
94
+ while (isWordBound(text[end]!)) end--
95
+
96
+ // Add a trailing word bound to make handling edges more convenient
97
+ const normalizedText = `${text}\n`
98
+
99
+ // calculate the number of words
100
+ for (let i = start; i <= end; i++) {
101
+ // A CJK character is a always word;
102
+ // A non-word bound followed by a word bound / CJK is the end of a word.
103
+ if (
104
+ isCJK(normalizedText[i]!) ||
105
+ (!isWordBound(normalizedText[i]!) &&
106
+ (isWordBound(normalizedText[i + 1]!) || isCJK(normalizedText[i + 1]!)))
107
+ ) {
108
+ words++
109
+ }
110
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
111
+ if (isCJK(normalizedText[i]!)) {
112
+ while (
113
+ i <= end &&
114
+ (isPunctuation(normalizedText[i + 1]!) || isWordBound(normalizedText[i + 1]!))
115
+ ) {
116
+ i++
117
+ }
118
+ }
119
+ }
120
+ return { total: words }
121
+ }
122
+
123
+ function readingTimeWithCount(
124
+ words: WordCountStats,
125
+ options: ReadingTimeOptions = {},
126
+ ): ReadingTimeStats {
127
+ const { wordsPerMinute = 200 } = options
128
+ // reading time stats
129
+ const minutes = words.total / wordsPerMinute
130
+ // Math.round used to resolve floating point funkiness
131
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
132
+ const time = Math.round(minutes * 60 * 1000)
133
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)))
134
+
135
+ return {
136
+ minutes: displayed,
137
+ time,
138
+ }
139
+ }
140
+
141
+ /**
142
+ * API: https://github.com/ngryman/reading-time
143
+ */
144
+ export function readingTime(text: string, options: ReadingTimeOptions = {}): ReadingTimeResult {
145
+ const words = countWords(text, options)
146
+ return {
147
+ ...readingTimeWithCount(words, options),
148
+ words,
149
+ }
150
+ }