@pallasoft/ps-core-components 2.40.0 → 2.41.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 (39) hide show
  1. package/dist/components/ps-linked-text.d.ts +5 -0
  2. package/dist/components/ps-linked-text.js +9 -0
  3. package/dist/components/ps-portable-text.js +11 -5
  4. package/dist/constants/ps-linked-text-link-rel.d.ts +1 -0
  5. package/dist/constants/ps-linked-text-link-rel.js +4 -0
  6. package/dist/constants/ps-portable-text-auto-link-type.d.ts +1 -0
  7. package/dist/constants/ps-portable-text-auto-link-type.js +4 -0
  8. package/dist/constants/ps-portable-text-link-class-name.d.ts +1 -0
  9. package/dist/constants/ps-portable-text-link-class-name.js +4 -0
  10. package/dist/constants/ps-text-link-pattern-source.d.ts +1 -0
  11. package/dist/constants/ps-text-link-pattern-source.js +4 -0
  12. package/dist/constants/ps-text-link-protocols.d.ts +1 -0
  13. package/dist/constants/ps-text-link-protocols.js +4 -0
  14. package/dist/constants/ps-text-link-trailing-characters.d.ts +1 -0
  15. package/dist/constants/ps-text-link-trailing-characters.js +4 -0
  16. package/dist/constants/ps-text-wrap-class-name.d.ts +1 -0
  17. package/dist/constants/ps-text-wrap-class-name.js +4 -0
  18. package/dist/esm/components/ps-linked-text.js +5 -0
  19. package/dist/esm/components/ps-portable-text.js +11 -5
  20. package/dist/esm/constants/ps-linked-text-link-rel.js +1 -0
  21. package/dist/esm/constants/ps-portable-text-auto-link-type.js +1 -0
  22. package/dist/esm/constants/ps-portable-text-link-class-name.js +1 -0
  23. package/dist/esm/constants/ps-text-link-pattern-source.js +1 -0
  24. package/dist/esm/constants/ps-text-link-protocols.js +1 -0
  25. package/dist/esm/constants/ps-text-link-trailing-characters.js +1 -0
  26. package/dist/esm/constants/ps-text-wrap-class-name.js +1 -0
  27. package/dist/esm/index.js +4 -0
  28. package/dist/esm/models/ps-text-link-segment-data.js +1 -0
  29. package/dist/esm/services/ps-portable-text-link-service.js +53 -0
  30. package/dist/esm/services/ps-text-link-service.js +63 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +4 -0
  33. package/dist/models/ps-text-link-segment-data.d.ts +4 -0
  34. package/dist/models/ps-text-link-segment-data.js +2 -0
  35. package/dist/services/ps-portable-text-link-service.d.ts +8 -0
  36. package/dist/services/ps-portable-text-link-service.js +57 -0
  37. package/dist/services/ps-text-link-service.d.ts +8 -0
  38. package/dist/services/ps-text-link-service.js +67 -0
  39. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ export interface PsLinkedTextProps {
2
+ linkClassName?: string;
3
+ text: string;
4
+ }
5
+ export declare const PsLinkedText: ({ linkClassName, text, }: PsLinkedTextProps) => import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsLinkedText = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_linked_text_link_rel_1 = require("../constants/ps-linked-text-link-rel");
6
+ const ps_portable_text_link_class_name_1 = require("../constants/ps-portable-text-link-class-name");
7
+ const ps_text_link_service_1 = require("../services/ps-text-link-service");
8
+ const PsLinkedText = ({ linkClassName = ps_portable_text_link_class_name_1.PsPortableTextLinkClassName, text, }) => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: ps_text_link_service_1.PsTextLinkService.segments(text).map((segment, index) => segment.href === null ? ((0, jsx_runtime_1.jsx)("span", { children: segment.text }, index)) : ((0, jsx_runtime_1.jsx)("a", { className: linkClassName, href: segment.href, rel: ps_linked_text_link_rel_1.PsLinkedTextLinkRel, target: '_blank', children: segment.text }, index))) }));
9
+ exports.PsLinkedText = PsLinkedText;
@@ -4,13 +4,19 @@ exports.PsPortableText = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
6
  const react_1 = require("@portabletext/react");
7
- const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', isLargeBodyFont = false, linkClassName = 'text-primary underline', LinkComponent = 'a', listClasses = 'text-ink', text, }) => {
7
+ const ps_linked_text_link_rel_1 = require("../constants/ps-linked-text-link-rel");
8
+ const ps_portable_text_auto_link_type_1 = require("../constants/ps-portable-text-auto-link-type");
9
+ const ps_portable_text_link_class_name_1 = require("../constants/ps-portable-text-link-class-name");
10
+ const ps_text_wrap_class_name_1 = require("../constants/ps-text-wrap-class-name");
11
+ const ps_portable_text_link_service_1 = require("../services/ps-portable-text-link-service");
12
+ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', isLargeBodyFont = false, linkClassName = ps_portable_text_link_class_name_1.PsPortableTextLinkClassName, LinkComponent = 'a', listClasses = 'text-ink', text, }) => {
8
13
  const hasContainerStyles = Object.keys(containerStyles).length > 0;
9
14
  const getBodyClass = hasContainerStyles ? '' : bodyClasses;
10
15
  const getHeaderClass = hasContainerStyles ? '' : headerClasses;
11
16
  const getListClass = hasContainerStyles ? '' : listClasses;
12
17
  const components = {
13
18
  marks: {
19
+ [ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType]: ({ children, value }) => ((0, jsx_runtime_1.jsx)("a", { href: value.href, target: '_blank', rel: ps_linked_text_link_rel_1.PsLinkedTextLinkRel, className: linkClassName, children: children })),
14
20
  link: ({ children, value }) => {
15
21
  const rel = !value.href.startsWith('/') ? 'noreferrer noopener' : undefined;
16
22
  return ((0, jsx_runtime_1.jsx)(LinkComponent, { href: value.href, rel: rel, className: linkClassName, "aria-label": value.children, children: children }));
@@ -21,11 +27,11 @@ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', conta
21
27
  number: ({ children }) => (0, jsx_runtime_1.jsx)("ol", { className: 'mb-3 mt-1 list-decimal ml-4', children: children }),
22
28
  },
23
29
  listItem: {
24
- bullet: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { className: ps_design_system_1.PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', getListClass), children: children })),
25
- number: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { className: ps_design_system_1.PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', getListClass), children: children })),
30
+ bullet: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { className: ps_design_system_1.PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', ps_text_wrap_class_name_1.PsTextWrapClassName, getListClass), children: children })),
31
+ number: ({ children }) => ((0, jsx_runtime_1.jsx)("li", { className: ps_design_system_1.PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', ps_text_wrap_class_name_1.PsTextWrapClassName, getListClass), children: children })),
26
32
  },
27
33
  block: {
28
- normal: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { className: ps_design_system_1.PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', getBodyClass), children: children })),
34
+ normal: ({ children }) => ((0, jsx_runtime_1.jsx)("p", { className: ps_design_system_1.PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', ps_text_wrap_class_name_1.PsTextWrapClassName, getBodyClass), children: children })),
29
35
  h2: ({ children }) => ((0, jsx_runtime_1.jsx)("h2", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-1 py-2', getHeaderClass), children: children })),
30
36
  h3: ({ children }) => ((0, jsx_runtime_1.jsx)("h3", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-2 py-2', getHeaderClass), children: children })),
31
37
  h4: ({ children }) => ((0, jsx_runtime_1.jsx)("h4", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-3 py-2', getHeaderClass), children: children })),
@@ -33,6 +39,6 @@ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', conta
33
39
  h6: ({ children }) => ((0, jsx_runtime_1.jsx)("h6", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-4 py-1', getHeaderClass), children: children })),
34
40
  },
35
41
  };
36
- return ((0, jsx_runtime_1.jsx)("div", { className: containerClasses, style: containerStyles, children: (0, jsx_runtime_1.jsx)(react_1.PortableText, { value: text, components: components }) }));
42
+ return ((0, jsx_runtime_1.jsx)("div", { className: containerClasses, style: containerStyles, children: (0, jsx_runtime_1.jsx)(react_1.PortableText, { value: ps_portable_text_link_service_1.PsPortableTextLinkService.linkify(text), components: components }) }));
37
43
  };
38
44
  exports.PsPortableText = PsPortableText;
@@ -0,0 +1 @@
1
+ export declare const PsLinkedTextLinkRel = "noopener noreferrer nofollow";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsLinkedTextLinkRel = void 0;
4
+ exports.PsLinkedTextLinkRel = 'noopener noreferrer nofollow';
@@ -0,0 +1 @@
1
+ export declare const PsPortableTextAutoLinkType = "autoLink";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextAutoLinkType = void 0;
4
+ exports.PsPortableTextAutoLinkType = 'autoLink';
@@ -0,0 +1 @@
1
+ export declare const PsPortableTextLinkClassName = "text-primary underline";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextLinkClassName = void 0;
4
+ exports.PsPortableTextLinkClassName = 'text-primary underline';
@@ -0,0 +1 @@
1
+ export declare const PsTextLinkPatternSource = "<([^<>\\s]+)>|(https?:\\/\\/[^\\s<>]+)";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkPatternSource = void 0;
4
+ exports.PsTextLinkPatternSource = '<([^<>\\s]+)>|(https?:\\/\\/[^\\s<>]+)';
@@ -0,0 +1 @@
1
+ export declare const PsTextLinkProtocols: string[];
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkProtocols = void 0;
4
+ exports.PsTextLinkProtocols = ['http:', 'https:', 'mailto:'];
@@ -0,0 +1 @@
1
+ export declare const PsTextLinkTrailingCharacters = ".,;:!?)]}'\"";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkTrailingCharacters = void 0;
4
+ exports.PsTextLinkTrailingCharacters = '.,;:!?)]}\'"';
@@ -0,0 +1 @@
1
+ export declare const PsTextWrapClassName = "[overflow-wrap:anywhere]";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextWrapClassName = void 0;
4
+ exports.PsTextWrapClassName = '[overflow-wrap:anywhere]';
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { PsLinkedTextLinkRel } from '../constants/ps-linked-text-link-rel.js';
3
+ import { PsPortableTextLinkClassName } from '../constants/ps-portable-text-link-class-name.js';
4
+ import { PsTextLinkService } from '../services/ps-text-link-service.js';
5
+ export const PsLinkedText = ({ linkClassName = PsPortableTextLinkClassName, text, }) => (_jsx(_Fragment, { children: PsTextLinkService.segments(text).map((segment, index) => segment.href === null ? (_jsx("span", { children: segment.text }, index)) : (_jsx("a", { className: linkClassName, href: segment.href, rel: PsLinkedTextLinkRel, target: '_blank', children: segment.text }, index))) }));
@@ -1,13 +1,19 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
3
  import { PortableText as Text } from '@portabletext/react';
4
- export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', isLargeBodyFont = false, linkClassName = 'text-primary underline', LinkComponent = 'a', listClasses = 'text-ink', text, }) => {
4
+ import { PsLinkedTextLinkRel } from '../constants/ps-linked-text-link-rel.js';
5
+ import { PsPortableTextAutoLinkType } from '../constants/ps-portable-text-auto-link-type.js';
6
+ import { PsPortableTextLinkClassName } from '../constants/ps-portable-text-link-class-name.js';
7
+ import { PsTextWrapClassName } from '../constants/ps-text-wrap-class-name.js';
8
+ import { PsPortableTextLinkService } from '../services/ps-portable-text-link-service.js';
9
+ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', isLargeBodyFont = false, linkClassName = PsPortableTextLinkClassName, LinkComponent = 'a', listClasses = 'text-ink', text, }) => {
5
10
  const hasContainerStyles = Object.keys(containerStyles).length > 0;
6
11
  const getBodyClass = hasContainerStyles ? '' : bodyClasses;
7
12
  const getHeaderClass = hasContainerStyles ? '' : headerClasses;
8
13
  const getListClass = hasContainerStyles ? '' : listClasses;
9
14
  const components = {
10
15
  marks: {
16
+ [PsPortableTextAutoLinkType]: ({ children, value }) => (_jsx("a", { href: value.href, target: '_blank', rel: PsLinkedTextLinkRel, className: linkClassName, children: children })),
11
17
  link: ({ children, value }) => {
12
18
  const rel = !value.href.startsWith('/') ? 'noreferrer noopener' : undefined;
13
19
  return (_jsx(LinkComponent, { href: value.href, rel: rel, className: linkClassName, "aria-label": value.children, children: children }));
@@ -18,11 +24,11 @@ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = ''
18
24
  number: ({ children }) => _jsx("ol", { className: 'mb-3 mt-1 list-decimal ml-4', children: children }),
19
25
  },
20
26
  listItem: {
21
- bullet: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', getListClass), children: children })),
22
- number: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', getListClass), children: children })),
27
+ bullet: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', PsTextWrapClassName, getListClass), children: children })),
28
+ number: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', PsTextWrapClassName, getListClass), children: children })),
23
29
  },
24
30
  block: {
25
- normal: ({ children }) => (_jsx("p", { className: PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', getBodyClass), children: children })),
31
+ normal: ({ children }) => (_jsx("p", { className: PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', PsTextWrapClassName, getBodyClass), children: children })),
26
32
  h2: ({ children }) => (_jsx("h2", { className: PallasoftClassNameService.merge('text-header-1 py-2', getHeaderClass), children: children })),
27
33
  h3: ({ children }) => (_jsx("h3", { className: PallasoftClassNameService.merge('text-header-2 py-2', getHeaderClass), children: children })),
28
34
  h4: ({ children }) => (_jsx("h4", { className: PallasoftClassNameService.merge('text-header-3 py-2', getHeaderClass), children: children })),
@@ -30,5 +36,5 @@ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = ''
30
36
  h6: ({ children }) => (_jsx("h6", { className: PallasoftClassNameService.merge('text-header-4 py-1', getHeaderClass), children: children })),
31
37
  },
32
38
  };
33
- return (_jsx("div", { className: containerClasses, style: containerStyles, children: _jsx(Text, { value: text, components: components }) }));
39
+ return (_jsx("div", { className: containerClasses, style: containerStyles, children: _jsx(Text, { value: PsPortableTextLinkService.linkify(text), components: components }) }));
34
40
  };
@@ -0,0 +1 @@
1
+ export const PsLinkedTextLinkRel = 'noopener noreferrer nofollow';
@@ -0,0 +1 @@
1
+ export const PsPortableTextAutoLinkType = 'autoLink';
@@ -0,0 +1 @@
1
+ export const PsPortableTextLinkClassName = 'text-primary underline';
@@ -0,0 +1 @@
1
+ export const PsTextLinkPatternSource = '<([^<>\\s]+)>|(https?:\\/\\/[^\\s<>]+)';
@@ -0,0 +1 @@
1
+ export const PsTextLinkProtocols = ['http:', 'https:', 'mailto:'];
@@ -0,0 +1 @@
1
+ export const PsTextLinkTrailingCharacters = '.,;:!?)]}\'"';
@@ -0,0 +1 @@
1
+ export const PsTextWrapClassName = '[overflow-wrap:anywhere]';
package/dist/esm/index.js CHANGED
@@ -35,6 +35,10 @@ export * from './components/ps-list-tile.js';
35
35
  export * from './components/ps-metric-card.js';
36
36
  export * from './components/ps-pill.js';
37
37
  export * from './components/ps-portable-text.js';
38
+ export * from './components/ps-linked-text.js';
39
+ export * from './services/ps-text-link-service.js';
40
+ export * from './models/ps-text-link-segment-data.js';
41
+ export * from './constants/ps-portable-text-link-class-name.js';
38
42
  export * from './components/ps-reflective-app-shell.js';
39
43
  export * from './components/ps-scaled-preview.js';
40
44
  export * from './components/ps-navigation-progress.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { PsPortableTextAutoLinkType } from '../constants/ps-portable-text-auto-link-type.js';
2
+ import { PsTextLinkService } from './ps-text-link-service.js';
3
+ export class PsPortableTextLinkService {
4
+ static linkify(blocks) {
5
+ return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
6
+ }
7
+ static isBlock(value) {
8
+ return 'children' in value && Array.isArray(value.children);
9
+ }
10
+ static isSpan(value) {
11
+ return value._type === 'span' && 'text' in value && typeof value.text === 'string';
12
+ }
13
+ static linkifyBlock(block) {
14
+ const markDefs = block.markDefs === undefined ? [] : block.markDefs;
15
+ const added = [];
16
+ const children = [];
17
+ for (const child of block.children) {
18
+ if (!this.isSpan(child) || this.hasLinkMark(child, markDefs)) {
19
+ children.push(child);
20
+ continue;
21
+ }
22
+ const segments = PsTextLinkService.segments(child.text);
23
+ if (segments.length === 1 && segments[0].href === null) {
24
+ children.push(child);
25
+ continue;
26
+ }
27
+ const marks = child.marks === undefined ? [] : child.marks;
28
+ segments.forEach((segment, index) => {
29
+ const key = child._key === undefined ? undefined : `${child._key}-${index}`;
30
+ if (segment.href === null) {
31
+ const plain = { ...child, _key: key, text: segment.text };
32
+ children.push(plain);
33
+ return;
34
+ }
35
+ const markKey = `${PsPortableTextAutoLinkType}-${added.length}`;
36
+ added.push({ _key: markKey, _type: PsPortableTextAutoLinkType, href: segment.href });
37
+ const linked = {
38
+ ...child,
39
+ _key: key,
40
+ marks: [...marks, markKey],
41
+ text: segment.text,
42
+ };
43
+ children.push(linked);
44
+ });
45
+ }
46
+ return { ...block, children, markDefs: [...markDefs, ...added] };
47
+ }
48
+ static hasLinkMark(span, markDefs) {
49
+ if (span.marks === undefined)
50
+ return false;
51
+ return span.marks.some((mark) => markDefs.some((definition) => definition._key === mark && definition._type === 'link'));
52
+ }
53
+ }
@@ -0,0 +1,63 @@
1
+ import { PsTextLinkPatternSource } from '../constants/ps-text-link-pattern-source.js';
2
+ import { PsTextLinkProtocols } from '../constants/ps-text-link-protocols.js';
3
+ import { PsTextLinkTrailingCharacters } from '../constants/ps-text-link-trailing-characters.js';
4
+ export class PsTextLinkService {
5
+ static segments(text) {
6
+ const segments = [];
7
+ const pattern = new RegExp(PsTextLinkPatternSource, 'g');
8
+ let cursor = 0;
9
+ let match = pattern.exec(text);
10
+ while (match !== null) {
11
+ this.pushText(segments, text.slice(cursor, match.index));
12
+ cursor = match.index + match[0].length;
13
+ if (match[1] !== undefined)
14
+ this.pushAngle(segments, match[0], match[1]);
15
+ else
16
+ this.pushBare(segments, match[2]);
17
+ match = pattern.exec(text);
18
+ }
19
+ this.pushText(segments, text.slice(cursor));
20
+ return segments;
21
+ }
22
+ static pushAngle(segments, raw, href) {
23
+ const url = this.parse(href);
24
+ if (url === null) {
25
+ this.pushText(segments, raw);
26
+ return;
27
+ }
28
+ const label = url.protocol === 'mailto:' ? url.pathname : url.hostname.replace(/^www\./, '');
29
+ if (label === '') {
30
+ this.pushText(segments, raw);
31
+ return;
32
+ }
33
+ segments.push({ href, text: label });
34
+ }
35
+ static pushBare(segments, raw) {
36
+ let end = raw.length;
37
+ while (end > 0 && PsTextLinkTrailingCharacters.includes(raw.charAt(end - 1)))
38
+ end -= 1;
39
+ const href = raw.slice(0, end);
40
+ if (this.parse(href) === null) {
41
+ this.pushText(segments, raw);
42
+ return;
43
+ }
44
+ segments.push({ href, text: href });
45
+ this.pushText(segments, raw.slice(end));
46
+ }
47
+ static pushText(segments, text) {
48
+ if (text === '')
49
+ return;
50
+ const last = segments[segments.length - 1];
51
+ if (last !== undefined && last.href === null) {
52
+ last.text += text;
53
+ return;
54
+ }
55
+ segments.push({ href: null, text });
56
+ }
57
+ static parse(href) {
58
+ if (!URL.canParse(href))
59
+ return null;
60
+ const url = new URL(href);
61
+ return PsTextLinkProtocols.includes(url.protocol) ? url : null;
62
+ }
63
+ }
package/dist/index.d.ts CHANGED
@@ -35,6 +35,10 @@ export * from './components/ps-list-tile';
35
35
  export * from './components/ps-metric-card';
36
36
  export * from './components/ps-pill';
37
37
  export * from './components/ps-portable-text';
38
+ export * from './components/ps-linked-text';
39
+ export * from './services/ps-text-link-service';
40
+ export * from './models/ps-text-link-segment-data';
41
+ export * from './constants/ps-portable-text-link-class-name';
38
42
  export * from './components/ps-reflective-app-shell';
39
43
  export * from './components/ps-scaled-preview';
40
44
  export * from './components/ps-navigation-progress';
package/dist/index.js CHANGED
@@ -51,6 +51,10 @@ __exportStar(require("./components/ps-list-tile"), exports);
51
51
  __exportStar(require("./components/ps-metric-card"), exports);
52
52
  __exportStar(require("./components/ps-pill"), exports);
53
53
  __exportStar(require("./components/ps-portable-text"), exports);
54
+ __exportStar(require("./components/ps-linked-text"), exports);
55
+ __exportStar(require("./services/ps-text-link-service"), exports);
56
+ __exportStar(require("./models/ps-text-link-segment-data"), exports);
57
+ __exportStar(require("./constants/ps-portable-text-link-class-name"), exports);
54
58
  __exportStar(require("./components/ps-reflective-app-shell"), exports);
55
59
  __exportStar(require("./components/ps-scaled-preview"), exports);
56
60
  __exportStar(require("./components/ps-navigation-progress"), exports);
@@ -0,0 +1,4 @@
1
+ export interface PsTextLinkSegmentData {
2
+ href: string | null;
3
+ text: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ import type { TypedObject } from '@portabletext/types';
2
+ export declare class PsPortableTextLinkService {
3
+ static linkify(blocks: TypedObject[]): TypedObject[];
4
+ private static isBlock;
5
+ private static isSpan;
6
+ private static linkifyBlock;
7
+ private static hasLinkMark;
8
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextLinkService = void 0;
4
+ const ps_portable_text_auto_link_type_1 = require("../constants/ps-portable-text-auto-link-type");
5
+ const ps_text_link_service_1 = require("./ps-text-link-service");
6
+ class PsPortableTextLinkService {
7
+ static linkify(blocks) {
8
+ return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
9
+ }
10
+ static isBlock(value) {
11
+ return 'children' in value && Array.isArray(value.children);
12
+ }
13
+ static isSpan(value) {
14
+ return value._type === 'span' && 'text' in value && typeof value.text === 'string';
15
+ }
16
+ static linkifyBlock(block) {
17
+ const markDefs = block.markDefs === undefined ? [] : block.markDefs;
18
+ const added = [];
19
+ const children = [];
20
+ for (const child of block.children) {
21
+ if (!this.isSpan(child) || this.hasLinkMark(child, markDefs)) {
22
+ children.push(child);
23
+ continue;
24
+ }
25
+ const segments = ps_text_link_service_1.PsTextLinkService.segments(child.text);
26
+ if (segments.length === 1 && segments[0].href === null) {
27
+ children.push(child);
28
+ continue;
29
+ }
30
+ const marks = child.marks === undefined ? [] : child.marks;
31
+ segments.forEach((segment, index) => {
32
+ const key = child._key === undefined ? undefined : `${child._key}-${index}`;
33
+ if (segment.href === null) {
34
+ const plain = { ...child, _key: key, text: segment.text };
35
+ children.push(plain);
36
+ return;
37
+ }
38
+ const markKey = `${ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType}-${added.length}`;
39
+ added.push({ _key: markKey, _type: ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType, href: segment.href });
40
+ const linked = {
41
+ ...child,
42
+ _key: key,
43
+ marks: [...marks, markKey],
44
+ text: segment.text,
45
+ };
46
+ children.push(linked);
47
+ });
48
+ }
49
+ return { ...block, children, markDefs: [...markDefs, ...added] };
50
+ }
51
+ static hasLinkMark(span, markDefs) {
52
+ if (span.marks === undefined)
53
+ return false;
54
+ return span.marks.some((mark) => markDefs.some((definition) => definition._key === mark && definition._type === 'link'));
55
+ }
56
+ }
57
+ exports.PsPortableTextLinkService = PsPortableTextLinkService;
@@ -0,0 +1,8 @@
1
+ import type { PsTextLinkSegmentData } from '../models/ps-text-link-segment-data';
2
+ export declare class PsTextLinkService {
3
+ static segments(text: string): PsTextLinkSegmentData[];
4
+ private static pushAngle;
5
+ private static pushBare;
6
+ private static pushText;
7
+ private static parse;
8
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkService = void 0;
4
+ const ps_text_link_pattern_source_1 = require("../constants/ps-text-link-pattern-source");
5
+ const ps_text_link_protocols_1 = require("../constants/ps-text-link-protocols");
6
+ const ps_text_link_trailing_characters_1 = require("../constants/ps-text-link-trailing-characters");
7
+ class PsTextLinkService {
8
+ static segments(text) {
9
+ const segments = [];
10
+ const pattern = new RegExp(ps_text_link_pattern_source_1.PsTextLinkPatternSource, 'g');
11
+ let cursor = 0;
12
+ let match = pattern.exec(text);
13
+ while (match !== null) {
14
+ this.pushText(segments, text.slice(cursor, match.index));
15
+ cursor = match.index + match[0].length;
16
+ if (match[1] !== undefined)
17
+ this.pushAngle(segments, match[0], match[1]);
18
+ else
19
+ this.pushBare(segments, match[2]);
20
+ match = pattern.exec(text);
21
+ }
22
+ this.pushText(segments, text.slice(cursor));
23
+ return segments;
24
+ }
25
+ static pushAngle(segments, raw, href) {
26
+ const url = this.parse(href);
27
+ if (url === null) {
28
+ this.pushText(segments, raw);
29
+ return;
30
+ }
31
+ const label = url.protocol === 'mailto:' ? url.pathname : url.hostname.replace(/^www\./, '');
32
+ if (label === '') {
33
+ this.pushText(segments, raw);
34
+ return;
35
+ }
36
+ segments.push({ href, text: label });
37
+ }
38
+ static pushBare(segments, raw) {
39
+ let end = raw.length;
40
+ while (end > 0 && ps_text_link_trailing_characters_1.PsTextLinkTrailingCharacters.includes(raw.charAt(end - 1)))
41
+ end -= 1;
42
+ const href = raw.slice(0, end);
43
+ if (this.parse(href) === null) {
44
+ this.pushText(segments, raw);
45
+ return;
46
+ }
47
+ segments.push({ href, text: href });
48
+ this.pushText(segments, raw.slice(end));
49
+ }
50
+ static pushText(segments, text) {
51
+ if (text === '')
52
+ return;
53
+ const last = segments[segments.length - 1];
54
+ if (last !== undefined && last.href === null) {
55
+ last.text += text;
56
+ return;
57
+ }
58
+ segments.push({ href: null, text });
59
+ }
60
+ static parse(href) {
61
+ if (!URL.canParse(href))
62
+ return null;
63
+ const url = new URL(href);
64
+ return ps_text_link_protocols_1.PsTextLinkProtocols.includes(url.protocol) ? url : null;
65
+ }
66
+ }
67
+ exports.PsTextLinkService = PsTextLinkService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-core-components",
3
- "version": "2.40.0",
3
+ "version": "2.41.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",