@superleapai/flow-ui 2.3.6 → 2.3.7
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/components/icon.js +34 -0
- package/components/richtext-editor.js +336 -0
- package/core/flow.js +43 -0
- package/dist/output.css +1 -1
- package/dist/superleap-flow.min.js +2 -2
- package/index.js +2 -0
- package/package.json +1 -1
package/components/icon.js
CHANGED
|
@@ -77,6 +77,40 @@
|
|
|
77
77
|
/** Filled circle for "just color" mode (IconOrColor when only icon_color is set) */
|
|
78
78
|
IconCircleFilled:
|
|
79
79
|
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" fill="currentColor"/></svg>',
|
|
80
|
+
|
|
81
|
+
// Rich text editor / toolbar (Tabler Icons outline, stroke 2)
|
|
82
|
+
IconBold:
|
|
83
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-bold"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6l0 -7" /><path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" /></svg>',
|
|
84
|
+
IconItalic:
|
|
85
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-italic"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11 5l6 0" /><path d="M7 19l6 0" /><path d="M14 5l-4 14" /></svg>',
|
|
86
|
+
IconUnderline:
|
|
87
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-underline"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 5v5a5 5 0 0 0 10 0v-5" /><path d="M5 19h14" /></svg>',
|
|
88
|
+
IconH1:
|
|
89
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 18v-8l-2 2" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
|
|
90
|
+
IconH2:
|
|
91
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-2"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 12a2 2 0 1 1 4 0c0 .591 -.417 1.318 -.816 1.858l-3.184 4.143l4 0" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
|
|
92
|
+
IconH3:
|
|
93
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-3"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 14a2 2 0 1 0 -2 -2" /><path d="M17 16a2 2 0 1 0 2 -2" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
|
|
94
|
+
IconList:
|
|
95
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-list"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l11 0" /><path d="M9 12l11 0" /><path d="M9 18l11 0" /><path d="M5 6l0 .01" /><path d="M5 12l0 .01" /><path d="M5 18l0 .01" /></svg>',
|
|
96
|
+
IconListNumbers:
|
|
97
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-list-numbers"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11 6h9" /><path d="M11 12h9" /><path d="M12 18h8" /><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" /><path d="M6 10v-6l-2 2" /></svg>',
|
|
98
|
+
IconAlignLeft:
|
|
99
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M4 12l10 0" /><path d="M4 18l14 0" /></svg>',
|
|
100
|
+
IconAlignCenter:
|
|
101
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-center"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M8 12l8 0" /><path d="M6 18l12 0" /></svg>',
|
|
102
|
+
IconAlignRight:
|
|
103
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M10 12l10 0" /><path d="M6 18l14 0" /></svg>',
|
|
104
|
+
IconCode:
|
|
105
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-code"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 8l-4 4l4 4" /><path d="M17 8l4 4l-4 4" /><path d="M14 4l-4 16" /></svg>',
|
|
106
|
+
IconLink:
|
|
107
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 15l6 -6" /><path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /><path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /></svg>',
|
|
108
|
+
IconPhoto:
|
|
109
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5" /><path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3" /></svg>',
|
|
110
|
+
IconArrowBackUp:
|
|
111
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-back-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 14l-4 -4l4 -4" /><path d="M5 10h11a4 4 0 1 1 0 8h-1" /></svg>',
|
|
112
|
+
IconArrowForwardUp:
|
|
113
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-forward-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 14l4 -4l-4 -4" /><path d="M19 10h-11a4 4 0 1 0 0 8h1" /></svg>',
|
|
80
114
|
};
|
|
81
115
|
|
|
82
116
|
function join() {
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich Text Editor Component (vanilla JS)
|
|
3
|
+
* Toolbar + contenteditable area with formatting (bold, italic, underline, headings, lists, alignment, link, image, code block, undo/redo).
|
|
4
|
+
* Styling matches design: rounded-12, toolbar bg-fill-tertiary-fill-light-gray, content area with min-height.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var RICH_TEXT_CONTENT_STYLES =
|
|
11
|
+
".rich-text-editor-content ul{list-style-type:disc;padding-left:1.5em;margin:0.5em 0}" +
|
|
12
|
+
".rich-text-editor-content ol{list-style-type:decimal;padding-left:1.5em;margin:0.5em 0}" +
|
|
13
|
+
".rich-text-editor-content li{margin:0.25em 0}" +
|
|
14
|
+
".rich-text-editor-content li p{margin:0}" +
|
|
15
|
+
".rich-text-editor-content h1{font-size:1.5rem;font-weight:700;line-height:1.3;margin:0.75em 0 0.5em}" +
|
|
16
|
+
".rich-text-editor-content h2{font-size:1.25rem;font-weight:600;line-height:1.3;margin:0.75em 0 0.5em}" +
|
|
17
|
+
".rich-text-editor-content h3{font-size:1.125rem;font-weight:600;line-height:1.3;margin:0.75em 0 0.5em}" +
|
|
18
|
+
".rich-text-editor-content p{margin:0.5em 0}" +
|
|
19
|
+
".rich-text-editor-content a{color:var(--color-primary-500);text-decoration:underline;cursor:pointer}" +
|
|
20
|
+
".rich-text-editor-content pre{background:var(--color-neutral-100);border-radius:var(--sizes-size-8);padding:0.75em 1em;margin:0.5em 0;overflow-x:auto}" +
|
|
21
|
+
".rich-text-editor-content code{font-family:ui-monospace,monospace;font-size:0.875em}" +
|
|
22
|
+
".rich-text-editor-content img{max-width:100%;height:auto;margin:0.5em 0}" +
|
|
23
|
+
".rich-text-editor-content blockquote{border-left:3px solid var(--color-neutral-200);padding-left:1em;margin:0.5em 0;color:var(--color-neutral-600)}" +
|
|
24
|
+
".rich-text-editor-content hr{border:none;border-top:1px solid var(--color-neutral-150);margin:1em 0}" +
|
|
25
|
+
".rich-text-editor-content .ProseMirror,.rich-text-editor-content [contenteditable]{outline:none}";
|
|
26
|
+
|
|
27
|
+
function join() {
|
|
28
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getDep(name) {
|
|
32
|
+
if (typeof global.FlowUI !== "undefined" && typeof global.FlowUI._getComponent === "function") {
|
|
33
|
+
var c = global.FlowUI._getComponent(name);
|
|
34
|
+
if (c) return c;
|
|
35
|
+
}
|
|
36
|
+
return global[name];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Get Tabler icon element (16px) from Icon component for toolbar. */
|
|
40
|
+
function getTablerIcon(iconName) {
|
|
41
|
+
var Icon = getDep("Icon");
|
|
42
|
+
if (!Icon || !Icon.iconMap || !Icon.iconMap[iconName]) return null;
|
|
43
|
+
var svgStr = Icon.iconMap[iconName];
|
|
44
|
+
var s16 = svgStr.replace(/width="24"/, 'width="16"').replace(/height="24"/, 'height="16"').replace(/width="20"/, 'width="16"').replace(/height="20"/, 'height="16"');
|
|
45
|
+
var span = document.createElement("span");
|
|
46
|
+
span.className = "flex items-center justify-center size-16";
|
|
47
|
+
span.innerHTML = s16;
|
|
48
|
+
return span;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createToolbarButton(opts) {
|
|
52
|
+
var Button = getDep("Button");
|
|
53
|
+
if (!Button || typeof Button.create !== "function") {
|
|
54
|
+
throw new Error("RichTextEditor requires Button");
|
|
55
|
+
}
|
|
56
|
+
var icon = opts.iconStr ? getTablerIcon(opts.iconStr) : null;
|
|
57
|
+
return Button.create({
|
|
58
|
+
variant: opts.active ? "primary" : "outline",
|
|
59
|
+
size: "default",
|
|
60
|
+
title: opts.title,
|
|
61
|
+
icon: icon,
|
|
62
|
+
onClick: opts.onClick,
|
|
63
|
+
disabled: opts.disabled,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createSeparator() {
|
|
68
|
+
var sep = document.createElement("div");
|
|
69
|
+
sep.className = "w-px h-16 bg-border-primary mx-4";
|
|
70
|
+
sep.setAttribute("aria-hidden", "true");
|
|
71
|
+
return sep;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a rich text editor
|
|
76
|
+
* @param {Object} config
|
|
77
|
+
* @param {string} [config.value] - Initial HTML content
|
|
78
|
+
* @param {string} [config.placeholder] - Placeholder when empty
|
|
79
|
+
* @param {number} [config.minHeightPx] - Min height of editor area (default 400)
|
|
80
|
+
* @param {boolean} [config.disabled]
|
|
81
|
+
* @param {Function} [config.onChange] - (html: string) => void
|
|
82
|
+
* @returns {HTMLElement} Wrapper element with getValue/setValue/setDisabled/getInput
|
|
83
|
+
*/
|
|
84
|
+
function create(config) {
|
|
85
|
+
var value = config.value != null ? String(config.value) : "";
|
|
86
|
+
var placeholder = config.placeholder != null ? config.placeholder : "";
|
|
87
|
+
var minHeightPx = config.minHeightPx != null ? config.minHeightPx : 400;
|
|
88
|
+
var disabled = !!config.disabled;
|
|
89
|
+
var onChange = config.onChange;
|
|
90
|
+
|
|
91
|
+
var wrapper = document.createElement("div");
|
|
92
|
+
wrapper.className = "w-full rounded-12 border border-borderColor-border-primary shadow-soft-2x-small";
|
|
93
|
+
|
|
94
|
+
var styleEl = document.createElement("style");
|
|
95
|
+
styleEl.textContent = RICH_TEXT_CONTENT_STYLES;
|
|
96
|
+
wrapper.appendChild(styleEl);
|
|
97
|
+
|
|
98
|
+
var toolbar = document.createElement("div");
|
|
99
|
+
toolbar.className =
|
|
100
|
+
"flex flex-wrap gap-4 rounded-t-12 border-borderColor-border-primary bg-fill-tertiary-fill-light-gray p-6";
|
|
101
|
+
toolbar.setAttribute("role", "toolbar");
|
|
102
|
+
|
|
103
|
+
var editorEl = document.createElement("div");
|
|
104
|
+
editorEl.contentEditable = !disabled;
|
|
105
|
+
editorEl.className = join(
|
|
106
|
+
"rich-text-editor-content max-w-none rounded-b-12 border-t border-borderColor-border-primary p-8 text-reg-14 text-typography-primary-text"
|
|
107
|
+
);
|
|
108
|
+
editorEl.style.minHeight = minHeightPx + "px";
|
|
109
|
+
if (value) editorEl.innerHTML = value;
|
|
110
|
+
editorEl.setAttribute("data-placeholder", placeholder);
|
|
111
|
+
|
|
112
|
+
function isEmpty() {
|
|
113
|
+
var text = (editorEl.textContent || "").trim();
|
|
114
|
+
if (text) return false;
|
|
115
|
+
var html = (editorEl.innerHTML || "").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "");
|
|
116
|
+
return !html.trim();
|
|
117
|
+
}
|
|
118
|
+
function updatePlaceholder() {
|
|
119
|
+
if (placeholder && isEmpty()) {
|
|
120
|
+
editorEl.classList.add("empty");
|
|
121
|
+
editorEl.setAttribute("data-placeholder", placeholder);
|
|
122
|
+
} else {
|
|
123
|
+
editorEl.classList.remove("empty");
|
|
124
|
+
editorEl.removeAttribute("data-placeholder");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
updatePlaceholder();
|
|
128
|
+
|
|
129
|
+
function getHtml() {
|
|
130
|
+
return editorEl.innerHTML;
|
|
131
|
+
}
|
|
132
|
+
function setHtml(html) {
|
|
133
|
+
editorEl.innerHTML = html || "";
|
|
134
|
+
updatePlaceholder();
|
|
135
|
+
}
|
|
136
|
+
function notifyChange() {
|
|
137
|
+
if (typeof onChange === "function") onChange(getHtml());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isActive(cmd, val) {
|
|
141
|
+
try {
|
|
142
|
+
return document.queryCommandState(cmd);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function blockTag() {
|
|
148
|
+
var sel = window.getSelection();
|
|
149
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
150
|
+
var node = sel.anchorNode;
|
|
151
|
+
while (node && node !== editorEl) {
|
|
152
|
+
if (node.nodeType === 1) {
|
|
153
|
+
var n = node.nodeName.toLowerCase();
|
|
154
|
+
if (["h1", "h2", "h3", "p", "div", "pre", "blockquote"].indexOf(n) !== -1) return n;
|
|
155
|
+
}
|
|
156
|
+
node = node.parentNode;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function isAlignment(align) {
|
|
161
|
+
try {
|
|
162
|
+
if (align === "left") return document.queryCommandState("justifyLeft");
|
|
163
|
+
if (align === "center") return document.queryCommandState("justifyCenter");
|
|
164
|
+
if (align === "right") return document.queryCommandState("justifyRight");
|
|
165
|
+
} catch (e) {}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function refreshToolbar() {
|
|
170
|
+
toolbar.querySelectorAll("button").forEach(function (btn) {
|
|
171
|
+
var cmd = btn.getAttribute("data-command");
|
|
172
|
+
var val = btn.getAttribute("data-value");
|
|
173
|
+
if (!cmd) return;
|
|
174
|
+
var active = false;
|
|
175
|
+
if (cmd === "formatBlock") active = blockTag() === val;
|
|
176
|
+
else if (cmd === "justifyLeft" && val === "left") active = isAlignment("left");
|
|
177
|
+
else if (cmd === "justifyCenter" && val === "center") active = isAlignment("center");
|
|
178
|
+
else if (cmd === "justifyRight" && val === "right") active = isAlignment("right");
|
|
179
|
+
else active = isActive(cmd);
|
|
180
|
+
btn.classList.toggle("bg-primary-base", active);
|
|
181
|
+
btn.classList.toggle("border-primary-base", active);
|
|
182
|
+
btn.classList.toggle("text-typography-invert-text", active);
|
|
183
|
+
btn.classList.toggle("bg-fill-quarternary-fill-white", !active);
|
|
184
|
+
btn.classList.toggle("border-border-primary", !active);
|
|
185
|
+
btn.classList.toggle("text-typography-primary-text", !active);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function exec(cmd, value) {
|
|
190
|
+
editorEl.focus();
|
|
191
|
+
document.execCommand(cmd, false, value != null ? value : null);
|
|
192
|
+
refreshToolbar();
|
|
193
|
+
notifyChange();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function insertCodeBlock() {
|
|
197
|
+
editorEl.focus();
|
|
198
|
+
var sel = window.getSelection();
|
|
199
|
+
if (sel && sel.rangeCount) {
|
|
200
|
+
var range = sel.getRangeAt(0);
|
|
201
|
+
var pre = document.createElement("pre");
|
|
202
|
+
var code = document.createElement("code");
|
|
203
|
+
code.textContent = "code here";
|
|
204
|
+
pre.appendChild(code);
|
|
205
|
+
range.insertNode(pre);
|
|
206
|
+
range.setStart(code, 0);
|
|
207
|
+
range.setEnd(code, 0);
|
|
208
|
+
sel.removeAllRanges();
|
|
209
|
+
sel.addRange(range);
|
|
210
|
+
}
|
|
211
|
+
refreshToolbar();
|
|
212
|
+
notifyChange();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function addLink() {
|
|
216
|
+
var url = window.prompt("Enter the URL:", "https://");
|
|
217
|
+
if (url) {
|
|
218
|
+
exec("createLink", url);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function addImage() {
|
|
223
|
+
var input = document.createElement("input");
|
|
224
|
+
input.type = "file";
|
|
225
|
+
input.accept = "image/*";
|
|
226
|
+
input.onchange = function (e) {
|
|
227
|
+
var file = e.target && e.target.files && e.target.files[0];
|
|
228
|
+
if (file) {
|
|
229
|
+
var reader = new FileReader();
|
|
230
|
+
reader.onload = function (ev) {
|
|
231
|
+
var src = ev.target && ev.target.result;
|
|
232
|
+
if (src) {
|
|
233
|
+
editorEl.focus();
|
|
234
|
+
document.execCommand("insertImage", false, src);
|
|
235
|
+
notifyChange();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
reader.readAsDataURL(file);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
input.click();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function undo() {
|
|
245
|
+
editorEl.focus();
|
|
246
|
+
document.execCommand("undo", false, null);
|
|
247
|
+
refreshToolbar();
|
|
248
|
+
notifyChange();
|
|
249
|
+
}
|
|
250
|
+
function redo() {
|
|
251
|
+
editorEl.focus();
|
|
252
|
+
document.execCommand("redo", false, null);
|
|
253
|
+
refreshToolbar();
|
|
254
|
+
notifyChange();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function addBtn(iconStr, title, onClick, dataCommand, dataValue) {
|
|
258
|
+
var btn = createToolbarButton({
|
|
259
|
+
iconStr: iconStr,
|
|
260
|
+
title: title,
|
|
261
|
+
onClick: function () {
|
|
262
|
+
if (disabled) return;
|
|
263
|
+
onClick();
|
|
264
|
+
},
|
|
265
|
+
active: false,
|
|
266
|
+
});
|
|
267
|
+
if (dataCommand) btn.setAttribute("data-command", dataCommand);
|
|
268
|
+
if (dataValue != null) btn.setAttribute("data-value", dataValue);
|
|
269
|
+
toolbar.appendChild(btn);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
addBtn("IconBold", "Bold (Ctrl+B)", function () { exec("bold"); }, "bold");
|
|
273
|
+
addBtn("IconItalic", "Italic (Ctrl+I)", function () { exec("italic"); }, "italic");
|
|
274
|
+
addBtn("IconUnderline", "Underline (Ctrl+U)", function () { exec("underline"); }, "underline");
|
|
275
|
+
toolbar.appendChild(createSeparator());
|
|
276
|
+
addBtn("IconH1", "Heading 1", function () { exec("formatBlock", "h1"); }, "formatBlock", "h1");
|
|
277
|
+
addBtn("IconH2", "Heading 2", function () { exec("formatBlock", "h2"); }, "formatBlock", "h2");
|
|
278
|
+
addBtn("IconH3", "Heading 3", function () { exec("formatBlock", "h3"); }, "formatBlock", "h3");
|
|
279
|
+
toolbar.appendChild(createSeparator());
|
|
280
|
+
addBtn("IconList", "Bullet List", function () { exec("insertUnorderedList"); }, "insertUnorderedList");
|
|
281
|
+
addBtn("IconListNumbers", "Ordered List", function () { exec("insertOrderedList"); }, "insertOrderedList");
|
|
282
|
+
toolbar.appendChild(createSeparator());
|
|
283
|
+
addBtn("IconAlignLeft", "Align Left", function () { exec("justifyLeft"); }, "justifyLeft", "left");
|
|
284
|
+
addBtn("IconAlignCenter", "Align Center", function () { exec("justifyCenter"); }, "justifyCenter", "center");
|
|
285
|
+
addBtn("IconAlignRight", "Align Right", function () { exec("justifyRight"); }, "justifyRight", "right");
|
|
286
|
+
toolbar.appendChild(createSeparator());
|
|
287
|
+
addBtn("IconCode", "Code Block", insertCodeBlock, "formatBlock", "pre");
|
|
288
|
+
addBtn("IconLink", "Add Link", addLink, "link");
|
|
289
|
+
addBtn("IconPhoto", "Insert Image", addImage);
|
|
290
|
+
toolbar.appendChild(createSeparator());
|
|
291
|
+
addBtn("IconArrowBackUp", "Undo", undo);
|
|
292
|
+
addBtn("IconArrowForwardUp", "Redo", redo);
|
|
293
|
+
|
|
294
|
+
editorEl.addEventListener("input", function () {
|
|
295
|
+
updatePlaceholder();
|
|
296
|
+
refreshToolbar();
|
|
297
|
+
notifyChange();
|
|
298
|
+
});
|
|
299
|
+
editorEl.addEventListener("keyup", refreshToolbar);
|
|
300
|
+
editorEl.addEventListener("mouseup", refreshToolbar);
|
|
301
|
+
editorEl.addEventListener("focus", refreshToolbar);
|
|
302
|
+
|
|
303
|
+
if (placeholder) {
|
|
304
|
+
var placeholderStyles = document.createElement("style");
|
|
305
|
+
placeholderStyles.textContent =
|
|
306
|
+
".rich-text-editor-content.empty:before{content:attr(data-placeholder);color:var(--color-neutral-400);pointer-events:none}";
|
|
307
|
+
wrapper.appendChild(placeholderStyles);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
wrapper.appendChild(toolbar);
|
|
311
|
+
wrapper.appendChild(editorEl);
|
|
312
|
+
|
|
313
|
+
wrapper.getInput = function () {
|
|
314
|
+
return editorEl;
|
|
315
|
+
};
|
|
316
|
+
wrapper.getValue = function () {
|
|
317
|
+
return getHtml();
|
|
318
|
+
};
|
|
319
|
+
wrapper.setValue = function (v) {
|
|
320
|
+
setHtml(v);
|
|
321
|
+
};
|
|
322
|
+
wrapper.setDisabled = function (d) {
|
|
323
|
+
disabled = !!d;
|
|
324
|
+
editorEl.contentEditable = !disabled;
|
|
325
|
+
toolbar.querySelectorAll("button").forEach(function (b) {
|
|
326
|
+
b.disabled = disabled;
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
return wrapper;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
global.RichTextEditorComponent = {
|
|
334
|
+
create: create,
|
|
335
|
+
};
|
|
336
|
+
})(typeof window !== "undefined" ? window : this);
|
package/core/flow.js
CHANGED
|
@@ -248,6 +248,48 @@
|
|
|
248
248
|
return field;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Create a rich text editor field
|
|
253
|
+
* @param {Object} config - Configuration object
|
|
254
|
+
* @param {string} config.label - Field label
|
|
255
|
+
* @param {string} config.fieldId - State key for this field
|
|
256
|
+
* @param {string} [config.placeholder] - Placeholder when empty
|
|
257
|
+
* @param {boolean} [config.required] - Whether field is required
|
|
258
|
+
* @param {string} [config.helpText] - Optional help text for tooltip
|
|
259
|
+
* @param {number} [config.minHeightPx] - Min height of editor area in pixels (default 400)
|
|
260
|
+
* @param {boolean} [config.disabled] - Whether editor is disabled
|
|
261
|
+
* @returns {HTMLElement} Field element
|
|
262
|
+
*/
|
|
263
|
+
function createRichTextEditor(config) {
|
|
264
|
+
const { label, fieldId, placeholder, required = false, helpText = null, minHeightPx = 400, disabled = false } = config;
|
|
265
|
+
|
|
266
|
+
const field = createFieldWrapper(label, required, helpText);
|
|
267
|
+
field.setAttribute("data-field-id", fieldId);
|
|
268
|
+
|
|
269
|
+
if (getComponent("RichTextEditorComponent") && getComponent("RichTextEditorComponent").create) {
|
|
270
|
+
const currentValue = get(fieldId) || "";
|
|
271
|
+
const editorEl = getComponent("RichTextEditorComponent").create({
|
|
272
|
+
value: currentValue,
|
|
273
|
+
placeholder: placeholder || "",
|
|
274
|
+
minHeightPx,
|
|
275
|
+
disabled,
|
|
276
|
+
onChange: (html) => set(fieldId, html),
|
|
277
|
+
});
|
|
278
|
+
editorEl._fieldId = fieldId;
|
|
279
|
+
field.appendChild(editorEl);
|
|
280
|
+
return field;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const fallback = document.createElement("textarea");
|
|
284
|
+
fallback.className = "textarea min-h-[400px]";
|
|
285
|
+
fallback.placeholder = placeholder || `Enter ${label.toLowerCase()}`;
|
|
286
|
+
fallback.value = get(fieldId) || "";
|
|
287
|
+
fallback.disabled = disabled;
|
|
288
|
+
fallback.addEventListener("change", (e) => set(fieldId, e.target.value));
|
|
289
|
+
field.appendChild(fallback);
|
|
290
|
+
return field;
|
|
291
|
+
}
|
|
292
|
+
|
|
251
293
|
/**
|
|
252
294
|
* Create a select dropdown field (using custom select component)
|
|
253
295
|
* @param {Object} config - Configuration object
|
|
@@ -1658,6 +1700,7 @@
|
|
|
1658
1700
|
// Form components
|
|
1659
1701
|
createInput,
|
|
1660
1702
|
createTextarea,
|
|
1703
|
+
createRichTextEditor,
|
|
1661
1704
|
createSelect,
|
|
1662
1705
|
createTimePicker,
|
|
1663
1706
|
createDateTimePicker,
|