smeditor 0.2.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/app/assets/javascripts/smeditor.js +438 -179
- data/app/assets/stylesheets/smeditor.css +33 -6
- data/lib/smeditor/rails/form_helper.rb +2 -0
- data/lib/smeditor/rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: deea7d855b9ea5c7d61a51d82adeb656e3583db0dc9ed8d677345fe2d1bac550
|
|
4
|
+
data.tar.gz: 99a68a8823548919c2acb1aaea114822b677e33d464cd37a66f42fe4cc802e73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cf34629807ab5b326dff8ddaa85e80bc2f4b5c7de86a233e4d2a9c1196a3151df18af818f658f6826e0f21c7d90317c8f190df1697f5ecc8501c4f24511c9b7
|
|
7
|
+
data.tar.gz: 3f72f9d9fc819294563dd2519eb07e84dd159432fd2bc8ad6111b0ed125c46790163ef7caae49ae1b76ea7ced5406baf65b1dc9a2350e6115edc15b34c3d57bc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog — smeditor
|
|
2
2
|
|
|
3
|
+
## 0.2.2 — 2026-09-09
|
|
4
|
+
|
|
5
|
+
- Fixed caret jumps and reordered typing inside the Rails Shadow DOM editor. The core now resolves the live DOM Selection from the editor's own root instead of always using `document.getSelection()`.
|
|
6
|
+
- Selection restore and caret coordinate ranges now use the editor element's owner document, keeping the DOM integration correct in isolated roots and embedded documents.
|
|
7
|
+
|
|
8
|
+
## 0.2.1 — 2026-09-09
|
|
9
|
+
|
|
10
|
+
- Rails editor now renders inside Shadow DOM so host application CSS cannot alter the SMEditor toolbar or editing surface.
|
|
11
|
+
- Rails toolbar now uses the same SMEditor visual language and control layout as the playground instead of native selects/text buttons.
|
|
12
|
+
- Theme tokens work inside the isolated editor and continue to follow light/dark preference.
|
|
13
|
+
- The self-contained gem still requires no npm, React, esbuild, or jsbundling-rails in the host Rails application.
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
All notable changes to the Rails adapter are recorded here. The format is
|
|
4
17
|
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the gem
|
|
5
18
|
follows [RubyGems versioning](https://guides.rubygems.org/patterns/).
|
|
@@ -13,6 +13,53 @@ const starter_kit_1 = require("@smeditor/starter-kit");
|
|
|
13
13
|
const full_kit_1 = require("@smeditor/full-kit");
|
|
14
14
|
const instances = new Map();
|
|
15
15
|
const disposers = new Map();
|
|
16
|
+
const ICONS = {
|
|
17
|
+
undo: '<path d="M3.5 8h7a3 3 0 0 1 0 6H6"/><path d="m6 4-3 4 3 4"/>',
|
|
18
|
+
redo: '<path d="M12.5 8h-7a3 3 0 0 0 0 6H10"/><path d="m10 4 3 4-3 4"/>',
|
|
19
|
+
bold: '<path d="M4 3h4.5a2.5 2.5 0 0 1 0 5H4z"/><path d="M4 8h5a2.5 2.5 0 0 1 0 5H4z"/>',
|
|
20
|
+
italic: '<path d="M10 3 6 13"/><path d="M6 3h5"/><path d="M5 13h5"/>',
|
|
21
|
+
strike: '<path d="M3 8h10"/><path d="M5.5 4.5C6 4 6.7 3.5 8 3.5c2 0 3 1 3 2.5"/><path d="M5 11c0 1.4 1.3 2.5 3 2.5 1.8 0 3-1 3-2.2"/>',
|
|
22
|
+
underline: '<path d="M4 3v5a4 4 0 0 0 8 0V3"/><path d="M3.5 13.5h9"/>',
|
|
23
|
+
code: '<path d="m6 5-3 3 3 3"/><path d="m10 5 3 3-3 3"/>',
|
|
24
|
+
eraser: '<path d="m9 3 5 5-6 6H4l-1-1z"/><path d="m6 6 5 5"/><path d="M9 14h5"/>',
|
|
25
|
+
link: '<path d="M7 9a3 3 0 0 0 4 0l2-2a3 3 0 1 0-4-4L8 4"/><path d="M9 7a3 3 0 0 0-4 0l-2 2a3 3 0 1 0 4 4l1-1"/>',
|
|
26
|
+
highlight: '<path d="M3 13l3-1 6-6-2-2-6 6z"/><path d="M2 14h12"/>',
|
|
27
|
+
textColor: '<text x="3" y="11" font-size="9" font-weight="700" fill="currentColor" stroke="none">A</text><rect x="3" y="13" width="10" height="2" fill="currentColor" stroke="none"/>',
|
|
28
|
+
background: '<rect x="2" y="2" width="12" height="12" rx="1.5"/><path d="M2 2l12 12"/>',
|
|
29
|
+
blockquote: '<path d="M3 5v3c0 1.5-.7 2.5-2 3"/><path d="M9 5v3c0 1.5-.7 2.5-2 3"/>',
|
|
30
|
+
codeBlock: '<rect x="1.5" y="3" width="13" height="10" rx="1.5"/><path d="m5 6.5-2 1.5 2 1.5"/><path d="m11 6.5 2 1.5-2 1.5"/>',
|
|
31
|
+
hr: '<path d="M2 8h12"/><path d="M2 4h8"/><path d="M2 12h6"/>',
|
|
32
|
+
bullet: '<circle cx="3.5" cy="4.5" r=".7" fill="currentColor"/><circle cx="3.5" cy="8" r=".7" fill="currentColor"/><circle cx="3.5" cy="11.5" r=".7" fill="currentColor"/><path d="M6 4.5h7M6 8h7M6 11.5h7"/>',
|
|
33
|
+
ordered: '<text x="2" y="6" font-size="4" font-family="monospace" fill="currentColor" stroke="none">1.</text><text x="2" y="13" font-size="4" font-family="monospace" fill="currentColor" stroke="none">2.</text><path d="M6 4.5h7M6 11.5h7"/>',
|
|
34
|
+
task: '<rect x="2" y="3" width="3" height="3" rx=".5"/><path d="m2.5 4.5 1 1 1.5-1.5"/><rect x="2" y="10" width="3" height="3" rx=".5"/><path d="M7 4.5h6M7 11.5h6"/>',
|
|
35
|
+
alignLeft: '<path d="M2.5 4h11M2.5 7h7M2.5 10h11M2.5 13h7"/>',
|
|
36
|
+
alignCenter: '<path d="M2.5 4h11M4.5 7h7M2.5 10h11M4.5 13h7"/>',
|
|
37
|
+
alignRight: '<path d="M2.5 4h11M6.5 7h7M2.5 10h11M6.5 13h7"/>',
|
|
38
|
+
alignJustify: '<path d="M2.5 4h11M2.5 7h11M2.5 10h11M2.5 13h11"/>',
|
|
39
|
+
indent: '<path d="M2 4h12M7 8h7M2 12h12"/><path d="m2 7 2 1.5L2 10" fill="currentColor"/>',
|
|
40
|
+
outdent: '<path d="M2 4h12M7 8h7M2 12h12"/><path d="m6 7-2 1.5L6 10" fill="currentColor"/>',
|
|
41
|
+
image: '<rect x="2" y="3" width="12" height="10" rx="1.5"/><circle cx="5.5" cy="6.5" r="1.2"/><path d="m2.5 11 3.5-3.5 2.5 2.5L11 7.5l2.5 2.5"/>',
|
|
42
|
+
table: '<rect x="2" y="3" width="12" height="10" rx="1"/><path d="M2 6.5h12M2 10h12M6 3v10M10 3v10"/>',
|
|
43
|
+
more: '<circle cx="3.5" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="12.5" cy="8" r="1.2" fill="currentColor"/>',
|
|
44
|
+
heading: '<text x="2" y="12" font-family="sans-serif" font-size="9" font-weight="700" fill="currentColor" stroke="none">H</text><text x="9.5" y="13" font-family="sans-serif" font-size="6" font-weight="700" fill="currentColor" stroke="none">2</text>',
|
|
45
|
+
paragraph: '<text x="3" y="12" font-family="serif" font-size="11" font-weight="700" fill="currentColor" stroke="none">¶</text>',
|
|
46
|
+
subscript: '<path d="M3 4l4 5"/><path d="M7 4l-4 5"/><text x="9" y="14" font-size="6" font-weight="700" fill="currentColor" stroke="none">x</text>',
|
|
47
|
+
superscript: '<path d="M3 6l4 5"/><path d="M7 6l-4 5"/><text x="9" y="7" font-size="6" font-weight="700" fill="currentColor" stroke="none">x</text>',
|
|
48
|
+
};
|
|
49
|
+
function svgIcon(name, strokeWidth = 1.5) {
|
|
50
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
51
|
+
svg.setAttribute("width", "16");
|
|
52
|
+
svg.setAttribute("height", "16");
|
|
53
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
54
|
+
svg.setAttribute("fill", "none");
|
|
55
|
+
svg.setAttribute("stroke", "currentColor");
|
|
56
|
+
svg.setAttribute("stroke-width", String(strokeWidth));
|
|
57
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
58
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
59
|
+
svg.setAttribute("aria-hidden", "true");
|
|
60
|
+
svg.innerHTML = ICONS[name] || "";
|
|
61
|
+
return svg;
|
|
62
|
+
}
|
|
16
63
|
function csrfToken() {
|
|
17
64
|
var _a, _b;
|
|
18
65
|
return (_b = (_a = document.querySelector('meta[name="csrf-token"]')) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : null;
|
|
@@ -45,16 +92,16 @@ function kitExtensions(kit, uploadUrl) {
|
|
|
45
92
|
return [...base];
|
|
46
93
|
return base.map((extension) => extension.name === "image" ? starter_kit_1.ImageExtension.configure({ upload }) : extension);
|
|
47
94
|
}
|
|
48
|
-
function
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
95
|
+
function run(editor, command, ...args) {
|
|
96
|
+
const fn = editor.commands[command];
|
|
97
|
+
if (typeof fn !== "function")
|
|
98
|
+
return false;
|
|
99
|
+
const result = fn(...args);
|
|
100
|
+
editor.focus();
|
|
101
|
+
return Boolean(result);
|
|
102
|
+
}
|
|
103
|
+
function hasCommand(editor, command) {
|
|
104
|
+
return typeof editor.commands[command] === "function";
|
|
58
105
|
}
|
|
59
106
|
function divider() {
|
|
60
107
|
const el = document.createElement("span");
|
|
@@ -63,170 +110,358 @@ function divider() {
|
|
|
63
110
|
el.setAttribute("aria-hidden", "true");
|
|
64
111
|
return el;
|
|
65
112
|
}
|
|
66
|
-
function
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
function commandButton(editor, iconName, title, command, active, label) {
|
|
114
|
+
const el = document.createElement("button");
|
|
115
|
+
el.type = "button";
|
|
116
|
+
el.className = "smeditor-button";
|
|
117
|
+
el.title = title;
|
|
118
|
+
el.setAttribute("aria-label", title);
|
|
119
|
+
if (label)
|
|
120
|
+
el.textContent = label;
|
|
121
|
+
else
|
|
122
|
+
el.appendChild(svgIcon(iconName, iconName === "bold" ? 2 : 1.5));
|
|
123
|
+
el.addEventListener("mousedown", (event) => event.preventDefault());
|
|
124
|
+
el.addEventListener("click", () => command());
|
|
125
|
+
const refresh = () => {
|
|
126
|
+
if (active) {
|
|
127
|
+
const value = active();
|
|
128
|
+
el.dataset.active = value ? "true" : "false";
|
|
129
|
+
el.classList.toggle("is-active", value);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
el.__smeditorRefresh = refresh;
|
|
133
|
+
refresh();
|
|
134
|
+
return el;
|
|
82
135
|
}
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
136
|
+
function dropdown(ariaLabel, triggerContent, items, options = {}) {
|
|
137
|
+
const wrapper = document.createElement("div");
|
|
138
|
+
wrapper.className = "smeditor-dropdown";
|
|
139
|
+
const trigger = document.createElement("button");
|
|
140
|
+
trigger.type = "button";
|
|
141
|
+
trigger.className = "smeditor-button smeditor-dropdown__trigger";
|
|
142
|
+
trigger.setAttribute("aria-haspopup", "menu");
|
|
143
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
144
|
+
trigger.setAttribute("aria-label", ariaLabel);
|
|
145
|
+
trigger.title = ariaLabel;
|
|
146
|
+
const label = document.createElement("span");
|
|
147
|
+
label.className = "smeditor-dropdown__label";
|
|
148
|
+
if (typeof triggerContent === "string")
|
|
149
|
+
label.textContent = triggerContent;
|
|
150
|
+
else
|
|
151
|
+
label.appendChild(triggerContent);
|
|
152
|
+
trigger.appendChild(label);
|
|
153
|
+
let summary = null;
|
|
154
|
+
if (options.summary) {
|
|
155
|
+
summary = document.createElement("span");
|
|
156
|
+
summary.className = "smeditor-dropdown__summary";
|
|
157
|
+
trigger.appendChild(summary);
|
|
158
|
+
}
|
|
159
|
+
const chevron = document.createElement("span");
|
|
160
|
+
chevron.className = "smeditor-dropdown__chevron";
|
|
161
|
+
chevron.setAttribute("aria-hidden", "true");
|
|
162
|
+
chevron.textContent = "▾";
|
|
163
|
+
trigger.appendChild(chevron);
|
|
164
|
+
wrapper.appendChild(trigger);
|
|
165
|
+
let menu = null;
|
|
166
|
+
const close = () => {
|
|
167
|
+
wrapper.classList.remove("is-open");
|
|
168
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
169
|
+
menu === null || menu === void 0 ? void 0 : menu.remove();
|
|
170
|
+
menu = null;
|
|
171
|
+
};
|
|
172
|
+
const open = () => {
|
|
173
|
+
var _a, _b, _c, _d;
|
|
174
|
+
if (menu)
|
|
175
|
+
return close();
|
|
176
|
+
wrapper.classList.add("is-open");
|
|
177
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
178
|
+
menu = document.createElement("div");
|
|
179
|
+
menu.className = "smeditor-dropdown__menu" + (options.align === "right" ? " is-right" : "");
|
|
180
|
+
menu.setAttribute("role", "menu");
|
|
181
|
+
menu.setAttribute("aria-label", ariaLabel);
|
|
182
|
+
for (const spec of items()) {
|
|
183
|
+
if (spec.separatorBefore) {
|
|
184
|
+
const sep = document.createElement("div");
|
|
185
|
+
sep.className = "smeditor-dropdown__separator";
|
|
186
|
+
sep.setAttribute("role", "separator");
|
|
187
|
+
menu.appendChild(sep);
|
|
188
|
+
}
|
|
189
|
+
if (spec.content) {
|
|
190
|
+
menu.appendChild(spec.content);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const item = document.createElement("button");
|
|
194
|
+
item.type = "button";
|
|
195
|
+
item.className = "smeditor-dropdown__item";
|
|
196
|
+
item.setAttribute("role", spec.active ? "menuitemcheckbox" : "menuitem");
|
|
197
|
+
const isActive = (_b = (_a = spec.active) === null || _a === void 0 ? void 0 : _a.call(spec)) !== null && _b !== void 0 ? _b : false;
|
|
198
|
+
if (spec.active)
|
|
199
|
+
item.setAttribute("aria-checked", String(isActive));
|
|
200
|
+
item.classList.toggle("is-active", isActive);
|
|
201
|
+
item.dataset.active = isActive ? "true" : "false";
|
|
202
|
+
const isDisabled = (_d = (_c = spec.disabled) === null || _c === void 0 ? void 0 : _c.call(spec)) !== null && _d !== void 0 ? _d : false;
|
|
203
|
+
item.disabled = isDisabled;
|
|
204
|
+
if (isDisabled)
|
|
205
|
+
item.setAttribute("aria-disabled", "true");
|
|
206
|
+
if (spec.icon) {
|
|
207
|
+
const icon = document.createElement("span");
|
|
208
|
+
icon.className = "smeditor-dropdown__item-icon";
|
|
209
|
+
icon.appendChild(svgIcon(spec.icon));
|
|
210
|
+
item.appendChild(icon);
|
|
211
|
+
}
|
|
212
|
+
const text = document.createElement("span");
|
|
213
|
+
text.className = "smeditor-dropdown__item-label";
|
|
214
|
+
text.textContent = spec.label;
|
|
215
|
+
item.appendChild(text);
|
|
216
|
+
if (spec.shortcut) {
|
|
217
|
+
const shortcut = document.createElement("span");
|
|
218
|
+
shortcut.className = "smeditor-dropdown__item-shortcut";
|
|
219
|
+
shortcut.textContent = spec.shortcut;
|
|
220
|
+
item.appendChild(shortcut);
|
|
221
|
+
}
|
|
222
|
+
item.addEventListener("mousedown", (event) => event.preventDefault());
|
|
223
|
+
item.addEventListener("click", () => {
|
|
224
|
+
var _a;
|
|
225
|
+
if (item.disabled)
|
|
226
|
+
return;
|
|
227
|
+
(_a = spec.action) === null || _a === void 0 ? void 0 : _a.call(spec);
|
|
228
|
+
close();
|
|
229
|
+
});
|
|
230
|
+
menu.appendChild(item);
|
|
231
|
+
}
|
|
232
|
+
wrapper.appendChild(menu);
|
|
233
|
+
};
|
|
234
|
+
trigger.addEventListener("mousedown", (event) => event.preventDefault());
|
|
235
|
+
trigger.addEventListener("click", (event) => {
|
|
236
|
+
event.stopPropagation();
|
|
237
|
+
open();
|
|
238
|
+
});
|
|
239
|
+
const outside = (event) => {
|
|
240
|
+
if (!menu)
|
|
241
|
+
return;
|
|
242
|
+
const path = event.composedPath();
|
|
243
|
+
if (!path.includes(wrapper))
|
|
244
|
+
close();
|
|
245
|
+
};
|
|
246
|
+
document.addEventListener("pointerdown", outside, true);
|
|
247
|
+
const refresh = () => {
|
|
248
|
+
if (summary && options.summary)
|
|
249
|
+
summary.textContent = options.summary();
|
|
250
|
+
if (options.active) {
|
|
251
|
+
const value = options.active();
|
|
252
|
+
trigger.dataset.active = value ? "true" : "false";
|
|
253
|
+
trigger.classList.toggle("is-active", value);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
wrapper.__smeditorRefresh = refresh;
|
|
257
|
+
wrapper.__smeditorDispose = () => document.removeEventListener("pointerdown", outside, true);
|
|
258
|
+
refresh();
|
|
259
|
+
return wrapper;
|
|
260
|
+
}
|
|
261
|
+
function colorDropdown(editor, ariaLabel, iconName, markName, setCommand, unsetCommand, extraAttrs = {}) {
|
|
262
|
+
const colors = [
|
|
263
|
+
"#1a1a1f", "#6b7280", "#ef4444", "#f97316", "#eab308",
|
|
264
|
+
"#22c55e", "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899",
|
|
265
|
+
"#ffffff", "#d1d5db", "#fecaca", "#fed7aa", "#fef08a",
|
|
266
|
+
"#bbf7d0", "#a5f3fc", "#bfdbfe", "#ddd6fe", "#fbcfe8",
|
|
267
|
+
];
|
|
268
|
+
return dropdown(ariaLabel, svgIcon(iconName), () => {
|
|
269
|
+
const grid = document.createElement("div");
|
|
270
|
+
grid.className = "smeditor-color-grid";
|
|
271
|
+
grid.setAttribute("role", "group");
|
|
272
|
+
const clear = document.createElement("button");
|
|
273
|
+
clear.type = "button";
|
|
274
|
+
clear.className = "smeditor-color-grid__swatch smeditor-color-grid__swatch--clear";
|
|
275
|
+
clear.title = `Clear ${ariaLabel.toLowerCase()}`;
|
|
276
|
+
clear.setAttribute("aria-label", clear.title);
|
|
277
|
+
clear.addEventListener("mousedown", (e) => e.preventDefault());
|
|
278
|
+
clear.addEventListener("click", () => run(editor, unsetCommand));
|
|
279
|
+
grid.appendChild(clear);
|
|
280
|
+
for (const color of colors) {
|
|
281
|
+
const swatch = document.createElement("button");
|
|
282
|
+
swatch.type = "button";
|
|
283
|
+
swatch.className = "smeditor-color-grid__swatch";
|
|
284
|
+
swatch.style.backgroundColor = color;
|
|
285
|
+
swatch.title = color;
|
|
286
|
+
swatch.setAttribute("aria-label", `${ariaLabel}: ${color}`);
|
|
287
|
+
swatch.addEventListener("mousedown", (e) => e.preventDefault());
|
|
288
|
+
swatch.addEventListener("click", () => run(editor, setCommand, { color, ...extraAttrs }));
|
|
289
|
+
grid.appendChild(swatch);
|
|
290
|
+
}
|
|
291
|
+
return [{ label: "", content: grid }];
|
|
292
|
+
}, { active: () => editor.isActive(markName) });
|
|
293
|
+
}
|
|
294
|
+
function blockTypeSummary(editor) {
|
|
295
|
+
if ((0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "blockquote"))
|
|
296
|
+
return "❝";
|
|
297
|
+
if (editor.isActive("code_block"))
|
|
298
|
+
return "</>";
|
|
299
|
+
for (const level of [1, 2, 3, 4, 5, 6]) {
|
|
300
|
+
if (editor.isActive("heading", { level }))
|
|
301
|
+
return `H${level}`;
|
|
302
|
+
}
|
|
303
|
+
return "P";
|
|
304
|
+
}
|
|
305
|
+
function tablePicker(editor) {
|
|
306
|
+
return dropdown("Insert table", svgIcon("table"), () => {
|
|
307
|
+
const grid = document.createElement("div");
|
|
308
|
+
grid.className = "smeditor-table-picker";
|
|
309
|
+
grid.setAttribute("role", "group");
|
|
310
|
+
grid.setAttribute("aria-label", "Insert table size");
|
|
311
|
+
const cells = [];
|
|
312
|
+
const refreshHover = (rows, cols) => {
|
|
313
|
+
cells.forEach((cell, idx) => {
|
|
314
|
+
const r = Math.floor(idx / 6) + 1;
|
|
315
|
+
const c = (idx % 6) + 1;
|
|
316
|
+
cell.classList.toggle("is-on", r <= rows && c <= cols);
|
|
317
|
+
});
|
|
318
|
+
label.textContent = rows && cols ? `${rows} × ${cols}` : "Select size";
|
|
319
|
+
};
|
|
320
|
+
for (let r = 1; r <= 6; r++) {
|
|
321
|
+
for (let c = 1; c <= 6; c++) {
|
|
322
|
+
const cell = document.createElement("button");
|
|
323
|
+
cell.type = "button";
|
|
324
|
+
cell.className = "smeditor-table-picker__cell";
|
|
325
|
+
cell.setAttribute("aria-label", `Insert ${r} by ${c} table`);
|
|
326
|
+
cell.addEventListener("mouseenter", () => refreshHover(r, c));
|
|
327
|
+
cell.addEventListener("mousedown", (e) => e.preventDefault());
|
|
328
|
+
cell.addEventListener("click", () => run(editor, "insertTable", { rows: r, cols: c }));
|
|
329
|
+
cells.push(cell);
|
|
330
|
+
grid.appendChild(cell);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const label = document.createElement("div");
|
|
334
|
+
label.className = "smeditor-table-picker__label";
|
|
335
|
+
label.textContent = "Select size";
|
|
336
|
+
const box = document.createElement("div");
|
|
337
|
+
box.append(grid, label);
|
|
338
|
+
box.addEventListener("mouseleave", () => refreshHover(0, 0));
|
|
339
|
+
return [{ label: "", content: box }];
|
|
340
|
+
});
|
|
90
341
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
342
|
+
function textDropdown(editor, ariaLabel, label, values, command, unset) {
|
|
343
|
+
return dropdown(ariaLabel, label, () => values.map(([text, value]) => ({
|
|
344
|
+
label: text,
|
|
345
|
+
action: () => value === null && unset ? run(editor, unset) : run(editor, command, { [command === "setFontFamily" ? "family" : command === "setFontSize" ? "size" : "value"]: value }),
|
|
346
|
+
})));
|
|
93
347
|
}
|
|
94
348
|
function buildToolbar(editor, kit, uploadUrl) {
|
|
95
|
-
var _a;
|
|
96
349
|
const toolbar = document.createElement("div");
|
|
97
350
|
toolbar.className = "smeditor-toolbar";
|
|
98
351
|
toolbar.setAttribute("role", "toolbar");
|
|
99
352
|
toolbar.setAttribute("aria-label", "SMEditor toolbar");
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
toolbar.append(
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
bold.style.fontWeight = "700";
|
|
122
|
-
italic.style.fontStyle = "italic";
|
|
123
|
-
underline.style.textDecoration = "underline";
|
|
124
|
-
strike.style.textDecoration = "line-through";
|
|
125
|
-
toolbar.append(bold, italic, underline, strike);
|
|
126
|
-
if (kit === "full") {
|
|
127
|
-
const inlineCode = button("</>", "Inline code", () => run(editor, "toggleCode"));
|
|
128
|
-
const sub = button("x₂", "Subscript", () => run(editor, "toggleSubscript"));
|
|
129
|
-
const sup = button("x²", "Superscript", () => run(editor, "toggleSuperscript"));
|
|
130
|
-
toolbar.append(inlineCode, sub, sup);
|
|
131
|
-
}
|
|
132
|
-
const clear = button("Tx", "Clear formatting", () => run(editor, "clearFormatting"));
|
|
133
|
-
const link = button("🔗", "Link", () => {
|
|
353
|
+
toolbar.setAttribute("aria-orientation", "horizontal");
|
|
354
|
+
const controls = [];
|
|
355
|
+
const add = (...els) => { toolbar.append(...els); controls.push(...els); };
|
|
356
|
+
const undo = commandButton(editor, "undo", "Undo", () => run(editor, "undo"));
|
|
357
|
+
const redo = commandButton(editor, "redo", "Redo", () => run(editor, "redo"));
|
|
358
|
+
add(undo, redo, divider());
|
|
359
|
+
const block = dropdown("Block type", svgIcon("heading"), () => {
|
|
360
|
+
const items = [{ label: "Paragraph", icon: "paragraph", active: () => editor.isActive("paragraph"), action: () => run(editor, "setParagraph") }];
|
|
361
|
+
for (let level = 1; level <= 6; level++)
|
|
362
|
+
items.push({ label: `Heading ${level}`, icon: "heading", shortcut: `⌘⌥${level}`, active: () => editor.isActive("heading", { level }), action: () => run(editor, "setHeading", { level }), separatorBefore: level === 1 });
|
|
363
|
+
items.push({ label: "Quote", icon: "blockquote", active: () => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "blockquote"), action: () => run(editor, "toggleBlockquote"), separatorBefore: true });
|
|
364
|
+
items.push({ label: "Code block", icon: "codeBlock", active: () => editor.isActive("code_block"), action: () => run(editor, "setCodeBlock") });
|
|
365
|
+
return items;
|
|
366
|
+
}, { summary: () => blockTypeSummary(editor) });
|
|
367
|
+
const lists = dropdown("Lists", svgIcon("bullet"), () => [
|
|
368
|
+
{ label: "Bullet List", icon: "bullet", shortcut: "⌘⇧8", active: () => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "bullet_list"), action: () => run(editor, "toggleBulletList") },
|
|
369
|
+
{ label: "Ordered List", icon: "ordered", shortcut: "⌘⇧7", active: () => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "ordered_list"), action: () => run(editor, "toggleOrderedList") },
|
|
370
|
+
{ label: "Task List", icon: "task", shortcut: "⌘⇧9", active: () => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "task_list"), action: () => run(editor, "toggleTaskList") },
|
|
371
|
+
], { active: () => ["bullet_list", "ordered_list", "task_list"].some((name) => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), name)) });
|
|
372
|
+
add(block, lists, divider());
|
|
373
|
+
add(commandButton(editor, "bold", "Bold", () => run(editor, "toggleBold"), () => editor.isActive("bold")), commandButton(editor, "italic", "Italic", () => run(editor, "toggleItalic"), () => editor.isActive("italic")), commandButton(editor, "strike", "Strike-through", () => run(editor, "toggleStrike"), () => editor.isActive("strike")), commandButton(editor, "underline", "Underline", () => run(editor, "toggleUnderline"), () => editor.isActive("underline")), commandButton(editor, "eraser", "Clear formatting", () => run(editor, "clearFormatting")), commandButton(editor, "link", "Link", () => {
|
|
134
374
|
if (editor.isActive("link"))
|
|
135
375
|
return run(editor, "unsetLink");
|
|
136
376
|
const href = window.prompt("Link URL", "https://");
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
377
|
+
return href ? run(editor, "setLink", { href }) : false;
|
|
378
|
+
}, () => editor.isActive("link")), divider());
|
|
379
|
+
const align = dropdown("Alignment", svgIcon("alignLeft"), () => [
|
|
380
|
+
{ label: "Left", icon: "alignLeft", active: () => editor.isActive("text_align", { align: "left" }), action: () => run(editor, "setTextAlign", { align: "left" }) },
|
|
381
|
+
{ label: "Center", icon: "alignCenter", active: () => editor.isActive("text_align", { align: "center" }), action: () => run(editor, "setTextAlign", { align: "center" }) },
|
|
382
|
+
{ label: "Right", icon: "alignRight", active: () => editor.isActive("text_align", { align: "right" }), action: () => run(editor, "setTextAlign", { align: "right" }) },
|
|
383
|
+
{ label: "Justify", icon: "alignJustify", active: () => editor.isActive("text_align", { align: "justify" }), action: () => run(editor, "setTextAlign", { align: "justify" }) },
|
|
384
|
+
{ label: "Increase indent", icon: "indent", action: () => run(editor, "indent"), separatorBefore: true },
|
|
385
|
+
{ label: "Decrease indent", icon: "outdent", action: () => run(editor, "outdent") },
|
|
386
|
+
]);
|
|
387
|
+
add(align, divider());
|
|
388
|
+
add(commandButton(editor, "blockquote", "Blockquote", () => run(editor, "toggleBlockquote"), () => (0, core_1.selectionInsideWrapper)(editor.getJSON(), editor.getSelection(), "blockquote")), commandButton(editor, "codeBlock", "Code block", () => run(editor, "setCodeBlock"), () => editor.isActive("code_block")), commandButton(editor, "hr", "Horizontal rule", () => run(editor, "insertHorizontalRule")));
|
|
389
|
+
const image = dropdown("Insert image", svgIcon("image"), () => {
|
|
390
|
+
const items = [{ label: "From URL…", icon: "link", action: () => {
|
|
391
|
+
var _a;
|
|
392
|
+
const src = window.prompt("Image URL", "https://");
|
|
393
|
+
if (!src)
|
|
394
|
+
return;
|
|
395
|
+
const alt = (_a = window.prompt("Alt text (optional)", "")) !== null && _a !== void 0 ? _a : "";
|
|
396
|
+
run(editor, "insertImage", { src, alt: alt || undefined });
|
|
397
|
+
} }];
|
|
398
|
+
if (uploadUrl && hasCommand(editor, "uploadImage"))
|
|
399
|
+
items.push({ label: "Upload image…", icon: "image", action: () => {
|
|
400
|
+
const input = document.createElement("input");
|
|
401
|
+
input.type = "file";
|
|
402
|
+
input.accept = "image/*";
|
|
403
|
+
input.onchange = () => { var _a; const file = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0]; if (file)
|
|
404
|
+
run(editor, "uploadImage", file); };
|
|
405
|
+
input.click();
|
|
406
|
+
} });
|
|
407
|
+
return items;
|
|
159
408
|
});
|
|
160
|
-
|
|
161
|
-
if (uploadUrl && typeof editor.commands.uploadImage === "function") {
|
|
162
|
-
const file = document.createElement("input");
|
|
163
|
-
file.type = "file";
|
|
164
|
-
file.accept = "image/*";
|
|
165
|
-
file.className = "smeditor-file-input";
|
|
166
|
-
file.tabIndex = -1;
|
|
167
|
-
file.setAttribute("aria-hidden", "true");
|
|
168
|
-
const upload = button("Upload", "Upload image", () => file.click());
|
|
169
|
-
file.addEventListener("change", () => {
|
|
170
|
-
var _a;
|
|
171
|
-
const selected = (_a = file.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
172
|
-
if (selected)
|
|
173
|
-
run(editor, "uploadImage", selected);
|
|
174
|
-
file.value = "";
|
|
175
|
-
});
|
|
176
|
-
toolbar.append(upload, file);
|
|
177
|
-
}
|
|
178
|
-
toolbar.append(button("Table", "Insert table", () => {
|
|
179
|
-
const rows = Number(window.prompt("Rows", "3") || "3");
|
|
180
|
-
const cols = Number(window.prompt("Columns", "3") || "3");
|
|
181
|
-
run(editor, "insertTable", { rows, cols, withHeaderRow: true });
|
|
182
|
-
}));
|
|
409
|
+
add(image, tablePicker(editor));
|
|
183
410
|
if (kit === "full") {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
411
|
+
add(divider());
|
|
412
|
+
add(colorDropdown(editor, "Highlight", "highlight", "highlight", "setHighlight", "unsetHighlight"), colorDropdown(editor, "Text color", "textColor", "text_color", "setTextColor", "unsetTextColor"), colorDropdown(editor, "Text stroke", "textColor", "text_stroke", "setTextStroke", "unsetTextStroke", { width: 1 }), colorDropdown(editor, "Background color", "background", "background_color", "setBackgroundColor", "unsetBackgroundColor"), textDropdown(editor, "Font family", "Font", [["Default", null], ["Sans-serif", "ui-sans-serif, system-ui, sans-serif"], ["Serif", "ui-serif, Georgia, serif"], ["Monospace", "ui-monospace, Menlo, monospace"], ["Georgia", "Georgia, serif"], ["Courier", "'Courier New', monospace"]], "setFontFamily", "unsetFontFamily"), textDropdown(editor, "Font size", "Size", [["Default", null], ["Small · 13px", "13px"], ["Normal · 16px", "16px"], ["Large · 20px", "20px"], ["Huge · 28px", "28px"]], "setFontSize", "unsetFontSize"), textDropdown(editor, "Line height", "Spacing", [["Default", null], ["Tight · 1", "1"], ["Snug · 1.15", "1.15"], ["Normal · 1.5", "1.5"], ["Relaxed · 2", "2"]], "setLineHeight"));
|
|
413
|
+
if (hasCommand(editor, "addComment")) {
|
|
414
|
+
add(commandButton(editor, "", "Comment", () => {
|
|
415
|
+
const body = window.prompt("New comment");
|
|
416
|
+
if (!body)
|
|
417
|
+
return false;
|
|
418
|
+
return run(editor, "addComment", { threadId: `comment-${Date.now()}` });
|
|
419
|
+
}, undefined, "Comment"));
|
|
420
|
+
}
|
|
421
|
+
const more = dropdown("More tools", svgIcon("more"), () => [
|
|
422
|
+
{ label: "Inline code", icon: "code", shortcut: "⌘E", active: () => editor.isActive("code"), action: () => run(editor, "toggleCode") },
|
|
423
|
+
{ label: "Subscript", icon: "subscript", active: () => editor.isActive("subscript"), action: () => run(editor, "toggleSubscript"), separatorBefore: true },
|
|
424
|
+
{ label: "Superscript", icon: "superscript", active: () => editor.isActive("superscript"), action: () => run(editor, "toggleSuperscript") },
|
|
425
|
+
{ label: "Increase indent", icon: "indent", action: () => run(editor, "indent"), separatorBefore: true },
|
|
426
|
+
{ label: "Decrease indent", icon: "outdent", action: () => run(editor, "outdent") },
|
|
427
|
+
], { align: "right" });
|
|
428
|
+
add(more);
|
|
429
|
+
}
|
|
430
|
+
const refresh = () => {
|
|
431
|
+
var _a, _b;
|
|
432
|
+
undo.toggleAttribute("disabled", !editor.canUndo());
|
|
433
|
+
redo.toggleAttribute("disabled", !editor.canRedo());
|
|
434
|
+
for (const control of controls)
|
|
435
|
+
(_b = (_a = control).__smeditorRefresh) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
436
|
+
};
|
|
437
|
+
const disposeUpdate = editor.on("update", refresh);
|
|
438
|
+
const disposeSelection = editor.on("selectionUpdate", refresh);
|
|
439
|
+
queueMicrotask(refresh);
|
|
440
|
+
toolbar.__smeditorDispose = () => {
|
|
441
|
+
var _a, _b;
|
|
442
|
+
disposeUpdate();
|
|
443
|
+
disposeSelection();
|
|
444
|
+
for (const control of controls)
|
|
445
|
+
(_b = (_a = control).__smeditorDispose) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
214
446
|
};
|
|
215
|
-
const disposeUpdate = editor.on("update", updateState);
|
|
216
|
-
const disposeSelection = editor.on("selectionUpdate", updateState);
|
|
217
|
-
queueMicrotask(updateState);
|
|
218
|
-
const listForMount = (_a = disposers.get(editor.element)) !== null && _a !== void 0 ? _a : [];
|
|
219
|
-
listForMount.push(disposeUpdate, disposeSelection);
|
|
220
|
-
disposers.set(editor.element, listForMount);
|
|
221
447
|
return toolbar;
|
|
222
448
|
}
|
|
449
|
+
function shadowRootFor(mount) {
|
|
450
|
+
const root = mount.shadowRoot || mount.attachShadow({ mode: "open" });
|
|
451
|
+
root.innerHTML = "";
|
|
452
|
+
const stylesheet = mount.dataset.smeditorStylesheet;
|
|
453
|
+
if (stylesheet) {
|
|
454
|
+
const link = document.createElement("link");
|
|
455
|
+
link.rel = "stylesheet";
|
|
456
|
+
link.href = stylesheet;
|
|
457
|
+
root.appendChild(link);
|
|
458
|
+
}
|
|
459
|
+
return root;
|
|
460
|
+
}
|
|
223
461
|
function bootMount(mount) {
|
|
462
|
+
var _a, _b;
|
|
224
463
|
if (instances.has(mount))
|
|
225
464
|
return;
|
|
226
|
-
if (mount.smeditorInstance) {
|
|
227
|
-
instances.set(mount, mount.smeditorInstance);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
465
|
const field = mount.closest(".smeditor-field");
|
|
231
466
|
const input = field === null || field === void 0 ? void 0 : field.querySelector("[data-smeditor-input]");
|
|
232
467
|
if (!field || !input)
|
|
@@ -234,14 +469,18 @@ function bootMount(mount) {
|
|
|
234
469
|
const kit = mount.dataset.smeditorKit === "full" ? "full" : "starter";
|
|
235
470
|
const uploadUrl = mount.dataset.smeditorUploadUrl || undefined;
|
|
236
471
|
const placeholder = mount.dataset.smeditorPlaceholder || undefined;
|
|
237
|
-
mount.
|
|
472
|
+
const explicitTheme = ((_b = (_a = mount.parentElement) === null || _a === void 0 ? void 0 : _a.closest("[data-theme]")) === null || _b === void 0 ? void 0 : _b.dataset.theme)
|
|
473
|
+
|| document.documentElement.dataset.theme;
|
|
474
|
+
if (explicitTheme === "light" || explicitTheme === "dark")
|
|
475
|
+
mount.dataset.theme = explicitTheme;
|
|
476
|
+
const root = shadowRootFor(mount);
|
|
238
477
|
const shell = document.createElement("div");
|
|
239
478
|
shell.className = "smeditor";
|
|
240
479
|
const surface = document.createElement("div");
|
|
241
480
|
surface.className = "smeditor-editor";
|
|
242
481
|
surface.setAttribute("aria-label", mount.dataset.smeditorLabel || "Rich text editor");
|
|
243
482
|
shell.appendChild(surface);
|
|
244
|
-
|
|
483
|
+
root.appendChild(shell);
|
|
245
484
|
const editor = (0, core_1.createEditor)({
|
|
246
485
|
element: surface,
|
|
247
486
|
extensions: kitExtensions(kit, uploadUrl),
|
|
@@ -254,11 +493,13 @@ function bootMount(mount) {
|
|
|
254
493
|
input.dispatchEvent(new CustomEvent("smeditor:change", { bubbles: true, detail: { editor: current } }));
|
|
255
494
|
},
|
|
256
495
|
});
|
|
257
|
-
|
|
496
|
+
const toolbar = buildToolbar(editor, kit, uploadUrl);
|
|
497
|
+
shell.insertBefore(toolbar, surface);
|
|
258
498
|
input.smeditorInstance = editor;
|
|
259
499
|
mount.smeditorInstance = editor;
|
|
260
500
|
mount.dataset.smeditorBooted = "1";
|
|
261
501
|
instances.set(mount, editor);
|
|
502
|
+
disposers.set(mount, [() => { var _a, _b; return (_b = (_a = toolbar).__smeditorDispose) === null || _b === void 0 ? void 0 : _b.call(_a); }]);
|
|
262
503
|
}
|
|
263
504
|
function boot(root = document) {
|
|
264
505
|
root.querySelectorAll("[data-smeditor]").forEach(bootMount);
|
|
@@ -269,12 +510,9 @@ function destroy(root = document) {
|
|
|
269
510
|
const editor = instances.get(mount);
|
|
270
511
|
if (!editor)
|
|
271
512
|
return;
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
dispose();
|
|
276
|
-
disposers.delete(surface);
|
|
277
|
-
}
|
|
513
|
+
for (const dispose of (_a = disposers.get(mount)) !== null && _a !== void 0 ? _a : [])
|
|
514
|
+
dispose();
|
|
515
|
+
disposers.delete(mount);
|
|
278
516
|
editor.destroy();
|
|
279
517
|
const field = mount.closest(".smeditor-field");
|
|
280
518
|
const input = field === null || field === void 0 ? void 0 : field.querySelector("[data-smeditor-input]");
|
|
@@ -283,7 +521,8 @@ function destroy(root = document) {
|
|
|
283
521
|
delete mount.smeditorInstance;
|
|
284
522
|
delete mount.dataset.smeditorBooted;
|
|
285
523
|
instances.delete(mount);
|
|
286
|
-
mount.
|
|
524
|
+
if (mount.shadowRoot)
|
|
525
|
+
mount.shadowRoot.innerHTML = "";
|
|
287
526
|
});
|
|
288
527
|
}
|
|
289
528
|
const api = { boot, destroy, createEditor: core_1.createEditor, StarterKit: starter_kit_1.StarterKit, FullKit: full_kit_1.FullKit, ImageExtension: starter_kit_1.ImageExtension };
|
|
@@ -291,12 +530,10 @@ if (typeof window !== "undefined") {
|
|
|
291
530
|
window.SMEditor = Object.assign(window.SMEditor || {}, api);
|
|
292
531
|
if (!window.__smeditorRailsEventsBound) {
|
|
293
532
|
window.__smeditorRailsEventsBound = true;
|
|
294
|
-
if (document.readyState === "loading")
|
|
533
|
+
if (document.readyState === "loading")
|
|
295
534
|
document.addEventListener("DOMContentLoaded", () => { var _a; return (_a = window.SMEditor) === null || _a === void 0 ? void 0 : _a.boot(); });
|
|
296
|
-
|
|
297
|
-
else {
|
|
535
|
+
else
|
|
298
536
|
boot();
|
|
299
|
-
}
|
|
300
537
|
document.addEventListener("turbo:load", () => { var _a; return (_a = window.SMEditor) === null || _a === void 0 ? void 0 : _a.boot(); });
|
|
301
538
|
document.addEventListener("turbo:frame-load", (event) => { var _a; return (_a = window.SMEditor) === null || _a === void 0 ? void 0 : _a.boot(event.target || document); });
|
|
302
539
|
document.addEventListener("turbo:before-cache", () => { var _a; return (_a = window.SMEditor) === null || _a === void 0 ? void 0 : _a.destroy(); });
|
|
@@ -526,14 +763,14 @@ class Editor {
|
|
|
526
763
|
*/
|
|
527
764
|
coordsAtPoint(point) {
|
|
528
765
|
var _a;
|
|
529
|
-
if (!this.element
|
|
766
|
+
if (!this.element)
|
|
530
767
|
return null;
|
|
531
768
|
const deep = (_a = this.options.deepSelection) !== null && _a !== void 0 ? _a : false;
|
|
532
769
|
const loc = domNodeFromPoint(point, this.element, deep);
|
|
533
770
|
if (!loc)
|
|
534
771
|
return null;
|
|
535
772
|
try {
|
|
536
|
-
const range =
|
|
773
|
+
const range = this.element.ownerDocument.createRange();
|
|
537
774
|
const offset = Math.min(loc.offset, lengthOf(loc.node));
|
|
538
775
|
range.setStart(loc.node, offset);
|
|
539
776
|
range.collapse(true);
|
|
@@ -1006,9 +1243,7 @@ class Editor {
|
|
|
1006
1243
|
var _a, _b;
|
|
1007
1244
|
if (this.updatingDOM || !this.element)
|
|
1008
1245
|
return;
|
|
1009
|
-
|
|
1010
|
-
return;
|
|
1011
|
-
const domSel = document.getSelection();
|
|
1246
|
+
const domSel = selectionForElement(this.element);
|
|
1012
1247
|
if (!domSel)
|
|
1013
1248
|
return;
|
|
1014
1249
|
if (!this.element.contains(domSel.anchorNode))
|
|
@@ -1030,9 +1265,9 @@ class Editor {
|
|
|
1030
1265
|
}
|
|
1031
1266
|
readSelectionFromDOM() {
|
|
1032
1267
|
var _a;
|
|
1033
|
-
if (!this.element
|
|
1268
|
+
if (!this.element)
|
|
1034
1269
|
return null;
|
|
1035
|
-
const sel =
|
|
1270
|
+
const sel = selectionForElement(this.element);
|
|
1036
1271
|
if (!sel || sel.rangeCount === 0)
|
|
1037
1272
|
return null;
|
|
1038
1273
|
const deep = (_a = this.options.deepSelection) !== null && _a !== void 0 ? _a : false;
|
|
@@ -1053,10 +1288,10 @@ class Editor {
|
|
|
1053
1288
|
}
|
|
1054
1289
|
applySelectionToDOM() {
|
|
1055
1290
|
var _a;
|
|
1056
|
-
if (!this.selection || !this.element
|
|
1291
|
+
if (!this.selection || !this.element) {
|
|
1057
1292
|
return;
|
|
1058
1293
|
}
|
|
1059
|
-
const sel =
|
|
1294
|
+
const sel = selectionForElement(this.element);
|
|
1060
1295
|
if (!sel)
|
|
1061
1296
|
return;
|
|
1062
1297
|
const deep = (_a = this.options.deepSelection) !== null && _a !== void 0 ? _a : false;
|
|
@@ -1065,7 +1300,7 @@ class Editor {
|
|
|
1065
1300
|
if (!anchorNode || !headNode)
|
|
1066
1301
|
return;
|
|
1067
1302
|
try {
|
|
1068
|
-
const range =
|
|
1303
|
+
const range = this.element.ownerDocument.createRange();
|
|
1069
1304
|
range.setStart(anchorNode.node, Math.min(anchorNode.offset, lengthOf(anchorNode.node)));
|
|
1070
1305
|
range.setEnd(headNode.node, Math.min(headNode.offset, lengthOf(headNode.node)));
|
|
1071
1306
|
sel.removeAllRanges();
|
|
@@ -1116,6 +1351,30 @@ class Editor {
|
|
|
1116
1351
|
// ============================================================================
|
|
1117
1352
|
// Helpers (module-private)
|
|
1118
1353
|
// ============================================================================
|
|
1354
|
+
/**
|
|
1355
|
+
* Return the Selection object that actually owns the editor caret.
|
|
1356
|
+
*
|
|
1357
|
+
* `document.getSelection()` is not sufficient for editors mounted inside an
|
|
1358
|
+
* open ShadowRoot: Chromium exposes a composed selection on the document whose
|
|
1359
|
+
* anchor can be the shadow host instead of the text node inside the editor.
|
|
1360
|
+
* Reading that selection produces a null/wrong editor position; the next DOM
|
|
1361
|
+
* render then loses the caret (most visibly when typing spaces).
|
|
1362
|
+
*
|
|
1363
|
+
* Both Document and Chromium's ShadowRoot expose getSelection(). Prefer the
|
|
1364
|
+
* editor's root and fall back to its owner document for browsers that do not
|
|
1365
|
+
* expose ShadowRoot#getSelection.
|
|
1366
|
+
*/
|
|
1367
|
+
function selectionForElement(element) {
|
|
1368
|
+
var _a, _b, _c, _d;
|
|
1369
|
+
const root = (_a = element.getRootNode) === null || _a === void 0 ? void 0 : _a.call(element);
|
|
1370
|
+
const rootedGetSelection = root === null || root === void 0 ? void 0 : root.getSelection;
|
|
1371
|
+
if (root && typeof rootedGetSelection === "function") {
|
|
1372
|
+
const selection = rootedGetSelection.call(root);
|
|
1373
|
+
if (selection)
|
|
1374
|
+
return selection;
|
|
1375
|
+
}
|
|
1376
|
+
return (_d = (_c = (_b = element.ownerDocument) === null || _b === void 0 ? void 0 : _b.getSelection) === null || _c === void 0 ? void 0 : _c.call(_b)) !== null && _d !== void 0 ? _d : null;
|
|
1377
|
+
}
|
|
1119
1378
|
/**
|
|
1120
1379
|
* Strip noise and unsafe elements from pasted HTML.
|
|
1121
1380
|
*
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* light, and inherited by everything not explicitly tagged dark.
|
|
18
18
|
* ------------------------------------------------------------------------ */
|
|
19
19
|
:root,
|
|
20
|
+
:host,
|
|
21
|
+
:host([data-theme="light"]),
|
|
20
22
|
[data-theme="light"] {
|
|
21
23
|
/* Surface */
|
|
22
24
|
--sme-color-surface: #ffffff;
|
|
@@ -44,7 +46,8 @@
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/* Common (non-color) tokens — shared by both themes. */
|
|
47
|
-
:root
|
|
49
|
+
:root,
|
|
50
|
+
:host {
|
|
48
51
|
/* Spacing */
|
|
49
52
|
--sme-space-1: 4px;
|
|
50
53
|
--sme-space-2: 8px;
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
* Dark theme — applied when explicitly tagged, OR via media query when
|
|
83
86
|
* no explicit theme is set on the document.
|
|
84
87
|
* ------------------------------------------------------------------------ */
|
|
88
|
+
:host([data-theme="dark"]),
|
|
85
89
|
[data-theme="dark"] {
|
|
86
90
|
--sme-color-surface: #0f1115;
|
|
87
91
|
--sme-color-surface-muted: #1a1d23;
|
|
@@ -105,7 +109,8 @@
|
|
|
105
109
|
* `:not([data-theme])` guard means a `data-theme="light"` setting on
|
|
106
110
|
* any ancestor wins, even at night. */
|
|
107
111
|
@media (prefers-color-scheme: dark) {
|
|
108
|
-
:root:not([data-theme="light"])
|
|
112
|
+
:root:not([data-theme="light"]),
|
|
113
|
+
:host(:not([data-theme="light"])) {
|
|
109
114
|
--sme-color-surface: #0f1115;
|
|
110
115
|
--sme-color-surface-muted: #1a1d23;
|
|
111
116
|
--sme-color-border: #2a2d35;
|
|
@@ -1210,9 +1215,31 @@
|
|
|
1210
1215
|
}
|
|
1211
1216
|
|
|
1212
1217
|
|
|
1213
|
-
/* Rails self-contained adapter
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1218
|
+
/* Rails self-contained adapter / style isolation */
|
|
1219
|
+
:host {
|
|
1220
|
+
display: block;
|
|
1221
|
+
width: 100%;
|
|
1222
|
+
color: var(--sme-color-text);
|
|
1223
|
+
font-family: var(--sme-font-sans);
|
|
1224
|
+
font-size: var(--sme-text-base);
|
|
1225
|
+
line-height: normal;
|
|
1226
|
+
text-align: initial;
|
|
1227
|
+
}
|
|
1228
|
+
.smeditor,
|
|
1229
|
+
.smeditor *,
|
|
1230
|
+
.smeditor *::before,
|
|
1231
|
+
.smeditor *::after { box-sizing: border-box; }
|
|
1232
|
+
.smeditor button,
|
|
1233
|
+
.smeditor input,
|
|
1234
|
+
.smeditor select,
|
|
1235
|
+
.smeditor textarea {
|
|
1236
|
+
margin: 0;
|
|
1237
|
+
font: inherit;
|
|
1238
|
+
color: inherit;
|
|
1239
|
+
letter-spacing: normal;
|
|
1240
|
+
text-transform: none;
|
|
1241
|
+
}
|
|
1242
|
+
.smeditor button { -webkit-tap-highlight-color: transparent; }
|
|
1243
|
+
.smeditor svg { display: block; flex: 0 0 auto; }
|
|
1217
1244
|
.smeditor-file-input { display: none !important; }
|
|
1218
1245
|
.smeditor-mount { width: 100%; }
|
|
@@ -30,6 +30,7 @@ module SMEditor
|
|
|
30
30
|
kit = (options[:kit] || SMEditor.config.default_kit).to_s
|
|
31
31
|
upload_url = options.fetch(:upload_url, smeditor_upload_url)
|
|
32
32
|
include_assets = options.fetch(:include_assets, SMEditor.config.auto_include_assets)
|
|
33
|
+
stylesheet_url = asset_path("smeditor.css")
|
|
33
34
|
|
|
34
35
|
hidden = form.hidden_field(
|
|
35
36
|
method,
|
|
@@ -47,6 +48,7 @@ module SMEditor
|
|
|
47
48
|
smeditor_placeholder: options[:placeholder],
|
|
48
49
|
smeditor_upload_url: upload_url,
|
|
49
50
|
smeditor_label: options[:label],
|
|
51
|
+
smeditor_stylesheet: stylesheet_url,
|
|
50
52
|
}.compact,
|
|
51
53
|
)
|
|
52
54
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smeditor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SMEditor contributors
|
|
@@ -96,7 +96,7 @@ metadata:
|
|
|
96
96
|
source_code_uri: https://github.com/sCruze/smeditor/tree/main/gems/smeditor
|
|
97
97
|
changelog_uri: https://github.com/sCruze/smeditor/blob/main/gems/smeditor/CHANGELOG.md
|
|
98
98
|
bug_tracker_uri: https://github.com/sCruze/smeditor/issues
|
|
99
|
-
documentation_uri: https://rubydoc.info/gems/smeditor/0.2.
|
|
99
|
+
documentation_uri: https://rubydoc.info/gems/smeditor/0.2.2
|
|
100
100
|
rubygems_mfa_required: 'true'
|
|
101
101
|
rdoc_options: []
|
|
102
102
|
require_paths:
|