@pallasoft/ps-core-components 2.40.0 → 2.42.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 (64) 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-image.d.ts +7 -0
  4. package/dist/components/ps-portable-text-image.js +15 -0
  5. package/dist/components/ps-portable-text-message.d.ts +2 -0
  6. package/dist/components/ps-portable-text-message.js +49 -0
  7. package/dist/components/ps-portable-text.d.ts +6 -1
  8. package/dist/components/ps-portable-text.js +17 -6
  9. package/dist/constants/ps-linked-text-link-rel.d.ts +1 -0
  10. package/dist/constants/ps-linked-text-link-rel.js +4 -0
  11. package/dist/constants/ps-portable-text-auto-link-type.d.ts +1 -0
  12. package/dist/constants/ps-portable-text-auto-link-type.js +4 -0
  13. package/dist/constants/ps-portable-text-link-class-name.d.ts +1 -0
  14. package/dist/constants/ps-portable-text-link-class-name.js +4 -0
  15. package/dist/constants/ps-portable-text-message-config.d.ts +32 -0
  16. package/dist/constants/ps-portable-text-message-config.js +35 -0
  17. package/dist/constants/ps-portable-text-variant.d.ts +4 -0
  18. package/dist/constants/ps-portable-text-variant.js +8 -0
  19. package/dist/constants/ps-text-link-gap.d.ts +4 -0
  20. package/dist/constants/ps-text-link-gap.js +7 -0
  21. package/dist/constants/ps-text-link-pattern-source.d.ts +1 -0
  22. package/dist/constants/ps-text-link-pattern-source.js +4 -0
  23. package/dist/constants/ps-text-link-protocols.d.ts +1 -0
  24. package/dist/constants/ps-text-link-protocols.js +4 -0
  25. package/dist/constants/ps-text-link-trailing-characters.d.ts +1 -0
  26. package/dist/constants/ps-text-link-trailing-characters.js +4 -0
  27. package/dist/constants/ps-text-wrap-class-name.d.ts +1 -0
  28. package/dist/constants/ps-text-wrap-class-name.js +4 -0
  29. package/dist/esm/components/ps-linked-text.js +5 -0
  30. package/dist/esm/components/ps-portable-text-image.js +11 -0
  31. package/dist/esm/components/ps-portable-text-message.js +45 -0
  32. package/dist/esm/components/ps-portable-text.js +17 -6
  33. package/dist/esm/constants/ps-linked-text-link-rel.js +1 -0
  34. package/dist/esm/constants/ps-portable-text-auto-link-type.js +1 -0
  35. package/dist/esm/constants/ps-portable-text-link-class-name.js +1 -0
  36. package/dist/esm/constants/ps-portable-text-message-config.js +32 -0
  37. package/dist/esm/constants/ps-portable-text-variant.js +5 -0
  38. package/dist/esm/constants/ps-text-link-gap.js +4 -0
  39. package/dist/esm/constants/ps-text-link-pattern-source.js +1 -0
  40. package/dist/esm/constants/ps-text-link-protocols.js +1 -0
  41. package/dist/esm/constants/ps-text-link-trailing-characters.js +1 -0
  42. package/dist/esm/constants/ps-text-wrap-class-name.js +1 -0
  43. package/dist/esm/index.js +11 -0
  44. package/dist/esm/models/ps-portable-text-image-data.js +1 -0
  45. package/dist/esm/models/ps-portable-text-image-labels.js +1 -0
  46. package/dist/esm/models/ps-portable-text-message-props.js +1 -0
  47. package/dist/esm/models/ps-text-link-segment-data.js +1 -0
  48. package/dist/esm/services/ps-portable-text-link-service.js +72 -0
  49. package/dist/esm/services/ps-text-link-service.js +72 -0
  50. package/dist/index.d.ts +11 -0
  51. package/dist/index.js +11 -0
  52. package/dist/models/ps-portable-text-image-data.d.ts +5 -0
  53. package/dist/models/ps-portable-text-image-data.js +2 -0
  54. package/dist/models/ps-portable-text-image-labels.d.ts +5 -0
  55. package/dist/models/ps-portable-text-image-labels.js +2 -0
  56. package/dist/models/ps-portable-text-message-props.d.ts +19 -0
  57. package/dist/models/ps-portable-text-message-props.js +2 -0
  58. package/dist/models/ps-text-link-segment-data.d.ts +4 -0
  59. package/dist/models/ps-text-link-segment-data.js +2 -0
  60. package/dist/services/ps-portable-text-link-service.d.ts +11 -0
  61. package/dist/services/ps-portable-text-link-service.js +76 -0
  62. package/dist/services/ps-text-link-service.d.ts +9 -0
  63. package/dist/services/ps-text-link-service.js +76 -0
  64. package/package.json +2 -2
@@ -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;
@@ -0,0 +1,7 @@
1
+ import type { PortableTextTypeComponentProps } from '@portabletext/react';
2
+ import type { PsPortableTextImageData } from '../models/ps-portable-text-image-data';
3
+ export interface PsPortableTextImageProps extends PortableTextTypeComponentProps<PsPortableTextImageData> {
4
+ hiddenLabel: string;
5
+ isShown: boolean;
6
+ }
7
+ export declare const PsPortableTextImage: ({ hiddenLabel, isShown, value }: PsPortableTextImageProps) => import("react").JSX.Element | null;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextImage = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_portable_text_message_config_1 = require("../constants/ps-portable-text-message-config");
6
+ const ps_portable_text_link_service_1 = require("../services/ps-portable-text-link-service");
7
+ const PsPortableTextImage = ({ hiddenLabel, isShown, value }) => {
8
+ if (typeof value.src !== 'string' || !ps_portable_text_link_service_1.PsPortableTextLinkService.isSafeImage(value.src))
9
+ return null;
10
+ const alt = typeof value.alt === 'string' ? value.alt : '';
11
+ if (!isShown)
12
+ return ((0, jsx_runtime_1.jsx)("div", { className: ps_portable_text_message_config_1.PsPortableTextMessageConfig.placeholderClassName, children: alt === '' ? hiddenLabel : alt }));
13
+ return ((0, jsx_runtime_1.jsx)("img", { alt: alt, className: ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageClassName, decoding: ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageDecoding, loading: ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageLoading, referrerPolicy: ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageReferrerPolicy, src: value.src }));
14
+ };
15
+ exports.PsPortableTextImage = PsPortableTextImage;
@@ -0,0 +1,2 @@
1
+ import type { PsPortableTextMessageProps } from '../models/ps-portable-text-message-props';
2
+ export declare const PsPortableTextMessage: ({ containerClasses, containerStyles, imageLabels, isImagesShown, linkClassName, LinkComponent, text, }: PsPortableTextMessageProps) => import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextMessage = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
+ const react_2 = require("@portabletext/react");
8
+ const ps_linked_text_link_rel_1 = require("../constants/ps-linked-text-link-rel");
9
+ const ps_portable_text_auto_link_type_1 = require("../constants/ps-portable-text-auto-link-type");
10
+ const ps_portable_text_message_config_1 = require("../constants/ps-portable-text-message-config");
11
+ const ps_portable_text_link_service_1 = require("../services/ps-portable-text-link-service");
12
+ const ps_portable_text_image_1 = require("./ps-portable-text-image");
13
+ const ps_text_link_1 = require("./ps-text-link");
14
+ const PsPortableTextMessage = ({ containerClasses, containerStyles, imageLabels, isImagesShown, linkClassName, LinkComponent, text, }) => {
15
+ const config = ps_portable_text_message_config_1.PsPortableTextMessageConfig;
16
+ const [isShown, setIsShown] = (0, react_1.useState)(isImagesShown);
17
+ const labels = { ...config.defaultLabels, ...imageLabels };
18
+ const linkClasses = linkClassName === undefined ? config.linkClassName : linkClassName;
19
+ const components = {
20
+ types: {
21
+ [config.imageType]: ({ value, ...props }) => ((0, jsx_runtime_1.jsx)(ps_portable_text_image_1.PsPortableTextImage, { ...props, hiddenLabel: labels.imageHidden, isShown: isShown, value: value })),
22
+ },
23
+ marks: {
24
+ code: ({ children }) => (0, jsx_runtime_1.jsx)("code", { className: config.codeClassName, children: children }),
25
+ em: ({ children }) => (0, jsx_runtime_1.jsx)("em", { children: children }),
26
+ strong: ({ children }) => (0, jsx_runtime_1.jsx)("strong", { children: children }),
27
+ underline: ({ children }) => (0, jsx_runtime_1.jsx)("u", { children: children }),
28
+ [ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType]: ({ children, value }) => ((0, jsx_runtime_1.jsx)("a", { className: linkClasses, href: value.href, rel: ps_linked_text_link_rel_1.PsLinkedTextLinkRel, target: config.linkTarget, children: children })),
29
+ link: ({ children, value }) => typeof value?.href === 'string' && ps_portable_text_link_service_1.PsPortableTextLinkService.isSafeLink(value.href) ? ((0, jsx_runtime_1.jsx)(LinkComponent, { className: linkClasses, href: value.href, rel: config.linkRel, target: config.linkTarget, children: children })) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children })),
30
+ },
31
+ list: {
32
+ bullet: ({ children }) => (0, jsx_runtime_1.jsx)("ul", { className: config.bulletListClassName, children: children }),
33
+ number: ({ children }) => (0, jsx_runtime_1.jsx)("ol", { className: config.numberListClassName, children: children }),
34
+ },
35
+ listItem: {
36
+ bullet: ({ children }) => (0, jsx_runtime_1.jsx)("li", { className: config.listItemClassName, children: children }),
37
+ number: ({ children }) => (0, jsx_runtime_1.jsx)("li", { className: config.listItemClassName, children: children }),
38
+ },
39
+ block: {
40
+ normal: ({ children }) => (0, jsx_runtime_1.jsx)("p", { className: config.paragraphClassName, children: children }),
41
+ blockquote: ({ children }) => ((0, jsx_runtime_1.jsx)("blockquote", { className: config.quoteClassName, children: children })),
42
+ h1: ({ children }) => (0, jsx_runtime_1.jsx)("h3", { className: config.headingClassNames.h1, children: children }),
43
+ h2: ({ children }) => (0, jsx_runtime_1.jsx)("h4", { className: config.headingClassNames.h2, children: children }),
44
+ h3: ({ children }) => (0, jsx_runtime_1.jsx)("h5", { className: config.headingClassNames.h3, children: children }),
45
+ },
46
+ };
47
+ return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(config.rootClassName, containerClasses), style: containerStyles, children: [!isShown && ps_portable_text_link_service_1.PsPortableTextLinkService.hasImages(text) ? ((0, jsx_runtime_1.jsxs)("p", { className: config.imageBarClassName, children: [(0, jsx_runtime_1.jsx)("span", { children: labels.imagesHidden }), (0, jsx_runtime_1.jsx)(ps_text_link_1.PsTextLink, { onClick: () => setIsShown(true), size: 'compact', children: labels.showImages })] })) : null, (0, jsx_runtime_1.jsx)(react_2.PortableText, { components: components, value: ps_portable_text_link_service_1.PsPortableTextLinkService.linkify(text) })] }));
48
+ };
49
+ exports.PsPortableTextMessage = PsPortableTextMessage;
@@ -1,10 +1,14 @@
1
1
  import type { CSSProperties, ElementType, ReactNode } from 'react';
2
2
  import { TypedObject } from '@portabletext/types';
3
+ import { PsPortableTextVariant } from '../constants/ps-portable-text-variant';
4
+ import type { PsPortableTextImageLabels } from '../models/ps-portable-text-image-labels';
3
5
  export interface PsPortableTextProps {
4
6
  bodyClasses?: string;
5
7
  containerClasses?: string;
6
8
  containerStyles?: CSSProperties;
7
9
  headerClasses?: string;
10
+ imageLabels?: Partial<PsPortableTextImageLabels>;
11
+ isImagesShown?: boolean;
8
12
  isLargeBodyFont?: boolean;
9
13
  linkClassName?: string;
10
14
  LinkComponent?: ElementType<{
@@ -16,5 +20,6 @@ export interface PsPortableTextProps {
16
20
  }>;
17
21
  listClasses?: string;
18
22
  text: TypedObject[];
23
+ variant?: PsPortableTextVariant;
19
24
  }
20
- export declare const PsPortableText: ({ bodyClasses, containerClasses, containerStyles, headerClasses, isLargeBodyFont, linkClassName, LinkComponent, listClasses, text, }: PsPortableTextProps) => import("react").JSX.Element;
25
+ export declare const PsPortableText: ({ bodyClasses, containerClasses, containerStyles, headerClasses, imageLabels, isImagesShown, isLargeBodyFont, linkClassName, LinkComponent, listClasses, text, variant, }: PsPortableTextProps) => import("react").JSX.Element;
@@ -4,16 +4,27 @@ 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_portable_text_variant_1 = require("../constants/ps-portable-text-variant");
11
+ const ps_text_wrap_class_name_1 = require("../constants/ps-text-wrap-class-name");
12
+ const ps_portable_text_link_service_1 = require("../services/ps-portable-text-link-service");
13
+ const ps_portable_text_message_1 = require("./ps-portable-text-message");
14
+ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', imageLabels, isImagesShown = false, isLargeBodyFont = false, linkClassName, LinkComponent = 'a', listClasses = 'text-ink', text, variant = ps_portable_text_variant_1.PsPortableTextVariant.Article, }) => {
15
+ if (variant === ps_portable_text_variant_1.PsPortableTextVariant.Message)
16
+ return ((0, jsx_runtime_1.jsx)(ps_portable_text_message_1.PsPortableTextMessage, { containerClasses: containerClasses, containerStyles: containerStyles, imageLabels: imageLabels, isImagesShown: isImagesShown, linkClassName: linkClassName, LinkComponent: LinkComponent, text: text }));
17
+ const articleLinkClassName = linkClassName === undefined ? ps_portable_text_link_class_name_1.PsPortableTextLinkClassName : linkClassName;
8
18
  const hasContainerStyles = Object.keys(containerStyles).length > 0;
9
19
  const getBodyClass = hasContainerStyles ? '' : bodyClasses;
10
20
  const getHeaderClass = hasContainerStyles ? '' : headerClasses;
11
21
  const getListClass = hasContainerStyles ? '' : listClasses;
12
22
  const components = {
13
23
  marks: {
24
+ [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: articleLinkClassName, children: children })),
14
25
  link: ({ children, value }) => {
15
26
  const rel = !value.href.startsWith('/') ? 'noreferrer noopener' : undefined;
16
- return ((0, jsx_runtime_1.jsx)(LinkComponent, { href: value.href, rel: rel, className: linkClassName, "aria-label": value.children, children: children }));
27
+ return ((0, jsx_runtime_1.jsx)(LinkComponent, { href: value.href, rel: rel, className: articleLinkClassName, "aria-label": value.children, children: children }));
17
28
  },
18
29
  },
19
30
  list: {
@@ -21,11 +32,11 @@ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', conta
21
32
  number: ({ children }) => (0, jsx_runtime_1.jsx)("ol", { className: 'mb-3 mt-1 list-decimal ml-4', children: children }),
22
33
  },
23
34
  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 })),
35
+ 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 })),
36
+ 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
37
  },
27
38
  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 })),
39
+ 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
40
  h2: ({ children }) => ((0, jsx_runtime_1.jsx)("h2", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-1 py-2', getHeaderClass), children: children })),
30
41
  h3: ({ children }) => ((0, jsx_runtime_1.jsx)("h3", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-2 py-2', getHeaderClass), children: children })),
31
42
  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 +44,6 @@ const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', conta
33
44
  h6: ({ children }) => ((0, jsx_runtime_1.jsx)("h6", { className: ps_design_system_1.PallasoftClassNameService.merge('text-header-4 py-1', getHeaderClass), children: children })),
34
45
  },
35
46
  };
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 }) }));
47
+ 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
48
  };
38
49
  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,32 @@
1
+ export declare const PsPortableTextMessageConfig: {
2
+ readonly rootClassName: "flex min-w-0 flex-col gap-3 text-sm leading-6 text-inherit [overflow-wrap:anywhere]";
3
+ readonly paragraphClassName: "whitespace-pre-line";
4
+ readonly quoteClassName: "whitespace-pre-line border-l-2 border-line pl-3 opacity-80";
5
+ readonly linkClassName: "underline underline-offset-2";
6
+ readonly codeClassName: "rounded bg-ink/10 px-1 font-mono text-[0.85em]";
7
+ readonly bulletListClassName: "flex list-disc flex-col gap-1 pl-5";
8
+ readonly numberListClassName: "flex list-decimal flex-col gap-1 pl-5";
9
+ readonly listItemClassName: "whitespace-pre-line";
10
+ readonly defaultLabels: {
11
+ imageHidden: string;
12
+ imagesHidden: string;
13
+ showImages: string;
14
+ };
15
+ readonly imageBarClassName: "flex flex-wrap items-center gap-x-2 gap-y-1 text-xs opacity-70";
16
+ readonly placeholderClassName: "flex min-h-10 items-center rounded-ps-control border border-dashed border-line px-3 py-2 text-xs opacity-70";
17
+ readonly imageClassName: "h-auto max-w-full rounded-ps-control border border-line";
18
+ readonly headingClassNames: {
19
+ readonly h1: "mt-1 text-lg font-semibold leading-snug";
20
+ readonly h2: "mt-1 text-base font-semibold leading-snug";
21
+ readonly h3: "mt-1 text-sm font-semibold leading-snug";
22
+ };
23
+ readonly linkRel: "noopener noreferrer";
24
+ readonly linkTarget: "_blank";
25
+ readonly imageLoading: "lazy";
26
+ readonly imageDecoding: "async";
27
+ readonly imageReferrerPolicy: "no-referrer";
28
+ readonly imageType: "image";
29
+ readonly imageProtocolPattern: RegExp;
30
+ readonly relativePrefix: "/";
31
+ readonly protocolRelativePrefix: "//";
32
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextMessageConfig = void 0;
4
+ exports.PsPortableTextMessageConfig = {
5
+ rootClassName: 'flex min-w-0 flex-col gap-3 text-sm leading-6 text-inherit [overflow-wrap:anywhere]',
6
+ paragraphClassName: 'whitespace-pre-line',
7
+ quoteClassName: 'whitespace-pre-line border-l-2 border-line pl-3 opacity-80',
8
+ linkClassName: 'underline underline-offset-2',
9
+ codeClassName: 'rounded bg-ink/10 px-1 font-mono text-[0.85em]',
10
+ bulletListClassName: 'flex list-disc flex-col gap-1 pl-5',
11
+ numberListClassName: 'flex list-decimal flex-col gap-1 pl-5',
12
+ listItemClassName: 'whitespace-pre-line',
13
+ defaultLabels: {
14
+ imageHidden: 'Image not shown',
15
+ imagesHidden: 'Images are hidden to protect your privacy.',
16
+ showImages: 'Show images',
17
+ },
18
+ imageBarClassName: 'flex flex-wrap items-center gap-x-2 gap-y-1 text-xs opacity-70',
19
+ placeholderClassName: 'flex min-h-10 items-center rounded-ps-control border border-dashed border-line px-3 py-2 text-xs opacity-70',
20
+ imageClassName: 'h-auto max-w-full rounded-ps-control border border-line',
21
+ headingClassNames: {
22
+ h1: 'mt-1 text-lg font-semibold leading-snug',
23
+ h2: 'mt-1 text-base font-semibold leading-snug',
24
+ h3: 'mt-1 text-sm font-semibold leading-snug',
25
+ },
26
+ linkRel: 'noopener noreferrer',
27
+ linkTarget: '_blank',
28
+ imageLoading: 'lazy',
29
+ imageDecoding: 'async',
30
+ imageReferrerPolicy: 'no-referrer',
31
+ imageType: 'image',
32
+ imageProtocolPattern: /^https?:\/\//i,
33
+ relativePrefix: '/',
34
+ protocolRelativePrefix: '//',
35
+ };
@@ -0,0 +1,4 @@
1
+ export declare enum PsPortableTextVariant {
2
+ Article = "article",
3
+ Message = "message"
4
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPortableTextVariant = void 0;
4
+ var PsPortableTextVariant;
5
+ (function (PsPortableTextVariant) {
6
+ PsPortableTextVariant["Article"] = "article";
7
+ PsPortableTextVariant["Message"] = "message";
8
+ })(PsPortableTextVariant || (exports.PsPortableTextVariant = PsPortableTextVariant = {}));
@@ -0,0 +1,4 @@
1
+ export declare const PsTextLinkGap: {
2
+ readonly text: " ";
3
+ readonly trailingSpacePattern: RegExp;
4
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkGap = void 0;
4
+ exports.PsTextLinkGap = {
5
+ text: ' ',
6
+ trailingSpacePattern: /\s$/,
7
+ };
@@ -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))) }));
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PsPortableTextMessageConfig } from '../constants/ps-portable-text-message-config.js';
3
+ import { PsPortableTextLinkService } from '../services/ps-portable-text-link-service.js';
4
+ export const PsPortableTextImage = ({ hiddenLabel, isShown, value }) => {
5
+ if (typeof value.src !== 'string' || !PsPortableTextLinkService.isSafeImage(value.src))
6
+ return null;
7
+ const alt = typeof value.alt === 'string' ? value.alt : '';
8
+ if (!isShown)
9
+ return (_jsx("div", { className: PsPortableTextMessageConfig.placeholderClassName, children: alt === '' ? hiddenLabel : alt }));
10
+ return (_jsx("img", { alt: alt, className: PsPortableTextMessageConfig.imageClassName, decoding: PsPortableTextMessageConfig.imageDecoding, loading: PsPortableTextMessageConfig.imageLoading, referrerPolicy: PsPortableTextMessageConfig.imageReferrerPolicy, src: value.src }));
11
+ };
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
+ import { PortableText as Text } from '@portabletext/react';
5
+ import { PsLinkedTextLinkRel } from '../constants/ps-linked-text-link-rel.js';
6
+ import { PsPortableTextAutoLinkType } from '../constants/ps-portable-text-auto-link-type.js';
7
+ import { PsPortableTextMessageConfig } from '../constants/ps-portable-text-message-config.js';
8
+ import { PsPortableTextLinkService } from '../services/ps-portable-text-link-service.js';
9
+ import { PsPortableTextImage } from './ps-portable-text-image.js';
10
+ import { PsTextLink } from './ps-text-link.js';
11
+ export const PsPortableTextMessage = ({ containerClasses, containerStyles, imageLabels, isImagesShown, linkClassName, LinkComponent, text, }) => {
12
+ const config = PsPortableTextMessageConfig;
13
+ const [isShown, setIsShown] = useState(isImagesShown);
14
+ const labels = { ...config.defaultLabels, ...imageLabels };
15
+ const linkClasses = linkClassName === undefined ? config.linkClassName : linkClassName;
16
+ const components = {
17
+ types: {
18
+ [config.imageType]: ({ value, ...props }) => (_jsx(PsPortableTextImage, { ...props, hiddenLabel: labels.imageHidden, isShown: isShown, value: value })),
19
+ },
20
+ marks: {
21
+ code: ({ children }) => _jsx("code", { className: config.codeClassName, children: children }),
22
+ em: ({ children }) => _jsx("em", { children: children }),
23
+ strong: ({ children }) => _jsx("strong", { children: children }),
24
+ underline: ({ children }) => _jsx("u", { children: children }),
25
+ [PsPortableTextAutoLinkType]: ({ children, value }) => (_jsx("a", { className: linkClasses, href: value.href, rel: PsLinkedTextLinkRel, target: config.linkTarget, children: children })),
26
+ link: ({ children, value }) => typeof value?.href === 'string' && PsPortableTextLinkService.isSafeLink(value.href) ? (_jsx(LinkComponent, { className: linkClasses, href: value.href, rel: config.linkRel, target: config.linkTarget, children: children })) : (_jsx(_Fragment, { children: children })),
27
+ },
28
+ list: {
29
+ bullet: ({ children }) => _jsx("ul", { className: config.bulletListClassName, children: children }),
30
+ number: ({ children }) => _jsx("ol", { className: config.numberListClassName, children: children }),
31
+ },
32
+ listItem: {
33
+ bullet: ({ children }) => _jsx("li", { className: config.listItemClassName, children: children }),
34
+ number: ({ children }) => _jsx("li", { className: config.listItemClassName, children: children }),
35
+ },
36
+ block: {
37
+ normal: ({ children }) => _jsx("p", { className: config.paragraphClassName, children: children }),
38
+ blockquote: ({ children }) => (_jsx("blockquote", { className: config.quoteClassName, children: children })),
39
+ h1: ({ children }) => _jsx("h3", { className: config.headingClassNames.h1, children: children }),
40
+ h2: ({ children }) => _jsx("h4", { className: config.headingClassNames.h2, children: children }),
41
+ h3: ({ children }) => _jsx("h5", { className: config.headingClassNames.h3, children: children }),
42
+ },
43
+ };
44
+ return (_jsxs("div", { className: PallasoftClassNameService.merge(config.rootClassName, containerClasses), style: containerStyles, children: [!isShown && PsPortableTextLinkService.hasImages(text) ? (_jsxs("p", { className: config.imageBarClassName, children: [_jsx("span", { children: labels.imagesHidden }), _jsx(PsTextLink, { onClick: () => setIsShown(true), size: 'compact', children: labels.showImages })] })) : null, _jsx(Text, { components: components, value: PsPortableTextLinkService.linkify(text) })] }));
45
+ };
@@ -1,16 +1,27 @@
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 { PsPortableTextVariant } from '../constants/ps-portable-text-variant.js';
8
+ import { PsTextWrapClassName } from '../constants/ps-text-wrap-class-name.js';
9
+ import { PsPortableTextLinkService } from '../services/ps-portable-text-link-service.js';
10
+ import { PsPortableTextMessage } from './ps-portable-text-message.js';
11
+ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = '', containerStyles = {}, headerClasses = 'text-ink', imageLabels, isImagesShown = false, isLargeBodyFont = false, linkClassName, LinkComponent = 'a', listClasses = 'text-ink', text, variant = PsPortableTextVariant.Article, }) => {
12
+ if (variant === PsPortableTextVariant.Message)
13
+ return (_jsx(PsPortableTextMessage, { containerClasses: containerClasses, containerStyles: containerStyles, imageLabels: imageLabels, isImagesShown: isImagesShown, linkClassName: linkClassName, LinkComponent: LinkComponent, text: text }));
14
+ const articleLinkClassName = linkClassName === undefined ? PsPortableTextLinkClassName : linkClassName;
5
15
  const hasContainerStyles = Object.keys(containerStyles).length > 0;
6
16
  const getBodyClass = hasContainerStyles ? '' : bodyClasses;
7
17
  const getHeaderClass = hasContainerStyles ? '' : headerClasses;
8
18
  const getListClass = hasContainerStyles ? '' : listClasses;
9
19
  const components = {
10
20
  marks: {
21
+ [PsPortableTextAutoLinkType]: ({ children, value }) => (_jsx("a", { href: value.href, target: '_blank', rel: PsLinkedTextLinkRel, className: articleLinkClassName, children: children })),
11
22
  link: ({ children, value }) => {
12
23
  const rel = !value.href.startsWith('/') ? 'noreferrer noopener' : undefined;
13
- return (_jsx(LinkComponent, { href: value.href, rel: rel, className: linkClassName, "aria-label": value.children, children: children }));
24
+ return (_jsx(LinkComponent, { href: value.href, rel: rel, className: articleLinkClassName, "aria-label": value.children, children: children }));
14
25
  },
15
26
  },
16
27
  list: {
@@ -18,11 +29,11 @@ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = ''
18
29
  number: ({ children }) => _jsx("ol", { className: 'mb-3 mt-1 list-decimal ml-4', children: children }),
19
30
  },
20
31
  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 })),
32
+ bullet: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', PsTextWrapClassName, getListClass), children: children })),
33
+ number: ({ children }) => (_jsx("li", { className: PallasoftClassNameService.merge('md:text-body-2 max-md:text-body-3', PsTextWrapClassName, getListClass), children: children })),
23
34
  },
24
35
  block: {
25
- normal: ({ children }) => (_jsx("p", { className: PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', getBodyClass), children: children })),
36
+ normal: ({ children }) => (_jsx("p", { className: PallasoftClassNameService.merge(isLargeBodyFont ? 'text-body-1' : 'text-body-2', 'whitespace-pre-line', PsTextWrapClassName, getBodyClass), children: children })),
26
37
  h2: ({ children }) => (_jsx("h2", { className: PallasoftClassNameService.merge('text-header-1 py-2', getHeaderClass), children: children })),
27
38
  h3: ({ children }) => (_jsx("h3", { className: PallasoftClassNameService.merge('text-header-2 py-2', getHeaderClass), children: children })),
28
39
  h4: ({ children }) => (_jsx("h4", { className: PallasoftClassNameService.merge('text-header-3 py-2', getHeaderClass), children: children })),
@@ -30,5 +41,5 @@ export const PsPortableText = ({ bodyClasses = 'text-ink', containerClasses = ''
30
41
  h6: ({ children }) => (_jsx("h6", { className: PallasoftClassNameService.merge('text-header-4 py-1', getHeaderClass), children: children })),
31
42
  },
32
43
  };
33
- return (_jsx("div", { className: containerClasses, style: containerStyles, children: _jsx(Text, { value: text, components: components }) }));
44
+ return (_jsx("div", { className: containerClasses, style: containerStyles, children: _jsx(Text, { value: PsPortableTextLinkService.linkify(text), components: components }) }));
34
45
  };
@@ -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,32 @@
1
+ export const PsPortableTextMessageConfig = {
2
+ rootClassName: 'flex min-w-0 flex-col gap-3 text-sm leading-6 text-inherit [overflow-wrap:anywhere]',
3
+ paragraphClassName: 'whitespace-pre-line',
4
+ quoteClassName: 'whitespace-pre-line border-l-2 border-line pl-3 opacity-80',
5
+ linkClassName: 'underline underline-offset-2',
6
+ codeClassName: 'rounded bg-ink/10 px-1 font-mono text-[0.85em]',
7
+ bulletListClassName: 'flex list-disc flex-col gap-1 pl-5',
8
+ numberListClassName: 'flex list-decimal flex-col gap-1 pl-5',
9
+ listItemClassName: 'whitespace-pre-line',
10
+ defaultLabels: {
11
+ imageHidden: 'Image not shown',
12
+ imagesHidden: 'Images are hidden to protect your privacy.',
13
+ showImages: 'Show images',
14
+ },
15
+ imageBarClassName: 'flex flex-wrap items-center gap-x-2 gap-y-1 text-xs opacity-70',
16
+ placeholderClassName: 'flex min-h-10 items-center rounded-ps-control border border-dashed border-line px-3 py-2 text-xs opacity-70',
17
+ imageClassName: 'h-auto max-w-full rounded-ps-control border border-line',
18
+ headingClassNames: {
19
+ h1: 'mt-1 text-lg font-semibold leading-snug',
20
+ h2: 'mt-1 text-base font-semibold leading-snug',
21
+ h3: 'mt-1 text-sm font-semibold leading-snug',
22
+ },
23
+ linkRel: 'noopener noreferrer',
24
+ linkTarget: '_blank',
25
+ imageLoading: 'lazy',
26
+ imageDecoding: 'async',
27
+ imageReferrerPolicy: 'no-referrer',
28
+ imageType: 'image',
29
+ imageProtocolPattern: /^https?:\/\//i,
30
+ relativePrefix: '/',
31
+ protocolRelativePrefix: '//',
32
+ };
@@ -0,0 +1,5 @@
1
+ export var PsPortableTextVariant;
2
+ (function (PsPortableTextVariant) {
3
+ PsPortableTextVariant["Article"] = "article";
4
+ PsPortableTextVariant["Message"] = "message";
5
+ })(PsPortableTextVariant || (PsPortableTextVariant = {}));
@@ -0,0 +1,4 @@
1
+ export const PsTextLinkGap = {
2
+ text: ' ',
3
+ trailingSpacePattern: /\s$/,
4
+ };
@@ -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,17 @@ 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-portable-text-image.js';
39
+ export * from './components/ps-portable-text-message.js';
40
+ export * from './constants/ps-portable-text-variant.js';
41
+ export * from './constants/ps-portable-text-message-config.js';
42
+ export * from './models/ps-portable-text-image-data.js';
43
+ export * from './models/ps-portable-text-image-labels.js';
44
+ export * from './models/ps-portable-text-message-props.js';
45
+ export * from './components/ps-linked-text.js';
46
+ export * from './services/ps-text-link-service.js';
47
+ export * from './models/ps-text-link-segment-data.js';
48
+ export * from './constants/ps-portable-text-link-class-name.js';
38
49
  export * from './components/ps-reflective-app-shell.js';
39
50
  export * from './components/ps-scaled-preview.js';
40
51
  export * from './components/ps-navigation-progress.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { PsPortableTextAutoLinkType } from '../constants/ps-portable-text-auto-link-type.js';
2
+ import { PsPortableTextMessageConfig } from '../constants/ps-portable-text-message-config.js';
3
+ import { PsTextLinkProtocols } from '../constants/ps-text-link-protocols.js';
4
+ import { PsTextLinkService } from './ps-text-link-service.js';
5
+ export class PsPortableTextLinkService {
6
+ static isSafeLink(href) {
7
+ if (href.startsWith(PsPortableTextMessageConfig.relativePrefix) &&
8
+ !href.startsWith(PsPortableTextMessageConfig.protocolRelativePrefix))
9
+ return true;
10
+ return URL.canParse(href) && PsTextLinkProtocols.includes(new URL(href).protocol);
11
+ }
12
+ static hasImages(blocks) {
13
+ return blocks.some((block) => block._type === PsPortableTextMessageConfig.imageType &&
14
+ 'src' in block &&
15
+ typeof block.src === 'string' &&
16
+ PsPortableTextLinkService.isSafeImage(block.src));
17
+ }
18
+ static isSafeImage(src) {
19
+ return (PsPortableTextMessageConfig.imageProtocolPattern.test(src) &&
20
+ URL.canParse(src) &&
21
+ PsTextLinkProtocols.includes(new URL(src).protocol));
22
+ }
23
+ static linkify(blocks) {
24
+ return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
25
+ }
26
+ static isBlock(value) {
27
+ return 'children' in value && Array.isArray(value.children);
28
+ }
29
+ static isSpan(value) {
30
+ return value._type === 'span' && 'text' in value && typeof value.text === 'string';
31
+ }
32
+ static linkifyBlock(block) {
33
+ const markDefs = block.markDefs === undefined ? [] : block.markDefs;
34
+ const added = [];
35
+ const children = [];
36
+ for (const child of block.children) {
37
+ if (!this.isSpan(child) || this.hasLinkMark(child, markDefs)) {
38
+ children.push(child);
39
+ continue;
40
+ }
41
+ const segments = PsTextLinkService.segments(child.text);
42
+ if (segments.length === 1 && segments[0].href === null) {
43
+ children.push(child);
44
+ continue;
45
+ }
46
+ const marks = child.marks === undefined ? [] : child.marks;
47
+ segments.forEach((segment, index) => {
48
+ const key = child._key === undefined ? undefined : `${child._key}-${index}`;
49
+ if (segment.href === null) {
50
+ const plain = { ...child, _key: key, text: segment.text };
51
+ children.push(plain);
52
+ return;
53
+ }
54
+ const markKey = `${PsPortableTextAutoLinkType}-${added.length}`;
55
+ added.push({ _key: markKey, _type: PsPortableTextAutoLinkType, href: segment.href });
56
+ const linked = {
57
+ ...child,
58
+ _key: key,
59
+ marks: [...marks, markKey],
60
+ text: segment.text,
61
+ };
62
+ children.push(linked);
63
+ });
64
+ }
65
+ return { ...block, children, markDefs: [...markDefs, ...added] };
66
+ }
67
+ static hasLinkMark(span, markDefs) {
68
+ if (span.marks === undefined)
69
+ return false;
70
+ return span.marks.some((mark) => markDefs.some((definition) => definition._key === mark && definition._type === 'link'));
71
+ }
72
+ }
@@ -0,0 +1,72 @@
1
+ import { PsTextLinkGap } from '../constants/ps-text-link-gap.js';
2
+ import { PsTextLinkPatternSource } from '../constants/ps-text-link-pattern-source.js';
3
+ import { PsTextLinkProtocols } from '../constants/ps-text-link-protocols.js';
4
+ import { PsTextLinkTrailingCharacters } from '../constants/ps-text-link-trailing-characters.js';
5
+ export class PsTextLinkService {
6
+ static segments(text) {
7
+ const segments = [];
8
+ const pattern = new RegExp(PsTextLinkPatternSource, 'g');
9
+ let cursor = 0;
10
+ let match = pattern.exec(text);
11
+ while (match !== null) {
12
+ this.pushText(segments, text.slice(cursor, match.index));
13
+ cursor = match.index + match[0].length;
14
+ if (match[1] !== undefined)
15
+ this.pushAngle(segments, match[0], match[1]);
16
+ else
17
+ this.pushBare(segments, match[2]);
18
+ match = pattern.exec(text);
19
+ }
20
+ this.pushText(segments, text.slice(cursor));
21
+ return segments;
22
+ }
23
+ static pushAngle(segments, raw, href) {
24
+ const url = this.parse(href);
25
+ if (url === null) {
26
+ this.pushText(segments, raw);
27
+ return;
28
+ }
29
+ const label = url.protocol === 'mailto:' ? url.pathname : url.hostname.replace(/^www\./, '');
30
+ if (label === '') {
31
+ this.pushText(segments, raw);
32
+ return;
33
+ }
34
+ this.separate(segments);
35
+ segments.push({ href, text: label });
36
+ }
37
+ static separate(segments) {
38
+ const last = segments[segments.length - 1];
39
+ if (last !== undefined &&
40
+ last.href === null &&
41
+ !PsTextLinkGap.trailingSpacePattern.test(last.text))
42
+ last.text += PsTextLinkGap.text;
43
+ }
44
+ static pushBare(segments, raw) {
45
+ let end = raw.length;
46
+ while (end > 0 && PsTextLinkTrailingCharacters.includes(raw.charAt(end - 1)))
47
+ end -= 1;
48
+ const href = raw.slice(0, end);
49
+ if (this.parse(href) === null) {
50
+ this.pushText(segments, raw);
51
+ return;
52
+ }
53
+ segments.push({ href, text: href });
54
+ this.pushText(segments, raw.slice(end));
55
+ }
56
+ static pushText(segments, text) {
57
+ if (text === '')
58
+ return;
59
+ const last = segments[segments.length - 1];
60
+ if (last !== undefined && last.href === null) {
61
+ last.text += text;
62
+ return;
63
+ }
64
+ segments.push({ href: null, text });
65
+ }
66
+ static parse(href) {
67
+ if (!URL.canParse(href))
68
+ return null;
69
+ const url = new URL(href);
70
+ return PsTextLinkProtocols.includes(url.protocol) ? url : null;
71
+ }
72
+ }
package/dist/index.d.ts CHANGED
@@ -35,6 +35,17 @@ 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-portable-text-image';
39
+ export * from './components/ps-portable-text-message';
40
+ export * from './constants/ps-portable-text-variant';
41
+ export * from './constants/ps-portable-text-message-config';
42
+ export * from './models/ps-portable-text-image-data';
43
+ export * from './models/ps-portable-text-image-labels';
44
+ export * from './models/ps-portable-text-message-props';
45
+ export * from './components/ps-linked-text';
46
+ export * from './services/ps-text-link-service';
47
+ export * from './models/ps-text-link-segment-data';
48
+ export * from './constants/ps-portable-text-link-class-name';
38
49
  export * from './components/ps-reflective-app-shell';
39
50
  export * from './components/ps-scaled-preview';
40
51
  export * from './components/ps-navigation-progress';
package/dist/index.js CHANGED
@@ -51,6 +51,17 @@ __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-portable-text-image"), exports);
55
+ __exportStar(require("./components/ps-portable-text-message"), exports);
56
+ __exportStar(require("./constants/ps-portable-text-variant"), exports);
57
+ __exportStar(require("./constants/ps-portable-text-message-config"), exports);
58
+ __exportStar(require("./models/ps-portable-text-image-data"), exports);
59
+ __exportStar(require("./models/ps-portable-text-image-labels"), exports);
60
+ __exportStar(require("./models/ps-portable-text-message-props"), exports);
61
+ __exportStar(require("./components/ps-linked-text"), exports);
62
+ __exportStar(require("./services/ps-text-link-service"), exports);
63
+ __exportStar(require("./models/ps-text-link-segment-data"), exports);
64
+ __exportStar(require("./constants/ps-portable-text-link-class-name"), exports);
54
65
  __exportStar(require("./components/ps-reflective-app-shell"), exports);
55
66
  __exportStar(require("./components/ps-scaled-preview"), exports);
56
67
  __exportStar(require("./components/ps-navigation-progress"), exports);
@@ -0,0 +1,5 @@
1
+ export interface PsPortableTextImageData {
2
+ _type: 'image';
3
+ alt?: string;
4
+ src: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface PsPortableTextImageLabels {
2
+ imageHidden: string;
3
+ imagesHidden: string;
4
+ showImages: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import type { CSSProperties, ElementType, ReactNode } from 'react';
2
+ import type { TypedObject } from '@portabletext/types';
3
+ import type { PsPortableTextImageLabels } from './ps-portable-text-image-labels';
4
+ export interface PsPortableTextMessageProps {
5
+ containerClasses: string;
6
+ imageLabels?: Partial<PsPortableTextImageLabels>;
7
+ isImagesShown: boolean;
8
+ containerStyles: CSSProperties;
9
+ linkClassName?: string;
10
+ LinkComponent: ElementType<{
11
+ 'aria-label'?: string;
12
+ 'children'?: ReactNode;
13
+ 'className'?: string;
14
+ 'href': string;
15
+ 'rel'?: string;
16
+ 'target'?: string;
17
+ }>;
18
+ text: TypedObject[];
19
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,11 @@
1
+ import type { TypedObject } from '@portabletext/types';
2
+ export declare class PsPortableTextLinkService {
3
+ static isSafeLink(href: string): boolean;
4
+ static hasImages(blocks: TypedObject[]): boolean;
5
+ static isSafeImage(src: string): boolean;
6
+ static linkify(blocks: TypedObject[]): TypedObject[];
7
+ private static isBlock;
8
+ private static isSpan;
9
+ private static linkifyBlock;
10
+ private static hasLinkMark;
11
+ }
@@ -0,0 +1,76 @@
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_portable_text_message_config_1 = require("../constants/ps-portable-text-message-config");
6
+ const ps_text_link_protocols_1 = require("../constants/ps-text-link-protocols");
7
+ const ps_text_link_service_1 = require("./ps-text-link-service");
8
+ class PsPortableTextLinkService {
9
+ static isSafeLink(href) {
10
+ if (href.startsWith(ps_portable_text_message_config_1.PsPortableTextMessageConfig.relativePrefix) &&
11
+ !href.startsWith(ps_portable_text_message_config_1.PsPortableTextMessageConfig.protocolRelativePrefix))
12
+ return true;
13
+ return URL.canParse(href) && ps_text_link_protocols_1.PsTextLinkProtocols.includes(new URL(href).protocol);
14
+ }
15
+ static hasImages(blocks) {
16
+ return blocks.some((block) => block._type === ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageType &&
17
+ 'src' in block &&
18
+ typeof block.src === 'string' &&
19
+ PsPortableTextLinkService.isSafeImage(block.src));
20
+ }
21
+ static isSafeImage(src) {
22
+ return (ps_portable_text_message_config_1.PsPortableTextMessageConfig.imageProtocolPattern.test(src) &&
23
+ URL.canParse(src) &&
24
+ ps_text_link_protocols_1.PsTextLinkProtocols.includes(new URL(src).protocol));
25
+ }
26
+ static linkify(blocks) {
27
+ return blocks.map((block) => (this.isBlock(block) ? this.linkifyBlock(block) : block));
28
+ }
29
+ static isBlock(value) {
30
+ return 'children' in value && Array.isArray(value.children);
31
+ }
32
+ static isSpan(value) {
33
+ return value._type === 'span' && 'text' in value && typeof value.text === 'string';
34
+ }
35
+ static linkifyBlock(block) {
36
+ const markDefs = block.markDefs === undefined ? [] : block.markDefs;
37
+ const added = [];
38
+ const children = [];
39
+ for (const child of block.children) {
40
+ if (!this.isSpan(child) || this.hasLinkMark(child, markDefs)) {
41
+ children.push(child);
42
+ continue;
43
+ }
44
+ const segments = ps_text_link_service_1.PsTextLinkService.segments(child.text);
45
+ if (segments.length === 1 && segments[0].href === null) {
46
+ children.push(child);
47
+ continue;
48
+ }
49
+ const marks = child.marks === undefined ? [] : child.marks;
50
+ segments.forEach((segment, index) => {
51
+ const key = child._key === undefined ? undefined : `${child._key}-${index}`;
52
+ if (segment.href === null) {
53
+ const plain = { ...child, _key: key, text: segment.text };
54
+ children.push(plain);
55
+ return;
56
+ }
57
+ const markKey = `${ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType}-${added.length}`;
58
+ added.push({ _key: markKey, _type: ps_portable_text_auto_link_type_1.PsPortableTextAutoLinkType, href: segment.href });
59
+ const linked = {
60
+ ...child,
61
+ _key: key,
62
+ marks: [...marks, markKey],
63
+ text: segment.text,
64
+ };
65
+ children.push(linked);
66
+ });
67
+ }
68
+ return { ...block, children, markDefs: [...markDefs, ...added] };
69
+ }
70
+ static hasLinkMark(span, markDefs) {
71
+ if (span.marks === undefined)
72
+ return false;
73
+ return span.marks.some((mark) => markDefs.some((definition) => definition._key === mark && definition._type === 'link'));
74
+ }
75
+ }
76
+ exports.PsPortableTextLinkService = PsPortableTextLinkService;
@@ -0,0 +1,9 @@
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 separate;
6
+ private static pushBare;
7
+ private static pushText;
8
+ private static parse;
9
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTextLinkService = void 0;
4
+ const ps_text_link_gap_1 = require("../constants/ps-text-link-gap");
5
+ const ps_text_link_pattern_source_1 = require("../constants/ps-text-link-pattern-source");
6
+ const ps_text_link_protocols_1 = require("../constants/ps-text-link-protocols");
7
+ const ps_text_link_trailing_characters_1 = require("../constants/ps-text-link-trailing-characters");
8
+ class PsTextLinkService {
9
+ static segments(text) {
10
+ const segments = [];
11
+ const pattern = new RegExp(ps_text_link_pattern_source_1.PsTextLinkPatternSource, 'g');
12
+ let cursor = 0;
13
+ let match = pattern.exec(text);
14
+ while (match !== null) {
15
+ this.pushText(segments, text.slice(cursor, match.index));
16
+ cursor = match.index + match[0].length;
17
+ if (match[1] !== undefined)
18
+ this.pushAngle(segments, match[0], match[1]);
19
+ else
20
+ this.pushBare(segments, match[2]);
21
+ match = pattern.exec(text);
22
+ }
23
+ this.pushText(segments, text.slice(cursor));
24
+ return segments;
25
+ }
26
+ static pushAngle(segments, raw, href) {
27
+ const url = this.parse(href);
28
+ if (url === null) {
29
+ this.pushText(segments, raw);
30
+ return;
31
+ }
32
+ const label = url.protocol === 'mailto:' ? url.pathname : url.hostname.replace(/^www\./, '');
33
+ if (label === '') {
34
+ this.pushText(segments, raw);
35
+ return;
36
+ }
37
+ this.separate(segments);
38
+ segments.push({ href, text: label });
39
+ }
40
+ static separate(segments) {
41
+ const last = segments[segments.length - 1];
42
+ if (last !== undefined &&
43
+ last.href === null &&
44
+ !ps_text_link_gap_1.PsTextLinkGap.trailingSpacePattern.test(last.text))
45
+ last.text += ps_text_link_gap_1.PsTextLinkGap.text;
46
+ }
47
+ static pushBare(segments, raw) {
48
+ let end = raw.length;
49
+ while (end > 0 && ps_text_link_trailing_characters_1.PsTextLinkTrailingCharacters.includes(raw.charAt(end - 1)))
50
+ end -= 1;
51
+ const href = raw.slice(0, end);
52
+ if (this.parse(href) === null) {
53
+ this.pushText(segments, raw);
54
+ return;
55
+ }
56
+ segments.push({ href, text: href });
57
+ this.pushText(segments, raw.slice(end));
58
+ }
59
+ static pushText(segments, text) {
60
+ if (text === '')
61
+ return;
62
+ const last = segments[segments.length - 1];
63
+ if (last !== undefined && last.href === null) {
64
+ last.text += text;
65
+ return;
66
+ }
67
+ segments.push({ href: null, text });
68
+ }
69
+ static parse(href) {
70
+ if (!URL.canParse(href))
71
+ return null;
72
+ const url = new URL(href);
73
+ return ps_text_link_protocols_1.PsTextLinkProtocols.includes(url.protocol) ? url : null;
74
+ }
75
+ }
76
+ 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.42.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -48,7 +48,7 @@
48
48
  "@dnd-kit/utilities": "^3.2.2",
49
49
  "@pallasoft/ps-icons": "^1.5.4",
50
50
  "@pallasoft/ps-motion-components": "^1.2.2",
51
- "@pallasoft/ps-utilities": "^2.12.0",
51
+ "@pallasoft/ps-utilities": "^2.23.0",
52
52
  "@portabletext/react": "^3.2.4",
53
53
  "@portabletext/types": "^2.0.13",
54
54
  "@radix-ui/react-accordion": "^1.2.12",