smeditor 0.2.0 → 0.2.1
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 +8 -0
- data/app/assets/javascripts/smeditor.js +406 -169
- 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: 84ee0bbaa2884f82336e20cf7962934d728d539ab71a68c87c865c09122d1100
|
|
4
|
+
data.tar.gz: 80da9de751d692cb0398e80f9b3017324fbf848873dc6a2e73dfa3c85fe9c8fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a905c9df109f03de095aab6652d2eeedd1d42acffeb505a020872c6ffaf5ad88b4ea3cb7cdf5a518b449528b009c6eb53735ef3d61aa50ad6cc554f19114aeb
|
|
7
|
+
data.tar.gz: eb385c65cbae2a229035f98a66950f160d7d301c475cf9976429eebda3e1cc32faf3acfe3206c3478689f9567d33d78a02554dae6d3fe58f01bd3b7a21dadd9c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog — smeditor
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-09-09
|
|
4
|
+
|
|
5
|
+
- Rails editor now renders inside Shadow DOM so host application CSS cannot alter the SMEditor toolbar or editing surface.
|
|
6
|
+
- Rails toolbar now uses the same SMEditor visual language and control layout as the playground instead of native selects/text buttons.
|
|
7
|
+
- Theme tokens work inside the isolated editor and continue to follow light/dark preference.
|
|
8
|
+
- The self-contained gem still requires no npm, React, esbuild, or jsbundling-rails in the host Rails application.
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
All notable changes to the Rails adapter are recorded here. The format is
|
|
4
12
|
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the gem
|
|
5
13
|
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(); });
|
|
@@ -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.1
|
|
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.1
|
|
100
100
|
rubygems_mfa_required: 'true'
|
|
101
101
|
rdoc_options: []
|
|
102
102
|
require_paths:
|