@pallasoft/ps-utilities 2.22.0 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/common/constants/html-entity-names.d.ts +1 -0
  2. package/dist/common/constants/html-entity-names.js +34 -0
  3. package/dist/common/constants/html-heading-styles.d.ts +1 -0
  4. package/dist/common/constants/html-heading-styles.js +11 -0
  5. package/dist/common/constants/html-text-config.d.ts +76 -0
  6. package/dist/common/constants/html-text-config.js +136 -0
  7. package/dist/common/constants/html-token-kind.d.ts +5 -0
  8. package/dist/common/constants/html-token-kind.js +8 -0
  9. package/dist/common/constants/portable-text-config.d.ts +16 -0
  10. package/dist/common/constants/portable-text-config.js +15 -1
  11. package/dist/common/html-entity-service.d.ts +6 -0
  12. package/dist/common/html-entity-service.js +34 -0
  13. package/dist/common/html-style-service.d.ts +12 -0
  14. package/dist/common/html-style-service.js +92 -0
  15. package/dist/common/html-token-service.d.ts +8 -0
  16. package/dist/common/html-token-service.js +90 -0
  17. package/dist/common/index.d.ts +1 -0
  18. package/dist/common/index.js +1 -0
  19. package/dist/common/portable-text-html-service.d.ts +28 -0
  20. package/dist/common/portable-text-html-service.js +392 -0
  21. package/dist/common/portable-text-link-service.d.ts +7 -0
  22. package/dist/common/portable-text-link-service.js +42 -0
  23. package/dist/common/portable-text-service.d.ts +7 -4
  24. package/dist/common/portable-text-service.js +15 -10
  25. package/dist/common/portable-text-tracking-service.d.ts +7 -0
  26. package/dist/common/portable-text-tracking-service.js +45 -0
  27. package/dist/esm/common/constants/html-entity-names.js +31 -0
  28. package/dist/esm/common/constants/html-heading-styles.js +8 -0
  29. package/dist/esm/common/constants/html-text-config.js +133 -0
  30. package/dist/esm/common/constants/html-token-kind.js +5 -0
  31. package/dist/esm/common/constants/portable-text-config.js +15 -1
  32. package/dist/esm/common/html-entity-service.js +25 -0
  33. package/dist/esm/common/html-style-service.js +85 -0
  34. package/dist/esm/common/html-token-service.js +83 -0
  35. package/dist/esm/common/index.js +1 -0
  36. package/dist/esm/common/portable-text-html-service.js +375 -0
  37. package/dist/esm/common/portable-text-link-service.js +35 -0
  38. package/dist/esm/common/portable-text-service.js +14 -9
  39. package/dist/esm/common/portable-text-tracking-service.js +34 -0
  40. package/dist/esm/models/html-attributes-data.js +1 -0
  41. package/dist/esm/models/html-close-token-data.js +1 -0
  42. package/dist/esm/models/html-context-data.js +1 -0
  43. package/dist/esm/models/html-conversion-data.js +1 -0
  44. package/dist/esm/models/html-declarations-data.js +1 -0
  45. package/dist/esm/models/html-frame-data.js +1 -0
  46. package/dist/esm/models/html-open-token-data.js +1 -0
  47. package/dist/esm/models/html-run-data.js +1 -0
  48. package/dist/esm/models/html-text-token-data.js +1 -0
  49. package/dist/esm/models/html-token-data.js +1 -0
  50. package/dist/esm/models/portable-text-block-link.js +1 -0
  51. package/dist/esm/models/portable-text-content-data.js +1 -0
  52. package/dist/esm/models/portable-text-image-data.js +1 -0
  53. package/dist/index.d.ts +3 -0
  54. package/dist/models/html-attributes-data.d.ts +1 -0
  55. package/dist/models/html-attributes-data.js +2 -0
  56. package/dist/models/html-close-token-data.d.ts +5 -0
  57. package/dist/models/html-close-token-data.js +2 -0
  58. package/dist/models/html-context-data.d.ts +6 -0
  59. package/dist/models/html-context-data.js +2 -0
  60. package/dist/models/html-conversion-data.d.ts +12 -0
  61. package/dist/models/html-conversion-data.js +2 -0
  62. package/dist/models/html-declarations-data.d.ts +1 -0
  63. package/dist/models/html-declarations-data.js +2 -0
  64. package/dist/models/html-frame-data.d.ts +13 -0
  65. package/dist/models/html-frame-data.js +2 -0
  66. package/dist/models/html-open-token-data.d.ts +8 -0
  67. package/dist/models/html-open-token-data.js +2 -0
  68. package/dist/models/html-run-data.d.ts +9 -0
  69. package/dist/models/html-run-data.js +2 -0
  70. package/dist/models/html-text-token-data.d.ts +5 -0
  71. package/dist/models/html-text-token-data.js +2 -0
  72. package/dist/models/html-token-data.d.ts +4 -0
  73. package/dist/models/html-token-data.js +2 -0
  74. package/dist/models/portable-text-block-link.d.ts +7 -0
  75. package/dist/models/portable-text-block-link.js +2 -0
  76. package/dist/models/portable-text-content-data.d.ts +3 -0
  77. package/dist/models/portable-text-content-data.js +2 -0
  78. package/dist/models/portable-text-image-data.d.ts +7 -0
  79. package/dist/models/portable-text-image-data.js +2 -0
  80. package/package.json +2 -2
@@ -0,0 +1,133 @@
1
+ export const HtmlTextConfig = {
2
+ skipTags: [
3
+ 'head',
4
+ 'script',
5
+ 'style',
6
+ 'title',
7
+ 'noscript',
8
+ 'template',
9
+ 'svg',
10
+ 'iframe',
11
+ 'object',
12
+ 'embed',
13
+ 'video',
14
+ 'audio',
15
+ 'canvas',
16
+ 'select',
17
+ 'textarea',
18
+ ],
19
+ rawTextTags: ['script', 'style', 'title', 'textarea'],
20
+ voidTags: [
21
+ 'area',
22
+ 'base',
23
+ 'br',
24
+ 'col',
25
+ 'embed',
26
+ 'hr',
27
+ 'img',
28
+ 'input',
29
+ 'link',
30
+ 'meta',
31
+ 'param',
32
+ 'source',
33
+ 'track',
34
+ 'wbr',
35
+ ],
36
+ blockTags: [
37
+ 'address',
38
+ 'article',
39
+ 'aside',
40
+ 'body',
41
+ 'center',
42
+ 'dd',
43
+ 'details',
44
+ 'div',
45
+ 'dl',
46
+ 'dt',
47
+ 'fieldset',
48
+ 'figcaption',
49
+ 'figure',
50
+ 'footer',
51
+ 'form',
52
+ 'header',
53
+ 'html',
54
+ 'main',
55
+ 'nav',
56
+ 'p',
57
+ 'pre',
58
+ 'section',
59
+ 'summary',
60
+ 'table',
61
+ 'tbody',
62
+ 'td',
63
+ 'tfoot',
64
+ 'th',
65
+ 'thead',
66
+ 'tr',
67
+ ],
68
+ strongTags: ['b', 'strong'],
69
+ emTags: ['i', 'em', 'cite', 'dfn'],
70
+ underlineTags: ['u', 'ins'],
71
+ codeTags: ['code', 'kbd', 'samp', 'tt'],
72
+ linkTag: 'a',
73
+ imageTag: 'img',
74
+ breakTag: 'br',
75
+ ruleTag: 'hr',
76
+ preTag: 'pre',
77
+ quoteTag: 'blockquote',
78
+ listItemTag: 'li',
79
+ orderedListTag: 'ol',
80
+ unorderedListTag: 'ul',
81
+ hiddenClassPattern: /(^|[\s_-])(preheader|pre-header|preview-text|previewtext)([\s_-]|$)/i,
82
+ hiddenDisplay: 'none',
83
+ hiddenVisibility: 'hidden',
84
+ hiddenMso: 'all',
85
+ hiddenAttribute: 'hidden',
86
+ hiddenAria: 'true',
87
+ tinyImageSize: 2,
88
+ importantPattern: /!important/i,
89
+ zeroPattern: /^0(\.0+)?(px|pt|em|rem|%)?$/,
90
+ pixelPattern: /^([0-9.]+)(px)?$/,
91
+ boldWeight: 600,
92
+ largeText: { heading3: 20, heading2: 24, heading1: 30, maxLength: 100 },
93
+ boldHeadingMaxLength: 80,
94
+ headingEndPattern: /[.,;:!?]$/,
95
+ pixelsPerPoint: 1.333,
96
+ pixelsPerEm: 16,
97
+ fontSizePattern: /^([0-9.]+)(px|pt|em|rem)$/,
98
+ commentStart: '<!--',
99
+ commentEnd: '-->',
100
+ declarationSeparator: ';',
101
+ propertySeparator: ':',
102
+ tagSource: '<(\\/?)([a-zA-Z][a-zA-Z0-9:-]*)((?:"[^"]*"|\'[^\']*\'|[^\'">])*)>',
103
+ tagFlags: 'y',
104
+ attributePattern: /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g,
105
+ selfClosingPattern: /\/\s*$/,
106
+ whitespacePattern: /[ \t\r\n\f\u00A0\u2007\u202F]+/g,
107
+ preLineBreakPattern: /\r\n?/g,
108
+ invisiblePattern: /\u00AD|\u034F|[\u200B-\u200F\u2028\u2029\u2060\uFEFF]/g,
109
+ entityPattern: /&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g,
110
+ maxCodePoint: 0x10ffff,
111
+ numericEntityPrefix: '#',
112
+ hexEntityPrefix: '#x',
113
+ rootName: 'root',
114
+ bodyListLevel: 1,
115
+ trailingBreakPattern: /[ \n]+$/,
116
+ leadingBreakPattern: /^[ \n]+/,
117
+ trailingSpacePattern: / +$/,
118
+ leadingSpacePattern: /^ +/,
119
+ nonBreakingPattern: /[\u00A0\u2007\u202F]/g,
120
+ srcAttribute: 'src',
121
+ altAttribute: 'alt',
122
+ hrefAttribute: 'href',
123
+ styleAttribute: 'style',
124
+ classAttribute: 'class',
125
+ widthAttribute: 'width',
126
+ heightAttribute: 'height',
127
+ aria: 'aria-hidden',
128
+ space: ' ',
129
+ newline: '\n',
130
+ trackingLinePattern: /^\[\s*(?:<)?(https?:\/\/[^\s\]>]+?)(?:>)?\s*\]$/i,
131
+ trackingEllipsis: '...',
132
+ trackingEllipsisCharacter: '\u2026',
133
+ };
@@ -0,0 +1,5 @@
1
+ export const HtmlTokenKind = {
2
+ Text: 'text',
3
+ Open: 'open',
4
+ Close: 'close',
5
+ };
@@ -9,7 +9,21 @@ export const PortableTextConfig = {
9
9
  bulletPrefix: '- ',
10
10
  quotePrefix: '> ',
11
11
  numberSuffix: '. ',
12
- keyPrefix: { block: 'b', span: 's' },
12
+ keyPrefix: { block: 'b', span: 's', link: 'l', image: 'i' },
13
+ blockType: Common.PortableTextConfig.blockType,
14
+ spanType: Common.PortableTextConfig.spanType,
15
+ linkType: Common.PortableTextConfig.linkType,
16
+ imageType: Common.PortableTextConfig.imageType,
17
+ decorators: Common.PortableTextConfig.decorators,
18
+ headingStyles: Common.PortableTextConfig.headingStyles,
19
+ messageStyles: Common.PortableTextConfig.messageStyles,
20
+ linkProtocols: Common.PortableTextConfig.linkProtocols,
21
+ maxListLevel: Common.PortableTextConfig.maxListLevel,
22
+ maxImageSrcLength: Common.PortableTextConfig.maxImageSrcLength,
23
+ maxImageAltLength: Common.PortableTextConfig.maxImageAltLength,
24
+ imageProtocolPattern: Common.PortableTextConfig.imageProtocolPattern,
25
+ maxControlCode: 31,
26
+ deleteCode: 127,
13
27
  normalStyle: Common.PortableTextConfig.styles[0],
14
28
  quoteStyle: Common.PortableTextConfig.styles[1],
15
29
  bulletItem: Common.PortableTextConfig.listItems[0],
@@ -0,0 +1,25 @@
1
+ import { HtmlEntityNames } from './constants/html-entity-names.js';
2
+ import { HtmlTextConfig } from './constants/html-text-config.js';
3
+ export class HtmlEntityService {
4
+ static decode(value) {
5
+ return value.replace(HtmlTextConfig.entityPattern, (match, body) => HtmlEntityService.resolve(match, body));
6
+ }
7
+ static resolve(match, body) {
8
+ if (body.startsWith(HtmlTextConfig.hexEntityPrefix))
9
+ return HtmlEntityService.fromCode(match, Number.parseInt(body.slice(2), 16));
10
+ if (body.startsWith(HtmlTextConfig.numericEntityPrefix))
11
+ return HtmlEntityService.fromCode(match, Number.parseInt(body.slice(1), 10));
12
+ if (HtmlEntityService.isNamed(body))
13
+ return HtmlEntityNames[body];
14
+ const lower = body.toLowerCase();
15
+ return HtmlEntityService.isNamed(lower) ? HtmlEntityNames[lower] : match;
16
+ }
17
+ static fromCode(match, code) {
18
+ return Number.isInteger(code) && code > 0 && code <= HtmlTextConfig.maxCodePoint
19
+ ? String.fromCodePoint(code)
20
+ : match;
21
+ }
22
+ static isNamed(name) {
23
+ return Object.prototype.hasOwnProperty.call(HtmlEntityNames, name);
24
+ }
25
+ }
@@ -0,0 +1,85 @@
1
+ import { HtmlTextConfig } from './constants/html-text-config.js';
2
+ export class HtmlStyleService {
3
+ static parse(style) {
4
+ const declarations = {};
5
+ if (typeof style !== 'string')
6
+ return declarations;
7
+ style.split(HtmlTextConfig.declarationSeparator).forEach((entry) => {
8
+ const index = entry.indexOf(HtmlTextConfig.propertySeparator);
9
+ if (index === -1)
10
+ return;
11
+ const property = entry.slice(0, index).trim().toLowerCase();
12
+ const value = entry
13
+ .slice(index + 1)
14
+ .replace(HtmlTextConfig.importantPattern, '')
15
+ .trim()
16
+ .toLowerCase();
17
+ if (property !== '' && value !== '')
18
+ declarations[property] = value;
19
+ });
20
+ return declarations;
21
+ }
22
+ static isHidden(declarations) {
23
+ return (declarations['display'] === HtmlTextConfig.hiddenDisplay ||
24
+ declarations['visibility'] === HtmlTextConfig.hiddenVisibility ||
25
+ declarations['mso-hide'] === HtmlTextConfig.hiddenMso ||
26
+ HtmlStyleService.isZero(declarations['max-height']) ||
27
+ HtmlStyleService.isZero(declarations['max-width']) ||
28
+ HtmlStyleService.isZero(declarations['font-size']) ||
29
+ HtmlStyleService.isZero(declarations['opacity']));
30
+ }
31
+ static isTiny(declarations) {
32
+ return (HtmlStyleService.pixels(declarations['width']) <= HtmlTextConfig.tinyImageSize ||
33
+ HtmlStyleService.pixels(declarations['height']) <= HtmlTextConfig.tinyImageSize);
34
+ }
35
+ static fontSize(declarations) {
36
+ const value = declarations['font-size'];
37
+ if (typeof value !== 'string')
38
+ return null;
39
+ const match = HtmlTextConfig.fontSizePattern.exec(value);
40
+ if (match === null)
41
+ return null;
42
+ const amount = Number.parseFloat(match[1]);
43
+ if (match[2] === 'pt')
44
+ return amount * HtmlTextConfig.pixelsPerPoint;
45
+ if (match[2] === 'em' || match[2] === 'rem')
46
+ return amount * HtmlTextConfig.pixelsPerEm;
47
+ return amount;
48
+ }
49
+ static bold(declarations) {
50
+ const value = declarations['font-weight'];
51
+ if (typeof value !== 'string')
52
+ return null;
53
+ if (value === 'bold' || value === 'bolder')
54
+ return true;
55
+ if (value === 'normal' || value === 'lighter')
56
+ return false;
57
+ const weight = Number.parseInt(value, 10);
58
+ return Number.isNaN(weight) ? null : weight >= HtmlTextConfig.boldWeight;
59
+ }
60
+ static italic(declarations) {
61
+ const value = declarations['font-style'];
62
+ if (typeof value !== 'string')
63
+ return null;
64
+ if (value === 'italic' || value === 'oblique')
65
+ return true;
66
+ return value === 'normal' ? false : null;
67
+ }
68
+ static underline(declarations) {
69
+ const value = declarations['text-decoration'];
70
+ if (typeof value !== 'string')
71
+ return null;
72
+ if (value.includes('underline'))
73
+ return true;
74
+ return value.includes('none') ? false : null;
75
+ }
76
+ static isZero(value) {
77
+ return typeof value === 'string' && HtmlTextConfig.zeroPattern.test(value);
78
+ }
79
+ static pixels(value) {
80
+ if (typeof value !== 'string')
81
+ return Number.POSITIVE_INFINITY;
82
+ const match = HtmlTextConfig.pixelPattern.exec(value);
83
+ return match === null ? Number.POSITIVE_INFINITY : Number.parseFloat(match[1]);
84
+ }
85
+ }
@@ -0,0 +1,83 @@
1
+ import { HtmlTextConfig } from './constants/html-text-config.js';
2
+ import { HtmlTokenKind } from './constants/html-token-kind.js';
3
+ import { HtmlEntityService } from './html-entity-service.js';
4
+ export class HtmlTokenService {
5
+ static tokenize(html) {
6
+ const tokens = [];
7
+ let cursor = 0;
8
+ while (cursor < html.length) {
9
+ const start = html.indexOf('<', cursor);
10
+ if (start === -1) {
11
+ HtmlTokenService.pushText(tokens, html.slice(cursor));
12
+ break;
13
+ }
14
+ HtmlTokenService.pushText(tokens, html.slice(cursor, start));
15
+ cursor = HtmlTokenService.consume(html, start, tokens);
16
+ }
17
+ return tokens;
18
+ }
19
+ static consume(html, start, tokens) {
20
+ if (html.startsWith(HtmlTextConfig.commentStart, start)) {
21
+ const end = html.indexOf(HtmlTextConfig.commentEnd, start + HtmlTextConfig.commentStart.length);
22
+ return end === -1 ? html.length : end + HtmlTextConfig.commentEnd.length;
23
+ }
24
+ const next = html.charAt(start + 1);
25
+ if (next === '!' || next === '?') {
26
+ const end = html.indexOf('>', start);
27
+ return end === -1 ? html.length : end + 1;
28
+ }
29
+ const pattern = new RegExp(HtmlTextConfig.tagSource, HtmlTextConfig.tagFlags);
30
+ pattern.lastIndex = start;
31
+ const match = pattern.exec(html);
32
+ if (match === null) {
33
+ HtmlTokenService.pushText(tokens, '<');
34
+ return start + 1;
35
+ }
36
+ const name = match[2].toLowerCase();
37
+ const end = start + match[0].length;
38
+ if (match[1] === '/') {
39
+ tokens.push({ kind: HtmlTokenKind.Close, name });
40
+ return end;
41
+ }
42
+ tokens.push({
43
+ attributes: HtmlTokenService.attributes(match[3]),
44
+ kind: HtmlTokenKind.Open,
45
+ name,
46
+ selfClosing: HtmlTextConfig.selfClosingPattern.test(match[3]),
47
+ });
48
+ return HtmlTextConfig.rawTextTags.includes(name)
49
+ ? HtmlTokenService.skipRawText(html, name, end, tokens)
50
+ : end;
51
+ }
52
+ static skipRawText(html, name, from, tokens) {
53
+ const closing = new RegExp(`</${name}\\s*>`, 'gi');
54
+ closing.lastIndex = from;
55
+ const match = closing.exec(html);
56
+ if (match === null)
57
+ return html.length;
58
+ tokens.push({ kind: HtmlTokenKind.Close, name });
59
+ return match.index + match[0].length;
60
+ }
61
+ static attributes(source) {
62
+ const attributes = {};
63
+ const pattern = new RegExp(HtmlTextConfig.attributePattern.source, HtmlTextConfig.attributePattern.flags);
64
+ let match = pattern.exec(source);
65
+ while (match !== null) {
66
+ const raw = [match[2], match[3], match[4]].find((value) => typeof value === 'string');
67
+ const key = match[1].toLowerCase();
68
+ if (!Object.prototype.hasOwnProperty.call(attributes, key))
69
+ attributes[key] = HtmlEntityService.decode(typeof raw === 'string' ? raw : '');
70
+ match = pattern.exec(source);
71
+ }
72
+ return attributes;
73
+ }
74
+ static pushText(tokens, text) {
75
+ if (text === '')
76
+ return;
77
+ const last = tokens[tokens.length - 1];
78
+ if (last !== undefined && last.kind === HtmlTokenKind.Text)
79
+ last.text += text;
80
+ else
81
+ tokens.push({ kind: HtmlTokenKind.Text, text });
82
+ }
83
+ }
@@ -24,5 +24,6 @@ export * from './error-response-service.js';
24
24
  export * from './relative-time-service.js';
25
25
  export * from './request-client-service.js';
26
26
  export * from './json-service.js';
27
+ export * from './portable-text-link-service.js';
27
28
  export * from './portable-text-service.js';
28
29
  export * from './text-tidy-service.js';