smartrte-react 0.3.1 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { MediaManager } from "./MediaManager.js";
4
4
  import { LinkEditorPopover } from "./LinkEditorPopover.js";
@@ -12,6 +12,72 @@ import { isShadowModeEnabled, runShadowCommand } from '../adapters/shadowMode.js
12
12
  import { getCoreInlineMarkResult, isCoreInlineMarkEnabled } from '../adapters/inlineMarkCoreExecution.js';
13
13
  import { closestFromTarget, isNode } from '../adapters/domTargets.js';
14
14
  import { ensureStyleSheet } from '../theme.js';
15
+ const iconPaths = {
16
+ undo: _jsxs(_Fragment, { children: [_jsx("path", { d: "M9 7 5 11l4 4" }), _jsx("path", { d: "M5 11h7a5 5 0 0 1 5 5" })] }),
17
+ redo: _jsxs(_Fragment, { children: [_jsx("path", { d: "m15 7 4 4-4 4" }), _jsx("path", { d: "M19 11h-7a5 5 0 0 0-5 5" })] }),
18
+ "align-left": _jsx(_Fragment, { children: _jsx("path", { d: "M4 6h16M4 10h11M4 14h16M4 18h11" }) }),
19
+ "align-center": _jsx(_Fragment, { children: _jsx("path", { d: "M4 6h16M7 10h10M4 14h16M7 18h10" }) }),
20
+ "align-right": _jsx(_Fragment, { children: _jsx("path", { d: "M4 6h16M9 10h11M4 14h16M9 18h11" }) }),
21
+ justify: _jsx(_Fragment, { children: _jsx("path", { d: "M4 6h16M4 10h16M4 14h16M4 18h16" }) }),
22
+ move: _jsx(_Fragment, { children: _jsx("path", { d: "m8 7 4-4 4 4M12 3v18M8 17l4 4 4-4" }) }),
23
+ up: _jsx(_Fragment, { children: _jsx("path", { d: "m7 10 5-5 5 5M12 5v14" }) }),
24
+ down: _jsx(_Fragment, { children: _jsx("path", { d: "m7 14 5 5 5-5M12 19V5" }) }),
25
+ outdent: _jsx(_Fragment, { children: _jsx("path", { d: "M11 6h9M11 10h7M11 14h9M11 18h7M8 9l-3 3 3 3" }) }),
26
+ indent: _jsx(_Fragment, { children: _jsx("path", { d: "M11 6h9M11 10h7M11 14h9M11 18h7M4 9l3 3-3 3" }) }),
27
+ link: _jsxs(_Fragment, { children: [_jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l2-2a5 5 0 0 0-7.07-7.07l-1.15 1.15" }), _jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-2 2a5 5 0 0 0 7.07 7.07l1.14-1.14" })] }),
28
+ quote: _jsx(_Fragment, { children: _jsx("path", { d: "M6 15h4V9H5v6a4 4 0 0 1-1 2.7M15 15h4V9h-5v6a4 4 0 0 1-1 2.7" }) }),
29
+ code: _jsx(_Fragment, { children: _jsx("path", { d: "m8 9-3 3 3 3M16 9l3 3-3 3M14 5l-4 14" }) }),
30
+ checklist: _jsx(_Fragment, { children: _jsx("path", { d: "m4 6 1.5 1.5L8 5M11 6h9M4 12l1.5 1.5L8 11M11 12h9M4 18l1.5 1.5L8 17M11 18h9" }) }),
31
+ bullets: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "5", cy: "6", r: "1" }), _jsx("circle", { cx: "5", cy: "12", r: "1" }), _jsx("circle", { cx: "5", cy: "18", r: "1" }), _jsx("path", { d: "M9 6h11M9 12h11M9 18h11" })] }),
32
+ numbers: _jsx(_Fragment, { children: _jsx("path", { d: "M4 6h1V3.8L4 4.5M4 11h2l-2 2h2M4 17h2l-2 2h2M10 6h10M10 12h10M10 18h10" }) }),
33
+ image: _jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }), _jsx("circle", { cx: "8.5", cy: "9", r: "1.5" }), _jsx("path", { d: "m21 15-5-5L5 20" })] }),
34
+ media: _jsxs(_Fragment, { children: [_jsx("path", { d: "M4 7h6l2 2h8v10H4z" }), _jsx("path", { d: "M8 14h8M12 11v6" })] }),
35
+ table: _jsxs(_Fragment, { children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "1" }), _jsx("path", { d: "M3 10h18M9 4v16M15 4v16" })] }),
36
+ formula: _jsx(_Fragment, { children: _jsx("path", { d: "M18 5H8l5 7-5 7h10" }) }),
37
+ insert: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 8v8M8 12h8" })] }),
38
+ import: _jsx(_Fragment, { children: _jsx("path", { d: "M12 3v12M8 11l4 4 4-4M5 20h14" }) }),
39
+ export: _jsx(_Fragment, { children: _jsx("path", { d: "M12 16V4M8 8l4-4 4 4M5 20h14" }) }),
40
+ omega: _jsx(_Fragment, { children: _jsx("path", { d: "M7 19h4v-2a7 7 0 1 1 2 0v2h4" }) }),
41
+ more: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "5", cy: "12", r: "1" }), _jsx("circle", { cx: "12", cy: "12", r: "1" }), _jsx("circle", { cx: "19", cy: "12", r: "1" })] }),
42
+ chevron: _jsx("path", { d: "m8 10 4 4 4-4" }),
43
+ };
44
+ function ToolbarIcon({ name, size = 16 }) {
45
+ return _jsx("svg", { "aria-hidden": "true", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: iconPaths[name] });
46
+ }
47
+ function ToolbarGroup({ label, priority, children }) {
48
+ return _jsx("div", { className: "srte-toolbar-group", role: "group", "aria-label": label, "data-srte-priority": priority || 1, children: children });
49
+ }
50
+ function ToolbarMenu({ label, icon, active, priority, children }) {
51
+ const menuRef = useRef(null);
52
+ useEffect(() => {
53
+ const closeOnOutsidePointer = (event) => {
54
+ if (menuRef.current?.open && !menuRef.current.contains(event.target))
55
+ menuRef.current.open = false;
56
+ };
57
+ document.addEventListener("pointerdown", closeOnOutsidePointer);
58
+ return () => document.removeEventListener("pointerdown", closeOnOutsidePointer);
59
+ }, []);
60
+ return (_jsxs("details", { ref: menuRef, className: "srte-toolbar-menu", "data-srte-priority": priority, onKeyDown: (event) => {
61
+ if (event.key === "Escape") {
62
+ event.preventDefault();
63
+ menuRef.current?.removeAttribute("open");
64
+ menuRef.current?.querySelector("summary")?.focus();
65
+ return;
66
+ }
67
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp")
68
+ return;
69
+ const items = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]:not([disabled])') || []);
70
+ if (!items.length)
71
+ return;
72
+ event.preventDefault();
73
+ const currentIndex = items.indexOf(document.activeElement);
74
+ const direction = event.key === "ArrowDown" ? 1 : -1;
75
+ items[(currentIndex + direction + items.length) % items.length]?.focus();
76
+ }, children: [_jsxs("summary", { className: `srte-tool-button srte-menu-trigger${active ? " srte-active" : ""}`, "aria-label": label, title: label, children: [_jsx(ToolbarIcon, { name: icon }), _jsx(ToolbarIcon, { name: "chevron", size: 12 })] }), _jsx("div", { className: "srte-menu", role: "menu", "aria-label": label, children: children })] }));
77
+ }
78
+ function MenuItem({ icon, label, active, disabled, onClick, title }) {
79
+ return _jsxs("button", { type: "button", role: "menuitem", className: "srte-menu-item", "aria-label": title || label, "aria-pressed": Boolean(active), "aria-checked": active || undefined, disabled: disabled, title: title || label, onClick: (event) => { onClick(); event.currentTarget.closest("details")?.removeAttribute("open"); }, children: [_jsx(ToolbarIcon, { name: icon }), _jsx("span", { children: label }), active && _jsx("span", { className: "srte-menu-check", "aria-hidden": "true", children: "\u2713" })] });
80
+ }
15
81
  // Initialize PDF.js worker
16
82
  if (typeof window !== 'undefined') {
17
83
  pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
@@ -847,19 +913,28 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
847
913
  const current = getCurrentBlock();
848
914
  return current ? [current] : [];
849
915
  };
850
- const getSelectedListItems = (blocks) => {
851
- const seen = new Set();
852
- const items = [];
853
- blocks.forEach((block) => {
854
- const li = block.tagName.toLowerCase() === "li"
855
- ? block
856
- : block.closest("li");
857
- if (li && editableRef.current?.contains(li) && !seen.has(li)) {
858
- seen.add(li);
859
- items.push(li);
916
+ const resolveSelectedListItems = (range) => {
917
+ const editor = editableRef.current;
918
+ if (!editor)
919
+ return [];
920
+ if (range.collapsed) {
921
+ const element = range.startContainer instanceof HTMLElement
922
+ ? range.startContainer
923
+ : range.startContainer.parentElement;
924
+ const item = element?.closest("li");
925
+ return item && editor.contains(item) ? [item] : [];
926
+ }
927
+ const ownsSelectedContent = (item) => Array.from(item.childNodes).some((node) => {
928
+ if (node instanceof HTMLElement && (node.matches("ul,ol") || node.dataset.srteCheck === "true"))
929
+ return false;
930
+ try {
931
+ return range.intersectsNode(node);
932
+ }
933
+ catch {
934
+ return false;
860
935
  }
861
936
  });
862
- return items;
937
+ return sortInDocumentOrder(Array.from(editor.querySelectorAll("li")).filter(ownsSelectedContent));
863
938
  };
864
939
  const getAlignmentTarget = (block) => {
865
940
  const item = block.tagName === "LI" ? block : block.closest("li");
@@ -1085,6 +1160,49 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1085
1160
  Array.from(list.attributes).forEach((attr) => clone.setAttribute(attr.name, attr.value));
1086
1161
  return clone;
1087
1162
  };
1163
+ const listItemContentSelector = "p,h1,h2,h3,h4,h5,h6,blockquote,pre";
1164
+ const createListItemFromBlock = (block) => {
1165
+ const item = document.createElement("li");
1166
+ item.style.textAlign = block.style.textAlign;
1167
+ item.appendChild(block);
1168
+ return item;
1169
+ };
1170
+ const extractListItemContent = (item) => {
1171
+ item.querySelectorAll(':scope > [data-srte-check]').forEach((control) => control.remove());
1172
+ const output = [];
1173
+ let inlineParagraph = null;
1174
+ const flushInlineParagraph = () => {
1175
+ if (!inlineParagraph)
1176
+ return;
1177
+ if (!inlineParagraph.childNodes.length)
1178
+ inlineParagraph.innerHTML = "<br>";
1179
+ output.push(inlineParagraph);
1180
+ inlineParagraph = null;
1181
+ };
1182
+ Array.from(item.childNodes).forEach((node) => {
1183
+ if (node instanceof HTMLElement && node.matches(listItemContentSelector)) {
1184
+ flushInlineParagraph();
1185
+ output.push(node);
1186
+ return;
1187
+ }
1188
+ if (node instanceof HTMLElement && node.matches("ul,ol")) {
1189
+ flushInlineParagraph();
1190
+ output.push(node);
1191
+ return;
1192
+ }
1193
+ inlineParagraph || (inlineParagraph = document.createElement("p"));
1194
+ inlineParagraph.appendChild(node);
1195
+ });
1196
+ flushInlineParagraph();
1197
+ if (item.style.textAlign) {
1198
+ output.forEach((node) => {
1199
+ if (node instanceof HTMLElement && node.matches(listItemContentSelector) && !node.style.textAlign) {
1200
+ node.style.textAlign = item.style.textAlign;
1201
+ }
1202
+ });
1203
+ }
1204
+ return output;
1205
+ };
1088
1206
  const mergeAdjacentCompatibleLists = (list) => {
1089
1207
  const isCompatible = (candidate) => candidate instanceof HTMLElement &&
1090
1208
  candidate.tagName === list.tagName &&
@@ -1118,6 +1236,40 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1118
1236
  }
1119
1237
  });
1120
1238
  };
1239
+ const syncChecklistItemControl = (item, strikeCompleted) => {
1240
+ const legacyCheckbox = item.querySelector(':scope > input[data-srte-check]');
1241
+ const checked = item.dataset.checked === "true" || Boolean(legacyCheckbox?.checked);
1242
+ const controls = Array.from(item.querySelectorAll(':scope > [data-srte-check]'));
1243
+ let control = controls.find((candidate) => candidate.tagName === "BUTTON");
1244
+ controls.forEach((candidate) => {
1245
+ if (candidate !== control)
1246
+ candidate.remove();
1247
+ });
1248
+ if (!control) {
1249
+ control = document.createElement("button");
1250
+ control.type = "button";
1251
+ control.dataset.srteCheck = "true";
1252
+ control.contentEditable = "false";
1253
+ control.tabIndex = -1;
1254
+ control.style.cssText = "border:0;padding:0;background:transparent;color:inherit;font:inherit;cursor:pointer";
1255
+ item.prepend(control);
1256
+ }
1257
+ item.dataset.checked = checked ? "true" : "false";
1258
+ control.dataset.checked = checked ? "true" : "false";
1259
+ control.setAttribute("aria-pressed", checked ? "true" : "false");
1260
+ control.setAttribute("aria-label", checked ? "Mark incomplete" : "Mark complete");
1261
+ control.textContent = "";
1262
+ item.style.textDecoration = strikeCompleted && checked ? "line-through" : "";
1263
+ };
1264
+ const syncChecklistControls = (root) => {
1265
+ root.querySelectorAll('ul[data-srte-checklist="true"]').forEach((list) => {
1266
+ const strikeCompleted = list.dataset.srteChecklistStrike === "true";
1267
+ Array.from(list.children).forEach((item) => {
1268
+ if (item instanceof HTMLElement && item.tagName === "LI")
1269
+ syncChecklistItemControl(item, strikeCompleted);
1270
+ });
1271
+ });
1272
+ };
1121
1273
  const decorateChecklist = (list, strikeCompleted) => {
1122
1274
  list.dataset.srteChecklist = "true";
1123
1275
  list.dataset.srteChecklistStrike = strikeCompleted ? "true" : "false";
@@ -1126,20 +1278,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1126
1278
  Array.from(list.children).forEach((item) => {
1127
1279
  if (!(item instanceof HTMLElement) || item.tagName !== "LI")
1128
1280
  return;
1129
- const legacyCheckbox = item.querySelector(':scope > input[data-srte-check]');
1130
- const checked = item.dataset.checked === "true" || Boolean(legacyCheckbox?.checked);
1131
- item.querySelectorAll(':scope > [data-srte-check]').forEach((control) => control.remove());
1132
- item.dataset.checked = checked ? "true" : "false";
1133
- const control = document.createElement("button");
1134
- control.type = "button";
1135
- control.dataset.srteCheck = "true";
1136
- control.contentEditable = "false";
1137
- control.tabIndex = -1;
1138
- control.setAttribute("aria-label", checked ? "Mark incomplete" : "Mark complete");
1139
- control.textContent = checked ? "☑" : "☐";
1140
- control.style.cssText = "margin-inline:-1.45em .45em;border:0;padding:0;background:transparent;color:inherit;font:inherit;cursor:pointer";
1141
- item.prepend(control);
1142
- item.style.textDecoration = strikeCompleted && checked ? "line-through" : "";
1281
+ syncChecklistItemControl(item, strikeCompleted);
1143
1282
  });
1144
1283
  return mergeAdjacentCompatibleLists(list);
1145
1284
  };
@@ -1167,9 +1306,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1167
1306
  const parent = list.parentElement;
1168
1307
  if (!parent)
1169
1308
  return;
1170
- const paragraph = document.createElement("p");
1171
- paragraph.innerHTML = li.innerHTML || "<br>";
1172
- paragraph.style.textAlign = li.style.textAlign;
1309
+ const content = extractListItemContent(li);
1173
1310
  const beforeList = cloneListShell(list);
1174
1311
  const afterList = cloneListShell(list);
1175
1312
  while (list.firstChild && list.firstChild !== li) {
@@ -1180,13 +1317,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1180
1317
  }
1181
1318
  if (beforeList.childNodes.length)
1182
1319
  parent.insertBefore(beforeList, list);
1183
- parent.insertBefore(paragraph, list);
1320
+ content.forEach((node) => parent.insertBefore(node, list));
1184
1321
  if (afterList.childNodes.length)
1185
1322
  parent.insertBefore(afterList, list);
1186
1323
  li.remove();
1187
1324
  if (!list.querySelector("li"))
1188
1325
  list.remove();
1189
- focusElementEnd(paragraph);
1326
+ const focusTarget = [...content].reverse().find((node) => node instanceof HTMLElement);
1327
+ focusElementEnd(focusTarget || parent);
1190
1328
  };
1191
1329
  const convertListTag = (list, listTag) => {
1192
1330
  const replacement = cloneListShell(list, listTag);
@@ -1195,69 +1333,149 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1195
1333
  const li = replacement.querySelector("li");
1196
1334
  focusElementEnd(li || replacement);
1197
1335
  };
1198
- const getOrCreateNestedList = (li, listTag) => {
1199
- let nested = Array.from(li.children).find((child) => {
1200
- const tag = child.tagName.toLowerCase();
1201
- return tag === "ul" || tag === "ol";
1336
+ const getOrCreateNestedList = (item, source) => {
1337
+ const compatible = Array.from(item.children).find((child) => child instanceof HTMLElement &&
1338
+ ["UL", "OL"].includes(child.tagName) &&
1339
+ child.tagName === source.tagName &&
1340
+ child.style.listStyleType === source.style.listStyleType &&
1341
+ child.dataset.srteChecklist === source.dataset.srteChecklist &&
1342
+ child.dataset.srteChecklistStrike === source.dataset.srteChecklistStrike);
1343
+ if (compatible)
1344
+ return compatible;
1345
+ const nested = cloneListShell(source);
1346
+ item.appendChild(nested);
1347
+ return nested;
1348
+ };
1349
+ const normalizeListItemStructure = (item) => {
1350
+ if (!item || item.tagName !== "LI")
1351
+ return;
1352
+ Array.from(item.children)
1353
+ .filter((child) => child instanceof HTMLElement && ["UL", "OL"].includes(child.tagName))
1354
+ .forEach((list) => {
1355
+ if (!list.querySelector(":scope > li"))
1356
+ list.remove();
1357
+ else
1358
+ item.appendChild(list);
1202
1359
  });
1203
- if (nested && nested.tagName.toLowerCase() !== listTag) {
1204
- const replacement = cloneListShell(nested, listTag);
1205
- replacement.innerHTML = nested.innerHTML;
1206
- nested.parentElement?.replaceChild(replacement, nested);
1207
- nested = replacement;
1360
+ };
1361
+ const captureRangeForListMove = () => {
1362
+ const range = getSelectionRangeInEditor();
1363
+ return range ? {
1364
+ startContainer: range.startContainer,
1365
+ startOffset: range.startOffset,
1366
+ endContainer: range.endContainer,
1367
+ endOffset: range.endOffset,
1368
+ } : null;
1369
+ };
1370
+ const restoreRangeAfterListMove = (snapshot) => {
1371
+ const editor = editableRef.current;
1372
+ if (!snapshot || !editor || !editor.contains(snapshot.startContainer) || !editor.contains(snapshot.endContainer))
1373
+ return false;
1374
+ try {
1375
+ const range = document.createRange();
1376
+ range.setStart(snapshot.startContainer, snapshot.startOffset);
1377
+ range.setEnd(snapshot.endContainer, snapshot.endOffset);
1378
+ safeSelectRange(range);
1379
+ savedRangeRef.current = range.cloneRange();
1380
+ return true;
1208
1381
  }
1209
- if (!nested) {
1210
- nested = document.createElement(listTag);
1211
- li.appendChild(nested);
1382
+ catch {
1383
+ return false;
1212
1384
  }
1213
- return nested;
1214
1385
  };
1215
- const nestSelectedListItems = (items, listTag) => {
1386
+ const nestSelectedListItems = (items) => {
1216
1387
  if (items.length === 0)
1217
1388
  return false;
1389
+ const savedRange = captureRangeForListMove();
1390
+ const selected = new Set(items);
1391
+ const sourceLists = Array.from(new Set(items.map((item) => item.parentElement)));
1218
1392
  let changed = false;
1219
- let lastTargetList = null;
1220
- items.forEach((li) => {
1221
- const previous = li.previousElementSibling;
1222
- if (!previous || previous.tagName.toLowerCase() !== "li")
1393
+ let lastMoved = null;
1394
+ sourceLists.forEach((source) => {
1395
+ if (!source || !["UL", "OL"].includes(source.tagName))
1223
1396
  return;
1224
- const targetList = getOrCreateNestedList(previous, listTag);
1225
- targetList.appendChild(li);
1226
- lastTargetList = targetList;
1227
- changed = true;
1397
+ const sourceItems = Array.from(source.children).filter((child) => child instanceof HTMLElement && child.tagName === "LI");
1398
+ const runs = [];
1399
+ sourceItems.forEach((item) => {
1400
+ if (!selected.has(item))
1401
+ return;
1402
+ const currentRun = runs[runs.length - 1];
1403
+ const previousIndex = currentRun ? sourceItems.indexOf(currentRun[currentRun.length - 1]) : -2;
1404
+ const itemIndex = sourceItems.indexOf(item);
1405
+ if (!currentRun || itemIndex !== previousIndex + 1)
1406
+ runs.push([item]);
1407
+ else
1408
+ currentRun.push(item);
1409
+ });
1410
+ runs.forEach((run) => {
1411
+ const previous = run[0].previousElementSibling;
1412
+ if (!previous || previous.tagName !== "LI" || selected.has(previous))
1413
+ return;
1414
+ const target = getOrCreateNestedList(previous, source);
1415
+ run.forEach((item) => {
1416
+ target.appendChild(item);
1417
+ lastMoved = item;
1418
+ changed = true;
1419
+ });
1420
+ normalizeListItemStructure(previous);
1421
+ });
1228
1422
  });
1229
- if (changed) {
1230
- const lastItem = lastTargetList?.lastElementChild;
1231
- if (lastItem)
1232
- focusElementEnd(lastItem);
1233
- }
1423
+ if (changed && !restoreRangeAfterListMove(savedRange) && lastMoved)
1424
+ focusElementEnd(lastMoved);
1234
1425
  return changed;
1235
1426
  };
1236
- const nestListSelection = () => {
1237
- const editor = editableRef.current;
1238
- if (!editor)
1239
- return;
1240
- if (!restoreSavedSelection())
1241
- safeSelectRange(getSelectionRangeInEditor());
1242
- const range = getSelectionRangeInEditor();
1243
- if (!range || range.collapsed)
1244
- return;
1245
- const items = sortInDocumentOrder(getSelectedListItems(getSelectedBlocks(range)));
1246
- const firstList = items[0]?.parentElement;
1247
- if (items.length === 0 ||
1248
- !firstList ||
1249
- !["ul", "ol"].includes(firstList.tagName.toLowerCase())) {
1250
- return;
1251
- }
1252
- const listTag = firstList.tagName.toLowerCase();
1253
- const directItems = items.filter((item) => item.parentElement === firstList);
1254
- if (directItems.length === 0 || !directItems[0].previousElementSibling)
1255
- return;
1427
+ const outdentSelectedListItems = (items) => {
1428
+ if (items.length === 0)
1429
+ return false;
1430
+ const savedRange = captureRangeForListMove();
1431
+ const sourceLists = Array.from(new Set(items.map((item) => item.parentElement)));
1432
+ const selected = new Set(items);
1433
+ let changed = false;
1434
+ let lastMoved = null;
1435
+ sourceLists.forEach((source) => {
1436
+ const parentItem = source?.parentElement;
1437
+ const outerList = parentItem?.parentElement;
1438
+ if (!source || parentItem?.tagName !== "LI" || !outerList || !["UL", "OL"].includes(outerList.tagName))
1439
+ return;
1440
+ const moving = Array.from(source.children).filter((child) => child instanceof HTMLElement && child.tagName === "LI" && selected.has(child));
1441
+ let insertionPoint = parentItem.nextSibling;
1442
+ moving.forEach((item) => {
1443
+ outerList.insertBefore(item, insertionPoint);
1444
+ lastMoved = item;
1445
+ changed = true;
1446
+ });
1447
+ if (!source.querySelector(":scope > li"))
1448
+ source.remove();
1449
+ normalizeListItemStructure(parentItem);
1450
+ });
1451
+ if (changed && !restoreRangeAfterListMove(savedRange) && lastMoved)
1452
+ focusElementEnd(lastMoved);
1453
+ return changed;
1454
+ };
1455
+ const canIndentListItems = (items) => {
1456
+ const selected = new Set(items);
1457
+ return items.some((item) => {
1458
+ const previous = item.previousElementSibling;
1459
+ return previous instanceof HTMLElement && previous.tagName === "LI" && !selected.has(previous);
1460
+ });
1461
+ };
1462
+ const canOutdentListItems = (items) => items.some((item) => {
1463
+ const list = item.parentElement;
1464
+ return list?.parentElement?.tagName === "LI" && ["UL", "OL"].includes(list.parentElement.parentElement?.tagName || "");
1465
+ });
1466
+ const applyListDepthChange = (items, direction) => {
1467
+ const enabled = direction === "indent" ? canIndentListItems(items) : canOutdentListItems(items);
1468
+ if (!enabled)
1469
+ return false;
1256
1470
  pushEditorHistory();
1257
- if (nestSelectedListItems(directItems, listTag)) {
1471
+ const changed = direction === "indent"
1472
+ ? nestSelectedListItems(items)
1473
+ : outdentSelectedListItems(items);
1474
+ if (changed) {
1258
1475
  handleInput();
1259
1476
  requestAnimationFrame(updateActiveState);
1260
1477
  }
1478
+ return changed;
1261
1479
  };
1262
1480
  const restyleSelectedListItems = (items, listTag, styleType) => {
1263
1481
  const editor = editableRef.current;
@@ -1303,6 +1521,66 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1303
1521
  });
1304
1522
  return lastSelected;
1305
1523
  };
1524
+ const getDirectSelectionCell = (range) => {
1525
+ const startCell = getClosestCell(range.startContainer);
1526
+ const endCell = getClosestCell(range.endContainer);
1527
+ if (!startCell || startCell !== endCell)
1528
+ return null;
1529
+ const contentBlock = (node) => {
1530
+ const element = node instanceof HTMLElement ? node : node.parentElement;
1531
+ const block = element?.closest(listItemContentSelector);
1532
+ return block && startCell.contains(block) ? block : null;
1533
+ };
1534
+ return contentBlock(range.startContainer) || contentBlock(range.endContainer)
1535
+ ? null
1536
+ : startCell;
1537
+ };
1538
+ const convertDirectTableCellSelectionToList = (range, listTag) => {
1539
+ const cell = getDirectSelectionCell(range);
1540
+ if (!cell || range.collapsed)
1541
+ return null;
1542
+ const fragment = range.extractContents();
1543
+ const parts = [document.createDocumentFragment()];
1544
+ Array.from(fragment.childNodes).forEach((node) => {
1545
+ if (node instanceof HTMLBRElement)
1546
+ parts.push(document.createDocumentFragment());
1547
+ else
1548
+ parts[parts.length - 1].appendChild(node);
1549
+ });
1550
+ const nonEmptyParts = parts.filter((part) => part.textContent?.trim() || part.childNodes.length > 0);
1551
+ if (nonEmptyParts.length === 0) {
1552
+ range.insertNode(fragment);
1553
+ return null;
1554
+ }
1555
+ const list = document.createElement(listTag);
1556
+ nonEmptyParts.forEach((part) => {
1557
+ const paragraph = document.createElement("p");
1558
+ paragraph.appendChild(part);
1559
+ if (!paragraph.innerHTML.trim())
1560
+ paragraph.innerHTML = "<br>";
1561
+ list.appendChild(createListItemFromBlock(paragraph));
1562
+ });
1563
+ range.insertNode(list);
1564
+ const lastItem = list.lastElementChild;
1565
+ if (lastItem)
1566
+ focusElementEnd(lastItem);
1567
+ return list;
1568
+ };
1569
+ const convertDirectTableCellContentsToList = (cell, listTag) => {
1570
+ const range = document.createRange();
1571
+ range.selectNodeContents(cell);
1572
+ if (!cell.textContent?.trim() && !cell.querySelector("img,br")) {
1573
+ const list = document.createElement(listTag);
1574
+ const paragraph = document.createElement("p");
1575
+ paragraph.innerHTML = "<br>";
1576
+ const item = createListItemFromBlock(paragraph);
1577
+ list.appendChild(item);
1578
+ cell.replaceChildren(list);
1579
+ focusElementEnd(item);
1580
+ return list;
1581
+ }
1582
+ return convertDirectTableCellSelectionToList(range, listTag);
1583
+ };
1306
1584
  const applyListStyle = (value) => {
1307
1585
  const listTag = value.startsWith("ordered:") ? "ol" : "ul";
1308
1586
  const styleType = value.replace(/^(ordered|bullet):/, "");
@@ -1311,8 +1589,29 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1311
1589
  const range = getSelectionRangeInEditor();
1312
1590
  if (!range)
1313
1591
  return;
1592
+ const currentBlock = range.collapsed ? getCurrentBlock() : null;
1593
+ if (currentBlock && ["TD", "TH"].includes(currentBlock.tagName)) {
1594
+ pushEditorHistory();
1595
+ const list = convertDirectTableCellContentsToList(currentBlock, listTag);
1596
+ if (list) {
1597
+ list.style.listStyleType = styleType;
1598
+ handleInput();
1599
+ requestAnimationFrame(updateActiveState);
1600
+ }
1601
+ return;
1602
+ }
1603
+ if (!range.collapsed && getDirectSelectionCell(range)) {
1604
+ pushEditorHistory();
1605
+ const list = convertDirectTableCellSelectionToList(range, listTag);
1606
+ if (list) {
1607
+ list.style.listStyleType = styleType;
1608
+ handleInput();
1609
+ requestAnimationFrame(updateActiveState);
1610
+ }
1611
+ return;
1612
+ }
1314
1613
  const selectedBlocks = getSelectedBlocks(range);
1315
- const selectedItems = getSelectedListItems(selectedBlocks);
1614
+ const selectedItems = resolveSelectedListItems(range);
1316
1615
  const selectedPlainBlocks = selectedBlocks.filter((block) => !block.closest("ul,ol"));
1317
1616
  if (selectedItems.length > 0) {
1318
1617
  pushEditorHistory();
@@ -1325,7 +1624,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1325
1624
  return;
1326
1625
  }
1327
1626
  const lists = new Set();
1328
- getSelectedListItems(getSelectedBlocks(range)).forEach((item) => {
1627
+ resolveSelectedListItems(range).forEach((item) => {
1329
1628
  const list = item.parentElement;
1330
1629
  if (list &&
1331
1630
  ["ul", "ol"].includes(list.tagName.toLowerCase())) {
@@ -1412,7 +1711,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1412
1711
  const range = getSelectionRangeInEditor();
1413
1712
  if (!range)
1414
1713
  return;
1415
- const selectedItems = getSelectedListItems(getSelectedBlocks(range));
1714
+ const selectedItems = resolveSelectedListItems(range);
1416
1715
  const selectedChecklists = selectedItems.filter((item) => item.parentElement?.dataset.srteChecklist === "true");
1417
1716
  if (toggleOff && selectedItems.length > 0 && selectedChecklists.length === selectedItems.length) {
1418
1717
  pushEditorHistory();
@@ -1460,7 +1759,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1460
1759
  const selected = blocks.filter((block) => {
1461
1760
  if (!editor.contains(block))
1462
1761
  return false;
1463
- if (block.tagName.toLowerCase() === "li")
1762
+ if (["li", "td", "th"].includes(block.tagName.toLowerCase()))
1464
1763
  return false;
1465
1764
  if (block.closest("ul,ol"))
1466
1765
  return false;
@@ -1488,11 +1787,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1488
1787
  list.style.listStyleType = styleType;
1489
1788
  parent.insertBefore(list, group[0]);
1490
1789
  group.forEach((block) => {
1491
- const li = document.createElement("li");
1492
- li.innerHTML = block.innerHTML || "<br>";
1493
- li.style.textAlign = block.style.textAlign;
1790
+ const li = createListItemFromBlock(block);
1494
1791
  list.appendChild(li);
1495
- block.remove();
1496
1792
  lastLi = li;
1497
1793
  });
1498
1794
  mergeAdjacentCompatibleLists(list);
@@ -1501,42 +1797,6 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1501
1797
  focusElementEnd(lastLi);
1502
1798
  return true;
1503
1799
  };
1504
- const convertTableCellSelectionToList = (range, listTag) => {
1505
- if (range.collapsed)
1506
- return false;
1507
- const startCell = getClosestCell(range.startContainer);
1508
- const endCell = getClosestCell(range.endContainer);
1509
- if (!startCell || startCell !== endCell)
1510
- return false;
1511
- const fragment = range.extractContents();
1512
- const parts = [document.createDocumentFragment()];
1513
- Array.from(fragment.childNodes).forEach((node) => {
1514
- if (node instanceof HTMLBRElement) {
1515
- parts.push(document.createDocumentFragment());
1516
- }
1517
- else {
1518
- parts[parts.length - 1].appendChild(node);
1519
- }
1520
- });
1521
- const nonEmptyParts = parts.filter((part) => part.textContent?.trim() || part.childNodes.length > 0);
1522
- if (nonEmptyParts.length === 0) {
1523
- range.insertNode(fragment);
1524
- return false;
1525
- }
1526
- const list = document.createElement(listTag);
1527
- nonEmptyParts.forEach((part) => {
1528
- const item = document.createElement("li");
1529
- item.appendChild(part);
1530
- if (!item.innerHTML.trim())
1531
- item.innerHTML = "<br>";
1532
- list.appendChild(item);
1533
- });
1534
- range.insertNode(list);
1535
- const lastItem = list.lastElementChild;
1536
- if (lastItem)
1537
- focusElementEnd(lastItem);
1538
- return true;
1539
- };
1540
1800
  const convertRootLineSelectionToList = (range, listTag) => {
1541
1801
  const editor = editableRef.current;
1542
1802
  if (!editor || range.collapsed)
@@ -1644,11 +1904,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1644
1904
  const isSelected = selected.has(child);
1645
1905
  if (isSelected && toggleOff) {
1646
1906
  flushPendingList();
1647
- const paragraph = document.createElement("p");
1648
- paragraph.innerHTML = child.innerHTML || "<br>";
1649
- paragraph.style.textAlign = child.style.textAlign;
1650
- parent.insertBefore(paragraph, list);
1651
- lastTarget = paragraph;
1907
+ const content = extractListItemContent(child);
1908
+ content.forEach((node) => parent.insertBefore(node, list));
1909
+ lastTarget = [...content].reverse().find((node) => node instanceof HTMLElement) || parent;
1652
1910
  child.remove();
1653
1911
  changed = true;
1654
1912
  return;
@@ -1686,8 +1944,17 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1686
1944
  };
1687
1945
  const range = getSelectionRangeInEditor();
1688
1946
  if (range && !range.collapsed) {
1947
+ if (getDirectSelectionCell(range)) {
1948
+ pushEditorHistory();
1949
+ if (convertDirectTableCellSelectionToList(range, listTag)) {
1950
+ setListActiveState(true);
1951
+ handleInput();
1952
+ requestAnimationFrame(updateActiveState);
1953
+ }
1954
+ return;
1955
+ }
1689
1956
  const blocks = getSelectedBlocks(range);
1690
- const selectedListItems = getSelectedListItems(blocks);
1957
+ const selectedListItems = resolveSelectedListItems(range);
1691
1958
  if (selectedListItems.length > 0) {
1692
1959
  pushEditorHistory();
1693
1960
  if (transformSelectedListItems(selectedListItems, listTag)) {
@@ -1714,12 +1981,6 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1714
1981
  requestAnimationFrame(updateActiveState);
1715
1982
  return;
1716
1983
  }
1717
- if (convertTableCellSelectionToList(range, listTag)) {
1718
- setListActiveState(true);
1719
- handleInput();
1720
- requestAnimationFrame(updateActiveState);
1721
- return;
1722
- }
1723
1984
  }
1724
1985
  }
1725
1986
  const block = getCurrentBlock();
@@ -1749,12 +2010,24 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1749
2010
  requestAnimationFrame(updateActiveState);
1750
2011
  return;
1751
2012
  }
2013
+ if (["TD", "TH"].includes(block.tagName)) {
2014
+ pushEditorHistory();
2015
+ if (convertDirectTableCellContentsToList(block, listTag)) {
2016
+ setListActiveState(true);
2017
+ handleInput();
2018
+ requestAnimationFrame(updateActiveState);
2019
+ }
2020
+ return;
2021
+ }
1752
2022
  pushEditorHistory();
2023
+ const parent = block.parentElement;
2024
+ if (!parent)
2025
+ return;
2026
+ const nextSibling = block.nextSibling;
1753
2027
  const list = document.createElement(listTag);
1754
- const li = document.createElement("li");
1755
- li.innerHTML = block.innerHTML || "<br>";
2028
+ const li = createListItemFromBlock(block);
1756
2029
  list.appendChild(li);
1757
- block.parentElement?.replaceChild(list, block);
2030
+ parent.insertBefore(list, nextSibling);
1758
2031
  focusElementEnd(li);
1759
2032
  setListActiveState(true);
1760
2033
  handleInput();
@@ -1831,7 +2104,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1831
2104
  }
1832
2105
  return merged;
1833
2106
  };
1834
- const selectedItems = new Set(getSelectedListItems(selected));
2107
+ const selectedItems = new Set(resolveSelectedListItems(range));
1835
2108
  const sourceLists = Array.from(new Set(Array.from(selectedItems, (item) => item.parentElement)));
1836
2109
  let lastWrapped = null;
1837
2110
  sourceLists.forEach((source) => {
@@ -1942,7 +2215,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1942
2215
  : getSelectedBlocks(range)).filter((block) => editor.contains(block));
1943
2216
  if (blocks.length === 0)
1944
2217
  return;
1945
- const listItems = getSelectedListItems(blocks);
2218
+ const listItems = resolveSelectedListItems(range);
1946
2219
  const plainBlocks = blocks.filter((block) => !block.closest("ul,ol") && !block.closest("table"));
1947
2220
  const getItemCode = (item) => item.querySelector(":scope > pre[data-srte-list-code]");
1948
2221
  const allTargetsActive = listItems.every((item) => Boolean(getItemCode(item))) &&
@@ -2576,15 +2849,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2576
2849
  const host = editableRef.current;
2577
2850
  if (!host)
2578
2851
  return;
2579
- host.focus();
2852
+ restoreSavedSelection();
2580
2853
  let sel = window.getSelection();
2581
- let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
2854
+ let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0).cloneRange() : null;
2582
2855
  if (!range || !host.contains(range.commonAncestorContainer)) {
2583
2856
  range = document.createRange();
2584
2857
  range.selectNodeContents(host);
2585
2858
  range.collapse(false);
2586
2859
  safeSelectRange(range);
2587
2860
  }
2861
+ pushEditorHistory();
2588
2862
  const span = document.createElement("span");
2589
2863
  span.setAttribute("data-formula", tex);
2590
2864
  try {
@@ -3840,6 +4114,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3840
4114
  ensureCaretBoundaryParagraphs(el);
3841
4115
  // Add resize handles to tables
3842
4116
  addTableResizeHandles();
4117
+ // Native Enter creates new list items; keep checklist controls in sync.
4118
+ syncChecklistControls(el);
3843
4119
  if (!onChange)
3844
4120
  return;
3845
4121
  const html = el.innerHTML;
@@ -4692,22 +4968,6 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4692
4968
  }
4693
4969
  return getTopLevelMovableElement();
4694
4970
  };
4695
- const outdentListItem = (item) => {
4696
- const list = item.parentElement;
4697
- const parentItem = list?.parentElement;
4698
- const outerList = parentItem?.parentElement;
4699
- if (!list ||
4700
- !parentItem ||
4701
- parentItem.tagName.toLowerCase() !== "li" ||
4702
- !outerList ||
4703
- !["ul", "ol"].includes(outerList.tagName.toLowerCase())) {
4704
- return false;
4705
- }
4706
- outerList.insertBefore(item, parentItem.nextSibling);
4707
- if (!list.querySelector("li"))
4708
- list.remove();
4709
- return true;
4710
- };
4711
4971
  const elementSibling = (element, direction) => {
4712
4972
  let sibling = direction === "previous" ? element.previousSibling : element.nextSibling;
4713
4973
  while (sibling &&
@@ -4766,14 +5026,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4766
5026
  return true;
4767
5027
  };
4768
5028
  const moveCurrentElement = (direction) => {
5029
+ restoreSavedSelection();
4769
5030
  const editor = editableRef.current;
5031
+ const range = getSelectionRangeInEditor();
5032
+ const selectedListItems = range ? resolveSelectedListItems(range) : [];
5033
+ if (selectedListItems.length > 0 && (direction === "left" || direction === "right")) {
5034
+ applyListDepthChange(selectedListItems, direction === "right" ? "indent" : "outdent");
5035
+ return;
5036
+ }
4770
5037
  if (moveSelectedBlocks(direction))
4771
5038
  return;
4772
5039
  let target = getMoveTarget();
4773
5040
  if (!editor || !target)
4774
5041
  return;
4775
- pushEditorHistory();
5042
+ let historyPushed = false;
4776
5043
  if (target.tagName === "TD" || target.tagName === "TH") {
5044
+ pushEditorHistory();
5045
+ historyPushed = true;
4777
5046
  const cell = target;
4778
5047
  const paragraph = document.createElement("p");
4779
5048
  while (cell.firstChild)
@@ -4785,30 +5054,58 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4785
5054
  const list = target.parentElement;
4786
5055
  if (!list)
4787
5056
  return;
5057
+ let changed = false;
4788
5058
  if (direction === "up") {
4789
5059
  const previous = elementSibling(target, "previous");
4790
- if (previous?.nodeName === "LI")
4791
- list.insertBefore(target, previous);
5060
+ if (previous?.nodeName !== "LI")
5061
+ return;
5062
+ pushEditorHistory();
5063
+ list.insertBefore(target, previous);
5064
+ changed = true;
4792
5065
  }
4793
5066
  else if (direction === "down") {
4794
5067
  const next = elementSibling(target, "next");
4795
- if (next?.nodeName === "LI")
4796
- list.insertBefore(next, target);
5068
+ if (next?.nodeName !== "LI")
5069
+ return;
5070
+ pushEditorHistory();
5071
+ list.insertBefore(next, target);
5072
+ changed = true;
4797
5073
  }
4798
5074
  else if (direction === "right") {
4799
- nestSelectedListItems([target], list.tagName.toLowerCase());
5075
+ if (!canIndentListItems([target]))
5076
+ return;
5077
+ pushEditorHistory();
5078
+ changed = nestSelectedListItems([target]);
4800
5079
  }
4801
5080
  else {
4802
- outdentListItem(target);
5081
+ if (!canOutdentListItems([target]))
5082
+ return;
5083
+ pushEditorHistory();
5084
+ changed = outdentSelectedListItems([target]);
4803
5085
  }
5086
+ if (!changed)
5087
+ return;
5088
+ if (direction === "up" || direction === "down")
5089
+ focusElementEnd(target);
5090
+ handleInput();
5091
+ requestAnimationFrame(updateActiveState);
5092
+ return;
4804
5093
  }
4805
5094
  else if (direction === "up") {
4806
5095
  const previous = elementSibling(target, "previous");
5096
+ if (!previous)
5097
+ return;
5098
+ if (!historyPushed)
5099
+ pushEditorHistory();
4807
5100
  if (previous)
4808
5101
  target.parentElement?.insertBefore(target, previous);
4809
5102
  }
4810
5103
  else if (direction === "down") {
4811
5104
  const next = elementSibling(target, "next");
5105
+ if (!next)
5106
+ return;
5107
+ if (!historyPushed)
5108
+ pushEditorHistory();
4812
5109
  if (next)
4813
5110
  target.parentElement?.insertBefore(next, target);
4814
5111
  }
@@ -4817,6 +5114,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4817
5114
  const nextMargin = direction === "right"
4818
5115
  ? Math.min(current + 24, 240)
4819
5116
  : Math.max(current - 24, 0);
5117
+ if (nextMargin === current)
5118
+ return;
5119
+ if (!historyPushed)
5120
+ pushEditorHistory();
4820
5121
  target.style.marginLeft = nextMargin ? `${nextMargin}px` : "";
4821
5122
  }
4822
5123
  focusElementEnd(target);
@@ -4851,14 +5152,13 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4851
5152
  const activeButtonStyle = (active = false, extra = {}) => ({
4852
5153
  height: 32,
4853
5154
  minWidth: 32,
4854
- padding: "0 8px",
4855
- border: active
4856
- ? "2px solid var(--srte-accent)"
4857
- : "1px solid var(--srte-input-border)",
4858
- borderRadius: 6,
4859
- background: active ? "var(--srte-accent-bg)" : "var(--srte-input-bg)",
4860
- color: "var(--srte-input-text)",
4861
- boxShadow: active ? "inset 0 0 0 1px var(--srte-accent)" : "none",
5155
+ padding: "0 7px",
5156
+ border: active ? "1px solid color-mix(in srgb, var(--srte-primary) 35%, transparent)" : "1px solid transparent",
5157
+ borderRadius: 8,
5158
+ background: active ? "var(--srte-accent-bg)" : "transparent",
5159
+ color: active ? "var(--srte-primary)" : "var(--srte-foreground)",
5160
+ boxShadow: "none",
5161
+ cursor: "pointer",
4862
5162
  ...extra,
4863
5163
  });
4864
5164
  const preserveToolbarMouseDown = (event) => {
@@ -4872,16 +5172,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4872
5172
  const editorClass = `srte-editor${theme === 'dark' ? ' srte-dark' : ''}${className ? ' ' + className : ''}`;
4873
5173
  return (_jsxs("div", { className: editorClass, style: {
4874
5174
  border: "1px solid var(--srte-border)",
4875
- borderRadius: 6,
5175
+ borderRadius: "var(--srte-radius)",
4876
5176
  width: "100%",
4877
5177
  maxWidth: "100vw",
4878
5178
  overflow: "visible",
4879
5179
  display: "flex",
4880
5180
  flexDirection: "column",
4881
- background: "var(--srte-bg)",
4882
- color: "var(--srte-text)",
5181
+ background: "var(--srte-background)",
5182
+ color: "var(--srte-foreground)",
4883
5183
  boxSizing: "border-box"
4884
- }, children: [_jsxs("div", { "aria-disabled": readOnly, onMouseDown: (event) => {
5184
+ }, children: [_jsxs("div", { className: "srte-toolbar", role: "toolbar", "aria-label": "Text formatting", "aria-disabled": readOnly, onMouseDown: (event) => {
4885
5185
  if (readOnly) {
4886
5186
  event.preventDefault();
4887
5187
  event.stopPropagation();
@@ -4899,16 +5199,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4899
5199
  event.preventDefault();
4900
5200
  event.stopPropagation();
4901
5201
  }, style: {
4902
- display: "flex",
4903
- flexWrap: "wrap",
4904
5202
  maxWidth: "100%",
4905
- gap: 8,
4906
- padding: 8,
4907
- borderBottom: "1px solid var(--srte-border-light)",
4908
- background: "var(--srte-toolbar-bg)",
4909
- position: "sticky",
4910
- top: 0,
4911
- zIndex: 1,
4912
5203
  opacity: readOnly ? 0.55 : 1,
4913
5204
  pointerEvents: readOnly ? "none" : "auto",
4914
5205
  userSelect: readOnly ? "none" : undefined,
@@ -4950,7 +5241,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4950
5241
  } }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
4951
5242
  importTextFile(e.currentTarget.files, "md");
4952
5243
  e.currentTarget.value = "";
4953
- } }), _jsxs("select", { value: currentBlockType, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (e) => {
5244
+ } }), _jsxs(ToolbarGroup, { label: "History", children: [_jsx("button", { type: "button", className: "srte-tool-button", "aria-label": "Undo", title: "Undo", onClick: () => exec("undo"), children: _jsx(ToolbarIcon, { name: "undo" }) }), _jsx("button", { type: "button", className: "srte-tool-button", "aria-label": "Redo", title: "Redo", onClick: () => exec("redo"), children: _jsx(ToolbarIcon, { name: "redo" }) })] }), _jsxs("select", { value: currentBlockType, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (e) => {
4954
5245
  const val = e.target.value;
4955
5246
  if (val === "p")
4956
5247
  applyFormatBlock("<p>");
@@ -4973,16 +5264,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4973
5264
  borderRadius: 6,
4974
5265
  background: "var(--srte-input-bg)",
4975
5266
  color: "var(--srte-input-text)",
4976
- }, children: [_jsx("option", { value: "mixed", disabled: true, children: "Mixed" }), _jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" }), _jsx("option", { value: "h4", children: "Heading 4" }), _jsx("option", { value: "h5", children: "Heading 5" }), _jsx("option", { value: "h6", children: "Heading 6" })] }), [
4977
- ["left", "Left", "Align left"],
4978
- ["center", "Center", "Align center"],
4979
- ["right", "Right", "Align right"],
4980
- ["justify", "Justify", "Justify"],
4981
- ].map(([alignment, label, title]) => (_jsx("button", { type: "button", title: title, "aria-label": title, "aria-pressed": currentAlignment === alignment, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onClick: () => applyTextAlignment(alignment), style: activeButtonStyle(currentAlignment === alignment, {
4982
- minWidth: 32,
4983
- padding: "0 6px",
4984
- fontSize: 10,
4985
- }), children: label }, alignment))), _jsx("button", { title: "Bold", onClick: () => exec("bold"), "aria-pressed": activeState.bold, style: activeButtonStyle(activeState.bold), children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), "aria-pressed": activeState.italic, style: activeButtonStyle(activeState.italic, { fontStyle: "italic" }), children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), "aria-pressed": activeState.underline, style: activeButtonStyle(activeState.underline, { textDecoration: "underline" }), children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), "aria-pressed": activeState.strikeThrough, style: activeButtonStyle(activeState.strikeThrough, { textDecoration: "line-through" }), children: "S" }), showFontSize && (_jsxs("select", { value: currentFontSize, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (e) => applyFontSize(e.target.value), title: "Font Size", style: {
5267
+ }, children: [_jsx("option", { value: "mixed", disabled: true, children: "Mixed" }), _jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" }), _jsx("option", { value: "h4", children: "Heading 4" }), _jsx("option", { value: "h5", children: "Heading 5" }), _jsx("option", { value: "h6", children: "Heading 6" })] }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.bold ? " srte-active" : ""}`, title: "Bold", onClick: () => exec("bold"), "aria-pressed": activeState.bold, style: activeButtonStyle(activeState.bold), children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.italic ? " srte-active" : ""}`, title: "Italic", onClick: () => exec("italic"), "aria-pressed": activeState.italic, style: activeButtonStyle(activeState.italic, { fontStyle: "italic" }), children: "I" }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.underline ? " srte-active" : ""}`, title: "Underline", onClick: () => exec("underline"), "aria-pressed": activeState.underline, style: activeButtonStyle(activeState.underline, { textDecoration: "underline" }), children: "U" }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.strikeThrough ? " srte-active" : ""}`, title: "Strikethrough", onClick: () => exec("strikeThrough"), "aria-pressed": activeState.strikeThrough, style: activeButtonStyle(activeState.strikeThrough, { textDecoration: "line-through" }), children: "S" }), showFontSize && (_jsxs("select", { value: currentFontSize, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (e) => applyFontSize(e.target.value), title: "Font Size", style: {
4986
5268
  height: 32,
4987
5269
  padding: "0 8px",
4988
5270
  border: "1px solid var(--srte-input-border)",
@@ -5006,7 +5288,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5006
5288
  background: "var(--srte-input-bg)",
5007
5289
  color: "var(--srte-input-text)",
5008
5290
  maxWidth: 100,
5009
- }, children: [_jsx("option", { value: "", disabled: true, children: "Font" }), fonts.map((f) => (_jsx("option", { value: f.value, children: f.name }, f.value)))] })), _jsx("button", { title: "Text Color", onClick: () => {
5291
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Font" }), fonts.map((f) => (_jsx("option", { value: f.value, children: f.name }, f.value)))] })), _jsx("button", { type: "button", className: "srte-tool-button", "aria-label": "Text color", title: "Text Color", onClick: () => {
5010
5292
  setColorPickerType('text');
5011
5293
  setShowColorPicker(true);
5012
5294
  }, style: {
@@ -5018,7 +5300,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5018
5300
  background: "var(--srte-input-bg)",
5019
5301
  color: "var(--srte-input-text)",
5020
5302
  position: "relative",
5021
- }, children: _jsx("span", { style: { fontWeight: 700, borderBottom: "3px solid currentColor", lineHeight: 1 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
5303
+ }, children: _jsx("span", { style: { fontWeight: 700, borderBottom: "3px solid currentColor", lineHeight: 1 }, children: "A" }) }), _jsx("button", { type: "button", className: "srte-tool-button", "aria-label": "Background color", title: "Background Color", onClick: () => {
5022
5304
  setColorPickerType('background');
5023
5305
  setShowColorPicker(true);
5024
5306
  }, style: {
@@ -5029,10 +5311,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5029
5311
  borderRadius: 6,
5030
5312
  background: "var(--srte-input-bg)",
5031
5313
  color: "var(--srte-input-text)",
5032
- }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), [
5314
+ }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { type: "button", className: `srte-tool-button${activeState.subscript ? " srte-active" : ""}`, title: "Subscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { type: "button", className: `srte-tool-button${activeState.superscript ? " srte-active" : ""}`, title: "Superscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), [
5033
5315
  {
5034
5316
  key: "check",
5035
- label: "",
5317
+ icon: "checklist",
5036
5318
  title: "Checklist",
5037
5319
  active: activeState.checklist,
5038
5320
  action: () => applyChecklist(false, true),
@@ -5040,7 +5322,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5040
5322
  },
5041
5323
  {
5042
5324
  key: "bullet",
5043
- label: "•≡",
5325
+ icon: "bullets",
5044
5326
  title: "Bulleted list",
5045
5327
  active: activeState.unorderedList,
5046
5328
  action: () => applyListStyle("bullet:disc"),
@@ -5048,13 +5330,13 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5048
5330
  },
5049
5331
  {
5050
5332
  key: "ordered",
5051
- label: "1≡",
5333
+ icon: "numbers",
5052
5334
  title: "Numbered list",
5053
5335
  active: activeState.orderedList,
5054
5336
  action: () => applyListStyle("ordered:decimal"),
5055
5337
  options: [["ordered:decimal", "1. 2. 3."], ["ordered:lower-alpha", "a. b. c."], ["ordered:upper-alpha", "A. B. C."], ["ordered:lower-roman", "i. ii. iii."], ["ordered:upper-roman", "I. II. III."]],
5056
5338
  },
5057
- ].map((control) => (_jsxs("span", { style: { display: "inline-flex", height: 32 }, children: [_jsx("button", { type: "button", title: control.title, onPointerDown: preserveEditorSelection, onClick: control.action, "aria-pressed": control.active, style: activeButtonStyle(control.active, { padding: "0 9px", borderRadius: "6px 0 0 6px" }), children: control.label }), _jsxs("select", { defaultValue: "", "aria-label": `${control.title} styles`, title: `${control.title} styles`, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (event) => {
5339
+ ].map((control) => (_jsxs("span", { className: "srte-split-control", children: [_jsx("button", { type: "button", className: `srte-tool-button${control.active ? " srte-active" : ""}`, title: control.title, onPointerDown: preserveEditorSelection, onClick: control.action, "aria-pressed": control.active, children: _jsx(ToolbarIcon, { name: control.icon }) }), _jsxs("select", { defaultValue: "", "aria-label": `${control.title} styles`, title: `${control.title} styles`, onPointerDown: preserveEditorSelection, onMouseDown: preserveEditorSelection, onChange: (event) => {
5058
5340
  const selected = event.currentTarget.value;
5059
5341
  if (selected === "check:plain")
5060
5342
  applyChecklist(false);
@@ -5063,200 +5345,62 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5063
5345
  else if (selected)
5064
5346
  applyListStyle(selected);
5065
5347
  event.currentTarget.value = "";
5348
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Style" }), control.options.map(([value, label]) => _jsx("option", { value: value, children: label }, value))] })] }, control.key))), _jsx("button", { type: "button", className: `srte-tool-button${activeState.blockquote ? " srte-active" : ""}`, title: "Blockquote", onPointerDown: preserveEditorSelection, onClick: toggleBlockquote, "aria-pressed": activeState.blockquote, children: _jsx(ToolbarIcon, { name: "quote" }) }), _jsx("button", { type: "button", className: "srte-tool-button", "aria-label": "Special characters", title: "Special characters", onClick: () => setShowSpecialChars(true), children: _jsx(ToolbarIcon, { name: "omega" }) }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.codeBlock ? " srte-active" : ""}`, title: "Code block", onPointerDown: preserveEditorSelection, onClick: toggleCodeBlock, "aria-pressed": activeState.codeBlock, children: _jsx(ToolbarIcon, { name: "code" }) }), _jsx("button", { type: "button", className: `srte-tool-button${activeState.link ? " srte-active" : ""}`, title: "Insert link", "aria-label": "Insert or edit link", "aria-pressed": activeState.link, onPointerDown: preserveEditorSelection, onClick: () => openLinkEditor(), children: _jsx(ToolbarIcon, { name: "link" }) }), (table || media || formula) && (_jsxs(ToolbarMenu, { label: "Insert", icon: "insert", priority: 2, children: [table && _jsx(MenuItem, { icon: "table", label: "Table", title: "Insert table", onClick: () => setShowTableDialog(true) }), media && _jsx(MenuItem, { icon: "image", label: "Upload image", title: "Insert image", onClick: insertImage }), media && _jsx(MenuItem, { icon: "media", label: "Media", title: "Open media", onClick: () => mediaManager ? setShowMediaManager(true) : insertImage() }), formula && _jsx(MenuItem, { icon: "formula", label: "Formula", title: "Insert formula", onClick: () => setShowFormulaDialog(true) })] })), selectedImage && (_jsxs(ToolbarMenu, { label: "Image alignment", icon: "image", children: [_jsx(MenuItem, { icon: "align-center", label: "Center image", onClick: () => {
5349
+ selectedImage.style.display = "block";
5350
+ selectedImage.style.margin = "0 auto";
5351
+ selectedImage.style.float = "none";
5352
+ scheduleImageOverlay();
5353
+ handleInput();
5354
+ } }), _jsx(MenuItem, { icon: "align-left", label: "Float left", onClick: () => {
5355
+ selectedImage.style.display = "inline";
5356
+ selectedImage.style.float = "left";
5357
+ selectedImage.style.margin = "0 8px 8px 0";
5358
+ scheduleImageOverlay();
5359
+ handleInput();
5360
+ } }), _jsx(MenuItem, { icon: "align-right", label: "Float right", onClick: () => {
5361
+ selectedImage.style.display = "inline";
5362
+ selectedImage.style.float = "right";
5363
+ selectedImage.style.margin = "0 0 8px 8px";
5364
+ scheduleImageOverlay();
5365
+ handleInput();
5366
+ } })] })), _jsxs(ToolbarGroup, { label: "Document", priority: 3, children: [_jsxs(ToolbarMenu, { label: loadingPdf || loadingDocx ? "Importing document" : "Import document", icon: "import", children: [_jsx(MenuItem, { icon: "import", label: "PDF (.pdf)", disabled: loadingPdf || loadingDocx, onClick: () => pdfInputRef.current?.click() }), _jsx(MenuItem, { icon: "import", label: "Microsoft Word (.docx)", disabled: loadingPdf || loadingDocx, onClick: () => docxInputRef.current?.click() }), _jsx(MenuItem, { icon: "import", label: "HTML (.html)", disabled: loadingPdf || loadingDocx, onClick: () => htmlInputRef.current?.click() }), _jsx(MenuItem, { icon: "import", label: "Markdown (.md)", disabled: loadingPdf || loadingDocx, onClick: () => mdInputRef.current?.click() })] }), _jsxs(ToolbarMenu, { label: "Export document", icon: "export", children: [_jsx(MenuItem, { icon: "export", label: "PDF", onClick: exportPdf }), _jsx(MenuItem, { icon: "export", label: "Microsoft Word (.docx)", onClick: () => void exportDocx() }), _jsx(MenuItem, { icon: "export", label: "HTML", onClick: exportHtml }), _jsx(MenuItem, { icon: "export", label: "Markdown", onClick: exportMarkdown })] }), _jsxs("select", { className: "srte-command-proxy", "aria-hidden": "true", tabIndex: -1, defaultValue: "", "aria-label": "Import document", title: "Import document", disabled: loadingPdf || loadingDocx, onChange: (event) => {
5367
+ const format = event.currentTarget.value;
5368
+ event.currentTarget.value = "";
5369
+ if (format === "pdf")
5370
+ pdfInputRef.current?.click();
5371
+ else if (format === "docx")
5372
+ docxInputRef.current?.click();
5373
+ else if (format === "html")
5374
+ htmlInputRef.current?.click();
5375
+ else if (format === "markdown")
5376
+ mdInputRef.current?.click();
5066
5377
  }, style: {
5067
- width: 28,
5068
- height: 32,
5069
- padding: 0,
5070
- border: "1px solid var(--srte-input-border)",
5071
- borderLeft: 0,
5072
- borderRadius: "0 6px 6px 0",
5073
- background: "var(--srte-input-bg)",
5074
- color: "var(--srte-input-text)",
5075
- }, children: [_jsx("option", { value: "", disabled: true, children: "Style" }), control.options.map(([value, label]) => _jsx("option", { value: value, children: label }, value))] })] }, control.key))), _jsx("button", { type: "button", title: "Blockquote", onPointerDown: preserveEditorSelection, onClick: toggleBlockquote, "aria-pressed": activeState.blockquote, style: activeButtonStyle(activeState.blockquote), children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
5076
- height: 32,
5077
- minWidth: 32,
5078
- padding: "0 8px",
5079
- border: "1px solid var(--srte-input-border)",
5080
- borderRadius: 6,
5081
- background: "var(--srte-input-bg)",
5082
- color: "var(--srte-input-text)",
5083
- }, children: "\u03A9" }), _jsx("button", { type: "button", title: "Code block", onPointerDown: preserveEditorSelection, onClick: toggleCodeBlock, "aria-pressed": activeState.codeBlock, style: activeButtonStyle(activeState.codeBlock, {
5084
- minWidth: 36,
5085
- fontFamily: "ui-monospace, SFMono-Regular, Menlo",
5086
- }), children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
5087
- height: 32,
5088
- minWidth: 32,
5089
- padding: "0 8px",
5090
- border: "1px solid var(--srte-input-border)",
5091
- borderRadius: 6,
5092
- background: "var(--srte-input-bg)",
5093
- color: "var(--srte-input-text)",
5094
- }, children: "\u2211" })), _jsx("button", { type: "button", title: "Insert link", "aria-label": "Insert or edit link", "aria-pressed": activeState.link, onPointerDown: preserveEditorSelection, onClick: () => openLinkEditor(), style: activeButtonStyle(activeState.link, { minWidth: 34, fontSize: 17 }), children: _jsx("span", { "aria-hidden": "true", children: "\u2197" }) }), _jsx("button", { type: "button", title: "Remove link", "aria-label": "Remove link", disabled: !activeState.link, onPointerDown: preserveEditorSelection, onClick: () => exec("unlink"), style: {
5095
- height: 32,
5096
- minWidth: 34,
5097
- padding: "0 8px",
5098
- border: "1px solid var(--srte-input-border)",
5099
- borderRadius: 6,
5100
- background: "var(--srte-input-bg)",
5101
- color: "var(--srte-input-text)",
5102
- cursor: activeState.link ? "pointer" : "not-allowed",
5103
- opacity: activeState.link ? 1 : 0.45,
5104
- fontSize: 16,
5105
- }, children: _jsx("span", { "aria-hidden": "true", children: "\u2197\u0338" }) }), media && (_jsxs(_Fragment, { children: [_jsx("button", { title: "Insert image", onClick: insertImage, style: {
5106
- height: 32,
5107
- padding: "0 10px",
5108
- border: "1px solid var(--srte-input-border)",
5109
- borderRadius: 6,
5110
- background: "var(--srte-input-bg)",
5111
- color: "var(--srte-input-text)",
5112
- }, children: "\uD83D\uDDBC\uFE0F Image" }), mediaManager && (_jsx("button", { title: "Open media manager", onClick: () => setShowMediaManager(true), style: {
5113
- height: 32,
5114
- padding: "0 10px",
5115
- border: "1px solid var(--srte-input-border)",
5116
- borderRadius: 6,
5117
- background: "var(--srte-input-bg)",
5118
- color: "var(--srte-input-text)",
5119
- }, children: "\uD83D\uDCC1 Media" })), _jsx("button", { title: "Import PDF", onClick: () => pdfInputRef.current?.click(), disabled: loadingPdf, style: {
5120
- height: 32,
5121
- padding: "0 10px",
5122
- border: "1px solid var(--srte-input-border)",
5123
- borderRadius: 6,
5124
- background: "var(--srte-input-bg)",
5125
- color: "var(--srte-input-text)",
5126
- opacity: loadingPdf ? 0.5 : 1,
5127
- }, children: loadingPdf ? '⌛ Importing...' : '📄 PDF' }), _jsx("button", { title: "Import DOCX", onClick: () => docxInputRef.current?.click(), disabled: loadingDocx, style: {
5128
- height: 32,
5129
- padding: "0 10px",
5130
- border: "1px solid var(--srte-input-border)",
5131
- borderRadius: 6,
5132
- background: "var(--srte-input-bg)",
5133
- color: "var(--srte-input-text)",
5134
- opacity: loadingDocx ? 0.5 : 1,
5135
- }, children: loadingDocx ? '⌛ Importing...' : '📝 DOCX' }), _jsx("button", { title: "Import HTML", onClick: () => htmlInputRef.current?.click(), style: {
5136
- height: 32,
5137
- padding: "0 10px",
5138
- border: "1px solid var(--srte-input-border)",
5139
- borderRadius: 6,
5140
- background: "var(--srte-input-bg)",
5141
- color: "var(--srte-input-text)",
5142
- }, children: "HTML" }), _jsx("button", { title: "Import Markdown", onClick: () => mdInputRef.current?.click(), style: {
5143
- height: 32,
5144
- padding: "0 10px",
5145
- border: "1px solid var(--srte-input-border)",
5146
- borderRadius: 6,
5147
- background: "var(--srte-input-bg)",
5148
- color: "var(--srte-input-text)",
5149
- }, children: "MD" }), _jsx("button", { title: "Export HTML", onClick: exportHtml, style: {
5150
- height: 32,
5151
- padding: "0 10px",
5152
- border: "1px solid var(--srte-input-border)",
5153
- borderRadius: 6,
5154
- background: "var(--srte-input-bg)",
5155
- color: "var(--srte-input-text)",
5156
- }, children: "Export HTML" }), _jsx("button", { title: "Export Markdown", onClick: exportMarkdown, style: {
5157
5378
  height: 32,
5158
- padding: "0 10px",
5379
+ padding: "0 8px",
5159
5380
  border: "1px solid var(--srte-input-border)",
5160
5381
  borderRadius: 6,
5161
5382
  background: "var(--srte-input-bg)",
5162
5383
  color: "var(--srte-input-text)",
5163
- }, children: "Export MD" }), _jsx("button", { title: "Export DOCX", onClick: exportDocx, style: {
5164
- height: 32,
5165
- padding: "0 10px",
5166
- border: "1px solid var(--srte-input-border)",
5167
- borderRadius: 6,
5168
- background: "var(--srte-input-bg)",
5169
- color: "var(--srte-input-text)",
5170
- }, children: "Export DOCX" }), _jsx("button", { title: "Export PDF", onClick: exportPdf, style: {
5384
+ opacity: loadingPdf || loadingDocx ? 0.6 : 1,
5385
+ }, children: [_jsx("option", { value: "", disabled: true, children: loadingPdf || loadingDocx ? "Importing…" : "Import…" }), _jsx("option", { value: "pdf", children: "PDF (.pdf)" }), _jsx("option", { value: "docx", children: "Word (.docx)" }), _jsx("option", { value: "html", children: "HTML (.html)" }), _jsx("option", { value: "markdown", children: "Markdown (.md)" })] }), _jsxs("select", { className: "srte-command-proxy", "aria-hidden": "true", tabIndex: -1, defaultValue: "", "aria-label": "Export document", title: "Export document", onChange: (event) => {
5386
+ const format = event.currentTarget.value;
5387
+ event.currentTarget.value = "";
5388
+ if (format === "html")
5389
+ exportHtml();
5390
+ else if (format === "markdown")
5391
+ exportMarkdown();
5392
+ else if (format === "docx")
5393
+ void exportDocx();
5394
+ else if (format === "pdf")
5395
+ exportPdf();
5396
+ }, style: {
5171
5397
  height: 32,
5172
- padding: "0 10px",
5398
+ padding: "0 8px",
5173
5399
  border: "1px solid var(--srte-input-border)",
5174
5400
  borderRadius: 6,
5175
5401
  background: "var(--srte-input-bg)",
5176
5402
  color: "var(--srte-input-text)",
5177
- }, children: "Export PDF" }), _jsxs("div", { style: {
5178
- display: "inline-flex",
5179
- gap: 4,
5180
- alignItems: "center",
5181
- marginLeft: 6,
5182
- }, children: [_jsx("span", { style: { fontSize: 12, opacity: 0.7 }, children: "Image align:" }), _jsx("button", { onClick: () => {
5183
- const img = selectedImage;
5184
- if (!img)
5185
- return;
5186
- img.style.display = "block";
5187
- img.style.margin = "0 auto";
5188
- img.style.float = "none";
5189
- scheduleImageOverlay();
5190
- handleInput();
5191
- }, title: "Center", style: {
5192
- height: 28,
5193
- minWidth: 28,
5194
- padding: "0 6px",
5195
- border: "1px solid var(--srte-input-border)",
5196
- borderRadius: 6,
5197
- background: "var(--srte-input-bg)",
5198
- color: "var(--srte-input-text)",
5199
- }, children: "\u2299" }), _jsx("button", { onClick: () => {
5200
- const img = selectedImage;
5201
- if (!img)
5202
- return;
5203
- img.style.display = "inline";
5204
- img.style.float = "left";
5205
- img.style.margin = "0 8px 8px 0";
5206
- scheduleImageOverlay();
5207
- handleInput();
5208
- }, title: "Float left", style: {
5209
- height: 28,
5210
- minWidth: 28,
5211
- padding: "0 6px",
5212
- border: "1px solid var(--srte-input-border)",
5213
- borderRadius: 6,
5214
- background: "var(--srte-input-bg)",
5215
- color: "var(--srte-input-text)",
5216
- }, children: "\u27F8" }), _jsx("button", { onClick: () => {
5217
- const img = selectedImage;
5218
- if (!img)
5219
- return;
5220
- img.style.display = "inline";
5221
- img.style.float = "right";
5222
- img.style.margin = "0 0 8px 8px";
5223
- scheduleImageOverlay();
5224
- handleInput();
5225
- }, title: "Float right", style: {
5226
- height: 28,
5227
- minWidth: 28,
5228
- padding: "0 6px",
5229
- border: "1px solid var(--srte-input-border)",
5230
- borderRadius: 6,
5231
- background: "var(--srte-input-bg)",
5232
- color: "var(--srte-input-text)",
5233
- }, children: "\u27F9" })] })] })), table && (_jsx("button", { title: "Insert table", onClick: () => setShowTableDialog(true), style: {
5234
- height: 32,
5235
- padding: "0 10px",
5236
- border: "1px solid var(--srte-input-border)",
5237
- borderRadius: 6,
5238
- background: "var(--srte-input-bg)",
5239
- color: "var(--srte-input-text)",
5240
- }, children: "\u2795 Table" })), _jsxs("div", { style: {
5241
- display: "inline-flex",
5242
- gap: 4,
5243
- alignItems: "center",
5244
- marginLeft: 6,
5245
- }, children: [_jsx("span", { style: { fontSize: 12, opacity: 0.7 }, children: "Move:" }), _jsx("button", { title: "Move selected block up", onClick: () => moveCurrentElement("up"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2191" }), _jsx("button", { title: "Move selected block down", onClick: () => moveCurrentElement("down"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2193" }), _jsx("button", { title: "Move selected block left", onClick: () => moveCurrentElement("left"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2190" }), _jsx("button", { title: "Move selected block right", onClick: () => moveCurrentElement("right"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2192" })] }), _jsx("button", { title: "Undo", onClick: () => exec("undo"), style: {
5246
- height: 32,
5247
- padding: "0 10px",
5248
- border: "1px solid var(--srte-input-border)",
5249
- borderRadius: 6,
5250
- background: "var(--srte-input-bg)",
5251
- color: "var(--srte-input-text)",
5252
- }, children: "\u238C Undo" }), _jsx("button", { title: "Redo", onClick: () => exec("redo"), style: {
5253
- height: 32,
5254
- padding: "0 10px",
5255
- border: "1px solid var(--srte-input-border)",
5256
- borderRadius: 6,
5257
- background: "var(--srte-input-bg)",
5258
- color: "var(--srte-input-text)",
5259
- }, children: "\u293E Redo" })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
5403
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Export\u2026" }), _jsx("option", { value: "html", children: "HTML (.html)" }), _jsx("option", { value: "markdown", children: "Markdown (.md)" }), _jsx("option", { value: "docx", children: "Word (.docx)" }), _jsx("option", { value: "pdf", children: "PDF (.pdf)" })] })] }), _jsx(ToolbarMenu, { label: "Text alignment", icon: currentAlignment === "center" ? "align-center" : currentAlignment === "right" ? "align-right" : currentAlignment === "justify" ? "justify" : "align-left", active: currentAlignment !== "left", children: ["left", "center", "right", "justify"].map((alignment) => (_jsx(MenuItem, { icon: alignment === "left" ? "align-left" : alignment === "center" ? "align-center" : alignment === "right" ? "align-right" : "justify", label: alignment === "justify" ? "Justify" : `Align ${alignment}`, title: alignment === "justify" ? "Justify" : `Align ${alignment}`, active: currentAlignment === alignment, onClick: () => applyTextAlignment(alignment) }, alignment))) }), _jsxs(ToolbarMenu, { label: "Move and indent", icon: "move", priority: 2, children: [_jsx(MenuItem, { icon: "up", label: "Move block up", title: "Move selected block up", onClick: () => moveCurrentElement("up") }), _jsx(MenuItem, { icon: "down", label: "Move block down", title: "Move selected block down", onClick: () => moveCurrentElement("down") }), _jsx("div", { className: "srte-menu-separator", role: "separator" }), _jsx(MenuItem, { icon: "outdent", label: "Decrease indent", title: "Move selected block left", onClick: () => moveCurrentElement("left") }), _jsx(MenuItem, { icon: "indent", label: "Increase indent", title: "Move selected block right", onClick: () => moveCurrentElement("right") })] }), _jsx("div", { className: "srte-mobile-more", children: _jsxs(ToolbarMenu, { label: "More editor actions", icon: "more", children: [table && _jsx(MenuItem, { icon: "table", label: "Insert table", onClick: () => setShowTableDialog(true) }), media && _jsx(MenuItem, { icon: "image", label: "Upload image", onClick: insertImage }), media && _jsx(MenuItem, { icon: "media", label: "Media", onClick: () => mediaManager ? setShowMediaManager(true) : insertImage() }), formula && _jsx(MenuItem, { icon: "formula", label: "Insert formula", onClick: () => setShowFormulaDialog(true) }), _jsx(MenuItem, { icon: "omega", label: "Special characters", onClick: () => setShowSpecialChars(true) }), _jsx(MenuItem, { icon: "up", label: "Move block up", onClick: () => moveCurrentElement("up") }), _jsx(MenuItem, { icon: "down", label: "Move block down", onClick: () => moveCurrentElement("down") }), _jsx(MenuItem, { icon: "outdent", label: "Decrease indent", onClick: () => moveCurrentElement("left") }), _jsx(MenuItem, { icon: "indent", label: "Increase indent", onClick: () => moveCurrentElement("right") }), _jsx("div", { className: "srte-menu-separator", role: "separator" }), _jsx(MenuItem, { icon: "import", label: "Import Word document", onClick: () => docxInputRef.current?.click() }), _jsx(MenuItem, { icon: "import", label: "Import HTML", onClick: () => htmlInputRef.current?.click() }), _jsx(MenuItem, { icon: "import", label: "Import Markdown", onClick: () => mdInputRef.current?.click() }), _jsx("div", { className: "srte-menu-separator", role: "separator" }), _jsx(MenuItem, { icon: "export", label: "Export PDF", onClick: exportPdf }), _jsx(MenuItem, { icon: "export", label: "Export Word document", onClick: () => void exportDocx() }), _jsx(MenuItem, { icon: "export", label: "Export HTML", onClick: exportHtml }), _jsx(MenuItem, { icon: "export", label: "Export Markdown", onClick: exportMarkdown })] }) })] }), media && mediaManager && (_jsx(MediaManager, { open: showMediaManager, onClose: () => setShowMediaManager(false), adapter: mediaManager, onSelect: (item) => {
5260
5404
  if (item?.url)
5261
5405
  insertImageAtSelection(item);
5262
5406
  } })), table && showTableDialog && (_jsx("div", { style: {
@@ -5716,7 +5860,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5716
5860
  pushEditorHistory();
5717
5861
  const checked = item.dataset.checked !== "true";
5718
5862
  item.dataset.checked = checked ? "true" : "false";
5719
- t.textContent = checked ? "" : "";
5863
+ t.dataset.checked = checked ? "true" : "false";
5864
+ t.setAttribute("aria-pressed", checked ? "true" : "false");
5720
5865
  t.setAttribute("aria-label", checked ? "Mark incomplete" : "Mark complete");
5721
5866
  item.style.textDecoration =
5722
5867
  list.dataset.srteChecklistStrike === "true" && checked ? "line-through" : "";
@@ -5813,26 +5958,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
5813
5958
  }
5814
5959
  // Keep Tab for indentation in lists; otherwise insert 2 spaces
5815
5960
  if (e.key === "Tab") {
5816
- e.preventDefault();
5817
5961
  const selection = getSelectionRangeInEditor();
5818
- const selectedListItems = selection && !selection.collapsed
5819
- ? getSelectedListItems(getSelectedBlocks(selection))
5820
- : [];
5962
+ const selectedListItems = selection ? resolveSelectedListItems(selection) : [];
5821
5963
  if (selectedListItems.length > 0) {
5822
- if (!e.shiftKey) {
5823
- nestListSelection();
5824
- return;
5825
- }
5826
- pushEditorHistory();
5827
- exec("outdent");
5964
+ e.preventDefault();
5965
+ applyListDepthChange(selectedListItems, e.shiftKey ? "outdent" : "indent");
5828
5966
  return;
5829
5967
  }
5968
+ e.preventDefault();
5830
5969
  const currentBlock = getCurrentBlock();
5831
- if (currentBlock?.closest("li")) {
5832
- pushEditorHistory();
5833
- exec(e.shiftKey ? "outdent" : "indent");
5834
- }
5835
- else {
5970
+ if (!currentBlock?.closest("li")) {
5836
5971
  captureInputHistory("insertText");
5837
5972
  document.execCommand("insertText", false, " ");
5838
5973
  }