@pallasoft/ps-input-components 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-portable-text-editor.d.ts +12 -0
- package/dist/components/ps-portable-text-editor.js +38 -0
- package/dist/components/ps-portable-text-link-dialog.d.ts +8 -0
- package/dist/components/ps-portable-text-link-dialog.js +35 -0
- package/dist/components/ps-portable-text-toolbar-button.d.ts +10 -0
- package/dist/components/ps-portable-text-toolbar-button.js +8 -0
- package/dist/components/ps-portable-text-toolbar.d.ts +6 -0
- package/dist/components/ps-portable-text-toolbar.js +62 -0
- package/dist/constants/ps-portable-text-editor-config.d.ts +65 -0
- package/dist/constants/ps-portable-text-editor-config.js +53 -0
- package/dist/constants/ps-portable-text-editor-renderers.d.ts +6 -0
- package/dist/constants/ps-portable-text-editor-renderers.js +32 -0
- package/dist/esm/components/ps-portable-text-editor.js +34 -0
- package/dist/esm/components/ps-portable-text-link-dialog.js +31 -0
- package/dist/esm/components/ps-portable-text-toolbar-button.js +4 -0
- package/dist/esm/components/ps-portable-text-toolbar.js +58 -0
- package/dist/esm/constants/ps-portable-text-editor-config.js +50 -0
- package/dist/esm/constants/ps-portable-text-editor-renderers.js +29 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/interfaces/ps-portable-text-editor-labels.js +1 -0
- package/dist/esm/services/ps-portable-text-editor-service.js +30 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/interfaces/ps-portable-text-editor-labels.d.ts +17 -0
- package/dist/interfaces/ps-portable-text-editor-labels.js +2 -0
- package/dist/services/ps-portable-text-editor-service.d.ts +5 -0
- package/dist/services/ps-portable-text-editor-service.js +34 -0
- package/package.json +12 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type PortableTextBlock } from '@portabletext/editor';
|
|
2
|
+
import type { PsPortableTextEditorLabels } from '../interfaces/ps-portable-text-editor-labels';
|
|
3
|
+
export interface PsPortableTextEditorProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
labels?: PsPortableTextEditorLabels;
|
|
7
|
+
minRows?: number;
|
|
8
|
+
onChange: (value: PortableTextBlock[]) => void;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
value: PortableTextBlock[];
|
|
11
|
+
}
|
|
12
|
+
export declare const PsPortableTextEditor: ({ className, disabled, labels, minRows, onChange, placeholder, value, }: PsPortableTextEditorProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextEditor = 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 editor_1 = require("@portabletext/editor");
|
|
8
|
+
const plugins_1 = require("@portabletext/editor/plugins");
|
|
9
|
+
const ps_portable_text_editor_config_1 = require("../constants/ps-portable-text-editor-config");
|
|
10
|
+
const ps_portable_text_editor_renderers_1 = require("../constants/ps-portable-text-editor-renderers");
|
|
11
|
+
const ps_portable_text_editor_service_1 = require("../services/ps-portable-text-editor-service");
|
|
12
|
+
const ps_portable_text_toolbar_1 = require("./ps-portable-text-toolbar");
|
|
13
|
+
const PsPortableTextEditor = ({ className, disabled = false, labels = ps_portable_text_editor_config_1.PsPortableTextEditorConfig.labels, minRows = 4, onChange, placeholder, value, }) => {
|
|
14
|
+
const [isMounted, setIsMounted] = (0, react_1.useState)(false);
|
|
15
|
+
const [version, setVersion] = (0, react_1.useState)(0);
|
|
16
|
+
const lastValue = (0, react_1.useRef)(value);
|
|
17
|
+
(0, react_1.useEffect)(() => {
|
|
18
|
+
setIsMounted(true);
|
|
19
|
+
}, []);
|
|
20
|
+
(0, react_1.useEffect)(() => {
|
|
21
|
+
if (ps_portable_text_editor_service_1.PsPortableTextEditorService.isSame(value, lastValue.current))
|
|
22
|
+
return;
|
|
23
|
+
lastValue.current = value;
|
|
24
|
+
setVersion((current) => current + 1);
|
|
25
|
+
}, [value]);
|
|
26
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.root, className), children: isMounted ? ((0, jsx_runtime_1.jsxs)(editor_1.EditorProvider, { initialConfig: {
|
|
27
|
+
initialValue: value,
|
|
28
|
+
readOnly: disabled,
|
|
29
|
+
schemaDefinition: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.schemaDefinition,
|
|
30
|
+
}, children: [(0, jsx_runtime_1.jsx)(plugins_1.EventListenerPlugin, { on: (event) => {
|
|
31
|
+
if (event.type !== 'mutation')
|
|
32
|
+
return;
|
|
33
|
+
const next = Array.isArray(event.value) ? event.value : [];
|
|
34
|
+
lastValue.current = next;
|
|
35
|
+
onChange(next);
|
|
36
|
+
} }), (0, jsx_runtime_1.jsx)(ps_portable_text_toolbar_1.PsPortableTextToolbar, { disabled: disabled, labels: labels }), (0, jsx_runtime_1.jsx)(editor_1.PortableTextEditable, { ...ps_portable_text_editor_renderers_1.PsPortableTextEditorRenderers, className: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.editable, readOnly: disabled, renderPlaceholder: () => (0, jsx_runtime_1.jsx)("span", { className: 'text-ink/45', children: placeholder }), style: { minHeight: ps_portable_text_editor_service_1.PsPortableTextEditorService.minHeight(minRows) } })] }, version)) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.toolbar, children: (0, jsx_runtime_1.jsx)("span", { className: 'h-9' }) }), (0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.editable, style: { minHeight: ps_portable_text_editor_service_1.PsPortableTextEditorService.minHeight(minRows) } })] })) }));
|
|
37
|
+
};
|
|
38
|
+
exports.PsPortableTextEditor = PsPortableTextEditor;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PsPortableTextEditorLabels } from '../interfaces/ps-portable-text-editor-labels';
|
|
2
|
+
export interface PsPortableTextLinkDialogProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
labels: PsPortableTextEditorLabels;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onSubmit: (href: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const PsPortableTextLinkDialog: ({ isOpen, labels, onClose, onSubmit, }: PsPortableTextLinkDialogProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextLinkDialog = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_popovers_1 = require("@pallasoft/ps-popovers");
|
|
7
|
+
const ps_portable_text_editor_config_1 = require("../constants/ps-portable-text-editor-config");
|
|
8
|
+
const ps_portable_text_editor_service_1 = require("../services/ps-portable-text-editor-service");
|
|
9
|
+
const ps_url_input_1 = require("./ps-url-input");
|
|
10
|
+
const PsPortableTextLinkDialog = ({ isOpen, labels, onClose, onSubmit, }) => {
|
|
11
|
+
const [address, setAddress] = (0, react_1.useState)('');
|
|
12
|
+
const [hasError, setHasError] = (0, react_1.useState)(false);
|
|
13
|
+
const close = () => {
|
|
14
|
+
setAddress('');
|
|
15
|
+
setHasError(false);
|
|
16
|
+
onClose();
|
|
17
|
+
};
|
|
18
|
+
const submit = (event) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
event.stopPropagation();
|
|
21
|
+
const href = ps_portable_text_editor_service_1.PsPortableTextEditorService.normalizeLink(address);
|
|
22
|
+
if (href === null) {
|
|
23
|
+
setHasError(true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setAddress('');
|
|
27
|
+
setHasError(false);
|
|
28
|
+
onSubmit(href);
|
|
29
|
+
};
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)(ps_popovers_1.PsFormModal, { cancelLabel: labels.cancel, isOpen: isOpen, isSubmitDisabled: address.trim() === '', onClose: close, onSubmit: submit, size: 'sm', submitLabel: labels.saveLink, title: labels.linkDialogTitle, children: (0, jsx_runtime_1.jsx)(ps_url_input_1.PsUrlInput, { "aria-invalid": hasError, autoFocus: true, helperText: hasError ? labels.invalidLink : undefined, label: labels.linkUrl, name: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.linkAnnotation, onChange: (event) => {
|
|
31
|
+
setAddress(event.target.value);
|
|
32
|
+
setHasError(false);
|
|
33
|
+
}, value: address }) }));
|
|
34
|
+
};
|
|
35
|
+
exports.PsPortableTextLinkDialog = PsPortableTextLinkDialog;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface PsPortableTextToolbarButtonProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
label: string;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const PsPortableTextToolbarButton: ({ children, className, disabled, isActive, label, onClick, }: PsPortableTextToolbarButtonProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextToolbarButton = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
|
+
const ps_portable_text_editor_config_1 = require("../constants/ps-portable-text-editor-config");
|
|
7
|
+
const PsPortableTextToolbarButton = ({ children, className, disabled, isActive, label, onClick, }) => ((0, jsx_runtime_1.jsx)("button", { "aria-label": label, "aria-pressed": isActive, className: ps_design_system_1.PallasoftClassNameService.merge(ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.button, isActive ? ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.buttonActive : '', className), disabled: disabled, onClick: onClick, onMouseDown: (event) => event.preventDefault(), title: label, type: 'button', children: children }));
|
|
8
|
+
exports.PsPortableTextToolbarButton = PsPortableTextToolbarButton;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PsPortableTextEditorLabels } from '../interfaces/ps-portable-text-editor-labels';
|
|
2
|
+
export interface PsPortableTextToolbarProps {
|
|
3
|
+
disabled: boolean;
|
|
4
|
+
labels: PsPortableTextEditorLabels;
|
|
5
|
+
}
|
|
6
|
+
export declare const PsPortableTextToolbar: ({ disabled, labels }: PsPortableTextToolbarProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextToolbar = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const editor_1 = require("@portabletext/editor");
|
|
7
|
+
const selectors_1 = require("@portabletext/editor/selectors");
|
|
8
|
+
const ps_portable_text_editor_config_1 = require("../constants/ps-portable-text-editor-config");
|
|
9
|
+
const ps_portable_text_link_dialog_1 = require("./ps-portable-text-link-dialog");
|
|
10
|
+
const ps_portable_text_toolbar_button_1 = require("./ps-portable-text-toolbar-button");
|
|
11
|
+
const DecoratorButton = ({ children, className, decorator, disabled, label, }) => {
|
|
12
|
+
const editor = (0, editor_1.useEditor)();
|
|
13
|
+
const isActive = (0, editor_1.useEditorSelector)(editor, (0, selectors_1.isActiveDecorator)(decorator));
|
|
14
|
+
return ((0, jsx_runtime_1.jsx)(ps_portable_text_toolbar_button_1.PsPortableTextToolbarButton, { className: className, disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
15
|
+
editor.send({ type: 'decorator.toggle', decorator });
|
|
16
|
+
editor.send({ type: 'focus' });
|
|
17
|
+
}, children: children }));
|
|
18
|
+
};
|
|
19
|
+
const ListButton = ({ children, disabled, label, listItem, }) => {
|
|
20
|
+
const editor = (0, editor_1.useEditor)();
|
|
21
|
+
const isActive = (0, editor_1.useEditorSelector)(editor, (0, selectors_1.isActiveListItem)(listItem));
|
|
22
|
+
return ((0, jsx_runtime_1.jsx)(ps_portable_text_toolbar_button_1.PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
23
|
+
editor.send({ type: 'list item.toggle', listItem });
|
|
24
|
+
editor.send({ type: 'focus' });
|
|
25
|
+
}, children: children }));
|
|
26
|
+
};
|
|
27
|
+
const QuoteButton = ({ children, disabled, label }) => {
|
|
28
|
+
const editor = (0, editor_1.useEditor)();
|
|
29
|
+
const isActive = (0, editor_1.useEditorSelector)(editor, (0, selectors_1.isActiveStyle)(ps_portable_text_editor_config_1.PsPortableTextEditorConfig.styles.blockquote));
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)(ps_portable_text_toolbar_button_1.PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
31
|
+
editor.send({ type: 'style.toggle', style: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.styles.blockquote });
|
|
32
|
+
editor.send({ type: 'focus' });
|
|
33
|
+
}, children: children }));
|
|
34
|
+
};
|
|
35
|
+
const LinkButton = ({ disabled, labels }) => {
|
|
36
|
+
const editor = (0, editor_1.useEditor)();
|
|
37
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
38
|
+
const isActive = (0, editor_1.useEditorSelector)(editor, (0, selectors_1.isActiveAnnotation)(ps_portable_text_editor_config_1.PsPortableTextEditorConfig.linkAnnotation));
|
|
39
|
+
const close = () => {
|
|
40
|
+
setIsOpen(false);
|
|
41
|
+
editor.send({ type: 'focus' });
|
|
42
|
+
};
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ps_portable_text_toolbar_button_1.PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: isActive ? labels.removeLink : labels.link, onClick: () => {
|
|
44
|
+
if (isActive) {
|
|
45
|
+
editor.send({
|
|
46
|
+
type: 'annotation.remove',
|
|
47
|
+
annotation: { name: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.linkAnnotation },
|
|
48
|
+
});
|
|
49
|
+
editor.send({ type: 'focus' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setIsOpen(true);
|
|
53
|
+
}, children: (0, jsx_runtime_1.jsx)("span", { className: 'underline', children: labels.link }) }), (0, jsx_runtime_1.jsx)(ps_portable_text_link_dialog_1.PsPortableTextLinkDialog, { isOpen: isOpen, labels: labels, onClose: close, onSubmit: (href) => {
|
|
54
|
+
editor.send({
|
|
55
|
+
type: 'annotation.add',
|
|
56
|
+
annotation: { name: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.linkAnnotation, value: { href } },
|
|
57
|
+
});
|
|
58
|
+
close();
|
|
59
|
+
} })] }));
|
|
60
|
+
};
|
|
61
|
+
const PsPortableTextToolbar = ({ disabled, labels }) => ((0, jsx_runtime_1.jsxs)("div", { "aria-label": labels.toolbar, className: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.toolbar, role: 'toolbar', children: [(0, jsx_runtime_1.jsx)(DecoratorButton, { className: 'font-bold', decorator: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.bold, disabled: disabled, label: labels.bold, children: "B" }), (0, jsx_runtime_1.jsx)(DecoratorButton, { className: 'italic', decorator: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.italic, disabled: disabled, label: labels.italic, children: "I" }), (0, jsx_runtime_1.jsx)(DecoratorButton, { className: 'underline', decorator: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.underline, disabled: disabled, label: labels.underline, children: "U" }), (0, jsx_runtime_1.jsx)(DecoratorButton, { className: 'font-mono', decorator: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.code, disabled: disabled, label: labels.code, children: '</>' }), (0, jsx_runtime_1.jsx)(LinkButton, { disabled: disabled, labels: labels }), (0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', className: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.classNames.separator }), (0, jsx_runtime_1.jsx)(ListButton, { disabled: disabled, label: labels.bulletedList, listItem: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.listItems.bullet, children: "\u2022" }), (0, jsx_runtime_1.jsx)(ListButton, { disabled: disabled, label: labels.numberedList, listItem: ps_portable_text_editor_config_1.PsPortableTextEditorConfig.listItems.number, children: "1." }), (0, jsx_runtime_1.jsx)(QuoteButton, { disabled: disabled, label: labels.quote, children: "\u201D" })] }));
|
|
62
|
+
exports.PsPortableTextToolbar = PsPortableTextToolbar;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export declare const PsPortableTextEditorConfig: {
|
|
2
|
+
readonly linkAnnotation: "link";
|
|
3
|
+
readonly mailtoPrefix: "mailto:";
|
|
4
|
+
readonly emailPattern: RegExp;
|
|
5
|
+
readonly lineHeightRem: 1.5;
|
|
6
|
+
readonly decorators: {
|
|
7
|
+
readonly bold: "strong";
|
|
8
|
+
readonly code: "code";
|
|
9
|
+
readonly italic: "em";
|
|
10
|
+
readonly underline: "underline";
|
|
11
|
+
};
|
|
12
|
+
readonly styles: {
|
|
13
|
+
readonly blockquote: "blockquote";
|
|
14
|
+
readonly normal: "normal";
|
|
15
|
+
};
|
|
16
|
+
readonly listItems: {
|
|
17
|
+
readonly bullet: "bullet";
|
|
18
|
+
readonly number: "number";
|
|
19
|
+
};
|
|
20
|
+
readonly schemaDefinition: {
|
|
21
|
+
readonly annotations: readonly [{
|
|
22
|
+
readonly name: "link";
|
|
23
|
+
readonly fields: readonly [{
|
|
24
|
+
readonly name: "href";
|
|
25
|
+
readonly type: "string";
|
|
26
|
+
}];
|
|
27
|
+
}];
|
|
28
|
+
readonly blockObjects: readonly [];
|
|
29
|
+
readonly decorators: {
|
|
30
|
+
name: "code" | "em" | "strong" | "underline";
|
|
31
|
+
}[];
|
|
32
|
+
readonly inlineObjects: readonly [];
|
|
33
|
+
readonly lists: {
|
|
34
|
+
name: "number" | "bullet";
|
|
35
|
+
}[];
|
|
36
|
+
readonly styles: {
|
|
37
|
+
name: "blockquote" | "normal";
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
readonly labels: {
|
|
41
|
+
bold: string;
|
|
42
|
+
bulletedList: string;
|
|
43
|
+
cancel: string;
|
|
44
|
+
code: string;
|
|
45
|
+
invalidLink: string;
|
|
46
|
+
italic: string;
|
|
47
|
+
link: string;
|
|
48
|
+
linkDialogTitle: string;
|
|
49
|
+
linkUrl: string;
|
|
50
|
+
numberedList: string;
|
|
51
|
+
quote: string;
|
|
52
|
+
removeLink: string;
|
|
53
|
+
saveLink: string;
|
|
54
|
+
toolbar: string;
|
|
55
|
+
underline: string;
|
|
56
|
+
};
|
|
57
|
+
readonly classNames: {
|
|
58
|
+
readonly editable: "ps-input-control w-full resize-none overflow-y-auto rounded-t-none border-t-0 leading-6 outline-none";
|
|
59
|
+
readonly root: "block";
|
|
60
|
+
readonly toolbar: "flex flex-wrap items-center gap-1 rounded-t-xl border border-line bg-white p-1 dark:bg-transparent";
|
|
61
|
+
readonly button: "ps-focus-ring inline-flex h-9 min-w-9 items-center justify-center rounded-lg px-2 text-sm text-ink/70 transition-colors hover:bg-ink/5 hover:text-ink disabled:cursor-not-allowed disabled:opacity-40";
|
|
62
|
+
readonly buttonActive: "bg-primary text-white hover:bg-primary hover:text-white";
|
|
63
|
+
readonly separator: "mx-1 h-5 w-px bg-line";
|
|
64
|
+
};
|
|
65
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextEditorConfig = void 0;
|
|
4
|
+
const ps_models_1 = require("@pallasoft/ps-models");
|
|
5
|
+
const editor_1 = require("@portabletext/editor");
|
|
6
|
+
const Models = ps_models_1.Common.PortableTextConfig;
|
|
7
|
+
exports.PsPortableTextEditorConfig = {
|
|
8
|
+
linkAnnotation: Models.linkType,
|
|
9
|
+
mailtoPrefix: 'mailto:',
|
|
10
|
+
emailPattern: /^[^\s@/:]+@[^\s@/:]+\.[^\s@/:]+$/,
|
|
11
|
+
lineHeightRem: 1.5,
|
|
12
|
+
decorators: {
|
|
13
|
+
bold: Models.decorators[0],
|
|
14
|
+
code: Models.decorators[3],
|
|
15
|
+
italic: Models.decorators[1],
|
|
16
|
+
underline: Models.decorators[2],
|
|
17
|
+
},
|
|
18
|
+
styles: { blockquote: Models.styles[1], normal: Models.styles[0] },
|
|
19
|
+
listItems: { bullet: Models.listItems[0], number: Models.listItems[1] },
|
|
20
|
+
schemaDefinition: (0, editor_1.defineSchema)({
|
|
21
|
+
annotations: [{ name: Models.linkType, fields: [{ name: 'href', type: 'string' }] }],
|
|
22
|
+
blockObjects: [],
|
|
23
|
+
decorators: Models.decorators.map((name) => ({ name })),
|
|
24
|
+
inlineObjects: [],
|
|
25
|
+
lists: Models.listItems.map((name) => ({ name })),
|
|
26
|
+
styles: Models.styles.map((name) => ({ name })),
|
|
27
|
+
}),
|
|
28
|
+
labels: {
|
|
29
|
+
bold: 'Bold',
|
|
30
|
+
bulletedList: 'Bulleted list',
|
|
31
|
+
cancel: 'Cancel',
|
|
32
|
+
code: 'Code',
|
|
33
|
+
invalidLink: 'Enter a web address or email address',
|
|
34
|
+
italic: 'Italic',
|
|
35
|
+
link: 'Link',
|
|
36
|
+
linkDialogTitle: 'Link',
|
|
37
|
+
linkUrl: 'Address',
|
|
38
|
+
numberedList: 'Numbered list',
|
|
39
|
+
quote: 'Quote',
|
|
40
|
+
removeLink: 'Remove link',
|
|
41
|
+
saveLink: 'Save',
|
|
42
|
+
toolbar: 'Formatting',
|
|
43
|
+
underline: 'Underline',
|
|
44
|
+
},
|
|
45
|
+
classNames: {
|
|
46
|
+
editable: 'ps-input-control w-full resize-none overflow-y-auto rounded-t-none border-t-0 leading-6 outline-none',
|
|
47
|
+
root: 'block',
|
|
48
|
+
toolbar: 'flex flex-wrap items-center gap-1 rounded-t-xl border border-line bg-white p-1 dark:bg-transparent',
|
|
49
|
+
button: 'ps-focus-ring inline-flex h-9 min-w-9 items-center justify-center rounded-lg px-2 text-sm text-ink/70 transition-colors hover:bg-ink/5 hover:text-ink disabled:cursor-not-allowed disabled:opacity-40',
|
|
50
|
+
buttonActive: 'bg-primary text-white hover:bg-primary hover:text-white',
|
|
51
|
+
separator: 'mx-1 h-5 w-px bg-line',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BlockAnnotationRenderProps, BlockDecoratorRenderProps, BlockRenderProps } from '@portabletext/editor';
|
|
2
|
+
export declare const PsPortableTextEditorRenderers: {
|
|
3
|
+
renderBlock: ({ children, listItem, style }: BlockRenderProps) => import("react").JSX.Element;
|
|
4
|
+
renderDecorator: ({ children, value }: BlockDecoratorRenderProps) => import("react").JSX.Element;
|
|
5
|
+
renderAnnotation: ({ children }: BlockAnnotationRenderProps) => import("react").JSX.Element;
|
|
6
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextEditorRenderers = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_portable_text_editor_config_1 = require("./ps-portable-text-editor-config");
|
|
6
|
+
exports.PsPortableTextEditorRenderers = {
|
|
7
|
+
renderBlock: ({ children, listItem, style }) => {
|
|
8
|
+
if (listItem === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.listItems.bullet) {
|
|
9
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "relative pl-6 before:absolute before:left-1 before:content-['\u2022']", children: children }));
|
|
10
|
+
}
|
|
11
|
+
if (listItem === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.listItems.number) {
|
|
12
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "relative pl-6 before:absolute before:left-0 before:content-[counter(ps-list)'.']", style: { counterIncrement: 'ps-list' }, children: children }));
|
|
13
|
+
}
|
|
14
|
+
if (style === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.styles.blockquote) {
|
|
15
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: 'border-l-2 border-primary pl-3 text-ink/80', style: { counterReset: 'ps-list' }, children: children }));
|
|
16
|
+
}
|
|
17
|
+
return (0, jsx_runtime_1.jsx)("div", { style: { counterReset: 'ps-list' }, children: children });
|
|
18
|
+
},
|
|
19
|
+
renderDecorator: ({ children, value }) => {
|
|
20
|
+
if (value === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.bold)
|
|
21
|
+
return (0, jsx_runtime_1.jsx)("strong", { children: children });
|
|
22
|
+
if (value === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.italic)
|
|
23
|
+
return (0, jsx_runtime_1.jsx)("em", { children: children });
|
|
24
|
+
if (value === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.underline)
|
|
25
|
+
return (0, jsx_runtime_1.jsx)("u", { children: children });
|
|
26
|
+
if (value === ps_portable_text_editor_config_1.PsPortableTextEditorConfig.decorators.code) {
|
|
27
|
+
return (0, jsx_runtime_1.jsx)("code", { className: 'rounded bg-ink/10 px-1 font-mono text-[0.9em]', children: children });
|
|
28
|
+
}
|
|
29
|
+
return children;
|
|
30
|
+
},
|
|
31
|
+
renderAnnotation: ({ children }) => ((0, jsx_runtime_1.jsx)("span", { className: 'text-primary underline', children: children })),
|
|
32
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { EditorProvider, PortableTextEditable } from '@portabletext/editor';
|
|
5
|
+
import { EventListenerPlugin } from '@portabletext/editor/plugins';
|
|
6
|
+
import { PsPortableTextEditorConfig } from '../constants/ps-portable-text-editor-config';
|
|
7
|
+
import { PsPortableTextEditorRenderers } from '../constants/ps-portable-text-editor-renderers';
|
|
8
|
+
import { PsPortableTextEditorService } from '../services/ps-portable-text-editor-service';
|
|
9
|
+
import { PsPortableTextToolbar } from './ps-portable-text-toolbar';
|
|
10
|
+
export const PsPortableTextEditor = ({ className, disabled = false, labels = PsPortableTextEditorConfig.labels, minRows = 4, onChange, placeholder, value, }) => {
|
|
11
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
12
|
+
const [version, setVersion] = useState(0);
|
|
13
|
+
const lastValue = useRef(value);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setIsMounted(true);
|
|
16
|
+
}, []);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (PsPortableTextEditorService.isSame(value, lastValue.current))
|
|
19
|
+
return;
|
|
20
|
+
lastValue.current = value;
|
|
21
|
+
setVersion((current) => current + 1);
|
|
22
|
+
}, [value]);
|
|
23
|
+
return (_jsx("div", { className: PallasoftClassNameService.merge(PsPortableTextEditorConfig.classNames.root, className), children: isMounted ? (_jsxs(EditorProvider, { initialConfig: {
|
|
24
|
+
initialValue: value,
|
|
25
|
+
readOnly: disabled,
|
|
26
|
+
schemaDefinition: PsPortableTextEditorConfig.schemaDefinition,
|
|
27
|
+
}, children: [_jsx(EventListenerPlugin, { on: (event) => {
|
|
28
|
+
if (event.type !== 'mutation')
|
|
29
|
+
return;
|
|
30
|
+
const next = Array.isArray(event.value) ? event.value : [];
|
|
31
|
+
lastValue.current = next;
|
|
32
|
+
onChange(next);
|
|
33
|
+
} }), _jsx(PsPortableTextToolbar, { disabled: disabled, labels: labels }), _jsx(PortableTextEditable, { ...PsPortableTextEditorRenderers, className: PsPortableTextEditorConfig.classNames.editable, readOnly: disabled, renderPlaceholder: () => _jsx("span", { className: 'text-ink/45', children: placeholder }), style: { minHeight: PsPortableTextEditorService.minHeight(minRows) } })] }, version)) : (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": 'true', className: PsPortableTextEditorConfig.classNames.toolbar, children: _jsx("span", { className: 'h-9' }) }), _jsx("div", { "aria-hidden": 'true', className: PsPortableTextEditorConfig.classNames.editable, style: { minHeight: PsPortableTextEditorService.minHeight(minRows) } })] })) }));
|
|
34
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { PsFormModal } from '@pallasoft/ps-popovers';
|
|
4
|
+
import { PsPortableTextEditorConfig } from '../constants/ps-portable-text-editor-config';
|
|
5
|
+
import { PsPortableTextEditorService } from '../services/ps-portable-text-editor-service';
|
|
6
|
+
import { PsUrlInput } from './ps-url-input';
|
|
7
|
+
export const PsPortableTextLinkDialog = ({ isOpen, labels, onClose, onSubmit, }) => {
|
|
8
|
+
const [address, setAddress] = useState('');
|
|
9
|
+
const [hasError, setHasError] = useState(false);
|
|
10
|
+
const close = () => {
|
|
11
|
+
setAddress('');
|
|
12
|
+
setHasError(false);
|
|
13
|
+
onClose();
|
|
14
|
+
};
|
|
15
|
+
const submit = (event) => {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
const href = PsPortableTextEditorService.normalizeLink(address);
|
|
19
|
+
if (href === null) {
|
|
20
|
+
setHasError(true);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
setAddress('');
|
|
24
|
+
setHasError(false);
|
|
25
|
+
onSubmit(href);
|
|
26
|
+
};
|
|
27
|
+
return (_jsx(PsFormModal, { cancelLabel: labels.cancel, isOpen: isOpen, isSubmitDisabled: address.trim() === '', onClose: close, onSubmit: submit, size: 'sm', submitLabel: labels.saveLink, title: labels.linkDialogTitle, children: _jsx(PsUrlInput, { "aria-invalid": hasError, autoFocus: true, helperText: hasError ? labels.invalidLink : undefined, label: labels.linkUrl, name: PsPortableTextEditorConfig.linkAnnotation, onChange: (event) => {
|
|
28
|
+
setAddress(event.target.value);
|
|
29
|
+
setHasError(false);
|
|
30
|
+
}, value: address }) }));
|
|
31
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsPortableTextEditorConfig } from '../constants/ps-portable-text-editor-config';
|
|
4
|
+
export const PsPortableTextToolbarButton = ({ children, className, disabled, isActive, label, onClick, }) => (_jsx("button", { "aria-label": label, "aria-pressed": isActive, className: PallasoftClassNameService.merge(PsPortableTextEditorConfig.classNames.button, isActive ? PsPortableTextEditorConfig.classNames.buttonActive : '', className), disabled: disabled, onClick: onClick, onMouseDown: (event) => event.preventDefault(), title: label, type: 'button', children: children }));
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useEditor, useEditorSelector } from '@portabletext/editor';
|
|
4
|
+
import { isActiveAnnotation, isActiveDecorator, isActiveListItem, isActiveStyle, } from '@portabletext/editor/selectors';
|
|
5
|
+
import { PsPortableTextEditorConfig } from '../constants/ps-portable-text-editor-config';
|
|
6
|
+
import { PsPortableTextLinkDialog } from './ps-portable-text-link-dialog';
|
|
7
|
+
import { PsPortableTextToolbarButton } from './ps-portable-text-toolbar-button';
|
|
8
|
+
const DecoratorButton = ({ children, className, decorator, disabled, label, }) => {
|
|
9
|
+
const editor = useEditor();
|
|
10
|
+
const isActive = useEditorSelector(editor, isActiveDecorator(decorator));
|
|
11
|
+
return (_jsx(PsPortableTextToolbarButton, { className: className, disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
12
|
+
editor.send({ type: 'decorator.toggle', decorator });
|
|
13
|
+
editor.send({ type: 'focus' });
|
|
14
|
+
}, children: children }));
|
|
15
|
+
};
|
|
16
|
+
const ListButton = ({ children, disabled, label, listItem, }) => {
|
|
17
|
+
const editor = useEditor();
|
|
18
|
+
const isActive = useEditorSelector(editor, isActiveListItem(listItem));
|
|
19
|
+
return (_jsx(PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
20
|
+
editor.send({ type: 'list item.toggle', listItem });
|
|
21
|
+
editor.send({ type: 'focus' });
|
|
22
|
+
}, children: children }));
|
|
23
|
+
};
|
|
24
|
+
const QuoteButton = ({ children, disabled, label }) => {
|
|
25
|
+
const editor = useEditor();
|
|
26
|
+
const isActive = useEditorSelector(editor, isActiveStyle(PsPortableTextEditorConfig.styles.blockquote));
|
|
27
|
+
return (_jsx(PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: label, onClick: () => {
|
|
28
|
+
editor.send({ type: 'style.toggle', style: PsPortableTextEditorConfig.styles.blockquote });
|
|
29
|
+
editor.send({ type: 'focus' });
|
|
30
|
+
}, children: children }));
|
|
31
|
+
};
|
|
32
|
+
const LinkButton = ({ disabled, labels }) => {
|
|
33
|
+
const editor = useEditor();
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
+
const isActive = useEditorSelector(editor, isActiveAnnotation(PsPortableTextEditorConfig.linkAnnotation));
|
|
36
|
+
const close = () => {
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
editor.send({ type: 'focus' });
|
|
39
|
+
};
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx(PsPortableTextToolbarButton, { disabled: disabled, isActive: isActive, label: isActive ? labels.removeLink : labels.link, onClick: () => {
|
|
41
|
+
if (isActive) {
|
|
42
|
+
editor.send({
|
|
43
|
+
type: 'annotation.remove',
|
|
44
|
+
annotation: { name: PsPortableTextEditorConfig.linkAnnotation },
|
|
45
|
+
});
|
|
46
|
+
editor.send({ type: 'focus' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setIsOpen(true);
|
|
50
|
+
}, children: _jsx("span", { className: 'underline', children: labels.link }) }), _jsx(PsPortableTextLinkDialog, { isOpen: isOpen, labels: labels, onClose: close, onSubmit: (href) => {
|
|
51
|
+
editor.send({
|
|
52
|
+
type: 'annotation.add',
|
|
53
|
+
annotation: { name: PsPortableTextEditorConfig.linkAnnotation, value: { href } },
|
|
54
|
+
});
|
|
55
|
+
close();
|
|
56
|
+
} })] }));
|
|
57
|
+
};
|
|
58
|
+
export const PsPortableTextToolbar = ({ disabled, labels }) => (_jsxs("div", { "aria-label": labels.toolbar, className: PsPortableTextEditorConfig.classNames.toolbar, role: 'toolbar', children: [_jsx(DecoratorButton, { className: 'font-bold', decorator: PsPortableTextEditorConfig.decorators.bold, disabled: disabled, label: labels.bold, children: "B" }), _jsx(DecoratorButton, { className: 'italic', decorator: PsPortableTextEditorConfig.decorators.italic, disabled: disabled, label: labels.italic, children: "I" }), _jsx(DecoratorButton, { className: 'underline', decorator: PsPortableTextEditorConfig.decorators.underline, disabled: disabled, label: labels.underline, children: "U" }), _jsx(DecoratorButton, { className: 'font-mono', decorator: PsPortableTextEditorConfig.decorators.code, disabled: disabled, label: labels.code, children: '</>' }), _jsx(LinkButton, { disabled: disabled, labels: labels }), _jsx("span", { "aria-hidden": 'true', className: PsPortableTextEditorConfig.classNames.separator }), _jsx(ListButton, { disabled: disabled, label: labels.bulletedList, listItem: PsPortableTextEditorConfig.listItems.bullet, children: "\u2022" }), _jsx(ListButton, { disabled: disabled, label: labels.numberedList, listItem: PsPortableTextEditorConfig.listItems.number, children: "1." }), _jsx(QuoteButton, { disabled: disabled, label: labels.quote, children: "\u201D" })] }));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Common } from '@pallasoft/ps-models';
|
|
2
|
+
import { defineSchema } from '@portabletext/editor';
|
|
3
|
+
const Models = Common.PortableTextConfig;
|
|
4
|
+
export const PsPortableTextEditorConfig = {
|
|
5
|
+
linkAnnotation: Models.linkType,
|
|
6
|
+
mailtoPrefix: 'mailto:',
|
|
7
|
+
emailPattern: /^[^\s@/:]+@[^\s@/:]+\.[^\s@/:]+$/,
|
|
8
|
+
lineHeightRem: 1.5,
|
|
9
|
+
decorators: {
|
|
10
|
+
bold: Models.decorators[0],
|
|
11
|
+
code: Models.decorators[3],
|
|
12
|
+
italic: Models.decorators[1],
|
|
13
|
+
underline: Models.decorators[2],
|
|
14
|
+
},
|
|
15
|
+
styles: { blockquote: Models.styles[1], normal: Models.styles[0] },
|
|
16
|
+
listItems: { bullet: Models.listItems[0], number: Models.listItems[1] },
|
|
17
|
+
schemaDefinition: defineSchema({
|
|
18
|
+
annotations: [{ name: Models.linkType, fields: [{ name: 'href', type: 'string' }] }],
|
|
19
|
+
blockObjects: [],
|
|
20
|
+
decorators: Models.decorators.map((name) => ({ name })),
|
|
21
|
+
inlineObjects: [],
|
|
22
|
+
lists: Models.listItems.map((name) => ({ name })),
|
|
23
|
+
styles: Models.styles.map((name) => ({ name })),
|
|
24
|
+
}),
|
|
25
|
+
labels: {
|
|
26
|
+
bold: 'Bold',
|
|
27
|
+
bulletedList: 'Bulleted list',
|
|
28
|
+
cancel: 'Cancel',
|
|
29
|
+
code: 'Code',
|
|
30
|
+
invalidLink: 'Enter a web address or email address',
|
|
31
|
+
italic: 'Italic',
|
|
32
|
+
link: 'Link',
|
|
33
|
+
linkDialogTitle: 'Link',
|
|
34
|
+
linkUrl: 'Address',
|
|
35
|
+
numberedList: 'Numbered list',
|
|
36
|
+
quote: 'Quote',
|
|
37
|
+
removeLink: 'Remove link',
|
|
38
|
+
saveLink: 'Save',
|
|
39
|
+
toolbar: 'Formatting',
|
|
40
|
+
underline: 'Underline',
|
|
41
|
+
},
|
|
42
|
+
classNames: {
|
|
43
|
+
editable: 'ps-input-control w-full resize-none overflow-y-auto rounded-t-none border-t-0 leading-6 outline-none',
|
|
44
|
+
root: 'block',
|
|
45
|
+
toolbar: 'flex flex-wrap items-center gap-1 rounded-t-xl border border-line bg-white p-1 dark:bg-transparent',
|
|
46
|
+
button: 'ps-focus-ring inline-flex h-9 min-w-9 items-center justify-center rounded-lg px-2 text-sm text-ink/70 transition-colors hover:bg-ink/5 hover:text-ink disabled:cursor-not-allowed disabled:opacity-40',
|
|
47
|
+
buttonActive: 'bg-primary text-white hover:bg-primary hover:text-white',
|
|
48
|
+
separator: 'mx-1 h-5 w-px bg-line',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsPortableTextEditorConfig } from './ps-portable-text-editor-config';
|
|
3
|
+
export const PsPortableTextEditorRenderers = {
|
|
4
|
+
renderBlock: ({ children, listItem, style }) => {
|
|
5
|
+
if (listItem === PsPortableTextEditorConfig.listItems.bullet) {
|
|
6
|
+
return (_jsx("div", { className: "relative pl-6 before:absolute before:left-1 before:content-['\u2022']", children: children }));
|
|
7
|
+
}
|
|
8
|
+
if (listItem === PsPortableTextEditorConfig.listItems.number) {
|
|
9
|
+
return (_jsx("div", { className: "relative pl-6 before:absolute before:left-0 before:content-[counter(ps-list)'.']", style: { counterIncrement: 'ps-list' }, children: children }));
|
|
10
|
+
}
|
|
11
|
+
if (style === PsPortableTextEditorConfig.styles.blockquote) {
|
|
12
|
+
return (_jsx("div", { className: 'border-l-2 border-primary pl-3 text-ink/80', style: { counterReset: 'ps-list' }, children: children }));
|
|
13
|
+
}
|
|
14
|
+
return _jsx("div", { style: { counterReset: 'ps-list' }, children: children });
|
|
15
|
+
},
|
|
16
|
+
renderDecorator: ({ children, value }) => {
|
|
17
|
+
if (value === PsPortableTextEditorConfig.decorators.bold)
|
|
18
|
+
return _jsx("strong", { children: children });
|
|
19
|
+
if (value === PsPortableTextEditorConfig.decorators.italic)
|
|
20
|
+
return _jsx("em", { children: children });
|
|
21
|
+
if (value === PsPortableTextEditorConfig.decorators.underline)
|
|
22
|
+
return _jsx("u", { children: children });
|
|
23
|
+
if (value === PsPortableTextEditorConfig.decorators.code) {
|
|
24
|
+
return _jsx("code", { className: 'rounded bg-ink/10 px-1 font-mono text-[0.9em]', children: children });
|
|
25
|
+
}
|
|
26
|
+
return children;
|
|
27
|
+
},
|
|
28
|
+
renderAnnotation: ({ children }) => (_jsx("span", { className: 'text-primary underline', children: children })),
|
|
29
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -45,3 +45,5 @@ export * from './components/ps-date-preset-field';
|
|
|
45
45
|
export * from './components/ps-select-with-create';
|
|
46
46
|
export * from './constants/ps-select-with-create-config';
|
|
47
47
|
export * from './interfaces/ps-date-preset-data';
|
|
48
|
+
export * from './components/ps-portable-text-editor';
|
|
49
|
+
export * from './interfaces/ps-portable-text-editor-labels';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Common } from '@pallasoft/ps-models';
|
|
2
|
+
import { PsPortableTextEditorConfig } from '../constants/ps-portable-text-editor-config';
|
|
3
|
+
import { PsTextFormatService } from './ps-text-format-service';
|
|
4
|
+
export class PsPortableTextEditorService {
|
|
5
|
+
static normalizeLink(raw) {
|
|
6
|
+
const trimmed = raw.trim();
|
|
7
|
+
if (trimmed === '')
|
|
8
|
+
return null;
|
|
9
|
+
const candidate = PsPortableTextEditorConfig.emailPattern.test(trimmed)
|
|
10
|
+
? `${PsPortableTextEditorConfig.mailtoPrefix}${trimmed}`
|
|
11
|
+
: trimmed.toLowerCase().startsWith(PsPortableTextEditorConfig.mailtoPrefix)
|
|
12
|
+
? trimmed
|
|
13
|
+
: PsTextFormatService.url(trimmed);
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(candidate);
|
|
16
|
+
return Common.PortableTextConfig.linkProtocols.includes(url.protocol)
|
|
17
|
+
? candidate
|
|
18
|
+
: null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
static minHeight(minRows) {
|
|
25
|
+
return `${minRows * PsPortableTextEditorConfig.lineHeightRem}rem`;
|
|
26
|
+
}
|
|
27
|
+
static isSame(first, second) {
|
|
28
|
+
return JSON.stringify(first) === JSON.stringify(second);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -45,3 +45,6 @@ export * from './components/ps-date-preset-field';
|
|
|
45
45
|
export * from './components/ps-select-with-create';
|
|
46
46
|
export * from './constants/ps-select-with-create-config';
|
|
47
47
|
export * from './interfaces/ps-date-preset-data';
|
|
48
|
+
export * from './components/ps-portable-text-editor';
|
|
49
|
+
export * from './interfaces/ps-portable-text-editor-labels';
|
|
50
|
+
export type { PortableTextBlock } from '@portabletext/editor';
|
package/dist/index.js
CHANGED
|
@@ -61,3 +61,5 @@ __exportStar(require("./components/ps-date-preset-field"), exports);
|
|
|
61
61
|
__exportStar(require("./components/ps-select-with-create"), exports);
|
|
62
62
|
__exportStar(require("./constants/ps-select-with-create-config"), exports);
|
|
63
63
|
__exportStar(require("./interfaces/ps-date-preset-data"), exports);
|
|
64
|
+
__exportStar(require("./components/ps-portable-text-editor"), exports);
|
|
65
|
+
__exportStar(require("./interfaces/ps-portable-text-editor-labels"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PsPortableTextEditorLabels {
|
|
2
|
+
bold: string;
|
|
3
|
+
bulletedList: string;
|
|
4
|
+
cancel: string;
|
|
5
|
+
code: string;
|
|
6
|
+
invalidLink: string;
|
|
7
|
+
italic: string;
|
|
8
|
+
link: string;
|
|
9
|
+
linkDialogTitle: string;
|
|
10
|
+
linkUrl: string;
|
|
11
|
+
numberedList: string;
|
|
12
|
+
quote: string;
|
|
13
|
+
removeLink: string;
|
|
14
|
+
saveLink: string;
|
|
15
|
+
toolbar: string;
|
|
16
|
+
underline: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsPortableTextEditorService = void 0;
|
|
4
|
+
const ps_models_1 = require("@pallasoft/ps-models");
|
|
5
|
+
const ps_portable_text_editor_config_1 = require("../constants/ps-portable-text-editor-config");
|
|
6
|
+
const ps_text_format_service_1 = require("./ps-text-format-service");
|
|
7
|
+
class PsPortableTextEditorService {
|
|
8
|
+
static normalizeLink(raw) {
|
|
9
|
+
const trimmed = raw.trim();
|
|
10
|
+
if (trimmed === '')
|
|
11
|
+
return null;
|
|
12
|
+
const candidate = ps_portable_text_editor_config_1.PsPortableTextEditorConfig.emailPattern.test(trimmed)
|
|
13
|
+
? `${ps_portable_text_editor_config_1.PsPortableTextEditorConfig.mailtoPrefix}${trimmed}`
|
|
14
|
+
: trimmed.toLowerCase().startsWith(ps_portable_text_editor_config_1.PsPortableTextEditorConfig.mailtoPrefix)
|
|
15
|
+
? trimmed
|
|
16
|
+
: ps_text_format_service_1.PsTextFormatService.url(trimmed);
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(candidate);
|
|
19
|
+
return ps_models_1.Common.PortableTextConfig.linkProtocols.includes(url.protocol)
|
|
20
|
+
? candidate
|
|
21
|
+
: null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static minHeight(minRows) {
|
|
28
|
+
return `${minRows * ps_portable_text_editor_config_1.PsPortableTextEditorConfig.lineHeightRem}rem`;
|
|
29
|
+
}
|
|
30
|
+
static isSame(first, second) {
|
|
31
|
+
return JSON.stringify(first) === JSON.stringify(second);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.PsPortableTextEditorService = PsPortableTextEditorService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-input-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -34,17 +34,23 @@
|
|
|
34
34
|
"format:prettier": "npx prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
35
35
|
"lint:eslint": "npx eslint \"src/**/*.{ts,tsx}\"",
|
|
36
36
|
"lint:eslint:fix": "npx eslint --fix \"src/**/*.{ts,tsx}\"",
|
|
37
|
-
"release": "semantic-release"
|
|
37
|
+
"release": "semantic-release",
|
|
38
|
+
"test": "vitest run"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"@internationalized/date": "^3.12.2",
|
|
41
42
|
"@pallasoft/ps-core-components": "^2.10.0",
|
|
42
43
|
"@pallasoft/ps-design-system": "^1.5.0",
|
|
43
44
|
"@pallasoft/ps-icons": "^1.5.4",
|
|
45
|
+
"@pallasoft/ps-models": "^1.4.0",
|
|
46
|
+
"@pallasoft/ps-popovers": "^1.7.0",
|
|
44
47
|
"@pallasoft/ps-utilities": "^2.13.0",
|
|
48
|
+
"@portabletext/editor": "^3.3.19",
|
|
49
|
+
"@portabletext/sanity-bridge": "^1.2.14",
|
|
45
50
|
"libphonenumber-js": "^1.13.13",
|
|
46
51
|
"react-aria-components": "^1.19.0",
|
|
47
|
-
"react-datepicker": "^9.1.0"
|
|
52
|
+
"react-datepicker": "^9.1.0",
|
|
53
|
+
"rxjs": "^7.8.2"
|
|
48
54
|
},
|
|
49
55
|
"peerDependencies": {
|
|
50
56
|
"react": "^18 || ^19",
|
|
@@ -73,6 +79,7 @@
|
|
|
73
79
|
"eslint-plugin-import": "^2.32.0",
|
|
74
80
|
"eslint-plugin-prettier": "^5.5.4",
|
|
75
81
|
"husky": "^9.1.7",
|
|
82
|
+
"jsdom": "^30.1.1",
|
|
76
83
|
"lint-staged": "^16.2.7",
|
|
77
84
|
"prettier": "^3.6.2",
|
|
78
85
|
"react": "^19.3.0",
|
|
@@ -80,7 +87,8 @@
|
|
|
80
87
|
"react-hook-form": "^7.89.0",
|
|
81
88
|
"semantic-release": "^24.2.0",
|
|
82
89
|
"ts-node": "^10.9.2",
|
|
83
|
-
"typescript": "^5.9.3"
|
|
90
|
+
"typescript": "^5.9.3",
|
|
91
|
+
"vitest": "^3.2.7"
|
|
84
92
|
},
|
|
85
93
|
"publishConfig": {
|
|
86
94
|
"access": "public",
|