@overlap/rte 0.1.0 → 0.1.2
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/README.md +62 -68
- package/dist/components/Editor.d.ts.map +1 -1
- package/dist/components/Icons.d.ts +3 -1
- package/dist/components/Icons.d.ts.map +1 -1
- package/dist/index.d.ts +59 -45
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +470 -197
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +472 -196
- package/dist/index.js.map +1 -1
- package/dist/plugins/blockFormat.d.ts +7 -0
- package/dist/plugins/blockFormat.d.ts.map +1 -0
- package/dist/plugins/headings.d.ts +1 -1
- package/dist/plugins/headings.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +8 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/listIndent.d.ts +10 -0
- package/dist/plugins/listIndent.d.ts.map +1 -0
- package/dist/plugins/optional.d.ts +1 -1
- package/dist/plugins/optional.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +16 -4
- package/src/components/Editor.tsx +92 -49
- package/src/components/Icons.tsx +306 -77
- package/src/index.ts +18 -18
- package/src/plugins/blockFormat.tsx +194 -0
- package/src/plugins/headings.tsx +39 -28
- package/src/plugins/index.tsx +161 -0
- package/src/plugins/listIndent.tsx +90 -0
- package/src/plugins/optional.tsx +216 -194
- package/src/types.ts +3 -0
- package/src/plugins/index.ts +0 -54
package/dist/index.esm.js
CHANGED
|
@@ -1,50 +1,78 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
|
|
4
|
-
const BoldIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" }) }));
|
|
5
|
-
const ItalicIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" }) }));
|
|
6
|
-
const UnderlineIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }) }));
|
|
7
|
-
const UndoIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" }) }));
|
|
8
|
-
const RedoIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" }) }));
|
|
9
|
-
const ClearFormattingIcon = ({ width = 18, height = 18, className }) => (
|
|
10
|
-
const LinkIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" }) }));
|
|
11
|
-
const QuoteIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z" }) }));
|
|
12
|
-
const BulletListIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z" }) }));
|
|
13
|
-
const NumberedListIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 11.9V11H2zm6-5v2h14V6H8zm0 14h14v-2H8v2zm0-6h14v-2H8v2z" }) }));
|
|
14
|
-
const TextColorIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 20h20v4H2v-4zm3.49-3h2.42l1.27-3.58h5.64L16.09 17h2.42L13.25 3h-2.5L5.49 17zm4.22-5.61l2.03-5.79h.12l2.03 5.79H9.71z" }) }));
|
|
15
|
-
const BackgroundColorIcon = ({ width = 18, height = 18, className }) => (
|
|
16
|
-
const HeadingIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M5 4v3h5.5v12h3V7H19V4H5z" }) }));
|
|
17
|
-
const FontSizeIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 4v3h5v12h3V7h5V4H9zm-6 8h3v8h3v-8h3V10H3z" }) }));
|
|
18
|
-
const ImageIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }) }));
|
|
19
|
-
const CloseIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }));
|
|
20
|
-
const LoadingIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" }) }));
|
|
21
|
-
const UploadIcon = ({ width = 18, height = 18, className }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" }) }));
|
|
4
|
+
const BoldIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" }) }));
|
|
5
|
+
const ItalicIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z" }) }));
|
|
6
|
+
const UnderlineIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }) }));
|
|
7
|
+
const UndoIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z" }) }));
|
|
8
|
+
const RedoIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z" }) }));
|
|
9
|
+
const ClearFormattingIcon = ({ width = 18, height = 18, className, }) => (jsxs("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: [jsx("path", { d: "M6 5v3h5v11h2V8h5V5H6z" }), jsx("path", { d: "M20.5 3.5L3.5 20.5l1.06 1.06L21.56 4.56z" })] }));
|
|
10
|
+
const LinkIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" }) }));
|
|
11
|
+
const QuoteIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z" }) }));
|
|
12
|
+
const BulletListIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z" }) }));
|
|
13
|
+
const NumberedListIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 11.9V11H2zm6-5v2h14V6H8zm0 14h14v-2H8v2zm0-6h14v-2H8v2z" }) }));
|
|
14
|
+
const TextColorIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M2 20h20v4H2v-4zm3.49-3h2.42l1.27-3.58h5.64L16.09 17h2.42L13.25 3h-2.5L5.49 17zm4.22-5.61l2.03-5.79h.12l2.03 5.79H9.71z" }) }));
|
|
15
|
+
const BackgroundColorIcon = ({ width = 18, height = 18, className, }) => (jsxs("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: [jsx("path", { d: "M17.5 4.5c-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5-1.45 0-2.99.22-4.28.79C1.49 5.62 1 6.33 1 7.14v11.28c0 1.3 1.22 2.26 2.48 1.94.98-.25 2.02-.36 3.02-.36 1.56 0 3.22.26 4.56.92.6.3 1.28.3 1.88 0 1.34-.67 3-.92 4.56-.92 1 0 2.04.11 3.02.36C22.78 20.68 24 19.72 24 18.42V7.14c0-.81-.49-1.52-1.22-1.85-1.29-.57-2.83-.79-4.28-.79zM21 17.23c0 .63-.58 1.09-1.2.98-.75-.14-1.53-.2-2.3-.2-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5.77 0 1.55.06 2.3.2.62.11 1.2.58 1.2 1.18v9.35z" }), jsx("rect", { x: "4", y: "13", width: "16", height: "6", fill: "currentColor", opacity: "0.5" })] }));
|
|
16
|
+
const HeadingIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M5 4v3h5.5v12h3V7H19V4H5z" }) }));
|
|
17
|
+
const FontSizeIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 4v3h5v12h3V7h5V4H9zm-6 8h3v8h3v-8h3V10H3z" }) }));
|
|
18
|
+
const ImageIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }) }));
|
|
19
|
+
const CloseIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) }));
|
|
20
|
+
const LoadingIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" }) }));
|
|
21
|
+
const UploadIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" }) }));
|
|
22
|
+
const IndentIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M3 21h18v-2H3v2zM3 8l4 4-4 4V8zm8 9h10v-2H11v2zM3 3v2h18V3H3zm8 6h10V7H11v2zm0 4h10v-2H11v2z" }) }));
|
|
23
|
+
const OutdentIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M3 21h18v-2H3v2zM11 8l4 4-4 4V8zM3 3v2h18V3H3zm0 4h10v2H3V7zm0 4h10v2H3v-2zm0 4h18v2H3v-2z" }) }));
|
|
22
24
|
const iconMap = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
"mdi:format-bold": BoldIcon,
|
|
26
|
+
"mdi:format-italic": ItalicIcon,
|
|
27
|
+
"mdi:format-underline": UnderlineIcon,
|
|
28
|
+
"mdi:undo": UndoIcon,
|
|
29
|
+
"mdi:redo": RedoIcon,
|
|
30
|
+
"mdi:format-clear": ClearFormattingIcon,
|
|
31
|
+
"mdi:link": LinkIcon,
|
|
32
|
+
"mdi:format-quote-close": QuoteIcon,
|
|
33
|
+
"mdi:format-list-bulleted": BulletListIcon,
|
|
34
|
+
"mdi:format-list-numbered": NumberedListIcon,
|
|
35
|
+
"mdi:format-color-text": TextColorIcon,
|
|
36
|
+
"mdi:format-color-fill": BackgroundColorIcon,
|
|
37
|
+
"mdi:format-header-1": HeadingIcon,
|
|
38
|
+
"mdi:format-size": FontSizeIcon,
|
|
39
|
+
"mdi:image": ImageIcon,
|
|
40
|
+
"mdi:close": CloseIcon,
|
|
41
|
+
"mdi:loading": LoadingIcon,
|
|
42
|
+
"mdi:upload": UploadIcon,
|
|
43
|
+
"mdi:format-indent-increase": IndentIcon,
|
|
44
|
+
"mdi:format-indent-decrease": OutdentIcon,
|
|
41
45
|
};
|
|
42
46
|
const Icon = ({ icon, width = 18, height = 18, className }) => {
|
|
43
47
|
const IconComponent = iconMap[icon];
|
|
44
48
|
if (!IconComponent) {
|
|
45
|
-
return jsx("span", { style: { width, height, display:
|
|
49
|
+
return jsx("span", { style: { width, height, display: "inline-block" } });
|
|
46
50
|
}
|
|
47
|
-
return jsx(IconComponent, { width: width, height: height, className: className });
|
|
51
|
+
return (jsx(IconComponent, { width: width, height: height, className: className }));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, }) => {
|
|
55
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
56
|
+
const dropdownRef = useRef(null);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const handleClickOutside = (event) => {
|
|
59
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
60
|
+
setIsOpen(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (isOpen) {
|
|
64
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
65
|
+
}
|
|
66
|
+
return () => {
|
|
67
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
68
|
+
};
|
|
69
|
+
}, [isOpen]);
|
|
70
|
+
const handleSelect = (value) => {
|
|
71
|
+
onSelect(value);
|
|
72
|
+
setIsOpen(false);
|
|
73
|
+
};
|
|
74
|
+
const currentOption = options.find(opt => opt.value === currentValue);
|
|
75
|
+
return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, children: [jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `rte-toolbar-button rte-dropdown-button ${currentOption ? 'rte-dropdown-button-has-value' : ''}`, title: label, "aria-label": label, children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), isOpen && (jsx("div", { className: "rte-dropdown-menu", children: options.map((option) => (jsxs("button", { type: "button", className: `rte-dropdown-item ${currentValue === option.value ? 'rte-dropdown-item-active' : ''}`, onClick: () => handleSelect(option.value), children: [option.color && (jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))) }))] }));
|
|
48
76
|
};
|
|
49
77
|
|
|
50
78
|
const IconWrapper = ({ icon, width = 18, height = 18, className }) => {
|
|
@@ -107,6 +135,168 @@ function createCommandPlugin(name, command, icon, label) {
|
|
|
107
135
|
};
|
|
108
136
|
}
|
|
109
137
|
|
|
138
|
+
const defaultHeadings$2 = ["h1", "h2", "h3"];
|
|
139
|
+
const headingLabels$1 = {
|
|
140
|
+
h1: "Überschrift 1",
|
|
141
|
+
h2: "Überschrift 2",
|
|
142
|
+
h3: "Überschrift 3",
|
|
143
|
+
h4: "Überschrift 4",
|
|
144
|
+
h5: "Überschrift 5",
|
|
145
|
+
h6: "Überschrift 6",
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Erstellt ein Block-Format-Plugin, das Headlines, Listen und Quote in einem Dropdown kombiniert
|
|
149
|
+
* @param headings - Array von Heading-Levels (z.B. ["h1", "h2", "h3"])
|
|
150
|
+
*/
|
|
151
|
+
function createBlockFormatPlugin(headings = defaultHeadings$2) {
|
|
152
|
+
const options = [
|
|
153
|
+
{ value: "p", label: "Normal", headingPreview: "p" },
|
|
154
|
+
...headings.map((h) => ({
|
|
155
|
+
value: h,
|
|
156
|
+
label: headingLabels$1[h] || h.toUpperCase(),
|
|
157
|
+
headingPreview: h,
|
|
158
|
+
})),
|
|
159
|
+
{
|
|
160
|
+
value: "ul",
|
|
161
|
+
label: "Aufzählungsliste",
|
|
162
|
+
icon: "mdi:format-list-bulleted",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
value: "ol",
|
|
166
|
+
label: "Nummerierte Liste",
|
|
167
|
+
icon: "mdi:format-list-numbered",
|
|
168
|
+
},
|
|
169
|
+
{ value: "blockquote", label: "Zitat", icon: "mdi:format-quote-close" },
|
|
170
|
+
];
|
|
171
|
+
return {
|
|
172
|
+
name: "blockFormat",
|
|
173
|
+
type: "block",
|
|
174
|
+
renderButton: (props) => {
|
|
175
|
+
// Aktuelles Format bestimmen
|
|
176
|
+
const editor = props.editorAPI;
|
|
177
|
+
let currentValue = props.currentValue;
|
|
178
|
+
if (!currentValue && editor) {
|
|
179
|
+
const selection = editor.getSelection();
|
|
180
|
+
if (selection && selection.rangeCount > 0) {
|
|
181
|
+
const range = selection.getRangeAt(0);
|
|
182
|
+
const container = range.commonAncestorContainer;
|
|
183
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
184
|
+
? container.parentElement
|
|
185
|
+
: container;
|
|
186
|
+
if (element) {
|
|
187
|
+
const tagName = element.tagName.toLowerCase();
|
|
188
|
+
// Prüfe auf Heading
|
|
189
|
+
if (headings.includes(tagName)) {
|
|
190
|
+
currentValue = tagName;
|
|
191
|
+
}
|
|
192
|
+
// Prüfe auf Blockquote
|
|
193
|
+
else if (element.closest("blockquote")) {
|
|
194
|
+
currentValue = "blockquote";
|
|
195
|
+
}
|
|
196
|
+
// Prüfe auf Liste
|
|
197
|
+
else if (element.closest("ul")) {
|
|
198
|
+
currentValue = "ul";
|
|
199
|
+
}
|
|
200
|
+
else if (element.closest("ol")) {
|
|
201
|
+
currentValue = "ol";
|
|
202
|
+
}
|
|
203
|
+
// Prüfe auf Paragraph
|
|
204
|
+
else if (tagName === "p") {
|
|
205
|
+
currentValue = "p";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return (jsx(Dropdown, { icon: "mdi:format-header-1", label: "Format", options: options, onSelect: (value) => {
|
|
211
|
+
// onSelect wird von der Toolbar übergeben und ruft handlePluginClick auf
|
|
212
|
+
if (props.onSelect) {
|
|
213
|
+
props.onSelect(value);
|
|
214
|
+
}
|
|
215
|
+
}, currentValue: currentValue, disabled: props.disabled }));
|
|
216
|
+
},
|
|
217
|
+
getCurrentValue: (editor) => {
|
|
218
|
+
const selection = editor.getSelection();
|
|
219
|
+
if (!selection || selection.rangeCount === 0)
|
|
220
|
+
return undefined;
|
|
221
|
+
const range = selection.getRangeAt(0);
|
|
222
|
+
const container = range.commonAncestorContainer;
|
|
223
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
224
|
+
? container.parentElement
|
|
225
|
+
: container;
|
|
226
|
+
if (!element)
|
|
227
|
+
return undefined;
|
|
228
|
+
const tagName = element.tagName.toLowerCase();
|
|
229
|
+
// Prüfe auf Heading
|
|
230
|
+
if (headings.includes(tagName)) {
|
|
231
|
+
return tagName;
|
|
232
|
+
}
|
|
233
|
+
// Prüfe auf Blockquote
|
|
234
|
+
if (element.closest("blockquote")) {
|
|
235
|
+
return "blockquote";
|
|
236
|
+
}
|
|
237
|
+
// Prüfe auf Liste
|
|
238
|
+
if (element.closest("ul")) {
|
|
239
|
+
return "ul";
|
|
240
|
+
}
|
|
241
|
+
if (element.closest("ol")) {
|
|
242
|
+
return "ol";
|
|
243
|
+
}
|
|
244
|
+
// Prüfe auf Paragraph
|
|
245
|
+
if (tagName === "p") {
|
|
246
|
+
return "p";
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
},
|
|
250
|
+
execute: (editor, value) => {
|
|
251
|
+
if (!value)
|
|
252
|
+
return;
|
|
253
|
+
if (value === "ul") {
|
|
254
|
+
editor.executeCommand("insertUnorderedList");
|
|
255
|
+
}
|
|
256
|
+
else if (value === "ol") {
|
|
257
|
+
editor.executeCommand("insertOrderedList");
|
|
258
|
+
}
|
|
259
|
+
else if (value === "blockquote") {
|
|
260
|
+
const selection = editor.getSelection();
|
|
261
|
+
if (selection && selection.rangeCount > 0) {
|
|
262
|
+
const range = selection.getRangeAt(0);
|
|
263
|
+
const container = range.commonAncestorContainer;
|
|
264
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
265
|
+
? container.parentElement
|
|
266
|
+
: container;
|
|
267
|
+
if (element?.closest("blockquote")) {
|
|
268
|
+
editor.executeCommand("formatBlock", "<p>");
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
editor.executeCommand("formatBlock", "<blockquote>");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
editor.executeCommand("formatBlock", `<${value}>`);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
isActive: (editor) => {
|
|
280
|
+
const selection = editor.getSelection();
|
|
281
|
+
if (!selection || selection.rangeCount === 0)
|
|
282
|
+
return false;
|
|
283
|
+
const range = selection.getRangeAt(0);
|
|
284
|
+
const container = range.commonAncestorContainer;
|
|
285
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
286
|
+
? container.parentElement
|
|
287
|
+
: container;
|
|
288
|
+
if (!element)
|
|
289
|
+
return false;
|
|
290
|
+
const tagName = element.tagName.toLowerCase();
|
|
291
|
+
return (headings.includes(tagName) ||
|
|
292
|
+
element.closest("blockquote") !== null ||
|
|
293
|
+
element.closest("ul") !== null ||
|
|
294
|
+
element.closest("ol") !== null);
|
|
295
|
+
},
|
|
296
|
+
canExecute: () => true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
110
300
|
/**
|
|
111
301
|
* Clear Formatting Plugin - Entfernt alle Formatierungen
|
|
112
302
|
*/
|
|
@@ -123,50 +313,89 @@ const clearFormattingPlugin = {
|
|
|
123
313
|
},
|
|
124
314
|
};
|
|
125
315
|
|
|
316
|
+
const defaultHeadings$1 = ["h1", "h2", "h3"];
|
|
126
317
|
/**
|
|
127
318
|
* Standard-Plugins
|
|
128
319
|
*/
|
|
129
|
-
const boldPlugin = createInlinePlugin(
|
|
130
|
-
const italicPlugin = createInlinePlugin(
|
|
131
|
-
const underlinePlugin = createInlinePlugin(
|
|
132
|
-
const undoPlugin = createCommandPlugin(
|
|
133
|
-
const redoPlugin = createCommandPlugin(
|
|
320
|
+
const boldPlugin = createInlinePlugin("bold", "bold", "mdi:format-bold", "Fett");
|
|
321
|
+
const italicPlugin = createInlinePlugin("italic", "italic", "mdi:format-italic", "Kursiv");
|
|
322
|
+
const underlinePlugin = createInlinePlugin("underline", "underline", "mdi:format-underline", "Unterstrichen");
|
|
323
|
+
const undoPlugin = createCommandPlugin("undo", "undo", "mdi:undo", "Rückgängig");
|
|
324
|
+
const redoPlugin = createCommandPlugin("redo", "redo", "mdi:redo", "Wiederholen");
|
|
325
|
+
/**
|
|
326
|
+
* Indent List Item Plugin (Tab für Unterliste)
|
|
327
|
+
*/
|
|
328
|
+
const indentListItemPlugin = {
|
|
329
|
+
name: "indentListItem",
|
|
330
|
+
type: "command",
|
|
331
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: "rte-toolbar-button", title: "Einr\u00FCcken (Unterliste)", "aria-label": "Einr\u00FCcken (Unterliste)", children: jsx(IconWrapper, { icon: "mdi:format-indent-increase", width: 18, height: 18 }) })),
|
|
332
|
+
execute: (editor) => {
|
|
333
|
+
editor.indentListItem();
|
|
334
|
+
},
|
|
335
|
+
canExecute: (editor) => {
|
|
336
|
+
const selection = editor.getSelection();
|
|
337
|
+
if (!selection || selection.rangeCount === 0)
|
|
338
|
+
return false;
|
|
339
|
+
const range = selection.getRangeAt(0);
|
|
340
|
+
const container = range.commonAncestorContainer;
|
|
341
|
+
const listItem = container.nodeType === Node.TEXT_NODE
|
|
342
|
+
? container.parentElement?.closest("li")
|
|
343
|
+
: container.closest("li");
|
|
344
|
+
return listItem !== null;
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Outdent List Item Plugin (Shift+Tab)
|
|
349
|
+
*/
|
|
350
|
+
const outdentListItemPlugin = {
|
|
351
|
+
name: "outdentListItem",
|
|
352
|
+
type: "command",
|
|
353
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: "rte-toolbar-button", title: "Ausr\u00FCcken", "aria-label": "Ausr\u00FCcken", children: jsx(IconWrapper, { icon: "mdi:format-indent-decrease", width: 18, height: 18 }) })),
|
|
354
|
+
execute: (editor) => {
|
|
355
|
+
editor.outdentListItem();
|
|
356
|
+
},
|
|
357
|
+
canExecute: (editor) => {
|
|
358
|
+
const selection = editor.getSelection();
|
|
359
|
+
if (!selection || selection.rangeCount === 0)
|
|
360
|
+
return false;
|
|
361
|
+
const range = selection.getRangeAt(0);
|
|
362
|
+
const container = range.commonAncestorContainer;
|
|
363
|
+
const listItem = container.nodeType === Node.TEXT_NODE
|
|
364
|
+
? container.parentElement?.closest("li")
|
|
365
|
+
: container.closest("li");
|
|
366
|
+
if (!listItem)
|
|
367
|
+
return false;
|
|
368
|
+
// Prüfe ob in verschachtelter Liste
|
|
369
|
+
const list = listItem.parentElement;
|
|
370
|
+
if (!list || (list.tagName !== "UL" && list.tagName !== "OL"))
|
|
371
|
+
return false;
|
|
372
|
+
const parentListItem = list.parentElement;
|
|
373
|
+
return parentListItem !== null && parentListItem.tagName === "LI";
|
|
374
|
+
},
|
|
375
|
+
};
|
|
134
376
|
/**
|
|
135
377
|
* Standard-Plugin-Liste
|
|
378
|
+
* Die Plugins werden hier direkt referenziert, um sicherzustellen, dass sie in defaultPlugins enthalten sind
|
|
136
379
|
*/
|
|
380
|
+
const _indentPlugin = indentListItemPlugin;
|
|
381
|
+
const _outdentPlugin = outdentListItemPlugin;
|
|
382
|
+
/**
|
|
383
|
+
* Standard Block-Format Plugin (Headlines, Listen, Quote in einem Dropdown)
|
|
384
|
+
* Verwendet standardmäßig h1, h2, h3, kann aber über Editor-Props angepasst werden
|
|
385
|
+
*/
|
|
386
|
+
const defaultBlockFormatPlugin = createBlockFormatPlugin(defaultHeadings$1);
|
|
137
387
|
const defaultPlugins = [
|
|
138
388
|
undoPlugin,
|
|
139
389
|
redoPlugin,
|
|
140
390
|
boldPlugin,
|
|
141
391
|
italicPlugin,
|
|
142
392
|
underlinePlugin,
|
|
393
|
+
defaultBlockFormatPlugin,
|
|
143
394
|
clearFormattingPlugin,
|
|
395
|
+
_indentPlugin,
|
|
396
|
+
_outdentPlugin,
|
|
144
397
|
];
|
|
145
398
|
|
|
146
|
-
const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, }) => {
|
|
147
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
148
|
-
const dropdownRef = useRef(null);
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
const handleClickOutside = (event) => {
|
|
151
|
-
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
152
|
-
setIsOpen(false);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
if (isOpen) {
|
|
156
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
157
|
-
}
|
|
158
|
-
return () => {
|
|
159
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
160
|
-
};
|
|
161
|
-
}, [isOpen]);
|
|
162
|
-
const handleSelect = (value) => {
|
|
163
|
-
onSelect(value);
|
|
164
|
-
setIsOpen(false);
|
|
165
|
-
};
|
|
166
|
-
const currentOption = options.find(opt => opt.value === currentValue);
|
|
167
|
-
return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, children: [jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `rte-toolbar-button rte-dropdown-button ${currentOption ? 'rte-dropdown-button-has-value' : ''}`, title: label, "aria-label": label, children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), isOpen && (jsx("div", { className: "rte-dropdown-menu", children: options.map((option) => (jsxs("button", { type: "button", className: `rte-dropdown-item ${currentValue === option.value ? 'rte-dropdown-item-active' : ''}`, onClick: () => handleSelect(option.value), children: [option.color && (jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))) }))] }));
|
|
168
|
-
};
|
|
169
|
-
|
|
170
399
|
/**
|
|
171
400
|
* Liest die aktuelle Font-Size aus dem DOM an der Cursor-Position
|
|
172
401
|
*/
|
|
@@ -484,64 +713,6 @@ function createFontSizePlugin(fontSizes = [12, 14, 16, 18, 20, 24]) {
|
|
|
484
713
|
};
|
|
485
714
|
}
|
|
486
715
|
|
|
487
|
-
const defaultHeadings = ['h1', 'h2', 'h3'];
|
|
488
|
-
const headingLabels = {
|
|
489
|
-
h1: 'Überschrift 1',
|
|
490
|
-
h2: 'Überschrift 2',
|
|
491
|
-
h3: 'Überschrift 3',
|
|
492
|
-
h4: 'Überschrift 4',
|
|
493
|
-
h5: 'Überschrift 5',
|
|
494
|
-
h6: 'Überschrift 6',
|
|
495
|
-
};
|
|
496
|
-
function createHeadingsPlugin(headings = defaultHeadings) {
|
|
497
|
-
const options = [
|
|
498
|
-
{ value: 'p', label: 'Normal', headingPreview: 'p' },
|
|
499
|
-
...headings.map(h => ({
|
|
500
|
-
value: h,
|
|
501
|
-
label: headingLabels[h] || h.toUpperCase(),
|
|
502
|
-
headingPreview: h,
|
|
503
|
-
})),
|
|
504
|
-
];
|
|
505
|
-
return {
|
|
506
|
-
name: 'headings',
|
|
507
|
-
type: 'block',
|
|
508
|
-
renderButton: (props) => {
|
|
509
|
-
// Aktuelles Heading aus State Reflection
|
|
510
|
-
const currentValue = props.currentValue || (props.editorAPI ? getCurrentHeading(props.editorAPI, headings) : undefined);
|
|
511
|
-
return (jsx(Dropdown, { icon: "mdi:format-header-1", label: "\u00DCberschrift", options: options, onSelect: (value) => {
|
|
512
|
-
if (props.onSelect) {
|
|
513
|
-
props.onSelect(value);
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
props.onClick();
|
|
517
|
-
}
|
|
518
|
-
}, currentValue: currentValue, disabled: props.disabled }));
|
|
519
|
-
},
|
|
520
|
-
getCurrentValue: (editor) => {
|
|
521
|
-
return getCurrentHeading(editor, headings);
|
|
522
|
-
},
|
|
523
|
-
execute: (editor, value) => {
|
|
524
|
-
const tag = value || 'p';
|
|
525
|
-
editor.executeCommand('formatBlock', `<${tag}>`);
|
|
526
|
-
},
|
|
527
|
-
isActive: (editor) => {
|
|
528
|
-
const selection = editor.getSelection();
|
|
529
|
-
if (!selection || selection.rangeCount === 0)
|
|
530
|
-
return false;
|
|
531
|
-
const range = selection.getRangeAt(0);
|
|
532
|
-
const container = range.commonAncestorContainer;
|
|
533
|
-
const element = container.nodeType === Node.TEXT_NODE
|
|
534
|
-
? container.parentElement
|
|
535
|
-
: container;
|
|
536
|
-
if (!element)
|
|
537
|
-
return false;
|
|
538
|
-
const tagName = element.tagName.toLowerCase();
|
|
539
|
-
return headings.includes(tagName);
|
|
540
|
-
},
|
|
541
|
-
canExecute: () => true,
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
|
|
545
716
|
/**
|
|
546
717
|
* Image-Plugin mit URL-Eingabe und File-Upload
|
|
547
718
|
*/
|
|
@@ -1337,8 +1508,16 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1337
1508
|
allPlugins.push(createTextColorPlugin(colors));
|
|
1338
1509
|
allPlugins.push(createBackgroundColorPlugin(colors));
|
|
1339
1510
|
}
|
|
1511
|
+
// BlockFormat Plugin ist bereits in defaultPlugins enthalten
|
|
1512
|
+
// Wenn custom headings angegeben sind, ersetze das Standard-Plugin
|
|
1340
1513
|
if (headings && headings.length > 0) {
|
|
1341
|
-
|
|
1514
|
+
// Entferne das Standard-BlockFormat-Plugin
|
|
1515
|
+
const blockFormatIndex = allPlugins.findIndex((p) => p.name === "blockFormat");
|
|
1516
|
+
if (blockFormatIndex !== -1) {
|
|
1517
|
+
allPlugins.splice(blockFormatIndex, 1);
|
|
1518
|
+
}
|
|
1519
|
+
// Füge das Plugin mit custom Headlines hinzu
|
|
1520
|
+
allPlugins.push(createBlockFormatPlugin(headings));
|
|
1342
1521
|
}
|
|
1343
1522
|
allPlugins.push(createImagePlugin(onImageUpload));
|
|
1344
1523
|
return allPlugins;
|
|
@@ -1352,7 +1531,7 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1352
1531
|
}
|
|
1353
1532
|
}, [onChange]);
|
|
1354
1533
|
const restoreSelection = useCallback((editor) => {
|
|
1355
|
-
if (typeof window ===
|
|
1534
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
1356
1535
|
return;
|
|
1357
1536
|
const range = document.createRange();
|
|
1358
1537
|
const selection = window.getSelection();
|
|
@@ -1545,7 +1724,7 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1545
1724
|
return {
|
|
1546
1725
|
executeCommand,
|
|
1547
1726
|
getSelection: () => {
|
|
1548
|
-
if (typeof window ===
|
|
1727
|
+
if (typeof window === "undefined")
|
|
1549
1728
|
return null;
|
|
1550
1729
|
return window.getSelection();
|
|
1551
1730
|
},
|
|
@@ -1737,6 +1916,40 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1737
1916
|
}, 0);
|
|
1738
1917
|
}
|
|
1739
1918
|
},
|
|
1919
|
+
indentListItem: () => {
|
|
1920
|
+
const editor = editorRef.current;
|
|
1921
|
+
if (!editor)
|
|
1922
|
+
return;
|
|
1923
|
+
const selection = window.getSelection();
|
|
1924
|
+
if (selection && selection.rangeCount > 0) {
|
|
1925
|
+
const currentContent = domToContent(editor);
|
|
1926
|
+
historyRef.current.push(currentContent);
|
|
1927
|
+
indentListItem(selection);
|
|
1928
|
+
setTimeout(() => {
|
|
1929
|
+
if (editor) {
|
|
1930
|
+
const content = domToContent(editor);
|
|
1931
|
+
notifyChange(content);
|
|
1932
|
+
}
|
|
1933
|
+
}, 0);
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
outdentListItem: () => {
|
|
1937
|
+
const editor = editorRef.current;
|
|
1938
|
+
if (!editor)
|
|
1939
|
+
return;
|
|
1940
|
+
const selection = window.getSelection();
|
|
1941
|
+
if (selection && selection.rangeCount > 0) {
|
|
1942
|
+
const currentContent = domToContent(editor);
|
|
1943
|
+
historyRef.current.push(currentContent);
|
|
1944
|
+
outdentListItem(selection);
|
|
1945
|
+
setTimeout(() => {
|
|
1946
|
+
if (editor) {
|
|
1947
|
+
const content = domToContent(editor);
|
|
1948
|
+
notifyChange(content);
|
|
1949
|
+
}
|
|
1950
|
+
}, 0);
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1740
1953
|
};
|
|
1741
1954
|
}, [
|
|
1742
1955
|
notifyChange,
|
|
@@ -1777,46 +1990,45 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1777
1990
|
const handleKeyDown = (e) => {
|
|
1778
1991
|
const isModifierPressed = e.metaKey || e.ctrlKey;
|
|
1779
1992
|
if (e.key === "Tab" && !isModifierPressed && !e.altKey) {
|
|
1993
|
+
// Immer preventDefault aufrufen (wie Lexical), damit Tab den Fokus nicht aus dem Editor entfernt
|
|
1994
|
+
e.preventDefault();
|
|
1995
|
+
e.stopPropagation();
|
|
1996
|
+
e.stopImmediatePropagation();
|
|
1780
1997
|
const selection = window.getSelection();
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
editor.contains(selection.getRangeAt(0).commonAncestorContainer);
|
|
1784
|
-
const isEditorFocused = document.activeElement === editor ||
|
|
1785
|
-
editor.contains(document.activeElement) ||
|
|
1786
|
-
isSelectionInEditor;
|
|
1787
|
-
if (!isEditorFocused) {
|
|
1998
|
+
if (!selection || selection.rangeCount === 0) {
|
|
1999
|
+
// Keine Selection: Tab verhindern, Fokus bleibt im Editor
|
|
1788
2000
|
return;
|
|
1789
2001
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
}
|
|
1810
|
-
setTimeout(() => {
|
|
1811
|
-
if (editor) {
|
|
1812
|
-
const content = domToContent(editor);
|
|
1813
|
-
notifyChange(content);
|
|
1814
|
-
}
|
|
1815
|
-
}, 0);
|
|
1816
|
-
return;
|
|
2002
|
+
const range = selection.getRangeAt(0);
|
|
2003
|
+
const container = range.commonAncestorContainer;
|
|
2004
|
+
if (!editor.contains(container)) {
|
|
2005
|
+
// Container nicht im Editor: Tab verhindern
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
// Prüfe ob wir in einer Liste sind
|
|
2009
|
+
const listItem = container.nodeType === Node.TEXT_NODE
|
|
2010
|
+
? container.parentElement?.closest("li")
|
|
2011
|
+
: container.closest("li");
|
|
2012
|
+
if (listItem && editor.contains(listItem)) {
|
|
2013
|
+
// In Liste: Indent/Outdent durchführen
|
|
2014
|
+
const currentContent = domToContent(editor);
|
|
2015
|
+
historyRef.current.push(currentContent);
|
|
2016
|
+
if (e.shiftKey) {
|
|
2017
|
+
outdentListItem(selection);
|
|
2018
|
+
}
|
|
2019
|
+
else {
|
|
2020
|
+
indentListItem(selection);
|
|
1817
2021
|
}
|
|
2022
|
+
setTimeout(() => {
|
|
2023
|
+
if (editor) {
|
|
2024
|
+
const content = domToContent(editor);
|
|
2025
|
+
notifyChange(content);
|
|
2026
|
+
}
|
|
2027
|
+
}, 0);
|
|
2028
|
+
return;
|
|
1818
2029
|
}
|
|
1819
|
-
|
|
2030
|
+
// Nicht in Liste: Tab verhindern, aber kein Tab-Zeichen einfügen
|
|
2031
|
+
// Der Fokus bleibt im Editor (durch preventDefault)
|
|
1820
2032
|
}
|
|
1821
2033
|
if (isModifierPressed && e.key === "z" && !e.shiftKey) {
|
|
1822
2034
|
e.preventDefault();
|
|
@@ -1831,10 +2043,10 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1831
2043
|
}
|
|
1832
2044
|
};
|
|
1833
2045
|
editor.addEventListener("input", handleInput);
|
|
1834
|
-
editor.addEventListener("keydown", handleKeyDown);
|
|
2046
|
+
editor.addEventListener("keydown", handleKeyDown, true);
|
|
1835
2047
|
return () => {
|
|
1836
2048
|
editor.removeEventListener("input", handleInput);
|
|
1837
|
-
editor.removeEventListener("keydown", handleKeyDown);
|
|
2049
|
+
editor.removeEventListener("keydown", handleKeyDown, true);
|
|
1838
2050
|
if (inputTimeout) {
|
|
1839
2051
|
clearTimeout(inputTimeout);
|
|
1840
2052
|
}
|
|
@@ -1910,15 +2122,76 @@ const Editor = ({ initialContent, onChange, plugins: providedPlugins, placeholde
|
|
|
1910
2122
|
return (jsxs("div", { className: `rte-container ${className || ""}`, style: containerStyle, children: [jsx(Toolbar, { plugins: plugins, editorAPI: editorAPI, className: toolbarClassName }), jsx("div", { ref: editorRef, contentEditable: true, className: `rte-editor ${editorClassName || ""}`, "data-placeholder": placeholder, onPaste: handlePaste, suppressContentEditableWarning: true })] }));
|
|
1911
2123
|
};
|
|
1912
2124
|
|
|
2125
|
+
const defaultHeadings = ["h1", "h2", "h3"];
|
|
2126
|
+
const headingLabels = {
|
|
2127
|
+
h1: "Überschrift 1",
|
|
2128
|
+
h2: "Überschrift 2",
|
|
2129
|
+
h3: "Überschrift 3",
|
|
2130
|
+
h4: "Überschrift 4",
|
|
2131
|
+
h5: "Überschrift 5",
|
|
2132
|
+
h6: "Überschrift 6",
|
|
2133
|
+
};
|
|
2134
|
+
function createHeadingsPlugin(headings = defaultHeadings) {
|
|
2135
|
+
const options = [
|
|
2136
|
+
{ value: "p", label: "Normal", headingPreview: "p" },
|
|
2137
|
+
...headings.map((h) => ({
|
|
2138
|
+
value: h,
|
|
2139
|
+
label: headingLabels[h] || h.toUpperCase(),
|
|
2140
|
+
headingPreview: h,
|
|
2141
|
+
})),
|
|
2142
|
+
];
|
|
2143
|
+
return {
|
|
2144
|
+
name: "headings",
|
|
2145
|
+
type: "block",
|
|
2146
|
+
renderButton: (props) => {
|
|
2147
|
+
// Aktuelles Heading aus State Reflection
|
|
2148
|
+
const currentValue = props.currentValue ||
|
|
2149
|
+
(props.editorAPI
|
|
2150
|
+
? getCurrentHeading(props.editorAPI, headings)
|
|
2151
|
+
: undefined);
|
|
2152
|
+
return (jsx(Dropdown, { icon: "mdi:format-header-1", label: "\u00DCberschrift", options: options, onSelect: (value) => {
|
|
2153
|
+
if (props.onSelect) {
|
|
2154
|
+
props.onSelect(value);
|
|
2155
|
+
}
|
|
2156
|
+
else {
|
|
2157
|
+
props.onClick();
|
|
2158
|
+
}
|
|
2159
|
+
}, currentValue: currentValue, disabled: props.disabled }));
|
|
2160
|
+
},
|
|
2161
|
+
getCurrentValue: (editor) => {
|
|
2162
|
+
return getCurrentHeading(editor, headings);
|
|
2163
|
+
},
|
|
2164
|
+
execute: (editor, value) => {
|
|
2165
|
+
const tag = value || "p";
|
|
2166
|
+
editor.executeCommand("formatBlock", `<${tag}>`);
|
|
2167
|
+
},
|
|
2168
|
+
isActive: (editor) => {
|
|
2169
|
+
const selection = editor.getSelection();
|
|
2170
|
+
if (!selection || selection.rangeCount === 0)
|
|
2171
|
+
return false;
|
|
2172
|
+
const range = selection.getRangeAt(0);
|
|
2173
|
+
const container = range.commonAncestorContainer;
|
|
2174
|
+
const element = container.nodeType === Node.TEXT_NODE
|
|
2175
|
+
? container.parentElement
|
|
2176
|
+
: container;
|
|
2177
|
+
if (!element)
|
|
2178
|
+
return false;
|
|
2179
|
+
const tagName = element.tagName.toLowerCase();
|
|
2180
|
+
return headings.includes(tagName);
|
|
2181
|
+
},
|
|
2182
|
+
canExecute: () => true,
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
|
|
1913
2186
|
/**
|
|
1914
2187
|
* Link-Plugin mit verbesserter Funktionalität
|
|
1915
2188
|
*/
|
|
1916
2189
|
function createLinkPlugin() {
|
|
1917
2190
|
return {
|
|
1918
|
-
name:
|
|
1919
|
-
type:
|
|
1920
|
-
command:
|
|
1921
|
-
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ?
|
|
2191
|
+
name: "link",
|
|
2192
|
+
type: "inline",
|
|
2193
|
+
command: "createLink",
|
|
2194
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Link einf\u00FCgen", "aria-label": "Link einf\u00FCgen", children: jsx(IconWrapper, { icon: "mdi:link", width: 18, height: 18 }) })),
|
|
1922
2195
|
execute: (editor) => {
|
|
1923
2196
|
const selection = editor.getSelection();
|
|
1924
2197
|
if (!selection || selection.rangeCount === 0)
|
|
@@ -1929,7 +2202,7 @@ function createLinkPlugin() {
|
|
|
1929
2202
|
? container.parentElement
|
|
1930
2203
|
: container;
|
|
1931
2204
|
// Prüfe ob bereits ein Link vorhanden ist
|
|
1932
|
-
const existingLink = element?.closest(
|
|
2205
|
+
const existingLink = element?.closest("a");
|
|
1933
2206
|
if (existingLink) {
|
|
1934
2207
|
// Link entfernen
|
|
1935
2208
|
const parent = existingLink.parentNode;
|
|
@@ -1948,9 +2221,9 @@ function createLinkPlugin() {
|
|
|
1948
2221
|
}
|
|
1949
2222
|
else {
|
|
1950
2223
|
// Neuen Link einfügen
|
|
1951
|
-
const url = prompt(
|
|
2224
|
+
const url = prompt("URL eingeben:");
|
|
1952
2225
|
if (url) {
|
|
1953
|
-
editor.executeCommand(
|
|
2226
|
+
editor.executeCommand("createLink", url);
|
|
1954
2227
|
}
|
|
1955
2228
|
}
|
|
1956
2229
|
},
|
|
@@ -1965,7 +2238,7 @@ function createLinkPlugin() {
|
|
|
1965
2238
|
: container;
|
|
1966
2239
|
if (!element)
|
|
1967
2240
|
return false;
|
|
1968
|
-
return element.closest(
|
|
2241
|
+
return element.closest("a") !== null;
|
|
1969
2242
|
},
|
|
1970
2243
|
getCurrentValue: (editor) => {
|
|
1971
2244
|
const selection = editor.getSelection();
|
|
@@ -1978,7 +2251,7 @@ function createLinkPlugin() {
|
|
|
1978
2251
|
: container;
|
|
1979
2252
|
if (!element)
|
|
1980
2253
|
return undefined;
|
|
1981
|
-
const link = element.closest(
|
|
2254
|
+
const link = element.closest("a");
|
|
1982
2255
|
return link ? link.href : undefined;
|
|
1983
2256
|
},
|
|
1984
2257
|
canExecute: (editor) => {
|
|
@@ -1992,10 +2265,10 @@ const linkPlugin = createLinkPlugin();
|
|
|
1992
2265
|
* Blockquote-Plugin
|
|
1993
2266
|
*/
|
|
1994
2267
|
const blockquotePlugin = {
|
|
1995
|
-
name:
|
|
1996
|
-
type:
|
|
1997
|
-
command:
|
|
1998
|
-
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ?
|
|
2268
|
+
name: "blockquote",
|
|
2269
|
+
type: "block",
|
|
2270
|
+
command: "formatBlock",
|
|
2271
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Zitat", "aria-label": "Zitat", children: jsx(IconWrapper, { icon: "mdi:format-quote-close", width: 18, height: 18 }) })),
|
|
1999
2272
|
execute: (editor) => {
|
|
2000
2273
|
const selection = editor.getSelection();
|
|
2001
2274
|
if (!selection || selection.rangeCount === 0)
|
|
@@ -2007,12 +2280,12 @@ const blockquotePlugin = {
|
|
|
2007
2280
|
: container;
|
|
2008
2281
|
if (!element)
|
|
2009
2282
|
return;
|
|
2010
|
-
const isBlockquote = element.closest(
|
|
2283
|
+
const isBlockquote = element.closest("blockquote") !== null;
|
|
2011
2284
|
if (isBlockquote) {
|
|
2012
|
-
editor.executeCommand(
|
|
2285
|
+
editor.executeCommand("formatBlock", "<p>");
|
|
2013
2286
|
}
|
|
2014
2287
|
else {
|
|
2015
|
-
editor.executeCommand(
|
|
2288
|
+
editor.executeCommand("formatBlock", "<blockquote>");
|
|
2016
2289
|
}
|
|
2017
2290
|
},
|
|
2018
2291
|
isActive: (editor) => {
|
|
@@ -2026,7 +2299,7 @@ const blockquotePlugin = {
|
|
|
2026
2299
|
: container;
|
|
2027
2300
|
if (!element)
|
|
2028
2301
|
return false;
|
|
2029
|
-
return element.closest(
|
|
2302
|
+
return element.closest("blockquote") !== null;
|
|
2030
2303
|
},
|
|
2031
2304
|
canExecute: (editor) => {
|
|
2032
2305
|
const selection = editor.getSelection();
|
|
@@ -2037,17 +2310,17 @@ const blockquotePlugin = {
|
|
|
2037
2310
|
* Unordered List Plugin
|
|
2038
2311
|
*/
|
|
2039
2312
|
const unorderedListPlugin = {
|
|
2040
|
-
name:
|
|
2041
|
-
type:
|
|
2042
|
-
command:
|
|
2043
|
-
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ?
|
|
2313
|
+
name: "unorderedList",
|
|
2314
|
+
type: "block",
|
|
2315
|
+
command: "insertUnorderedList",
|
|
2316
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Aufz\u00E4hlungsliste", "aria-label": "Aufz\u00E4hlungsliste", children: jsx(IconWrapper, { icon: "mdi:format-list-bulleted", width: 18, height: 18 }) })),
|
|
2044
2317
|
execute: (editor) => {
|
|
2045
|
-
editor.executeCommand(
|
|
2318
|
+
editor.executeCommand("insertUnorderedList");
|
|
2046
2319
|
},
|
|
2047
2320
|
isActive: (editor) => {
|
|
2048
|
-
if (typeof document ===
|
|
2321
|
+
if (typeof document === "undefined")
|
|
2049
2322
|
return false;
|
|
2050
|
-
return document.queryCommandState(
|
|
2323
|
+
return document.queryCommandState("insertUnorderedList");
|
|
2051
2324
|
},
|
|
2052
2325
|
canExecute: (editor) => {
|
|
2053
2326
|
const selection = editor.getSelection();
|
|
@@ -2058,17 +2331,17 @@ const unorderedListPlugin = {
|
|
|
2058
2331
|
* Ordered List Plugin
|
|
2059
2332
|
*/
|
|
2060
2333
|
const orderedListPlugin = {
|
|
2061
|
-
name:
|
|
2062
|
-
type:
|
|
2063
|
-
command:
|
|
2064
|
-
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ?
|
|
2334
|
+
name: "orderedList",
|
|
2335
|
+
type: "block",
|
|
2336
|
+
command: "insertOrderedList",
|
|
2337
|
+
renderButton: (props) => (jsx("button", { type: "button", onClick: props.onClick, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Nummerierte Liste", "aria-label": "Nummerierte Liste", children: jsx(IconWrapper, { icon: "mdi:format-list-numbered", width: 18, height: 18 }) })),
|
|
2065
2338
|
execute: (editor) => {
|
|
2066
|
-
editor.executeCommand(
|
|
2339
|
+
editor.executeCommand("insertOrderedList");
|
|
2067
2340
|
},
|
|
2068
2341
|
isActive: (editor) => {
|
|
2069
|
-
if (typeof document ===
|
|
2342
|
+
if (typeof document === "undefined")
|
|
2070
2343
|
return false;
|
|
2071
|
-
return document.queryCommandState(
|
|
2344
|
+
return document.queryCommandState("insertOrderedList");
|
|
2072
2345
|
},
|
|
2073
2346
|
canExecute: (editor) => {
|
|
2074
2347
|
const selection = editor.getSelection();
|
|
@@ -2076,5 +2349,5 @@ const orderedListPlugin = {
|
|
|
2076
2349
|
},
|
|
2077
2350
|
};
|
|
2078
2351
|
|
|
2079
|
-
export { Dropdown, Editor, HistoryManager, Toolbar, blockquotePlugin, boldPlugin, clearFormattingPlugin, contentToDOM, contentToHTML, createBackgroundColorPlugin, createEmptyContent, createFontSizePlugin, createHeadingsPlugin, createImagePlugin, createLinkPlugin, createTextColorPlugin, Editor as default, defaultPlugins, domToContent, getCurrentBackgroundColor, getCurrentFontSize, getCurrentHeading, getCurrentTextColor, htmlToContent, indentListItem, italicPlugin, linkPlugin, orderedListPlugin, outdentListItem, redoPlugin, underlinePlugin, undoPlugin, unorderedListPlugin };
|
|
2352
|
+
export { Dropdown, Editor, HistoryManager, Toolbar, blockquotePlugin, boldPlugin, clearFormattingPlugin, contentToDOM, contentToHTML, createBackgroundColorPlugin, createBlockFormatPlugin, createEmptyContent, createFontSizePlugin, createHeadingsPlugin, createImagePlugin, createLinkPlugin, createTextColorPlugin, Editor as default, defaultPlugins, domToContent, getCurrentBackgroundColor, getCurrentFontSize, getCurrentHeading, getCurrentTextColor, htmlToContent, indentListItem, indentListItemPlugin, italicPlugin, linkPlugin, orderedListPlugin, outdentListItem, outdentListItemPlugin, redoPlugin, underlinePlugin, undoPlugin, unorderedListPlugin };
|
|
2080
2353
|
//# sourceMappingURL=index.esm.js.map
|