@webround/editor 0.0.0-stage → 0.3.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/blocks.d.ts +90 -0
- package/dist/blocks.js +119 -0
- package/dist/bridge.d.ts +96 -0
- package/dist/bridge.js +970 -0
- package/dist/check/aria.d.ts +19 -0
- package/dist/check/aria.js +106 -0
- package/dist/check/cli.d.ts +3 -0
- package/dist/check/cli.js +30 -0
- package/dist/check/css.d.ts +13 -0
- package/dist/check/css.js +63 -0
- package/dist/check/html.d.ts +8 -0
- package/dist/check/html.js +29 -0
- package/dist/check/index.d.ts +4 -0
- package/dist/check/index.js +5 -0
- package/dist/check/links.d.ts +7 -0
- package/dist/check/links.js +23 -0
- package/dist/check/orphans.d.ts +20 -0
- package/dist/check/orphans.js +57 -0
- package/dist/check/producer-code.d.ts +53 -0
- package/dist/check/producer-code.js +379 -0
- package/dist/check/producer-dom.d.ts +21 -0
- package/dist/check/producer-dom.js +52 -0
- package/dist/check/project.d.ts +20 -0
- package/dist/check/project.js +51 -0
- package/dist/check/rules.d.ts +7 -0
- package/dist/check/rules.js +517 -0
- package/dist/check/types.d.ts +160 -0
- package/dist/check/types.js +22 -0
- package/dist/components/WrAltcha.d.ts +5 -0
- package/dist/components/WrAltcha.js +8 -0
- package/dist/components/WrButton.d.ts +8 -0
- package/dist/components/WrButton.js +34 -0
- package/dist/components/WrForm.d.ts +6 -0
- package/dist/components/WrForm.js +29 -0
- package/dist/components/WrFormPrivacy.d.ts +4 -0
- package/dist/components/WrFormPrivacy.js +7 -0
- package/dist/components/WrFormSuccess.d.ts +4 -0
- package/dist/components/WrFormSuccess.js +12 -0
- package/dist/components/WrIframe.d.ts +12 -0
- package/dist/components/WrIframe.js +30 -0
- package/dist/components/WrImage.d.ts +9 -0
- package/dist/components/WrImage.js +28 -0
- package/dist/components/WrInput.d.ts +8 -0
- package/dist/components/WrInput.js +29 -0
- package/dist/components/WrLink.d.ts +10 -0
- package/dist/components/WrLink.js +38 -0
- package/dist/components/WrNav.d.ts +26 -0
- package/dist/components/WrNav.js +30 -0
- package/dist/components/WrSection.d.ts +11 -0
- package/dist/components/WrSection.js +5 -0
- package/dist/components/WrShared.d.ts +12 -0
- package/dist/components/WrShared.js +20 -0
- package/dist/components/WrText.d.ts +11 -0
- package/dist/components/WrText.js +41 -0
- package/dist/components/WrVideo.d.ts +9 -0
- package/dist/components/WrVideo.js +73 -0
- package/dist/components/text-editor/CodeThemeContext.d.ts +8 -0
- package/dist/components/text-editor/CodeThemeContext.js +10 -0
- package/dist/components/text-editor/ExampleTheme.d.ts +77 -0
- package/dist/components/text-editor/ExampleTheme.js +76 -0
- package/dist/components/text-editor/ToolbarContext.d.ts +17 -0
- package/dist/components/text-editor/ToolbarContext.js +32 -0
- package/dist/components/text-editor/WrTextEditor.d.ts +13 -0
- package/dist/components/text-editor/WrTextEditor.js +48 -0
- package/dist/components/text-editor/common/constants.d.ts +109 -0
- package/dist/components/text-editor/common/constants.js +58 -0
- package/dist/components/text-editor/common/surface.d.ts +3 -0
- package/dist/components/text-editor/common/surface.js +8 -0
- package/dist/components/text-editor/common/utils.d.ts +7 -0
- package/dist/components/text-editor/common/utils.js +78 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.d.ts +5 -0
- package/dist/components/text-editor/hooks/useLexicalCommitOnBlur.js +41 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.d.ts +33 -0
- package/dist/components/text-editor/hooks/useLexicalMarkdown.js +284 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.d.ts +23 -0
- package/dist/components/text-editor/nodes/CodeBlockNode.js +138 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.d.ts +13 -0
- package/dist/components/text-editor/nodes/HorizontalRuleNode.js +22 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/CodeBlockPlugin.js +38 -0
- package/dist/components/text-editor/plugins/FontPlugin.d.ts +12 -0
- package/dist/components/text-editor/plugins/FontPlugin.js +114 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/FontSizePlugin.js +105 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.d.ts +6 -0
- package/dist/components/text-editor/plugins/FontWeightPlugin.js +103 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/HTMLTagPlugin.js +82 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/HorizontalRulePlugin.js +35 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/LineSpacingPlugin.js +83 -0
- package/dist/components/text-editor/plugins/LinkPlugin.d.ts +8 -0
- package/dist/components/text-editor/plugins/LinkPlugin.js +183 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/ListToolbarPlugin.js +40 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/LoadContentPlugin.js +63 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.d.ts +4 -0
- package/dist/components/text-editor/plugins/MarkdownExportPlugin.js +17 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.d.ts +2 -0
- package/dist/components/text-editor/plugins/MarkdownPastePlugin.js +45 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.d.ts +3 -0
- package/dist/components/text-editor/plugins/SanitizePastePlugin.js +40 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextAlignPlugin.js +65 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.d.ts +7 -0
- package/dist/components/text-editor/plugins/TextColorPlugin.js +117 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.d.ts +10 -0
- package/dist/components/text-editor/plugins/ToolbarPlugin.js +73 -0
- package/dist/components/text-editor/theme-colors.d.ts +11 -0
- package/dist/components/text-editor/theme-colors.js +28 -0
- package/dist/context/WrAppContext.d.ts +35 -0
- package/dist/context/WrAppContext.js +113 -0
- package/dist/context/WrLayout.d.ts +15 -0
- package/dist/context/WrLayout.js +22 -0
- package/dist/context/WrPageContext.d.ts +28 -0
- package/dist/context/WrPageContext.js +282 -0
- package/dist/context/restoreValues.d.ts +2 -0
- package/dist/context/restoreValues.js +6 -0
- package/dist/contrast.d.ts +45 -0
- package/dist/contrast.js +122 -0
- package/dist/drop.d.ts +7 -0
- package/dist/drop.js +51 -0
- package/dist/gaps.d.ts +14 -0
- package/dist/gaps.js +50 -0
- package/dist/hooks/useWrMessage.d.ts +5 -0
- package/dist/hooks/useWrMessage.js +24 -0
- package/dist/hooks/useWrSave.d.ts +2 -0
- package/dist/hooks/useWrSave.js +11 -0
- package/dist/index.css +467 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/interactive.d.ts +8 -0
- package/dist/interactive.js +13 -0
- package/dist/measure-rules.d.ts +34 -0
- package/dist/measure-rules.js +54 -0
- package/dist/measure.d.ts +11 -0
- package/dist/measure.js +139 -0
- package/dist/messages.d.ts +860 -0
- package/dist/messages.js +2 -0
- package/dist/nav.d.ts +5 -0
- package/dist/nav.js +10 -0
- package/dist/operations/animations-store.d.ts +6 -0
- package/dist/operations/animations-store.js +63 -0
- package/dist/operations/cli.d.ts +3 -0
- package/dist/operations/cli.js +52 -0
- package/dist/operations/comments-store.d.ts +17 -0
- package/dist/operations/comments-store.js +62 -0
- package/dist/operations/context.d.ts +12 -0
- package/dist/operations/context.js +35 -0
- package/dist/operations/define.d.ts +81 -0
- package/dist/operations/define.js +86 -0
- package/dist/operations/index.d.ts +6 -0
- package/dist/operations/index.js +6 -0
- package/dist/operations/ops/check.d.ts +5 -0
- package/dist/operations/ops/check.js +53 -0
- package/dist/operations/ops/comments.d.ts +6 -0
- package/dist/operations/ops/comments.js +133 -0
- package/dist/operations/ops/content.d.ts +16 -0
- package/dist/operations/ops/content.js +73 -0
- package/dist/operations/ops/json.d.ts +4 -0
- package/dist/operations/ops/json.js +27 -0
- package/dist/operations/ops/langs.d.ts +4 -0
- package/dist/operations/ops/langs.js +32 -0
- package/dist/operations/ops/nodes.d.ts +8 -0
- package/dist/operations/ops/nodes.js +270 -0
- package/dist/operations/ops/pages.d.ts +8 -0
- package/dist/operations/ops/pages.js +154 -0
- package/dist/operations/ops/project.d.ts +36 -0
- package/dist/operations/ops/project.js +97 -0
- package/dist/operations/ops/seo.d.ts +17 -0
- package/dist/operations/ops/seo.js +358 -0
- package/dist/operations/ops/style.d.ts +76 -0
- package/dist/operations/ops/style.js +268 -0
- package/dist/operations/ops/stylesets.d.ts +9 -0
- package/dist/operations/ops/stylesets.js +145 -0
- package/dist/operations/ops/theme.d.ts +6 -0
- package/dist/operations/ops/theme.js +304 -0
- package/dist/operations/registry.d.ts +8 -0
- package/dist/operations/registry.js +19 -0
- package/dist/operations/stylesets-store.d.ts +50 -0
- package/dist/operations/stylesets-store.js +216 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +24 -0
- package/dist/save.d.ts +65 -0
- package/dist/save.js +86 -0
- package/dist/schema.d.ts +69 -0
- package/dist/schema.js +110 -0
- package/dist/scope.d.ts +41 -0
- package/dist/scope.js +44 -0
- package/dist/vite-plugins/ast.d.ts +3 -0
- package/dist/vite-plugins/ast.js +238 -0
- package/dist/vite-plugins/core-swap.d.ts +3 -0
- package/dist/vite-plugins/core-swap.js +57 -0
- package/dist/vite-plugins/create-page.d.ts +4 -0
- package/dist/vite-plugins/create-page.js +59 -0
- package/dist/vite-plugins/guard.d.ts +15 -0
- package/dist/vite-plugins/guard.js +35 -0
- package/dist/vite-plugins/head.d.ts +13 -0
- package/dist/vite-plugins/head.js +53 -0
- package/dist/vite-plugins/index.d.ts +7 -0
- package/dist/vite-plugins/index.js +7 -0
- package/dist/vite-plugins/jsx-tree.d.ts +118 -0
- package/dist/vite-plugins/jsx-tree.js +686 -0
- package/dist/vite-plugins/langs.d.ts +3 -0
- package/dist/vite-plugins/langs.js +44 -0
- package/dist/vite-plugins/operations.d.ts +10 -0
- package/dist/vite-plugins/operations.js +66 -0
- package/dist/vite-plugins/pages-api.d.ts +3 -0
- package/dist/vite-plugins/pages-api.js +112 -0
- package/dist/vite-plugins/pages.d.ts +3 -0
- package/dist/vite-plugins/pages.js +64 -0
- package/dist/vite-plugins/read.d.ts +3 -0
- package/dist/vite-plugins/read.js +45 -0
- package/dist/vite-plugins/seo-files.d.ts +8 -0
- package/dist/vite-plugins/seo-files.js +42 -0
- package/dist/vite-plugins/theme.d.ts +3 -0
- package/dist/vite-plugins/theme.js +207 -0
- package/dist/vite-plugins/update.d.ts +3 -0
- package/dist/vite-plugins/update.js +46 -0
- package/dist/vite-plugins/wr-plugins.d.ts +15 -0
- package/dist/vite-plugins/wr-plugins.js +34 -0
- package/dist/vite-plugins.d.ts +11 -0
- package/dist/vite-plugins.js +518 -0
- package/package.json +96 -5
- package/README.md +0 -3
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
5
|
+
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
6
|
+
import { $patchStyleText, $getSelectionStyleValueForProperty } from '@lexical/selection';
|
|
7
|
+
import { BiCheckCircle } from 'react-icons/bi';
|
|
8
|
+
import { WR_SURFACE_STYLE } from '../common/surface';
|
|
9
|
+
// Punti di riferimento fissi per l'interpolazione fluida (fluid typography).
|
|
10
|
+
// Sotto MIN_VIEWPORT il size resta fisso a min, sopra MAX_VIEWPORT resta fisso a max,
|
|
11
|
+
// in mezzo scala linearmente in base alla viewport reale.
|
|
12
|
+
const MIN_VIEWPORT = 375;
|
|
13
|
+
const MAX_VIEWPORT = 1440;
|
|
14
|
+
function buildFontSizeValue(minPx, maxPx) {
|
|
15
|
+
if (minPx === maxPx)
|
|
16
|
+
return `${minPx}px`; // nessuno scaling: valore fisso, CSS più pulito
|
|
17
|
+
const lo = Math.min(minPx, maxPx);
|
|
18
|
+
const hi = Math.max(minPx, maxPx);
|
|
19
|
+
const slope = (maxPx - minPx) / (MAX_VIEWPORT - MIN_VIEWPORT);
|
|
20
|
+
const intercept = minPx - slope * MIN_VIEWPORT;
|
|
21
|
+
const preferred = `${intercept.toFixed(3)}px + ${(slope * 100).toFixed(3)}vw`;
|
|
22
|
+
return `clamp(${lo}px, ${preferred}, ${hi}px)`;
|
|
23
|
+
}
|
|
24
|
+
function parseFontSizeValue(value) {
|
|
25
|
+
const clampMatch = value.match(/^clamp\(\s*([\d.]+)px\s*,.*?,\s*([\d.]+)px\s*\)$/);
|
|
26
|
+
if (clampMatch) {
|
|
27
|
+
return { min: parseFloat(clampMatch[1]), max: parseFloat(clampMatch[2]) };
|
|
28
|
+
}
|
|
29
|
+
const flat = parseFloat(value);
|
|
30
|
+
const size = Number.isFinite(flat) ? flat : 16;
|
|
31
|
+
return { min: size, max: size };
|
|
32
|
+
}
|
|
33
|
+
function FontSizePlugin({ editorPathKey = '' }) {
|
|
34
|
+
const [editor] = useLexicalComposerContext();
|
|
35
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
36
|
+
const [minSize, setMinSize] = useState(16);
|
|
37
|
+
const [maxSize, setMaxSize] = useState(16);
|
|
38
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
39
|
+
const buttonRef = useRef(null);
|
|
40
|
+
const menuRef = useRef(null);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
return editor.registerUpdateListener(({ editorState }) => {
|
|
43
|
+
editorState.read(() => {
|
|
44
|
+
const selection = $getSelection();
|
|
45
|
+
if (!$isRangeSelection(selection))
|
|
46
|
+
return;
|
|
47
|
+
const fontSize = $getSelectionStyleValueForProperty(selection, 'font-size', '16px');
|
|
48
|
+
if (fontSize) {
|
|
49
|
+
const { min, max } = parseFontSizeValue(fontSize);
|
|
50
|
+
setMinSize(min);
|
|
51
|
+
setMaxSize(max);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}, [editor]);
|
|
56
|
+
const applyFontSize = useCallback((min, max) => {
|
|
57
|
+
const value = buildFontSizeValue(min, max);
|
|
58
|
+
editor.update(() => {
|
|
59
|
+
const selection = $getSelection();
|
|
60
|
+
if ($isRangeSelection(selection)) {
|
|
61
|
+
$patchStyleText(selection, { 'font-size': value });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
setMinSize(min);
|
|
65
|
+
setMaxSize(max);
|
|
66
|
+
setIsMenuOpen(false);
|
|
67
|
+
}, [editor]);
|
|
68
|
+
const calculateMenuPosition = useCallback(() => {
|
|
69
|
+
if (!buttonRef.current)
|
|
70
|
+
return;
|
|
71
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
72
|
+
const menuWidth = 168;
|
|
73
|
+
const pageWidth = document.body.clientWidth;
|
|
74
|
+
let left = rect.left;
|
|
75
|
+
if (left + menuWidth > pageWidth)
|
|
76
|
+
left = pageWidth - menuWidth - 10;
|
|
77
|
+
if (left < 0)
|
|
78
|
+
left = 10;
|
|
79
|
+
setMenuPosition({ top: rect.bottom, left });
|
|
80
|
+
}, []);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!isMenuOpen)
|
|
83
|
+
return;
|
|
84
|
+
calculateMenuPosition();
|
|
85
|
+
const handleClickOutside = (e) => {
|
|
86
|
+
if (menuRef.current?.contains(e.target) ||
|
|
87
|
+
buttonRef.current?.contains(e.target))
|
|
88
|
+
return;
|
|
89
|
+
setIsMenuOpen(false);
|
|
90
|
+
};
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
document.addEventListener('click', handleClickOutside);
|
|
93
|
+
}, 0);
|
|
94
|
+
return () => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
document.removeEventListener('click', handleClickOutside);
|
|
97
|
+
};
|
|
98
|
+
}, [isMenuOpen, calculateMenuPosition]);
|
|
99
|
+
const label = minSize === maxSize ? `${minSize}px` : `${minSize}–${maxSize}px`;
|
|
100
|
+
return (_jsxs("div", { className: `toolbarClass path-${editorPathKey}`, style: { position: 'relative', display: 'inline-block', textAlign: 'left' }, onClick: e => e.stopPropagation(), children: [_jsxs("button", { ref: buttonRef, type: "button", onPointerDown: e => e.preventDefault(), onClick: e => { e.stopPropagation(); setIsMenuOpen(p => !p); }, "aria-expanded": isMenuOpen, className: "wr-toolbar-trigger", children: [_jsx("span", { children: label }), _jsx("svg", { className: `wr-toolbar-chevron ${isMenuOpen ? 'wr-toolbar-chevron--open' : ''}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }), isMenuOpen && createPortal(_jsx("div", { ref: menuRef, className: `toolbarClass path-${editorPathKey} wr-toolbar-menu`, style: { ...WR_SURFACE_STYLE, top: menuPosition.top, left: menuPosition.left, width: 168 }, onPointerDown: e => e.preventDefault(), children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6, padding: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx("label", { style: { fontSize: 11, width: 24 }, children: "min" }), _jsx("input", { type: "number", className: "wr-core-input", value: minSize, onChange: e => setMinSize(Number(e.target.value)), onKeyDown: e => { if (e.key === 'Enter')
|
|
101
|
+
applyFontSize(minSize, maxSize); }, onPointerDown: e => e.stopPropagation() })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx("label", { style: { fontSize: 11, width: 24 }, children: "max" }), _jsx("input", { type: "number", className: "wr-core-input", value: maxSize, onChange: e => setMaxSize(Number(e.target.value)), onKeyDown: e => { if (e.key === 'Enter')
|
|
102
|
+
applyFontSize(minSize, maxSize); }, onPointerDown: e => e.stopPropagation() })] }), _jsx("button", { onClick: () => applyFontSize(minSize, maxSize), className: "wr-primary-btn", style: { marginTop: 0, width: '100%', padding: 4, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(BiCheckCircle, { size: 18 }) })] }) }), document.body)] }));
|
|
103
|
+
}
|
|
104
|
+
export default FontSizePlugin;
|
|
105
|
+
//# sourceMappingURL=FontSizePlugin.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, FORMAT_TEXT_COMMAND } from 'lexical';
|
|
5
|
+
import { $patchStyleText } from '@lexical/selection';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { MdFormatBold } from 'react-icons/md';
|
|
8
|
+
import { WR_SURFACE_STYLE } from '../common/surface';
|
|
9
|
+
const fontWeights = [
|
|
10
|
+
{ value: '100', label: 'Thin 100' },
|
|
11
|
+
{ value: '200', label: 'ExtraLight 200' },
|
|
12
|
+
{ value: '300', label: 'Light 300' },
|
|
13
|
+
{ value: '400', label: 'Regular 400' },
|
|
14
|
+
{ value: '500', label: 'Medium 500' },
|
|
15
|
+
{ value: '600', label: 'SemiBold 600' },
|
|
16
|
+
{ value: '700', label: 'Bold 700' },
|
|
17
|
+
{ value: '800', label: 'ExtraBold 800' },
|
|
18
|
+
{ value: '900', label: 'Black 900' },
|
|
19
|
+
];
|
|
20
|
+
function FontWeightPlugin({ editorPathKey = '' }) {
|
|
21
|
+
const [editor] = useLexicalComposerContext();
|
|
22
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
23
|
+
const [selectedWeight, setSelectedWeight] = useState(fontWeights[3].value);
|
|
24
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
25
|
+
const buttonRef = useRef(null);
|
|
26
|
+
const menuRef = useRef(null);
|
|
27
|
+
const applyFontWeight = useCallback((weight) => {
|
|
28
|
+
editor.update(() => {
|
|
29
|
+
const selection = $getSelection();
|
|
30
|
+
if ($isRangeSelection(selection)) {
|
|
31
|
+
$patchStyleText(selection, { 'font-weight': weight });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
editor.focus();
|
|
35
|
+
setSelectedWeight(weight);
|
|
36
|
+
setIsMenuOpen(false);
|
|
37
|
+
}, [editor]);
|
|
38
|
+
const calculateMenuPosition = () => {
|
|
39
|
+
if (buttonRef.current && menuRef.current) {
|
|
40
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
41
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
42
|
+
let top = buttonRect.bottom;
|
|
43
|
+
let left = buttonRect.left;
|
|
44
|
+
const pageWidth = document.body.clientWidth;
|
|
45
|
+
if (left + menuRect.width > pageWidth)
|
|
46
|
+
left = pageWidth - menuRect.width - 10;
|
|
47
|
+
if (left < 0)
|
|
48
|
+
left = 10;
|
|
49
|
+
setMenuPosition({ top, left });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!isMenuOpen)
|
|
54
|
+
return;
|
|
55
|
+
const handler = (e) => {
|
|
56
|
+
if (!menuRef.current?.contains(e.target) && !buttonRef.current?.contains(e.target)) {
|
|
57
|
+
setIsMenuOpen(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
document.addEventListener('mousedown', handler, true);
|
|
61
|
+
const raf = requestAnimationFrame(calculateMenuPosition);
|
|
62
|
+
return () => {
|
|
63
|
+
document.removeEventListener('mousedown', handler, true);
|
|
64
|
+
cancelAnimationFrame(raf);
|
|
65
|
+
};
|
|
66
|
+
}, [isMenuOpen]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
return editor.registerUpdateListener(() => {
|
|
69
|
+
editor.getEditorState().read(() => {
|
|
70
|
+
const selection = $getSelection();
|
|
71
|
+
if (!$isRangeSelection(selection))
|
|
72
|
+
return;
|
|
73
|
+
const styleString = selection.anchor.getNode().getStyle?.();
|
|
74
|
+
if (typeof styleString === 'string') {
|
|
75
|
+
const match = styleString.match(/font-weight:\s*(\d+)/i);
|
|
76
|
+
if (match)
|
|
77
|
+
setSelectedWeight(match[1]);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}, [editor]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
return editor.registerCommand(FORMAT_TEXT_COMMAND, (formatType) => {
|
|
84
|
+
if (formatType !== 'bold')
|
|
85
|
+
return false;
|
|
86
|
+
editor.update(() => {
|
|
87
|
+
const selection = $getSelection();
|
|
88
|
+
if (!$isRangeSelection(selection))
|
|
89
|
+
return;
|
|
90
|
+
const styleString = selection.anchor.getNode().getStyle?.() ?? '';
|
|
91
|
+
const match = styleString.match(/font-weight:\s*(\d+)/i);
|
|
92
|
+
const current = match ? parseInt(match[1], 10) : 400;
|
|
93
|
+
const newWeight = current >= 700 ? '400' : '700';
|
|
94
|
+
$patchStyleText(selection, { 'font-weight': newWeight });
|
|
95
|
+
setSelectedWeight(newWeight);
|
|
96
|
+
});
|
|
97
|
+
return true;
|
|
98
|
+
}, COMMAND_PRIORITY_LOW);
|
|
99
|
+
}, [editor]);
|
|
100
|
+
return (_jsxs("div", { className: `path-${editorPathKey} toolbarClass`, style: { position: 'relative', display: 'inline-block', textAlign: 'left' }, onClick: e => e.stopPropagation(), children: [_jsxs("button", { ref: buttonRef, type: "button", onPointerDown: e => e.preventDefault(), onClick: e => { e.stopPropagation(); setIsMenuOpen(p => !p); }, "aria-expanded": isMenuOpen, className: "wr-toolbar-trigger", children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(MdFormatBold, { style: { fontSize: 18 } }), _jsx("span", { children: fontWeights.find(fw => fw.value === selectedWeight)?.label })] }), _jsx("svg", { className: `wr-toolbar-chevron ${isMenuOpen ? 'wr-toolbar-chevron--open' : ''}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }), isMenuOpen && createPortal(_jsx("div", { ref: menuRef, className: `toolbarClass path-${editorPathKey} wr-toolbar-menu`, style: { ...WR_SURFACE_STYLE, top: menuPosition.top, left: menuPosition.left, width: 160 }, onPointerDown: e => e.preventDefault(), children: _jsx("div", { style: { padding: '4px 0' }, children: fontWeights.map(fw => (_jsx("button", { onClick: () => applyFontWeight(fw.value), className: `wr-toolbar-item ${fw.value === selectedWeight ? 'wr-toolbar-item--active' : ''}`, children: fw.label }, fw.value))) }) }), document.body)] }));
|
|
101
|
+
}
|
|
102
|
+
export default FontWeightPlugin;
|
|
103
|
+
//# sourceMappingURL=FontWeightPlugin.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getSelection, $isRangeSelection, $createParagraphNode } from 'lexical';
|
|
5
|
+
import { $createHeadingNode, $isHeadingNode } from '@lexical/rich-text';
|
|
6
|
+
import { $setBlocksType } from '@lexical/selection';
|
|
7
|
+
import { createPortal } from 'react-dom';
|
|
8
|
+
import { WR_SURFACE_STYLE } from '../common/surface';
|
|
9
|
+
const headings = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
|
|
10
|
+
function HTMLTagPlugin({ editorPathKey = '' }) {
|
|
11
|
+
const [editor] = useLexicalComposerContext();
|
|
12
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
13
|
+
const [selectedHeading, setSelectedHeading] = useState('p');
|
|
14
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
15
|
+
const buttonRef = useRef(null);
|
|
16
|
+
const menuRef = useRef(null);
|
|
17
|
+
const formatHeading = useCallback((tag) => {
|
|
18
|
+
editor.update(() => {
|
|
19
|
+
const selection = $getSelection();
|
|
20
|
+
if ($isRangeSelection(selection)) {
|
|
21
|
+
if (tag === 'p') {
|
|
22
|
+
$setBlocksType(selection, () => $createParagraphNode());
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
$setBlocksType(selection, () => $createHeadingNode(tag));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
setSelectedHeading(tag);
|
|
30
|
+
setIsMenuOpen(false);
|
|
31
|
+
}, [editor]);
|
|
32
|
+
const calculateMenuPosition = () => {
|
|
33
|
+
if (buttonRef.current && menuRef.current) {
|
|
34
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
35
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
36
|
+
let top = buttonRect.bottom;
|
|
37
|
+
let left = buttonRect.left;
|
|
38
|
+
const pageWidth = document.body.clientWidth;
|
|
39
|
+
if (left + menuRect.width > pageWidth)
|
|
40
|
+
left = pageWidth - menuRect.width - 10;
|
|
41
|
+
if (left < 0)
|
|
42
|
+
left = 10;
|
|
43
|
+
setMenuPosition({ top, left });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const handleClickOutside = (e) => {
|
|
48
|
+
if (menuRef.current && !menuRef.current.contains(e.target) && !buttonRef.current?.contains(e.target)) {
|
|
49
|
+
setIsMenuOpen(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
if (isMenuOpen) {
|
|
53
|
+
document.addEventListener('click', handleClickOutside);
|
|
54
|
+
calculateMenuPosition();
|
|
55
|
+
}
|
|
56
|
+
return () => document.removeEventListener('click', handleClickOutside);
|
|
57
|
+
}, [isMenuOpen]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
return editor.registerUpdateListener(() => {
|
|
60
|
+
editor.getEditorState().read(() => {
|
|
61
|
+
const selection = $getSelection();
|
|
62
|
+
if (!$isRangeSelection(selection))
|
|
63
|
+
return;
|
|
64
|
+
const anchorNode = selection.anchor.getNode();
|
|
65
|
+
let element = anchorNode.getKey() === 'root' ? anchorNode : anchorNode.getParentOrThrow();
|
|
66
|
+
while (element != null) {
|
|
67
|
+
if ($isHeadingNode(element)) {
|
|
68
|
+
setSelectedHeading(element.getTag());
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (element.getType() === 'root')
|
|
72
|
+
break;
|
|
73
|
+
element = element.getParent();
|
|
74
|
+
}
|
|
75
|
+
setSelectedHeading('p');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}, [editor]);
|
|
79
|
+
return (_jsxs("div", { className: `path-${editorPathKey} toolbarClass`, style: { position: 'relative', display: 'inline-block', textAlign: 'left' }, onClick: e => e.stopPropagation(), children: [_jsxs("button", { ref: buttonRef, type: "button", onClick: e => { e.stopPropagation(); setIsMenuOpen(p => !p); }, "aria-expanded": isMenuOpen, className: "wr-toolbar-trigger", style: { padding: '8px' }, children: [_jsx("span", { children: selectedHeading.toUpperCase() }), _jsx("svg", { className: `wr-toolbar-chevron ${isMenuOpen ? 'wr-toolbar-chevron--open' : ''}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }), isMenuOpen && createPortal(_jsx("div", { ref: menuRef, className: `path-${editorPathKey} toolbarClass wr-toolbar-menu`, style: { ...WR_SURFACE_STYLE, top: menuPosition.top, left: menuPosition.left, width: 128, maxHeight: 288 }, children: _jsx("div", { style: { padding: '4px 0' }, children: headings.map(tag => (_jsx("button", { onClick: () => formatHeading(tag), className: `wr-toolbar-item ${tag === selectedHeading ? 'wr-toolbar-item--active' : ''}`, children: tag.toUpperCase() }, tag))) }) }), document.body)] }));
|
|
80
|
+
}
|
|
81
|
+
export default HTMLTagPlugin;
|
|
82
|
+
//# sourceMappingURL=HTMLTagPlugin.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
3
|
+
import { $getSelection, $isRangeSelection, $isParagraphNode, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_LOW, $createRangeSelection, $setSelection, } from "lexical";
|
|
4
|
+
import { $createHorizontalRuleNode } from "../nodes/HorizontalRuleNode";
|
|
5
|
+
export default function HorizontalRulePlugin() {
|
|
6
|
+
const [editor] = useLexicalComposerContext();
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
return editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
|
|
9
|
+
const selection = $getSelection();
|
|
10
|
+
if (!$isRangeSelection(selection))
|
|
11
|
+
return false;
|
|
12
|
+
const anchor = selection.anchor.getNode();
|
|
13
|
+
const parent = anchor.getParent();
|
|
14
|
+
if (!$isParagraphNode(parent))
|
|
15
|
+
return false;
|
|
16
|
+
if (parent.getChildrenSize() !== 1)
|
|
17
|
+
return false;
|
|
18
|
+
if (anchor.getTextContent() !== "---")
|
|
19
|
+
return false;
|
|
20
|
+
event?.preventDefault();
|
|
21
|
+
const para = $createParagraphNode();
|
|
22
|
+
const hr = $createHorizontalRuleNode();
|
|
23
|
+
parent.replace(para);
|
|
24
|
+
para.insertBefore(hr);
|
|
25
|
+
const newSelection = $createRangeSelection();
|
|
26
|
+
const key = para.getKey();
|
|
27
|
+
newSelection.anchor.set(key, 0, "element");
|
|
28
|
+
newSelection.focus.set(key, 0, "element");
|
|
29
|
+
$setSelection(newSelection);
|
|
30
|
+
return true;
|
|
31
|
+
}, COMMAND_PRIORITY_LOW);
|
|
32
|
+
}, [editor]);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=HorizontalRulePlugin.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect, useState } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
5
|
+
import { $patchStyleText } from '@lexical/selection';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { MdFormatLineSpacing } from 'react-icons/md';
|
|
8
|
+
import { WR_SURFACE_STYLE } from '../common/surface';
|
|
9
|
+
const lineSpacings = [
|
|
10
|
+
{ label: '1.0 (Single)', value: '1' },
|
|
11
|
+
{ label: '1.15', value: '1.15' },
|
|
12
|
+
{ label: '1.5', value: '1.5' },
|
|
13
|
+
{ label: '2.0 (Double)', value: '2' },
|
|
14
|
+
{ label: '2.5', value: '2.5' },
|
|
15
|
+
{ label: '3.0', value: '3' },
|
|
16
|
+
];
|
|
17
|
+
function LineSpacingPlugin({ editorPathKey = '' }) {
|
|
18
|
+
const [editor] = useLexicalComposerContext();
|
|
19
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
20
|
+
const [selectedSpacing, setSelectedSpacing] = useState(lineSpacings[1].value);
|
|
21
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
22
|
+
const buttonRef = useRef(null);
|
|
23
|
+
const menuRef = useRef(null);
|
|
24
|
+
const applyLineSpacing = (spacing) => {
|
|
25
|
+
editor.update(() => {
|
|
26
|
+
const selection = $getSelection();
|
|
27
|
+
if ($isRangeSelection(selection)) {
|
|
28
|
+
$patchStyleText(selection, { 'line-height': spacing });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
setSelectedSpacing(spacing);
|
|
32
|
+
setIsMenuOpen(false);
|
|
33
|
+
};
|
|
34
|
+
const calculateMenuPosition = () => {
|
|
35
|
+
if (buttonRef.current && menuRef.current) {
|
|
36
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
37
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
38
|
+
let top = buttonRect.bottom;
|
|
39
|
+
let left = buttonRect.left;
|
|
40
|
+
const pageWidth = document.body.clientWidth;
|
|
41
|
+
if (left + menuRect.width > pageWidth)
|
|
42
|
+
left = pageWidth - menuRect.width - 10;
|
|
43
|
+
if (left < 0)
|
|
44
|
+
left = 10;
|
|
45
|
+
setMenuPosition({ top, left });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const handleClickOutside = (e) => {
|
|
50
|
+
if (menuRef.current && !menuRef.current.contains(e.target) && !buttonRef.current?.contains(e.target)) {
|
|
51
|
+
setIsMenuOpen(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
if (isMenuOpen) {
|
|
55
|
+
document.addEventListener('click', handleClickOutside);
|
|
56
|
+
calculateMenuPosition();
|
|
57
|
+
}
|
|
58
|
+
return () => document.removeEventListener('click', handleClickOutside);
|
|
59
|
+
}, [isMenuOpen]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
return editor.registerUpdateListener(() => {
|
|
62
|
+
editor.getEditorState().read(() => {
|
|
63
|
+
const selection = $getSelection();
|
|
64
|
+
if (!$isRangeSelection(selection))
|
|
65
|
+
return;
|
|
66
|
+
const anchorNode = selection.anchor.getNode();
|
|
67
|
+
if (!anchorNode)
|
|
68
|
+
return;
|
|
69
|
+
const styleString = anchorNode.getStyle?.();
|
|
70
|
+
if (anchorNode.getTextContent().trim() === '')
|
|
71
|
+
return;
|
|
72
|
+
if (styleString && typeof styleString === 'string') {
|
|
73
|
+
const match = styleString.match(/line-height:\s*([\d.]+)/i);
|
|
74
|
+
if (match)
|
|
75
|
+
setSelectedSpacing(match[1].trim());
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}, [editor]);
|
|
80
|
+
return (_jsxs("div", { className: `path-${editorPathKey} toolbarClass`, style: { position: 'relative', display: 'inline-block', textAlign: 'left' }, onClick: e => e.stopPropagation(), children: [_jsxs("button", { ref: buttonRef, type: "button", onClick: e => { e.stopPropagation(); setIsMenuOpen(p => !p); }, className: "wr-toolbar-trigger", "aria-expanded": isMenuOpen, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(MdFormatLineSpacing, { style: { fontSize: 18 } }), _jsx("span", { children: lineSpacings.find(s => s.value === selectedSpacing)?.label })] }), _jsx("svg", { className: `wr-toolbar-chevron ${isMenuOpen ? 'wr-toolbar-chevron--open' : ''}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }), isMenuOpen && createPortal(_jsx("div", { ref: menuRef, className: `toolbarClass path-${editorPathKey} wr-toolbar-menu`, style: { ...WR_SURFACE_STYLE, top: menuPosition.top, left: menuPosition.left, width: 160 }, children: _jsx("div", { style: { padding: '4px 0' }, children: lineSpacings.map(spacing => (_jsx("button", { onClick: () => applyLineSpacing(spacing.value), className: `wr-toolbar-item ${spacing.value === selectedSpacing ? 'wr-toolbar-item--active' : ''}`, children: spacing.label }, spacing.value))) }) }), document.body)] }));
|
|
81
|
+
}
|
|
82
|
+
export default LineSpacingPlugin;
|
|
83
|
+
//# sourceMappingURL=LineSpacingPlugin.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface Props {
|
|
3
|
+
availablePages?: string[];
|
|
4
|
+
editorPathKey?: string;
|
|
5
|
+
}
|
|
6
|
+
declare function LinkPlugin({ availablePages, editorPathKey }: Props): React.JSX.Element;
|
|
7
|
+
export default LinkPlugin;
|
|
8
|
+
//# sourceMappingURL=LinkPlugin.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
5
|
+
import { $toggleLink, $isLinkNode } from '@lexical/link';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { CiLink } from 'react-icons/ci';
|
|
8
|
+
import { IoCall, IoNavigate } from 'react-icons/io5';
|
|
9
|
+
import { MdAlternateEmail } from 'react-icons/md';
|
|
10
|
+
import { FaWhatsapp } from 'react-icons/fa';
|
|
11
|
+
import { WR_SURFACE_STYLE } from '../common/surface';
|
|
12
|
+
const t = {
|
|
13
|
+
title: 'Inserisci link',
|
|
14
|
+
linkTypeLabel: 'Tipo di link',
|
|
15
|
+
externalLink: 'Esterno',
|
|
16
|
+
internalLink: 'Interno',
|
|
17
|
+
phone: 'Telefono',
|
|
18
|
+
email: 'Email',
|
|
19
|
+
whatsapp: 'WhatsApp',
|
|
20
|
+
urlPlaceholder: 'https://...',
|
|
21
|
+
phonePlaceholder: '+39...',
|
|
22
|
+
emailPlaceholder: 'email@esempio.com',
|
|
23
|
+
anchorLabel: 'Ancora',
|
|
24
|
+
anchorPlaceholder: 'es. sezione-1',
|
|
25
|
+
selectPage: 'Seleziona pagina',
|
|
26
|
+
phoneHelp: 'Formato internazionale: +39...',
|
|
27
|
+
invalidValue: 'Valore non valido',
|
|
28
|
+
confirm: 'Applica',
|
|
29
|
+
};
|
|
30
|
+
const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/;
|
|
31
|
+
const phoneRegExp = /^\+[1-9]\d{1,14}$/;
|
|
32
|
+
const emailRegExp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
33
|
+
function validateInput(value, type) {
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'external': return urlRegExp.test(value);
|
|
36
|
+
case 'phone': return phoneRegExp.test(value);
|
|
37
|
+
case 'email': return emailRegExp.test(value);
|
|
38
|
+
case 'internal': return value !== '';
|
|
39
|
+
case 'whatsapp': return phoneRegExp.test(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function LinkPlugin({ availablePages = [], editorPathKey = '' }) {
|
|
43
|
+
const [editor] = useLexicalComposerContext();
|
|
44
|
+
const [isLinkEditorOpen, setIsLinkEditorOpen] = useState(false);
|
|
45
|
+
const [isValidUrl, setIsValidUrl] = useState(false);
|
|
46
|
+
const [linkUrl, setLinkUrl] = useState('');
|
|
47
|
+
const [anchorLink, setAnchorLink] = useState('');
|
|
48
|
+
const [linkType, setLinkType] = useState('external');
|
|
49
|
+
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
|
|
50
|
+
const [selectedPage, setSelectedPage] = useState('');
|
|
51
|
+
const buttonRef = useRef(null);
|
|
52
|
+
const menuRef = useRef(null);
|
|
53
|
+
const checkLinkSelection = useCallback(() => {
|
|
54
|
+
let isLinkSelected = false;
|
|
55
|
+
editor.update(() => {
|
|
56
|
+
const selection = $getSelection();
|
|
57
|
+
if ($isRangeSelection(selection)) {
|
|
58
|
+
const anchorNode = selection.anchor.getNode();
|
|
59
|
+
const parent = anchorNode.getParent();
|
|
60
|
+
if (parent !== null && $isLinkNode(parent)) {
|
|
61
|
+
const url = parent.getURL();
|
|
62
|
+
if (url.startsWith('tel:')) {
|
|
63
|
+
setLinkType('phone');
|
|
64
|
+
setLinkUrl(url.replace('tel:', ''));
|
|
65
|
+
setIsValidUrl(validateInput(url.replace('tel:', ''), 'phone'));
|
|
66
|
+
}
|
|
67
|
+
else if (url.startsWith('mailto:')) {
|
|
68
|
+
setLinkType('email');
|
|
69
|
+
setLinkUrl(url.replace('mailto:', ''));
|
|
70
|
+
setIsValidUrl(validateInput(url.replace('mailto:', ''), 'email'));
|
|
71
|
+
}
|
|
72
|
+
else if (url.startsWith('/')) {
|
|
73
|
+
setLinkType('internal');
|
|
74
|
+
const [path, anchor] = url.split('#');
|
|
75
|
+
setSelectedPage(path);
|
|
76
|
+
setAnchorLink(anchor || '');
|
|
77
|
+
}
|
|
78
|
+
else if (url.startsWith('https://wa.me/')) {
|
|
79
|
+
setLinkType('whatsapp');
|
|
80
|
+
setLinkUrl(url.replace('https://wa.me/', ''));
|
|
81
|
+
setIsValidUrl(validateInput(url.replace('https://wa.me/', ''), 'whatsapp'));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
setLinkType('external');
|
|
85
|
+
setLinkUrl(url);
|
|
86
|
+
setIsValidUrl(validateInput(url, 'external'));
|
|
87
|
+
}
|
|
88
|
+
isLinkSelected = true;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
if (linkType === 'external') {
|
|
92
|
+
setLinkUrl('https://');
|
|
93
|
+
setIsValidUrl(validateInput('https://', 'external'));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
setLinkUrl('');
|
|
97
|
+
setIsValidUrl(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return isLinkSelected;
|
|
103
|
+
}, [editor, linkType]);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
return editor.registerUpdateListener(() => { checkLinkSelection(); });
|
|
106
|
+
}, [editor, checkLinkSelection]);
|
|
107
|
+
const handleLinkClick = () => {
|
|
108
|
+
const isLinkSelected = checkLinkSelection();
|
|
109
|
+
editor.update(() => {
|
|
110
|
+
const selection = $getSelection();
|
|
111
|
+
if ($isRangeSelection(selection)) {
|
|
112
|
+
if (!isLinkSelected) {
|
|
113
|
+
setLinkUrl('https://');
|
|
114
|
+
setIsValidUrl(validateInput('https://', 'external'));
|
|
115
|
+
}
|
|
116
|
+
setIsLinkEditorOpen(true);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const applyLink = () => {
|
|
121
|
+
editor.update(() => {
|
|
122
|
+
let finalUrl = linkUrl;
|
|
123
|
+
if (linkType === 'phone')
|
|
124
|
+
finalUrl = `tel:${linkUrl}`;
|
|
125
|
+
else if (linkType === 'email')
|
|
126
|
+
finalUrl = `mailto:${linkUrl}`;
|
|
127
|
+
else if (linkType === 'internal') {
|
|
128
|
+
finalUrl = selectedPage;
|
|
129
|
+
if (anchorLink?.trim())
|
|
130
|
+
finalUrl += `#${anchorLink.trim()}`;
|
|
131
|
+
}
|
|
132
|
+
else if (linkType === 'whatsapp')
|
|
133
|
+
finalUrl = `https://wa.me/${linkUrl}`;
|
|
134
|
+
else if (linkType === 'external')
|
|
135
|
+
finalUrl = linkUrl.startsWith('http') ? linkUrl : `https://${linkUrl}`;
|
|
136
|
+
$toggleLink(finalUrl);
|
|
137
|
+
});
|
|
138
|
+
setIsLinkEditorOpen(false);
|
|
139
|
+
};
|
|
140
|
+
const calculateMenuPosition = () => {
|
|
141
|
+
if (buttonRef.current && menuRef.current) {
|
|
142
|
+
const buttonRect = buttonRef.current.getBoundingClientRect();
|
|
143
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
144
|
+
let top = buttonRect.bottom;
|
|
145
|
+
let left = buttonRect.left;
|
|
146
|
+
const pageWidth = document.body.clientWidth;
|
|
147
|
+
if (left + menuRect.width > pageWidth)
|
|
148
|
+
left = pageWidth - menuRect.width - 10;
|
|
149
|
+
if (left < 0)
|
|
150
|
+
left = 10;
|
|
151
|
+
setMenuPosition({ top, left });
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const handleClickOutside = (event) => {
|
|
156
|
+
if (menuRef.current && !menuRef.current.contains(event.target) && !buttonRef.current?.contains(event.target)) {
|
|
157
|
+
setIsLinkEditorOpen(false);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
if (isLinkEditorOpen) {
|
|
161
|
+
document.addEventListener('click', handleClickOutside);
|
|
162
|
+
calculateMenuPosition();
|
|
163
|
+
}
|
|
164
|
+
return () => document.removeEventListener('click', handleClickOutside);
|
|
165
|
+
}, [isLinkEditorOpen]);
|
|
166
|
+
const handleIconClick = () => {
|
|
167
|
+
if (!isValidUrl)
|
|
168
|
+
return;
|
|
169
|
+
if (linkType === 'external')
|
|
170
|
+
window.open(linkUrl.startsWith('http') ? linkUrl : `https://${linkUrl}`, '_blank');
|
|
171
|
+
else if (linkType === 'whatsapp')
|
|
172
|
+
window.open(`https://wa.me/${linkUrl}`, '_blank');
|
|
173
|
+
else if (linkType === 'phone')
|
|
174
|
+
window.location.href = `tel:${linkUrl}`;
|
|
175
|
+
else if (linkType === 'email')
|
|
176
|
+
window.location.href = `mailto:${linkUrl}`;
|
|
177
|
+
};
|
|
178
|
+
return (_jsxs("div", { className: `path-${editorPathKey} toolbarClass`, style: { position: 'relative', display: 'inline-block', textAlign: 'left' }, onPointerDown: e => e.stopPropagation(), children: [_jsxs("button", { ref: buttonRef, type: "button", onClick: handleLinkClick, className: `wr-toolbar-icon-btn ${isValidUrl ? 'wr-toolbar-icon-btn--active' : ''}`, children: [linkType === 'phone' && _jsx(IoCall, { size: 16 }), linkType === 'email' && _jsx(MdAlternateEmail, { size: 16 }), (linkType === 'external' || linkType === 'internal') && _jsx(CiLink, { size: 16 }), linkType === 'whatsapp' && _jsx(FaWhatsapp, { size: 16, color: "rgb(50,200,50)" })] }), isLinkEditorOpen && createPortal(_jsx("div", { ref: menuRef, className: `path-${editorPathKey} toolbarClass wr-toolbar-menu`, style: { ...WR_SURFACE_STYLE, top: menuPosition.top, left: menuPosition.left, width: 256 }, children: _jsxs("div", { style: { padding: '8px 16px' }, children: [_jsx("h3", { style: { color: 'var(--wr-text-2)', fontSize: 14, marginBottom: 8 }, children: t.title }), _jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { className: "wr-label", children: t.linkTypeLabel }), _jsxs("select", { value: linkType, onChange: e => setLinkType(e.target.value), className: "wr-core-input", children: [_jsx("option", { value: "external", children: t.externalLink }), _jsx("option", { value: "internal", children: t.internalLink }), _jsx("option", { value: "phone", children: t.phone }), _jsx("option", { value: "email", children: t.email }), _jsx("option", { value: "whatsapp", children: t.whatsapp })] })] }), linkType === 'external' && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', position: 'relative' }, children: [_jsx("input", { type: "text", placeholder: t.urlPlaceholder, value: linkUrl, onChange: e => { setLinkUrl(e.target.value); setIsValidUrl(validateInput(e.target.value, linkType)); }, className: "wr-core-input" }), _jsx(IoNavigate, { size: 16, style: { position: 'absolute', right: 8, color: 'var(--wr-text-3)', cursor: isValidUrl ? 'pointer' : 'not-allowed', opacity: isValidUrl ? 1 : 0.5 }, onClick: handleIconClick })] })), linkType === 'internal' && (_jsxs("div", { children: [_jsxs("select", { value: selectedPage, onChange: e => { setSelectedPage(e.target.value); setIsValidUrl(true); }, className: "wr-core-input", children: [_jsx("option", { value: "", children: t.selectPage }), availablePages.map((page, idx) => _jsx("option", { value: `/${page}`, children: `/${page}` }, idx))] }), _jsx("label", { className: "wr-label", style: { marginTop: 16, fontWeight: 500 }, children: t.anchorLabel }), _jsx("input", { type: "text", value: anchorLink, maxLength: 30, onChange: e => setAnchorLink(e.target.value.replace(/[^a-zA-Z0-9-_]/g, '')), placeholder: t.anchorPlaceholder, className: "wr-core-input" })] })), (linkType === 'phone' || linkType === 'whatsapp') && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', position: 'relative' }, children: [_jsx("input", { type: "text", placeholder: t.phonePlaceholder, value: linkUrl, onChange: e => { setLinkUrl(e.target.value); setIsValidUrl(validateInput(e.target.value, linkType)); }, className: "wr-core-input" }), linkType === 'phone'
|
|
179
|
+
? _jsx(IoCall, { size: 16, style: { position: 'absolute', right: 8, color: 'var(--wr-text-3)', cursor: isValidUrl ? 'pointer' : 'not-allowed', opacity: isValidUrl ? 1 : 0.5 }, onClick: handleIconClick })
|
|
180
|
+
: _jsx(FaWhatsapp, { size: 16, style: { position: 'absolute', right: 8, color: 'var(--wr-text-3)', cursor: isValidUrl ? 'pointer' : 'not-allowed', opacity: isValidUrl ? 1 : 0.5 }, onClick: handleIconClick })] })), (linkType === 'phone' || linkType === 'whatsapp') && _jsx("p", { className: "wr-help-text", children: t.phoneHelp }), linkType === 'email' && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', position: 'relative' }, children: [_jsx("input", { type: "text", placeholder: t.emailPlaceholder, value: linkUrl, onChange: e => { setLinkUrl(e.target.value); setIsValidUrl(validateInput(e.target.value, linkType)); }, className: "wr-core-input" }), _jsx(IoNavigate, { size: 16, style: { position: 'absolute', right: 8, color: 'var(--wr-text-3)', cursor: isValidUrl ? 'pointer' : 'not-allowed', opacity: isValidUrl ? 1 : 0.5 }, onClick: handleIconClick })] })), !isValidUrl && linkType !== 'internal' && _jsx("p", { className: "wr-error-text", children: t.invalidValue }), _jsx("div", { style: { marginBottom: 8 }, children: _jsx("button", { onClick: applyLink, disabled: !isValidUrl, className: "wr-primary-btn", children: t.confirm }) })] }) }), document.body)] }));
|
|
181
|
+
}
|
|
182
|
+
export default LinkPlugin;
|
|
183
|
+
//# sourceMappingURL=LinkPlugin.js.map
|