@notectl/core 1.0.9 → 1.6.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.
- package/dist/chunks/{AttrRegistry-D-przlzb.mjs → AttrRegistry-I-VdmRnN.mjs} +2 -2
- package/dist/chunks/{AttrRegistry-D-przlzb.mjs.map → AttrRegistry-I-VdmRnN.mjs.map} +1 -1
- package/dist/chunks/CaretNavigation-CAJXoaae.mjs +311 -0
- package/dist/chunks/CaretNavigation-CAJXoaae.mjs.map +1 -0
- package/dist/chunks/ColorNames-BdsHwA2f.mjs +59 -0
- package/dist/chunks/ColorNames-BdsHwA2f.mjs.map +1 -0
- package/dist/chunks/ColorPickerPopup-xL8ZUfpB.mjs +125 -0
- package/dist/chunks/ColorPickerPopup-xL8ZUfpB.mjs.map +1 -0
- package/dist/chunks/Commands-CicwYoTQ.mjs +286 -0
- package/dist/chunks/Commands-CicwYoTQ.mjs.map +1 -0
- package/dist/chunks/{Document-CI3iCwEP.mjs → Document-CuwE73FF.mjs} +35 -22
- package/dist/chunks/{Document-CI3iCwEP.mjs.map → Document-CuwE73FF.mjs.map} +1 -1
- package/dist/chunks/HTMLUtils-DTqQzcKR.mjs +35 -0
- package/dist/chunks/HTMLUtils-DTqQzcKR.mjs.map +1 -0
- package/dist/chunks/MarkCommands-CC37WA7Z.mjs +94 -0
- package/dist/chunks/MarkCommands-CC37WA7Z.mjs.map +1 -0
- package/dist/chunks/NavigationUtils-CyW0Vo8A.mjs +47 -0
- package/dist/chunks/NavigationUtils-CyW0Vo8A.mjs.map +1 -0
- package/dist/chunks/{NodeResolver-D68ZLXti.mjs → NodeResolver-B-wzthAQ.mjs} +2 -2
- package/dist/chunks/{NodeResolver-D68ZLXti.mjs.map → NodeResolver-B-wzthAQ.mjs.map} +1 -1
- package/dist/chunks/Platform-S_xqIqcM.mjs +26 -0
- package/dist/chunks/Platform-S_xqIqcM.mjs.map +1 -0
- package/dist/chunks/Selection-DmFDwiPJ.mjs +52 -0
- package/dist/chunks/Selection-DmFDwiPJ.mjs.map +1 -0
- package/dist/chunks/ToolbarItem-BpZggUGo.mjs +8 -0
- package/dist/chunks/ToolbarItem-BpZggUGo.mjs.map +1 -0
- package/dist/chunks/{ToolbarPlugin-DFkl1uYO.mjs → ToolbarPlugin-B6JETWaw.mjs} +3 -3
- package/dist/chunks/{ToolbarPlugin-DFkl1uYO.mjs.map → ToolbarPlugin-B6JETWaw.mjs.map} +1 -1
- package/dist/chunks/index--z_Txzej.mjs +467 -0
- package/dist/chunks/index--z_Txzej.mjs.map +1 -0
- package/dist/chunks/{index-o6hDR07y.mjs → index-83QoUENW.mjs} +25 -20
- package/dist/chunks/index-83QoUENW.mjs.map +1 -0
- package/dist/chunks/{index-DfO_fKw6.mjs → index-B0UFAfyb.mjs} +15 -15
- package/dist/chunks/{index-DfO_fKw6.mjs.map → index-B0UFAfyb.mjs.map} +1 -1
- package/dist/chunks/{index-BFsYGsBR.mjs → index-BDMP86F4.mjs} +39 -39
- package/dist/chunks/{index-BFsYGsBR.mjs.map → index-BDMP86F4.mjs.map} +1 -1
- package/dist/chunks/{index-Bsp4LC5t.mjs → index-BWXkaC2Z.mjs} +36 -36
- package/dist/chunks/index-BWXkaC2Z.mjs.map +1 -0
- package/dist/chunks/{index-DAFCISBN.mjs → index-BigJ0z6n.mjs} +3 -3
- package/dist/chunks/{index-DAFCISBN.mjs.map → index-BigJ0z6n.mjs.map} +1 -1
- package/dist/chunks/{index-uTnc9y7M.mjs → index-CPjf45-o.mjs} +28 -28
- package/dist/chunks/index-CPjf45-o.mjs.map +1 -0
- package/dist/chunks/{index-DGrRWMlW.mjs → index-CPyHuEzh.mjs} +105 -105
- package/dist/chunks/index-CPyHuEzh.mjs.map +1 -0
- package/dist/chunks/{index-BL56fbXt.mjs → index-D4GKlc-A.mjs} +89 -87
- package/dist/chunks/index-D4GKlc-A.mjs.map +1 -0
- package/dist/chunks/{index-BRYzYOYC.mjs → index-D67mP19g.mjs} +29 -29
- package/dist/chunks/index-D67mP19g.mjs.map +1 -0
- package/dist/chunks/{index-BIL37LSL.mjs → index-D8tbbW97.mjs} +18 -18
- package/dist/chunks/{index-BIL37LSL.mjs.map → index-D8tbbW97.mjs.map} +1 -1
- package/dist/chunks/{index-DC1EK22F.mjs → index-DIAXWwgJ.mjs} +24 -19
- package/dist/chunks/index-DIAXWwgJ.mjs.map +1 -0
- package/dist/chunks/{index-BSFLNveF.mjs → index-DQryjfJs.mjs} +352 -341
- package/dist/chunks/index-DQryjfJs.mjs.map +1 -0
- package/dist/chunks/{index-BTJfyJlJ.mjs → index-DRZwd-1W.mjs} +104 -104
- package/dist/chunks/index-DRZwd-1W.mjs.map +1 -0
- package/dist/chunks/{index-BQ8iIYLn.mjs → index-DUC1X43Z.mjs} +54 -54
- package/dist/chunks/index-DUC1X43Z.mjs.map +1 -0
- package/dist/chunks/{index-BCvuDpVr.mjs → index-DipAT_Q9.mjs} +31 -31
- package/dist/chunks/index-DipAT_Q9.mjs.map +1 -0
- package/dist/chunks/{index-DA8TG2S8.mjs → index-tBDVaSjG.mjs} +56 -56
- package/dist/chunks/index-tBDVaSjG.mjs.map +1 -0
- package/dist/chunks/resolvePluginLocale-B4w1OkPy.mjs.map +1 -1
- package/dist/commands/Commands.d.ts +13 -45
- package/dist/commands/Commands.d.ts.map +1 -1
- package/dist/commands/DeleteCommands.d.ts +15 -0
- package/dist/commands/DeleteCommands.d.ts.map +1 -0
- package/dist/commands/GapCursorCommands.d.ts +24 -0
- package/dist/commands/GapCursorCommands.d.ts.map +1 -0
- package/dist/commands/MarkCommands.d.ts +19 -0
- package/dist/commands/MarkCommands.d.ts.map +1 -0
- package/dist/commands/MovementCommands.d.ts +27 -0
- package/dist/commands/MovementCommands.d.ts.map +1 -0
- package/dist/commands/NodeSelectionCommands.d.ts +24 -0
- package/dist/commands/NodeSelectionCommands.d.ts.map +1 -0
- package/dist/commands/PasteCommand.d.ts.map +1 -1
- package/dist/commands/WordBoundary.d.ts +12 -0
- package/dist/commands/WordBoundary.d.ts.map +1 -0
- package/dist/editor/DocumentSerializer.d.ts.map +1 -1
- package/dist/editor/NotectlEditor.d.ts +7 -1
- package/dist/editor/NotectlEditor.d.ts.map +1 -1
- package/dist/index.d.ts +25 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/input/ClipboardHandler.d.ts.map +1 -1
- package/dist/input/CompositionTracker.d.ts +14 -0
- package/dist/input/CompositionTracker.d.ts.map +1 -0
- package/dist/input/FileHandlerRegistry.d.ts +16 -0
- package/dist/input/FileHandlerRegistry.d.ts.map +1 -0
- package/dist/input/InputHandler.d.ts +6 -4
- package/dist/input/InputHandler.d.ts.map +1 -1
- package/dist/input/InputRuleRegistry.d.ts +9 -0
- package/dist/input/InputRuleRegistry.d.ts.map +1 -0
- package/dist/input/KeyboardHandler.d.ts +20 -5
- package/dist/input/KeyboardHandler.d.ts.map +1 -1
- package/dist/input/Keymap.d.ts +11 -0
- package/dist/input/Keymap.d.ts.map +1 -1
- package/dist/input/KeymapRegistry.d.ts +19 -0
- package/dist/input/KeymapRegistry.d.ts.map +1 -0
- package/dist/input/PasteHandler.d.ts +3 -0
- package/dist/input/PasteHandler.d.ts.map +1 -1
- package/dist/model/Document.d.ts +8 -0
- package/dist/model/Document.d.ts.map +1 -1
- package/dist/model/GraphemeUtils.d.ts +20 -0
- package/dist/model/GraphemeUtils.d.ts.map +1 -0
- package/dist/model/HTMLUtils.d.ts +6 -1
- package/dist/model/HTMLUtils.d.ts.map +1 -1
- package/dist/model/NavigationUtils.d.ts +16 -0
- package/dist/model/NavigationUtils.d.ts.map +1 -0
- package/dist/model/SchemaRegistry.d.ts +0 -39
- package/dist/model/SchemaRegistry.d.ts.map +1 -1
- package/dist/model/Selection.d.ts +14 -3
- package/dist/model/Selection.d.ts.map +1 -1
- package/dist/notectl-core.mjs +2755 -2496
- package/dist/notectl-core.mjs.map +1 -1
- package/dist/notectl-core.umd.js +2672 -1680
- package/dist/notectl-core.umd.js.map +1 -1
- package/dist/plugins/Plugin.d.ts +17 -3
- package/dist/plugins/Plugin.d.ts.map +1 -1
- package/dist/plugins/PluginManager.d.ts +14 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -1
- package/dist/plugins/alignment.mjs +1 -1
- package/dist/plugins/blockquote.mjs +1 -1
- package/dist/plugins/caret-navigation/CaretNavigationPlugin.d.ts +35 -0
- package/dist/plugins/caret-navigation/CaretNavigationPlugin.d.ts.map +1 -0
- package/dist/plugins/caret-navigation/index.d.ts +2 -0
- package/dist/plugins/caret-navigation/index.d.ts.map +1 -0
- package/dist/plugins/caret-navigation.mjs +5 -0
- package/dist/plugins/caret-navigation.mjs.map +1 -0
- package/dist/plugins/code-block/CodeBlockKeyboardHandlers.d.ts.map +1 -1
- package/dist/plugins/code-block/CodeBlockPlugin.d.ts.map +1 -1
- package/dist/plugins/code-block.mjs +1 -1
- package/dist/plugins/font/FontPlugin.d.ts.map +1 -1
- package/dist/plugins/font-size/FontSizeOperations.d.ts.map +1 -1
- package/dist/plugins/font-size.mjs +1 -1
- package/dist/plugins/font.mjs +1 -1
- package/dist/plugins/gap-cursor/GapCursorPlugin.d.ts +15 -0
- package/dist/plugins/gap-cursor/GapCursorPlugin.d.ts.map +1 -0
- package/dist/plugins/gap-cursor/index.d.ts +2 -0
- package/dist/plugins/gap-cursor/index.d.ts.map +1 -0
- package/dist/plugins/gap-cursor.mjs +65 -0
- package/dist/plugins/gap-cursor.mjs.map +1 -0
- package/dist/plugins/hard-break.mjs +1 -1
- package/dist/plugins/heading/BlockTypePickerRegistry.d.ts +9 -0
- package/dist/plugins/heading/BlockTypePickerRegistry.d.ts.map +1 -0
- package/dist/plugins/heading/HeadingInputRules.d.ts.map +1 -1
- package/dist/plugins/heading/HeadingKeyboardHandlers.d.ts.map +1 -1
- package/dist/plugins/heading.mjs +1 -1
- package/dist/plugins/highlight/HighlightPlugin.d.ts.map +1 -1
- package/dist/plugins/highlight.mjs +1 -1
- package/dist/plugins/horizontal-rule/HorizontalRulePlugin.d.ts.map +1 -1
- package/dist/plugins/horizontal-rule.mjs +1 -1
- package/dist/plugins/image/ImageCommands.d.ts.map +1 -1
- package/dist/plugins/image.mjs +1 -1
- package/dist/plugins/link/LinkPlugin.d.ts.map +1 -1
- package/dist/plugins/link.mjs +1 -1
- package/dist/plugins/list/ListPlugin.d.ts.map +1 -1
- package/dist/plugins/list.mjs +1 -1
- package/dist/plugins/print/PrintStyleCollector.d.ts.map +1 -1
- package/dist/plugins/print/PrintTypes.d.ts +5 -1
- package/dist/plugins/print/PrintTypes.d.ts.map +1 -1
- package/dist/plugins/shared/ColorMarkOperations.d.ts.map +1 -1
- package/dist/plugins/shared/ColorValidation.d.ts +7 -0
- package/dist/plugins/shared/ColorValidation.d.ts.map +1 -1
- package/dist/plugins/strikethrough.mjs +1 -1
- package/dist/plugins/super-sub.mjs +1 -1
- package/dist/plugins/table/TableCommands.d.ts.map +1 -1
- package/dist/plugins/table/TableNavigation.d.ts.map +1 -1
- package/dist/plugins/table/TablePlugin.d.ts.map +1 -1
- package/dist/plugins/table.mjs +1 -1
- package/dist/plugins/text-color/TextColorPlugin.d.ts.map +1 -1
- package/dist/plugins/text-color.mjs +1 -1
- package/dist/plugins/text-formatting.mjs +1 -1
- package/dist/plugins/toolbar/ToolbarItem.d.ts.map +1 -1
- package/dist/plugins/toolbar/ToolbarRegistry.d.ts +12 -0
- package/dist/plugins/toolbar/ToolbarRegistry.d.ts.map +1 -0
- package/dist/plugins/toolbar.mjs +2 -2
- package/dist/state/EditorState.d.ts.map +1 -1
- package/dist/state/History.d.ts.map +1 -1
- package/dist/state/SelectionTransactions.d.ts +10 -0
- package/dist/state/SelectionTransactions.d.ts.map +1 -0
- package/dist/test/TestUtils.d.ts +23 -0
- package/dist/test/TestUtils.d.ts.map +1 -1
- package/dist/view/CaretNavigation.d.ts +48 -0
- package/dist/view/CaretNavigation.d.ts.map +1 -0
- package/dist/view/CursorWrapper.d.ts +19 -0
- package/dist/view/CursorWrapper.d.ts.map +1 -0
- package/dist/view/DomPointUtils.d.ts +20 -0
- package/dist/view/DomPointUtils.d.ts.map +1 -0
- package/dist/view/EditorView.d.ts +43 -0
- package/dist/view/EditorView.d.ts.map +1 -1
- package/dist/view/MarkRendering.d.ts +7 -0
- package/dist/view/MarkRendering.d.ts.map +1 -0
- package/dist/view/NodeViewRegistry.d.ts +9 -0
- package/dist/view/NodeViewRegistry.d.ts.map +1 -0
- package/dist/view/Platform.d.ts +29 -0
- package/dist/view/Platform.d.ts.map +1 -0
- package/dist/view/Reconciler.d.ts +4 -0
- package/dist/view/Reconciler.d.ts.map +1 -1
- package/dist/view/SelectionSync.d.ts +2 -0
- package/dist/view/SelectionSync.d.ts.map +1 -1
- package/dist/view/ViewMovementCommands.d.ts +40 -0
- package/dist/view/ViewMovementCommands.d.ts.map +1 -0
- package/package.json +10 -2
- package/dist/chunks/ColorNames-iaWsSZx1.mjs +0 -35
- package/dist/chunks/ColorNames-iaWsSZx1.mjs.map +0 -1
- package/dist/chunks/ColorValidation-Dyy4Bye8.mjs +0 -140
- package/dist/chunks/ColorValidation-Dyy4Bye8.mjs.map +0 -1
- package/dist/chunks/Commands-CSpSwGXv.mjs +0 -461
- package/dist/chunks/Commands-CSpSwGXv.mjs.map +0 -1
- package/dist/chunks/HTMLUtils-0wvCRvJc.mjs +0 -7
- package/dist/chunks/HTMLUtils-0wvCRvJc.mjs.map +0 -1
- package/dist/chunks/Selection-D5ZjXoB7.mjs +0 -44
- package/dist/chunks/Selection-D5ZjXoB7.mjs.map +0 -1
- package/dist/chunks/ToolbarItem-ugDymuXi.mjs +0 -7
- package/dist/chunks/ToolbarItem-ugDymuXi.mjs.map +0 -1
- package/dist/chunks/index-BCvuDpVr.mjs.map +0 -1
- package/dist/chunks/index-BL56fbXt.mjs.map +0 -1
- package/dist/chunks/index-BQ8iIYLn.mjs.map +0 -1
- package/dist/chunks/index-BRYzYOYC.mjs.map +0 -1
- package/dist/chunks/index-BSFLNveF.mjs.map +0 -1
- package/dist/chunks/index-BTJfyJlJ.mjs.map +0 -1
- package/dist/chunks/index-Bsp4LC5t.mjs.map +0 -1
- package/dist/chunks/index-DA8TG2S8.mjs.map +0 -1
- package/dist/chunks/index-DC1EK22F.mjs.map +0 -1
- package/dist/chunks/index-DGrRWMlW.mjs.map +0 -1
- package/dist/chunks/index-o6hDR07y.mjs.map +0 -1
- package/dist/chunks/index-uTnc9y7M.mjs.map +0 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
function t(t2, n2, r2) {
|
|
2
2
|
const o2 = s(t2), i2 = [];
|
|
3
|
-
let
|
|
3
|
+
let f2 = 0;
|
|
4
4
|
for (const t3 of o2) {
|
|
5
|
-
const o3 =
|
|
6
|
-
if (o3 <= n2 ||
|
|
7
|
-
|
|
5
|
+
const o3 = f2 + (e(t3) ? 1 : t3.text.length);
|
|
6
|
+
if (o3 <= n2 || f2 >= r2) {
|
|
7
|
+
f2 = o3;
|
|
8
8
|
continue;
|
|
9
9
|
}
|
|
10
10
|
if (e(t3)) {
|
|
11
|
-
|
|
11
|
+
f2 = o3;
|
|
12
12
|
continue;
|
|
13
13
|
}
|
|
14
|
-
const s2 = Math.max(0, n2 -
|
|
15
|
-
c2.length > 0 && i2.push({ text: c2, marks: t3.marks }),
|
|
14
|
+
const s2 = Math.max(0, n2 - f2), u2 = Math.min(t3.text.length, r2 - f2), c2 = t3.text.slice(s2, u2);
|
|
15
|
+
c2.length > 0 && i2.push({ text: c2, marks: t3.marks }), f2 = o3;
|
|
16
16
|
}
|
|
17
17
|
return i2;
|
|
18
18
|
}
|
|
@@ -34,17 +34,17 @@ function i(t2) {
|
|
|
34
34
|
function s(t2) {
|
|
35
35
|
return t2.children.filter((t3) => n(t3) || e(t3));
|
|
36
36
|
}
|
|
37
|
-
function
|
|
37
|
+
function f(t2) {
|
|
38
38
|
return t2.children.filter((t3) => r(t3));
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function u() {
|
|
41
41
|
return `block-${crypto.randomUUID()}`;
|
|
42
42
|
}
|
|
43
43
|
function c(t2) {
|
|
44
44
|
return { children: t2 ?? [l("paragraph")] };
|
|
45
45
|
}
|
|
46
46
|
function l(t2, n2, e2, r2) {
|
|
47
|
-
return { id: e2 ??
|
|
47
|
+
return { id: e2 ?? u(), type: t2, ...r2 ? { attrs: r2 } : {}, children: n2 ?? [a("")] };
|
|
48
48
|
}
|
|
49
49
|
function a(t2, n2) {
|
|
50
50
|
return { type: "text", text: t2, marks: n2 ?? [] };
|
|
@@ -133,6 +133,18 @@ function b(t2) {
|
|
|
133
133
|
return 0 === o2.length ? [a("")] : o2.some((t3) => n(t3)) ? o2 : [a(""), ...o2];
|
|
134
134
|
}
|
|
135
135
|
function j(t2, n2) {
|
|
136
|
+
const r2 = s(t2);
|
|
137
|
+
let o2 = 0, i2 = 0;
|
|
138
|
+
for (const t3 of r2) if (e(t3)) {
|
|
139
|
+
if (o2 >= n2) return i2;
|
|
140
|
+
o2 += 1;
|
|
141
|
+
} else {
|
|
142
|
+
if (o2 + t3.text.length >= n2) return i2 + (n2 - o2);
|
|
143
|
+
o2 += t3.text.length, i2 += t3.text.length;
|
|
144
|
+
}
|
|
145
|
+
return i2;
|
|
146
|
+
}
|
|
147
|
+
function v(t2, n2) {
|
|
136
148
|
const r2 = s(t2);
|
|
137
149
|
for (const { child: t3, from: o2 } of (function* (t4) {
|
|
138
150
|
let n3 = 0;
|
|
@@ -152,17 +164,17 @@ function j(t2, n2) {
|
|
|
152
164
|
}
|
|
153
165
|
export {
|
|
154
166
|
r as a,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
y as b,
|
|
168
|
+
s as c,
|
|
169
|
+
n as d,
|
|
170
|
+
u as e,
|
|
171
|
+
l as f,
|
|
172
|
+
p as g,
|
|
161
173
|
m as h,
|
|
162
174
|
e as i,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
a as j,
|
|
176
|
+
h as k,
|
|
177
|
+
v as l,
|
|
166
178
|
k as m,
|
|
167
179
|
b as n,
|
|
168
180
|
t as o,
|
|
@@ -171,7 +183,8 @@ export {
|
|
|
171
183
|
d as r,
|
|
172
184
|
o as s,
|
|
173
185
|
x as t,
|
|
174
|
-
u,
|
|
175
|
-
|
|
186
|
+
f as u,
|
|
187
|
+
j as v,
|
|
188
|
+
i as w
|
|
176
189
|
};
|
|
177
|
-
//# sourceMappingURL=Document-
|
|
190
|
+
//# sourceMappingURL=Document-CuwE73FF.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Document-CI3iCwEP.mjs","sources":["../../src/model/Document.ts"],"sourcesContent":["/**\n * Core document model types for the Notectl editor.\n * All types are deeply readonly — mutations create new instances.\n */\n\nimport type { BlockId, InlineTypeName, MarkTypeName, NodeTypeName } from './TypeBrands.js';\n\n// --- Mark Types ---\n\n/** @deprecated Use {@link MarkTypeName} for new code. */\nexport type MarkType = MarkTypeName;\n\nexport interface Mark {\n\treadonly type: MarkTypeName;\n\treadonly attrs?: Readonly<Record<string, string | number | boolean>>;\n}\n\nexport interface BoldMark extends Mark {\n\treadonly type: MarkTypeName & 'bold';\n}\n\nexport interface ItalicMark extends Mark {\n\treadonly type: MarkTypeName & 'italic';\n}\n\nexport interface UnderlineMark extends Mark {\n\treadonly type: MarkTypeName & 'underline';\n}\n\n// --- Node Types ---\n\n/** @deprecated Use {@link NodeTypeName} for new code. */\nexport type NodeType = NodeTypeName;\n\nexport interface BlockAttrs {\n\treadonly [key: string]: string | number | boolean;\n}\n\nexport interface TextNode {\n\treadonly type: 'text';\n\treadonly text: string;\n\treadonly marks: readonly Mark[];\n}\n\n/** Atomic inline element (width 1 in offset space). */\nexport interface InlineNode {\n\treadonly type: 'inline';\n\treadonly inlineType: InlineTypeName;\n\treadonly attrs: Readonly<Record<string, string | number | boolean>>;\n}\n\n/** A child of a BlockNode: text, inline element, or nested block. */\nexport type ChildNode = TextNode | InlineNode | BlockNode;\n\nexport interface BlockNode {\n\treadonly id: BlockId;\n\treadonly type: NodeTypeName;\n\treadonly attrs?: BlockAttrs;\n\treadonly children: readonly ChildNode[];\n}\n\nexport interface Document {\n\treadonly children: readonly BlockNode[];\n}\n\n// --- Text Segment (for mark-preserving undo) ---\n\nexport interface TextSegment {\n\treadonly text: string;\n\treadonly marks: readonly Mark[];\n}\n\n/** A content segment: either text (with marks) or an inline node. */\nexport type ContentSegment =\n\t| { readonly kind: 'text'; readonly text: string; readonly marks: readonly Mark[] }\n\t| { readonly kind: 'inline'; readonly node: InlineNode };\n\n/** Extracts TextNode segments within a block for the given offset range. */\nexport function getBlockSegmentsInRange(\n\tblock: BlockNode,\n\tfrom: number,\n\tto: number,\n): readonly TextSegment[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tconst segments: TextSegment[] = [];\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tconst childWidth: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tconst childEnd: number = pos + childWidth;\n\n\t\tif (childEnd <= from || pos >= to) {\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isInlineNode(child)) {\n\t\t\t// InlineNodes are skipped for TextSegment extraction\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sliceFrom: number = Math.max(0, from - pos);\n\t\tconst sliceTo: number = Math.min(child.text.length, to - pos);\n\t\tconst text: string = child.text.slice(sliceFrom, sliceTo);\n\n\t\tif (text.length > 0) {\n\t\t\tsegments.push({ text, marks: child.marks });\n\t\t}\n\n\t\tpos = childEnd;\n\t}\n\n\treturn segments;\n}\n\n/** Returns content segments (text and inline) for a block range. */\nexport function getBlockContentSegmentsInRange(\n\tblock: BlockNode,\n\tfrom: number,\n\tto: number,\n): readonly ContentSegment[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tconst segments: ContentSegment[] = [];\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tconst childWidth: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tconst childEnd: number = pos + childWidth;\n\n\t\tif (childEnd <= from || pos >= to) {\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isInlineNode(child)) {\n\t\t\tsegments.push({ kind: 'inline', node: child });\n\t\t} else {\n\t\t\tconst sliceFrom: number = Math.max(0, from - pos);\n\t\t\tconst sliceTo: number = Math.min(child.text.length, to - pos);\n\t\t\tconst text: string = child.text.slice(sliceFrom, sliceTo);\n\t\t\tif (text.length > 0) {\n\t\t\t\tsegments.push({ kind: 'text', text, marks: child.marks });\n\t\t\t}\n\t\t}\n\n\t\tpos = childEnd;\n\t}\n\n\treturn segments;\n}\n\n// --- Type Guards ---\n\n/** Checks whether a value is a {@link TextNode}. */\nexport function isTextNode(node: unknown): node is TextNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\t(node as TextNode).type === 'text' &&\n\t\ttypeof (node as TextNode).text === 'string'\n\t);\n}\n\n/** Checks whether a value is an {@link InlineNode}. */\nexport function isInlineNode(node: unknown): node is InlineNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\t(node as InlineNode).type === 'inline' &&\n\t\ttypeof (node as InlineNode).inlineType === 'string'\n\t);\n}\n\n/** Checks whether a value is a {@link BlockNode}. */\nexport function isBlockNode(node: unknown): node is BlockNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\ttypeof (node as BlockNode).id === 'string' &&\n\t\ttypeof (node as BlockNode).type === 'string' &&\n\t\t(node as BlockNode).type !== ('text' as string) &&\n\t\t(node as BlockNode).type !== ('inline' as string) &&\n\t\tArray.isArray((node as BlockNode).children)\n\t);\n}\n\n// --- Child Node Helpers ---\n\n/** Returns true if a block has only inline content (TextNodes and InlineNodes). */\nexport function isLeafBlock(node: BlockNode): boolean {\n\treturn node.children.every((c) => isTextNode(c) || isInlineNode(c));\n}\n\n/** Returns only the TextNode children of a block. */\nexport function getTextChildren(node: BlockNode): readonly TextNode[] {\n\treturn node.children.filter((c): c is TextNode => isTextNode(c));\n}\n\n/** Returns the inline content children (TextNode | InlineNode) of a block. */\nexport function getInlineChildren(node: BlockNode): readonly (TextNode | InlineNode)[] {\n\treturn node.children.filter((c): c is TextNode | InlineNode => isTextNode(c) || isInlineNode(c));\n}\n\n/** Returns only the BlockNode children of a block. */\nexport function getBlockChildren(node: BlockNode): readonly BlockNode[] {\n\treturn node.children.filter((c): c is BlockNode => isBlockNode(c));\n}\n\n// --- Factory Functions ---\n\n/** Generates a unique block ID using crypto.randomUUID(). */\nexport function generateBlockId(): BlockId {\n\treturn `block-${crypto.randomUUID()}` as BlockId;\n}\n\n/** Creates a new empty {@link Document} with a single empty paragraph. */\nexport function createDocument(children?: readonly BlockNode[]): Document {\n\treturn {\n\t\tchildren: children ?? [createBlockNode('paragraph' as NodeTypeName)],\n\t};\n}\n\n/** Creates a new {@link BlockNode}. */\nexport function createBlockNode(\n\ttype: NodeTypeName,\n\tchildren?: readonly ChildNode[],\n\tid?: BlockId,\n\tattrs?: BlockAttrs,\n): BlockNode {\n\treturn {\n\t\tid: id ?? generateBlockId(),\n\t\ttype,\n\t\t...(attrs ? { attrs } : {}),\n\t\tchildren: children ?? [createTextNode('')],\n\t};\n}\n\n/** Creates a new {@link TextNode}. */\nexport function createTextNode(text: string, marks?: readonly Mark[]): TextNode {\n\treturn {\n\t\ttype: 'text',\n\t\ttext,\n\t\tmarks: marks ?? [],\n\t};\n}\n\n/** Creates a new {@link InlineNode}. */\nexport function createInlineNode(\n\tinlineType: InlineTypeName,\n\tattrs?: Readonly<Record<string, string | number | boolean>>,\n): InlineNode {\n\treturn {\n\t\ttype: 'inline',\n\t\tinlineType,\n\t\tattrs: attrs ?? {},\n\t};\n}\n\n// --- Utility Functions ---\n\n/** Extracts plain text from a block (InlineNodes are skipped). */\nexport function getBlockText(block: BlockNode): string {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet text = '';\n\tfor (const child of inlineChildren) {\n\t\tif (isTextNode(child)) {\n\t\t\ttext += child.text;\n\t\t}\n\t}\n\treturn text;\n}\n\n/** Returns the length of a block's inline content (InlineNodes count as 1). */\nexport function getBlockLength(block: BlockNode): number {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet len = 0;\n\tfor (const child of inlineChildren) {\n\t\tlen += isInlineNode(child) ? 1 : child.text.length;\n\t}\n\treturn len;\n}\n\n/** Returns the marks active at the given offset (empty for InlineNode offsets). */\nexport function getBlockMarksAtOffset(block: BlockNode, offset: number): readonly Mark[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tif (isInlineNode(child)) {\n\t\t\tif (offset === pos) return [];\n\t\t\tpos += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tconst end: number = pos + child.text.length;\n\t\tif (offset >= pos && offset < end) {\n\t\t\treturn child.marks;\n\t\t}\n\t\tif (offset === pos && child.text.length === 0) {\n\t\t\treturn child.marks;\n\t\t}\n\t\tpos = end;\n\t}\n\n\t// Fall back to last text child's marks\n\tfor (let i: number = inlineChildren.length - 1; i >= 0; i--) {\n\t\tconst child: TextNode | InlineNode | undefined = inlineChildren[i];\n\t\tif (child && isTextNode(child)) {\n\t\t\treturn child.marks;\n\t\t}\n\t}\n\treturn [];\n}\n\n/** Checks whether two marks are equal by type and attrs. */\nexport function marksEqual(a: Mark, b: Mark): boolean {\n\tif (a.type !== b.type) return false;\n\tconst aAttrs = a.attrs;\n\tconst bAttrs = b.attrs;\n\tif (!aAttrs && !bAttrs) return true;\n\tif (!aAttrs || !bAttrs) return false;\n\tconst aKeys = Object.keys(aAttrs);\n\tconst bKeys = Object.keys(bAttrs);\n\tif (aKeys.length !== bKeys.length) return false;\n\treturn aKeys.every((key) => aAttrs[key] === bAttrs[key]);\n}\n\n/** Checks whether two mark arrays contain the same marks (order-independent). */\nexport function markSetsEqual(a: readonly Mark[], b: readonly Mark[]): boolean {\n\tif (a.length !== b.length) return false;\n\treturn a.every((markA) => b.some((markB) => marksEqual(markA, markB)));\n}\n\n/** Returns true if the mark set contains a mark of the given type. */\nexport function hasMark(marks: readonly Mark[], markType: MarkTypeName): boolean {\n\treturn marks.some((m) => m.type === markType);\n}\n\n/** Adds a mark to a mark set (no duplicates). */\nexport function addMarkToSet(marks: readonly Mark[], mark: Mark): readonly Mark[] {\n\tif (hasMark(marks, mark.type)) return marks;\n\treturn [...marks, mark];\n}\n\n/** Removes a mark type from a mark set. */\nexport function removeMarkFromSet(marks: readonly Mark[], markType: MarkTypeName): readonly Mark[] {\n\treturn marks.filter((m) => m.type !== markType);\n}\n\n/**\n * Normalizes text nodes within a block: merges adjacent nodes with identical marks,\n * removes empty text nodes (keeping at least one).\n */\nexport function normalizeTextNodes(nodes: readonly TextNode[]): readonly TextNode[] {\n\tif (nodes.length === 0) return [createTextNode('')];\n\n\tconst result: TextNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst prev: TextNode | undefined = result[result.length - 1];\n\t\tif (prev && markSetsEqual(prev.marks, node.marks)) {\n\t\t\tresult[result.length - 1] = createTextNode(prev.text + node.text, prev.marks);\n\t\t} else if (node.text.length > 0 || result.length === 0) {\n\t\t\tresult.push(node);\n\t\t}\n\t}\n\n\treturn result.length === 0 ? [createTextNode('')] : result;\n}\n\n/**\n * Normalizes mixed inline content: merges adjacent TextNodes with same marks,\n * removes empty TextNodes adjacent to InlineNodes, preserves InlineNodes as-is.\n * Guarantees at least one TextNode exists if all text is empty.\n */\nexport function normalizeInlineContent(\n\tnodes: readonly (TextNode | InlineNode)[],\n): readonly (TextNode | InlineNode)[] {\n\tif (nodes.length === 0) return [createTextNode('')];\n\n\t// Fast path: no InlineNodes, delegate to normalizeTextNodes\n\tif (nodes.every((n): n is TextNode => isTextNode(n))) {\n\t\treturn normalizeTextNodes(nodes);\n\t}\n\n\tconst result: (TextNode | InlineNode)[] = [];\n\n\tfor (const node of nodes) {\n\t\tif (isInlineNode(node)) {\n\t\t\tresult.push(node);\n\t\t\tcontinue;\n\t\t}\n\t\t// TextNode: try to merge with previous TextNode\n\t\tconst prev: TextNode | InlineNode | undefined = result[result.length - 1];\n\t\tif (prev && isTextNode(prev) && markSetsEqual(prev.marks, node.marks)) {\n\t\t\tresult[result.length - 1] = createTextNode(prev.text + node.text, prev.marks);\n\t\t} else if (node.text.length > 0 || result.length === 0) {\n\t\t\tresult.push(node);\n\t\t}\n\t}\n\n\t// Remove empty TextNodes that are adjacent to InlineNodes\n\tconst cleaned: (TextNode | InlineNode)[] = result.filter((node, i) => {\n\t\tif (isInlineNode(node)) return true;\n\t\tif (node.text.length > 0) return true;\n\t\t// Keep if it's the only node\n\t\tif (result.length === 1) return true;\n\t\t// Remove empty TextNode if adjacent to InlineNode\n\t\tconst prev: TextNode | InlineNode | undefined = result[i - 1];\n\t\tconst next: TextNode | InlineNode | undefined = result[i + 1];\n\t\tif ((prev && isInlineNode(prev)) || (next && isInlineNode(next))) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t});\n\n\t// Ensure at least one TextNode exists\n\tif (cleaned.length === 0) return [createTextNode('')];\n\tif (!cleaned.some((n) => isTextNode(n))) {\n\t\treturn [createTextNode(''), ...cleaned];\n\t}\n\n\treturn cleaned;\n}\n\n/** Yields each inline child with its offset range. InlineNodes have width 1. */\nexport function* walkInlineContent(children: readonly (TextNode | InlineNode)[]): Generator<{\n\treadonly child: TextNode | InlineNode;\n\treadonly from: number;\n\treadonly to: number;\n}> {\n\tlet pos = 0;\n\tfor (const child of children) {\n\t\tconst width: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tyield { child, from: pos, to: pos + width };\n\t\tpos += width;\n\t}\n}\n\n/** Returns the content at a specific offset: text char, inline node, or null. */\nexport function getContentAtOffset(\n\tblock: BlockNode,\n\toffset: number,\n):\n\t| { readonly kind: 'text'; readonly char: string; readonly marks: readonly Mark[] }\n\t| { readonly kind: 'inline'; readonly node: InlineNode }\n\t| null {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\n\tfor (const { child, from } of walkInlineContent(inlineChildren)) {\n\t\tif (isInlineNode(child)) {\n\t\t\tif (offset === from) return { kind: 'inline', node: child };\n\t\t\tcontinue;\n\t\t}\n\t\tconst localOffset: number = offset - from;\n\t\tif (localOffset >= 0 && localOffset < child.text.length) {\n\t\t\treturn {\n\t\t\t\tkind: 'text',\n\t\t\t\tchar: child.text[localOffset] ?? '',\n\t\t\t\tmarks: child.marks,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"names":["getBlockSegmentsInRange","block","from","to","inlineChildren","getInlineChildren","segments","pos","child","childEnd","isInlineNode","text","length","sliceFrom","Math","max","sliceTo","min","slice","push","marks","isTextNode","node","type","inlineType","isBlockNode","id","Array","isArray","children","isLeafBlock","every","c","getTextChildren","filter","getBlockChildren","generateBlockId","crypto","randomUUID","createDocument","createBlockNode","attrs","createTextNode","createInlineNode","getBlockText","getBlockLength","len","getBlockMarksAtOffset","offset","end","i","markSetsEqual","a","b","markA","some","markB","aAttrs","bAttrs","aKeys","Object","keys","bKeys","key","hasMark","markType","m","addMarkToSet","mark","removeMarkFromSet","normalizeInlineContent","nodes","n","result","prev","cleaned","next","getContentAtOffset","width","kind","localOffset","char"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Document-CuwE73FF.mjs","sources":["../../src/model/Document.ts"],"sourcesContent":["/**\n * Core document model types for the Notectl editor.\n * All types are deeply readonly — mutations create new instances.\n */\n\nimport type { BlockId, InlineTypeName, MarkTypeName, NodeTypeName } from './TypeBrands.js';\n\n// --- Mark Types ---\n\n/** @deprecated Use {@link MarkTypeName} for new code. */\nexport type MarkType = MarkTypeName;\n\nexport interface Mark {\n\treadonly type: MarkTypeName;\n\treadonly attrs?: Readonly<Record<string, string | number | boolean>>;\n}\n\nexport interface BoldMark extends Mark {\n\treadonly type: MarkTypeName & 'bold';\n}\n\nexport interface ItalicMark extends Mark {\n\treadonly type: MarkTypeName & 'italic';\n}\n\nexport interface UnderlineMark extends Mark {\n\treadonly type: MarkTypeName & 'underline';\n}\n\n// --- Node Types ---\n\n/** @deprecated Use {@link NodeTypeName} for new code. */\nexport type NodeType = NodeTypeName;\n\nexport interface BlockAttrs {\n\treadonly [key: string]: string | number | boolean;\n}\n\nexport interface TextNode {\n\treadonly type: 'text';\n\treadonly text: string;\n\treadonly marks: readonly Mark[];\n}\n\n/** Atomic inline element (width 1 in offset space). */\nexport interface InlineNode {\n\treadonly type: 'inline';\n\treadonly inlineType: InlineTypeName;\n\treadonly attrs: Readonly<Record<string, string | number | boolean>>;\n}\n\n/** A child of a BlockNode: text, inline element, or nested block. */\nexport type ChildNode = TextNode | InlineNode | BlockNode;\n\nexport interface BlockNode {\n\treadonly id: BlockId;\n\treadonly type: NodeTypeName;\n\treadonly attrs?: BlockAttrs;\n\treadonly children: readonly ChildNode[];\n}\n\nexport interface Document {\n\treadonly children: readonly BlockNode[];\n}\n\n// --- Text Segment (for mark-preserving undo) ---\n\nexport interface TextSegment {\n\treadonly text: string;\n\treadonly marks: readonly Mark[];\n}\n\n/** A content segment: either text (with marks) or an inline node. */\nexport type ContentSegment =\n\t| { readonly kind: 'text'; readonly text: string; readonly marks: readonly Mark[] }\n\t| { readonly kind: 'inline'; readonly node: InlineNode };\n\n/** Extracts TextNode segments within a block for the given offset range. */\nexport function getBlockSegmentsInRange(\n\tblock: BlockNode,\n\tfrom: number,\n\tto: number,\n): readonly TextSegment[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tconst segments: TextSegment[] = [];\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tconst childWidth: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tconst childEnd: number = pos + childWidth;\n\n\t\tif (childEnd <= from || pos >= to) {\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isInlineNode(child)) {\n\t\t\t// InlineNodes are skipped for TextSegment extraction\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sliceFrom: number = Math.max(0, from - pos);\n\t\tconst sliceTo: number = Math.min(child.text.length, to - pos);\n\t\tconst text: string = child.text.slice(sliceFrom, sliceTo);\n\n\t\tif (text.length > 0) {\n\t\t\tsegments.push({ text, marks: child.marks });\n\t\t}\n\n\t\tpos = childEnd;\n\t}\n\n\treturn segments;\n}\n\n/** Returns content segments (text and inline) for a block range. */\nexport function getBlockContentSegmentsInRange(\n\tblock: BlockNode,\n\tfrom: number,\n\tto: number,\n): readonly ContentSegment[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tconst segments: ContentSegment[] = [];\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tconst childWidth: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tconst childEnd: number = pos + childWidth;\n\n\t\tif (childEnd <= from || pos >= to) {\n\t\t\tpos = childEnd;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isInlineNode(child)) {\n\t\t\tsegments.push({ kind: 'inline', node: child });\n\t\t} else {\n\t\t\tconst sliceFrom: number = Math.max(0, from - pos);\n\t\t\tconst sliceTo: number = Math.min(child.text.length, to - pos);\n\t\t\tconst text: string = child.text.slice(sliceFrom, sliceTo);\n\t\t\tif (text.length > 0) {\n\t\t\t\tsegments.push({ kind: 'text', text, marks: child.marks });\n\t\t\t}\n\t\t}\n\n\t\tpos = childEnd;\n\t}\n\n\treturn segments;\n}\n\n// --- Type Guards ---\n\n/** Checks whether a value is a {@link TextNode}. */\nexport function isTextNode(node: unknown): node is TextNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\t(node as TextNode).type === 'text' &&\n\t\ttypeof (node as TextNode).text === 'string'\n\t);\n}\n\n/** Checks whether a value is an {@link InlineNode}. */\nexport function isInlineNode(node: unknown): node is InlineNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\t(node as InlineNode).type === 'inline' &&\n\t\ttypeof (node as InlineNode).inlineType === 'string'\n\t);\n}\n\n/** Checks whether a value is a {@link BlockNode}. */\nexport function isBlockNode(node: unknown): node is BlockNode {\n\treturn (\n\t\ttypeof node === 'object' &&\n\t\tnode !== null &&\n\t\ttypeof (node as BlockNode).id === 'string' &&\n\t\ttypeof (node as BlockNode).type === 'string' &&\n\t\t(node as BlockNode).type !== ('text' as string) &&\n\t\t(node as BlockNode).type !== ('inline' as string) &&\n\t\tArray.isArray((node as BlockNode).children)\n\t);\n}\n\n// --- Child Node Helpers ---\n\n/** Returns true if a block has only inline content (TextNodes and InlineNodes). */\nexport function isLeafBlock(node: BlockNode): boolean {\n\treturn node.children.every((c) => isTextNode(c) || isInlineNode(c));\n}\n\n/** Returns only the TextNode children of a block. */\nexport function getTextChildren(node: BlockNode): readonly TextNode[] {\n\treturn node.children.filter((c): c is TextNode => isTextNode(c));\n}\n\n/** Returns the inline content children (TextNode | InlineNode) of a block. */\nexport function getInlineChildren(node: BlockNode): readonly (TextNode | InlineNode)[] {\n\treturn node.children.filter((c): c is TextNode | InlineNode => isTextNode(c) || isInlineNode(c));\n}\n\n/** Returns only the BlockNode children of a block. */\nexport function getBlockChildren(node: BlockNode): readonly BlockNode[] {\n\treturn node.children.filter((c): c is BlockNode => isBlockNode(c));\n}\n\n// --- Factory Functions ---\n\n/** Generates a unique block ID using crypto.randomUUID(). */\nexport function generateBlockId(): BlockId {\n\treturn `block-${crypto.randomUUID()}` as BlockId;\n}\n\n/** Creates a new empty {@link Document} with a single empty paragraph. */\nexport function createDocument(children?: readonly BlockNode[]): Document {\n\treturn {\n\t\tchildren: children ?? [createBlockNode('paragraph' as NodeTypeName)],\n\t};\n}\n\n/** Creates a new {@link BlockNode}. */\nexport function createBlockNode(\n\ttype: NodeTypeName,\n\tchildren?: readonly ChildNode[],\n\tid?: BlockId,\n\tattrs?: BlockAttrs,\n): BlockNode {\n\treturn {\n\t\tid: id ?? generateBlockId(),\n\t\ttype,\n\t\t...(attrs ? { attrs } : {}),\n\t\tchildren: children ?? [createTextNode('')],\n\t};\n}\n\n/** Creates a new {@link TextNode}. */\nexport function createTextNode(text: string, marks?: readonly Mark[]): TextNode {\n\treturn {\n\t\ttype: 'text',\n\t\ttext,\n\t\tmarks: marks ?? [],\n\t};\n}\n\n/** Creates a new {@link InlineNode}. */\nexport function createInlineNode(\n\tinlineType: InlineTypeName,\n\tattrs?: Readonly<Record<string, string | number | boolean>>,\n): InlineNode {\n\treturn {\n\t\ttype: 'inline',\n\t\tinlineType,\n\t\tattrs: attrs ?? {},\n\t};\n}\n\n// --- Utility Functions ---\n\n/** Extracts plain text from a block (InlineNodes are skipped). */\nexport function getBlockText(block: BlockNode): string {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet text = '';\n\tfor (const child of inlineChildren) {\n\t\tif (isTextNode(child)) {\n\t\t\ttext += child.text;\n\t\t}\n\t}\n\treturn text;\n}\n\n/** Returns the length of a block's inline content (InlineNodes count as 1). */\nexport function getBlockLength(block: BlockNode): number {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet len = 0;\n\tfor (const child of inlineChildren) {\n\t\tlen += isInlineNode(child) ? 1 : child.text.length;\n\t}\n\treturn len;\n}\n\n/** Returns the marks active at the given offset (empty for InlineNode offsets). */\nexport function getBlockMarksAtOffset(block: BlockNode, offset: number): readonly Mark[] {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet pos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tif (isInlineNode(child)) {\n\t\t\tif (offset === pos) return [];\n\t\t\tpos += 1;\n\t\t\tcontinue;\n\t\t}\n\t\tconst end: number = pos + child.text.length;\n\t\tif (offset >= pos && offset < end) {\n\t\t\treturn child.marks;\n\t\t}\n\t\tif (offset === pos && child.text.length === 0) {\n\t\t\treturn child.marks;\n\t\t}\n\t\tpos = end;\n\t}\n\n\t// Fall back to last text child's marks\n\tfor (let i: number = inlineChildren.length - 1; i >= 0; i--) {\n\t\tconst child: TextNode | InlineNode | undefined = inlineChildren[i];\n\t\tif (child && isTextNode(child)) {\n\t\t\treturn child.marks;\n\t\t}\n\t}\n\treturn [];\n}\n\n/** Checks whether two marks are equal by type and attrs. */\nexport function marksEqual(a: Mark, b: Mark): boolean {\n\tif (a.type !== b.type) return false;\n\tconst aAttrs = a.attrs;\n\tconst bAttrs = b.attrs;\n\tif (!aAttrs && !bAttrs) return true;\n\tif (!aAttrs || !bAttrs) return false;\n\tconst aKeys = Object.keys(aAttrs);\n\tconst bKeys = Object.keys(bAttrs);\n\tif (aKeys.length !== bKeys.length) return false;\n\treturn aKeys.every((key) => aAttrs[key] === bAttrs[key]);\n}\n\n/** Checks whether two mark arrays contain the same marks (order-independent). */\nexport function markSetsEqual(a: readonly Mark[], b: readonly Mark[]): boolean {\n\tif (a.length !== b.length) return false;\n\treturn a.every((markA) => b.some((markB) => marksEqual(markA, markB)));\n}\n\n/** Returns true if the mark set contains a mark of the given type. */\nexport function hasMark(marks: readonly Mark[], markType: MarkTypeName): boolean {\n\treturn marks.some((m) => m.type === markType);\n}\n\n/** Adds a mark to a mark set (no duplicates). */\nexport function addMarkToSet(marks: readonly Mark[], mark: Mark): readonly Mark[] {\n\tif (hasMark(marks, mark.type)) return marks;\n\treturn [...marks, mark];\n}\n\n/** Removes a mark type from a mark set. */\nexport function removeMarkFromSet(marks: readonly Mark[], markType: MarkTypeName): readonly Mark[] {\n\treturn marks.filter((m) => m.type !== markType);\n}\n\n/**\n * Normalizes text nodes within a block: merges adjacent nodes with identical marks,\n * removes empty text nodes (keeping at least one).\n */\nexport function normalizeTextNodes(nodes: readonly TextNode[]): readonly TextNode[] {\n\tif (nodes.length === 0) return [createTextNode('')];\n\n\tconst result: TextNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst prev: TextNode | undefined = result[result.length - 1];\n\t\tif (prev && markSetsEqual(prev.marks, node.marks)) {\n\t\t\tresult[result.length - 1] = createTextNode(prev.text + node.text, prev.marks);\n\t\t} else if (node.text.length > 0 || result.length === 0) {\n\t\t\tresult.push(node);\n\t\t}\n\t}\n\n\treturn result.length === 0 ? [createTextNode('')] : result;\n}\n\n/**\n * Normalizes mixed inline content: merges adjacent TextNodes with same marks,\n * removes empty TextNodes adjacent to InlineNodes, preserves InlineNodes as-is.\n * Guarantees at least one TextNode exists if all text is empty.\n */\nexport function normalizeInlineContent(\n\tnodes: readonly (TextNode | InlineNode)[],\n): readonly (TextNode | InlineNode)[] {\n\tif (nodes.length === 0) return [createTextNode('')];\n\n\t// Fast path: no InlineNodes, delegate to normalizeTextNodes\n\tif (nodes.every((n): n is TextNode => isTextNode(n))) {\n\t\treturn normalizeTextNodes(nodes);\n\t}\n\n\tconst result: (TextNode | InlineNode)[] = [];\n\n\tfor (const node of nodes) {\n\t\tif (isInlineNode(node)) {\n\t\t\tresult.push(node);\n\t\t\tcontinue;\n\t\t}\n\t\t// TextNode: try to merge with previous TextNode\n\t\tconst prev: TextNode | InlineNode | undefined = result[result.length - 1];\n\t\tif (prev && isTextNode(prev) && markSetsEqual(prev.marks, node.marks)) {\n\t\t\tresult[result.length - 1] = createTextNode(prev.text + node.text, prev.marks);\n\t\t} else if (node.text.length > 0 || result.length === 0) {\n\t\t\tresult.push(node);\n\t\t}\n\t}\n\n\t// Remove empty TextNodes that are adjacent to InlineNodes\n\tconst cleaned: (TextNode | InlineNode)[] = result.filter((node, i) => {\n\t\tif (isInlineNode(node)) return true;\n\t\tif (node.text.length > 0) return true;\n\t\t// Keep if it's the only node\n\t\tif (result.length === 1) return true;\n\t\t// Remove empty TextNode if adjacent to InlineNode\n\t\tconst prev: TextNode | InlineNode | undefined = result[i - 1];\n\t\tconst next: TextNode | InlineNode | undefined = result[i + 1];\n\t\tif ((prev && isInlineNode(prev)) || (next && isInlineNode(next))) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t});\n\n\t// Ensure at least one TextNode exists\n\tif (cleaned.length === 0) return [createTextNode('')];\n\tif (!cleaned.some((n) => isTextNode(n))) {\n\t\treturn [createTextNode(''), ...cleaned];\n\t}\n\n\treturn cleaned;\n}\n\n/** Yields each inline child with its offset range. InlineNodes have width 1. */\nexport function* walkInlineContent(children: readonly (TextNode | InlineNode)[]): Generator<{\n\treadonly child: TextNode | InlineNode;\n\treadonly from: number;\n\treadonly to: number;\n}> {\n\tlet pos = 0;\n\tfor (const child of children) {\n\t\tconst width: number = isInlineNode(child) ? 1 : child.text.length;\n\t\tyield { child, from: pos, to: pos + width };\n\t\tpos += width;\n\t}\n}\n\n/**\n * Converts a block-space offset to a text-space offset.\n *\n * Block-space counts InlineNodes as width 1, but `getBlockText()` skips them.\n * This function maps a block offset to the equivalent position in the\n * text-only string returned by `getBlockText()`.\n */\nexport function blockOffsetToTextOffset(block: BlockNode, blockOffset: number): number {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\tlet blockPos = 0;\n\tlet textPos = 0;\n\n\tfor (const child of inlineChildren) {\n\t\tif (isInlineNode(child)) {\n\t\t\tif (blockPos >= blockOffset) return textPos;\n\t\t\tblockPos += 1;\n\t\t} else {\n\t\t\tif (blockPos + child.text.length >= blockOffset) {\n\t\t\t\treturn textPos + (blockOffset - blockPos);\n\t\t\t}\n\t\t\tblockPos += child.text.length;\n\t\t\ttextPos += child.text.length;\n\t\t}\n\t}\n\n\treturn textPos;\n}\n\n/** Returns the content at a specific offset: text char, inline node, or null. */\nexport function getContentAtOffset(\n\tblock: BlockNode,\n\toffset: number,\n):\n\t| { readonly kind: 'text'; readonly char: string; readonly marks: readonly Mark[] }\n\t| { readonly kind: 'inline'; readonly node: InlineNode }\n\t| null {\n\tconst inlineChildren: readonly (TextNode | InlineNode)[] = getInlineChildren(block);\n\n\tfor (const { child, from } of walkInlineContent(inlineChildren)) {\n\t\tif (isInlineNode(child)) {\n\t\t\tif (offset === from) return { kind: 'inline', node: child };\n\t\t\tcontinue;\n\t\t}\n\t\tconst localOffset: number = offset - from;\n\t\tif (localOffset >= 0 && localOffset < child.text.length) {\n\t\t\treturn {\n\t\t\t\tkind: 'text',\n\t\t\t\tchar: child.text[localOffset] ?? '',\n\t\t\t\tmarks: child.marks,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"names":["getBlockSegmentsInRange","block","from","to","inlineChildren","getInlineChildren","segments","pos","child","childEnd","isInlineNode","text","length","sliceFrom","Math","max","sliceTo","min","slice","push","marks","isTextNode","node","type","inlineType","isBlockNode","id","Array","isArray","children","isLeafBlock","every","c","getTextChildren","filter","getBlockChildren","generateBlockId","crypto","randomUUID","createDocument","createBlockNode","attrs","createTextNode","createInlineNode","getBlockText","getBlockLength","len","getBlockMarksAtOffset","offset","end","i","markSetsEqual","a","b","markA","some","markB","aAttrs","bAttrs","aKeys","Object","keys","bKeys","key","hasMark","markType","m","addMarkToSet","mark","removeMarkFromSet","normalizeInlineContent","nodes","n","result","prev","cleaned","next","blockOffsetToTextOffset","blockOffset","blockPos","textPos","getContentAtOffset","width","kind","localOffset","char"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function t(t2) {
|
|
2
|
+
return t2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3
|
+
}
|
|
4
|
+
const e = /* @__PURE__ */ new Set(["p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "blockquote", "table", "thead", "tbody", "tfoot", "tr", "td", "th", "pre", "figure", "figcaption", "section", "article", "header", "footer"]), n = /* @__PURE__ */ new Set(["br", "hr", "img", "input", "col", "area", "meta", "link"]);
|
|
5
|
+
function o(t2, o2 = " ") {
|
|
6
|
+
const a = (function(t3) {
|
|
7
|
+
const e2 = [], n2 = /(<\/?[a-zA-Z][^>]*\/?>)/g;
|
|
8
|
+
let o3 = 0, r2 = n2.exec(t3);
|
|
9
|
+
for (; null !== r2; ) {
|
|
10
|
+
const a3 = t3.slice(o3, r2.index);
|
|
11
|
+
a3.length > 0 && e2.push(a3), e2.push(r2[1] ?? r2[0]), o3 = n2.lastIndex, r2 = n2.exec(t3);
|
|
12
|
+
}
|
|
13
|
+
const a2 = t3.slice(o3);
|
|
14
|
+
return a2.length > 0 && e2.push(a2), e2;
|
|
15
|
+
})(t2), s = [];
|
|
16
|
+
let c = 0, h = "";
|
|
17
|
+
const l = () => {
|
|
18
|
+
const t3 = h.trim();
|
|
19
|
+
t3.length > 0 && s.push(`${o2.repeat(c)}${t3}`), h = "";
|
|
20
|
+
};
|
|
21
|
+
for (const t3 of a) {
|
|
22
|
+
const a2 = t3.startsWith("<") ? r(t3) : "", i = e.has(a2), u = n.has(a2);
|
|
23
|
+
i || u ? (l(), t3.startsWith("</") ? (c = Math.max(0, c - 1), s.push(`${o2.repeat(c)}${t3}`)) : (s.push(`${o2.repeat(c)}${t3}`), !i || u || t3.endsWith("/>") || (c += 1))) : h += t3;
|
|
24
|
+
}
|
|
25
|
+
return l(), s.join("\n");
|
|
26
|
+
}
|
|
27
|
+
function r(t2) {
|
|
28
|
+
const e2 = t2.match(/^<\/?([a-zA-Z][a-zA-Z0-9]*)/);
|
|
29
|
+
return e2?.[1]?.toLowerCase() ?? "";
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
t as e,
|
|
33
|
+
o as f
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=HTMLUtils-DTqQzcKR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HTMLUtils-DTqQzcKR.mjs","sources":["../../src/model/HTMLUtils.ts"],"sourcesContent":["/**\n * HTML string escaping and formatting utilities shared across serialization and parsing.\n */\n\n/** Escapes special HTML characters in text content. */\nexport function escapeHTML(text: string): string {\n\treturn text\n\t\t.replace(/&/g, '&')\n\t\t.replace(/</g, '<')\n\t\t.replace(/>/g, '>')\n\t\t.replace(/\"/g, '"');\n}\n\n/** Block-level HTML tags that should appear on their own line with indentation. */\nconst BLOCK_TAGS: ReadonlySet<string> = new Set([\n\t'p',\n\t'div',\n\t'h1',\n\t'h2',\n\t'h3',\n\t'h4',\n\t'h5',\n\t'h6',\n\t'ul',\n\t'ol',\n\t'li',\n\t'blockquote',\n\t'table',\n\t'thead',\n\t'tbody',\n\t'tfoot',\n\t'tr',\n\t'td',\n\t'th',\n\t'pre',\n\t'figure',\n\t'figcaption',\n\t'section',\n\t'article',\n\t'header',\n\t'footer',\n]);\n\n/** Void (self-closing) HTML tags that never have children. */\nconst VOID_TAGS: ReadonlySet<string> = new Set([\n\t'br',\n\t'hr',\n\t'img',\n\t'input',\n\t'col',\n\t'area',\n\t'meta',\n\t'link',\n]);\n\n/**\n * Pretty-prints an HTML string with indentation and line breaks for block-level elements.\n * Inline content (e.g. `<strong>bold</strong> text`) stays on a single line.\n */\nexport function formatHTML(html: string, indent = ' '): string {\n\tconst tokens: readonly string[] = tokenizeHTML(html);\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tlet inlineBuffer = '';\n\n\tconst flushInline = (): void => {\n\t\tconst trimmed: string = inlineBuffer.trim();\n\t\tif (trimmed.length > 0) {\n\t\t\tlines.push(`${indent.repeat(depth)}${trimmed}`);\n\t\t}\n\t\tinlineBuffer = '';\n\t};\n\n\tfor (const token of tokens) {\n\t\tconst tag: string = token.startsWith('<') ? extractTagName(token) : '';\n\t\tconst isBlock: boolean = BLOCK_TAGS.has(tag);\n\t\tconst isVoid: boolean = VOID_TAGS.has(tag);\n\n\t\tif (isBlock || isVoid) {\n\t\t\tflushInline();\n\n\t\t\tif (token.startsWith('</')) {\n\t\t\t\tdepth = Math.max(0, depth - 1);\n\t\t\t\tlines.push(`${indent.repeat(depth)}${token}`);\n\t\t\t} else {\n\t\t\t\tlines.push(`${indent.repeat(depth)}${token}`);\n\t\t\t\tif (isBlock && !isVoid && !token.endsWith('/>')) {\n\t\t\t\t\tdepth += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tinlineBuffer += token;\n\t\t}\n\t}\n\n\tflushInline();\n\treturn lines.join('\\n');\n}\n\n/** Splits HTML into a flat list of tokens: tags and text segments between block boundaries. */\nfunction tokenizeHTML(html: string): readonly string[] {\n\tconst tokens: string[] = [];\n\tconst tagPattern: RegExp = /(<\\/?[a-zA-Z][^>]*\\/?>)/g;\n\tlet lastIndex = 0;\n\n\tlet match: RegExpExecArray | null = tagPattern.exec(html);\n\twhile (match !== null) {\n\t\tconst text: string = html.slice(lastIndex, match.index);\n\t\tif (text.length > 0) {\n\t\t\ttokens.push(text);\n\t\t}\n\t\ttokens.push(match[1] ?? match[0]);\n\t\tlastIndex = tagPattern.lastIndex;\n\t\tmatch = tagPattern.exec(html);\n\t}\n\n\tconst remaining: string = html.slice(lastIndex);\n\tif (remaining.length > 0) {\n\t\ttokens.push(remaining);\n\t}\n\n\treturn tokens;\n}\n\n/** Extracts the tag name from an HTML tag string like `<p>` or `</div class=\"x\">`. */\nfunction extractTagName(tag: string): string {\n\tconst match: RegExpMatchArray | null = tag.match(/^<\\/?([a-zA-Z][a-zA-Z0-9]*)/);\n\treturn match?.[1]?.toLowerCase() ?? '';\n}\n"],"names":["escapeHTML","text","replace","BLOCK_TAGS","Set","VOID_TAGS","formatHTML","html","indent","tokens","tagPattern","lastIndex","match","exec","slice","index","length","push","remaining","lines","depth","inlineBuffer","flushInline","trimmed","trim","repeat","token","tag","startsWith","extractTagName","isBlock","has","isVoid","Math","max","endsWith","join","toLowerCase"],"mappings":"AAKO,SAASA,EAAWC,IAAAA;AAC1B,SAAOA,GACLC,QAAQ,MAAM,OAAA,EACdA,QAAQ,MAAM,MAAA,EACdA,QAAQ,MAAM,QACdA,QAAQ,MAAM,QAAA;AACjB;AAGA,MAAMC,wBAAsCC,IAAI,CAC/C,KACA,OACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,cACA,SACA,SACA,SACA,SACA,MACA,MACA,MACA,OACA,UACA,cACA,WACA,WACA,UACA,QAAA,CAAA,GAIKC,wBAAqCD,IAAI,CAC9C,MACA,MACA,OACA,SACA,OACA,QACA,QACA,MAAA,CAAA;AAOM,SAASE,EAAWC,IAAcC,KAAS,MAAA;AACjD,QAAMC,KAwCP,SAAsBF,IAAAA;AACrB,UAAME,KAAmB,CAAA,GACnBC,KAAqB;AAC3B,QAAIC,KAAY,GAEZC,KAAgCF,GAAWG,KAAKN,EAAAA;AACpD,WAAiB,SAAVK,MAAgB;AACtB,YAAMX,KAAeM,GAAKO,MAAMH,IAAWC,GAAMG,KAAAA;AAC7Cd,MAAAA,GAAKe,SAAS,KACjBP,GAAOQ,KAAKhB,EAAAA,GAEbQ,GAAOQ,KAAKL,GAAM,CAAA,KAAMA,GAAM,CAAA,CAAA,GAC9BD,KAAYD,GAAWC,WACvBC,KAAQF,GAAWG,KAAKN,EAAAA;AAAAA,IACzB;AAEA,UAAMW,KAAoBX,GAAKO,MAAMH,EAAAA;AAKrC,WAJIO,GAAUF,SAAS,KACtBP,GAAOQ,KAAKC,EAAAA,GAGNT;AAAAA,EACR,GA9DgDF,EAAAA,GACzCY,IAAkB;AACxB,MAAIC,IAAQ,GACRC,IAAe;AAEnB,QAAMC,IAAc,MAAA;AACnB,UAAMC,KAAkBF,EAAaG,KAAAA;AACjCD,IAAAA,GAAQP,SAAS,KACpBG,EAAMF,KAAK,GAAGT,GAAOiB,OAAOL,CAAAA,CAAAA,GAASG,EAAAA,EAAAA,GAEtCF,IAAe;AAAA,EAAA;AAGhB,aAAWK,MAASjB,GAAQ;AAC3B,UAAMkB,KAAcD,GAAME,WAAW,GAAA,IAAOC,EAAeH,EAAAA,IAAS,IAC9DI,IAAmB3B,EAAW4B,IAAIJ,EAAAA,GAClCK,IAAkB3B,EAAU0B,IAAIJ;AAElCG,SAAWE,KACdV,EAAAA,GAEII,GAAME,WAAW,IAAA,KACpBR,IAAQa,KAAKC,IAAI,GAAGd,IAAQ,CAAA,GAC5BD,EAAMF,KAAK,GAAGT,GAAOiB,OAAOL,CAAAA,CAAAA,GAASM,EAAAA,EAAAA,MAErCP,EAAMF,KAAK,GAAGT,GAAOiB,OAAOL,CAAAA,CAAAA,GAASM,EAAAA,EAAAA,GAAAA,CACjCI,KAAYE,KAAWN,GAAMS,SAAS,IAAA,MACzCf,KAAS,OAIXC,KAAgBK;AAAAA,EAElB;AAGA,SADAJ,EAAAA,GACOH,EAAMiB,KAAK,IAAA;AACnB;AA4BA,SAASP,EAAeF,IAAAA;AACvB,QAAMf,KAAiCe,GAAIf,MAAM,6BAAA;AACjD,SAAOA,KAAQ,CAAA,GAAIyB,YAAAA,KAAiB;AACrC;"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { h as e, g as n, b as t, c as r, d as o } from "./Document-CuwE73FF.mjs";
|
|
2
|
+
import { i as s, a as c, b as i, s as a } from "./Selection-DmFDwiPJ.mjs";
|
|
3
|
+
import { m as u } from "./TypeBrands-C09nE2qO.mjs";
|
|
4
|
+
import { f } from "./Commands-CicwYoTQ.mjs";
|
|
5
|
+
function l() {
|
|
6
|
+
return { nodeTypes: ["paragraph"], markTypes: ["bold", "italic", "underline"], getNodeSpec: () => {
|
|
7
|
+
} };
|
|
8
|
+
}
|
|
9
|
+
function d(e2) {
|
|
10
|
+
return { nodeTypes: e2.getNodeTypes(), markTypes: e2.getMarkTypes(), getNodeSpec: (n2) => e2.getNodeSpec(n2) };
|
|
11
|
+
}
|
|
12
|
+
function m(e2, n2) {
|
|
13
|
+
return e2.markTypes.includes(n2);
|
|
14
|
+
}
|
|
15
|
+
function k(e2, n2) {
|
|
16
|
+
return e2.nodeTypes.includes(n2);
|
|
17
|
+
}
|
|
18
|
+
const p = { bold: true, italic: true, underline: true };
|
|
19
|
+
function b(t2, r2, o2 = p) {
|
|
20
|
+
if ((function(e2, n2) {
|
|
21
|
+
const t3 = e2;
|
|
22
|
+
return "bold" === t3 ? !n2.bold : "italic" === t3 ? !n2.italic : "underline" === t3 && !n2.underline;
|
|
23
|
+
})(r2, o2)) return null;
|
|
24
|
+
if (!m(t2.schema, r2)) return null;
|
|
25
|
+
if (s(t2.selection) || c(t2.selection)) return null;
|
|
26
|
+
const u2 = { type: r2 }, l2 = t2.selection;
|
|
27
|
+
if (i(l2)) {
|
|
28
|
+
const o3 = t2.getBlock(l2.anchor.blockId);
|
|
29
|
+
if (!o3) return null;
|
|
30
|
+
const s2 = t2.storedMarks ?? n(o3, l2.anchor.offset), c2 = e(s2, r2) ? s2.filter((e2) => e2.type !== r2) : [...s2, u2];
|
|
31
|
+
return t2.transaction("command").setStoredMarks(c2, t2.storedMarks).setSelection(l2).build();
|
|
32
|
+
}
|
|
33
|
+
const d2 = a(l2, t2.getBlockOrder()), k2 = t2.transaction("command"), b2 = M(t2, r2);
|
|
34
|
+
return f(t2, d2, (e2, n2, t3) => {
|
|
35
|
+
b2 ? k2.removeMark(e2, n2, t3, u2) : k2.addMark(e2, n2, t3, u2);
|
|
36
|
+
}), k2.setSelection(l2), k2.build();
|
|
37
|
+
}
|
|
38
|
+
function g(e2, n2) {
|
|
39
|
+
return b(e2, u("bold"), n2);
|
|
40
|
+
}
|
|
41
|
+
function y(e2, n2) {
|
|
42
|
+
return b(e2, u("italic"), n2);
|
|
43
|
+
}
|
|
44
|
+
function T(e2, n2) {
|
|
45
|
+
return b(e2, u("underline"), n2);
|
|
46
|
+
}
|
|
47
|
+
function h(t2, r2) {
|
|
48
|
+
const o2 = t2.selection;
|
|
49
|
+
if (s(o2) || c(o2)) return false;
|
|
50
|
+
if (i(o2)) {
|
|
51
|
+
if (t2.storedMarks) return e(t2.storedMarks, r2);
|
|
52
|
+
const s2 = t2.getBlock(o2.anchor.blockId);
|
|
53
|
+
if (!s2) return false;
|
|
54
|
+
const c2 = n(s2, o2.anchor.offset);
|
|
55
|
+
return e(c2, r2);
|
|
56
|
+
}
|
|
57
|
+
return M(t2, r2);
|
|
58
|
+
}
|
|
59
|
+
function M(e2, n2) {
|
|
60
|
+
const r2 = e2.selection;
|
|
61
|
+
if (s(r2) || c(r2)) return false;
|
|
62
|
+
const o2 = e2.getBlockOrder(), i2 = a(r2, o2), u2 = o2.indexOf(i2.from.blockId), f2 = o2.indexOf(i2.to.blockId);
|
|
63
|
+
for (let r3 = u2; r3 <= f2; r3++) {
|
|
64
|
+
const s2 = o2[r3];
|
|
65
|
+
if (!s2) continue;
|
|
66
|
+
const c2 = e2.getBlock(s2);
|
|
67
|
+
if (!c2) continue;
|
|
68
|
+
const a2 = t(c2);
|
|
69
|
+
if (!S(c2, r3 === u2 ? i2.from.offset : 0, r3 === f2 ? i2.to.offset : a2, n2)) return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function S(n2, t2, s2, c2) {
|
|
74
|
+
if (t2 === s2) return false;
|
|
75
|
+
let i2 = 0;
|
|
76
|
+
for (const a2 of r(n2)) if (o(a2)) {
|
|
77
|
+
const n3 = i2 + a2.text.length;
|
|
78
|
+
if (n3 > t2 && i2 < s2 && !e(a2.marks, c2)) return false;
|
|
79
|
+
i2 = n3;
|
|
80
|
+
} else i2 += 1;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
k as a,
|
|
85
|
+
h as b,
|
|
86
|
+
y as c,
|
|
87
|
+
l as d,
|
|
88
|
+
b as e,
|
|
89
|
+
T as f,
|
|
90
|
+
m as i,
|
|
91
|
+
d as s,
|
|
92
|
+
g as t
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=MarkCommands-CC37WA7Z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkCommands-CC37WA7Z.mjs","sources":["../../src/model/Schema.ts","../../src/commands/MarkCommands.ts"],"sourcesContent":["/**\n * Schema definition for the Notectl editor.\n * Defines which node types and marks are allowed.\n */\n\nimport type { NodeSpec } from './NodeSpec.js';\nimport type { SchemaRegistry } from './SchemaRegistry.js';\n\nexport interface Schema {\n\treadonly nodeTypes: readonly string[];\n\treadonly markTypes: readonly string[];\n\t/** Looks up the full NodeSpec for a given type. Available when created via schemaFromRegistry. */\n\treadonly getNodeSpec?: (type: string) => NodeSpec | undefined;\n}\n\n/** Creates the default schema with paragraph nodes and bold/italic/underline marks. */\nexport function defaultSchema(): Schema {\n\treturn {\n\t\tnodeTypes: ['paragraph'],\n\t\tmarkTypes: ['bold', 'italic', 'underline'],\n\t\tgetNodeSpec: () => undefined,\n\t};\n}\n\n/** Derives a Schema from a SchemaRegistry's registered specs. */\nexport function schemaFromRegistry(registry: SchemaRegistry): Schema {\n\treturn {\n\t\tnodeTypes: registry.getNodeTypes(),\n\t\tmarkTypes: registry.getMarkTypes(),\n\t\tgetNodeSpec: (type: string) => registry.getNodeSpec(type),\n\t};\n}\n\n/** Checks whether the given mark type is allowed by the schema. */\nexport function isMarkAllowed(schema: Schema, markType: string): boolean {\n\treturn schema.markTypes.includes(markType);\n}\n\n/** Checks whether the given node type is allowed by the schema. */\nexport function isNodeTypeAllowed(schema: Schema, nodeType: string): boolean {\n\treturn schema.nodeTypes.includes(nodeType);\n}\n","/**\n * Mark-related commands: toggling bold/italic/underline and checking\n * whether a mark is active.\n */\n\nimport {\n\ttype BlockNode,\n\ttype Mark,\n\ttype MarkType,\n\tgetBlockLength,\n\tgetBlockMarksAtOffset,\n\tgetInlineChildren,\n\thasMark,\n\tisTextNode,\n} from '../model/Document.js';\nimport { isMarkAllowed } from '../model/Schema.js';\nimport { isCollapsed, isGapCursor, isNodeSelection, selectionRange } from '../model/Selection.js';\nimport { markType as mkType } from '../model/TypeBrands.js';\nimport type { EditorState } from '../state/EditorState.js';\nimport type { Transaction } from '../state/Transaction.js';\nimport { forEachBlockInRange } from './Commands.js';\n\n// --- Feature Configuration ---\n\nexport interface FeatureConfig {\n\treadonly bold: boolean;\n\treadonly italic: boolean;\n\treadonly underline: boolean;\n}\n\nconst defaultFeatures: FeatureConfig = { bold: true, italic: true, underline: true };\n\n/**\n * Toggles a mark on the current selection.\n * If collapsed, toggles stored marks. If range, applies/removes from text.\n */\nexport function toggleMark(\n\tstate: EditorState,\n\tmarkType: MarkType,\n\tfeatures: FeatureConfig = defaultFeatures,\n): Transaction | null {\n\tif (isFeatureGated(markType, features)) return null;\n\tif (!isMarkAllowed(state.schema, markType)) return null;\n\tif (isNodeSelection(state.selection) || isGapCursor(state.selection)) return null;\n\n\tconst mark: Mark = { type: markType };\n\tconst sel = state.selection;\n\n\tif (isCollapsed(sel)) {\n\t\t// Toggle stored marks\n\t\tconst anchorBlock = state.getBlock(sel.anchor.blockId);\n\t\tif (!anchorBlock) return null;\n\t\tconst currentMarks = state.storedMarks ?? getBlockMarksAtOffset(anchorBlock, sel.anchor.offset);\n\t\tconst hasIt = hasMark(currentMarks, markType);\n\t\tconst newMarks = hasIt\n\t\t\t? currentMarks.filter((m) => m.type !== markType)\n\t\t\t: [...currentMarks, mark];\n\n\t\treturn state\n\t\t\t.transaction('command')\n\t\t\t.setStoredMarks(newMarks, state.storedMarks)\n\t\t\t.setSelection(sel)\n\t\t\t.build();\n\t}\n\n\t// Range selection — apply/remove mark to all blocks in range\n\tconst range = selectionRange(sel, state.getBlockOrder());\n\tconst builder = state.transaction('command');\n\n\t// Determine if we should add or remove\n\tconst shouldRemove = isMarkActiveInRange(state, markType);\n\n\tforEachBlockInRange(state, range, (blockId, from, to) => {\n\t\tif (shouldRemove) {\n\t\t\tbuilder.removeMark(blockId, from, to, mark);\n\t\t} else {\n\t\t\tbuilder.addMark(blockId, from, to, mark);\n\t\t}\n\t});\n\n\tbuilder.setSelection(sel);\n\treturn builder.build();\n}\n\nexport function toggleBold(state: EditorState, features?: FeatureConfig): Transaction | null {\n\treturn toggleMark(state, mkType('bold'), features);\n}\n\nexport function toggleItalic(state: EditorState, features?: FeatureConfig): Transaction | null {\n\treturn toggleMark(state, mkType('italic'), features);\n}\n\nexport function toggleUnderline(state: EditorState, features?: FeatureConfig): Transaction | null {\n\treturn toggleMark(state, mkType('underline'), features);\n}\n\n/** Checks if a mark is active at the current selection. */\nexport function isMarkActive(state: EditorState, markType: MarkType): boolean {\n\tconst sel = state.selection;\n\tif (isNodeSelection(sel) || isGapCursor(sel)) return false;\n\n\tif (isCollapsed(sel)) {\n\t\tif (state.storedMarks) {\n\t\t\treturn hasMark(state.storedMarks, markType);\n\t\t}\n\t\tconst block = state.getBlock(sel.anchor.blockId);\n\t\tif (!block) return false;\n\t\tconst marks = getBlockMarksAtOffset(block, sel.anchor.offset);\n\t\treturn hasMark(marks, markType);\n\t}\n\n\treturn isMarkActiveInRange(state, markType);\n}\n\n/** Checks if a mark is active across the entire selection range. */\nfunction isMarkActiveInRange(state: EditorState, markType: MarkType): boolean {\n\tconst sel = state.selection;\n\tif (isNodeSelection(sel) || isGapCursor(sel)) return false;\n\tconst blockOrder = state.getBlockOrder();\n\tconst range = selectionRange(sel, blockOrder);\n\n\tconst fromIdx = blockOrder.indexOf(range.from.blockId);\n\tconst toIdx = blockOrder.indexOf(range.to.blockId);\n\n\tfor (let i = fromIdx; i <= toIdx; i++) {\n\t\tconst blockId = blockOrder[i];\n\t\tif (!blockId) continue;\n\t\tconst block = state.getBlock(blockId);\n\t\tif (!block) continue;\n\t\tconst blockLen = getBlockLength(block);\n\t\tconst from = i === fromIdx ? range.from.offset : 0;\n\t\tconst to = i === toIdx ? range.to.offset : blockLen;\n\n\t\tif (!isMarkActiveInBlock(block, from, to, markType)) return false;\n\t}\n\n\treturn true;\n}\n\nfunction isMarkActiveInBlock(\n\tblock: BlockNode,\n\tfrom: number,\n\tto: number,\n\tmarkType: MarkType,\n): boolean {\n\tif (from === to) return false;\n\tlet pos = 0;\n\tfor (const child of getInlineChildren(block)) {\n\t\tif (isTextNode(child)) {\n\t\t\tconst childEnd = pos + child.text.length;\n\t\t\tif (childEnd > from && pos < to) {\n\t\t\t\tif (!hasMark(child.marks, markType)) return false;\n\t\t\t}\n\t\t\tpos = childEnd;\n\t\t} else {\n\t\t\t// InlineNode: skip (width 1, no marks)\n\t\t\tpos += 1;\n\t\t}\n\t}\n\treturn true;\n}\n\nfunction isFeatureGated(type: MarkType, features: FeatureConfig): boolean {\n\tconst key = type as string;\n\tif (key === 'bold') return !features.bold;\n\tif (key === 'italic') return !features.italic;\n\tif (key === 'underline') return !features.underline;\n\treturn false;\n}\n"],"names":["defaultSchema","nodeTypes","markTypes","getNodeSpec","schemaFromRegistry","registry","getNodeTypes","getMarkTypes","type","isMarkAllowed","schema","markType","includes","isNodeTypeAllowed","nodeType","defaultFeatures","bold","italic","underline","toggleMark","state","features","key","isNodeSelection","selection","isGapCursor","mark","sel","isCollapsed","anchorBlock","getBlock","anchor","blockId","currentMarks","storedMarks","getBlockMarksAtOffset","offset","newMarks","hasMark","filter","m","transaction","setStoredMarks","setSelection","build","range","selectionRange","getBlockOrder","builder","shouldRemove","isMarkActiveInRange","forEachBlockInRange","from","to","removeMark","addMark","toggleBold","mkType","toggleItalic","toggleUnderline","isMarkActive","block","marks","blockOrder","fromIdx","indexOf","toIdx","i","blockLen","getBlockLength","isMarkActiveInBlock","pos","child","getInlineChildren","isTextNode","childEnd","text","length"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function t(t2, e2) {
|
|
2
|
+
const n2 = t2.getBlock(e2);
|
|
3
|
+
if (!n2) return false;
|
|
4
|
+
const o2 = t2.schema.getNodeSpec;
|
|
5
|
+
return !!o2 && true === o2(n2.type)?.isVoid;
|
|
6
|
+
}
|
|
7
|
+
function e(t2, e2, n2) {
|
|
8
|
+
const o2 = t2.getNodePath(e2), r2 = t2.getNodePath(n2);
|
|
9
|
+
if (!o2 || !r2) return false;
|
|
10
|
+
if (o2.length !== r2.length) return false;
|
|
11
|
+
for (let t3 = 0; t3 < o2.length - 1; t3++) if (o2[t3] !== r2[t3]) return false;
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
function n(t2, e2) {
|
|
15
|
+
const n2 = t2.schema.getNodeSpec;
|
|
16
|
+
if (!n2) return false;
|
|
17
|
+
const o2 = t2.getNodePath(e2);
|
|
18
|
+
if (!o2 || o2.length <= 1) return false;
|
|
19
|
+
for (let e3 = 0; e3 < o2.length - 1; e3++) {
|
|
20
|
+
const r2 = o2[e3];
|
|
21
|
+
if (!r2) continue;
|
|
22
|
+
const c = t2.getBlock(r2);
|
|
23
|
+
if (!c) continue;
|
|
24
|
+
const i = n2(c.type);
|
|
25
|
+
if (i?.isolating) return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function o(t2, e2) {
|
|
30
|
+
const n2 = t2.schema.getNodeSpec;
|
|
31
|
+
if (!n2) return false;
|
|
32
|
+
const o2 = t2.getBlock(e2);
|
|
33
|
+
return !!o2 && true === n2(o2.type)?.isolating;
|
|
34
|
+
}
|
|
35
|
+
function r(t2, r2, c) {
|
|
36
|
+
if (o(t2, r2) || o(t2, c)) return false;
|
|
37
|
+
const i = n(t2, r2), s = n(t2, c);
|
|
38
|
+
return i === s && (!i || !s || e(t2, r2, c));
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
o as a,
|
|
42
|
+
n as b,
|
|
43
|
+
r as c,
|
|
44
|
+
t as i,
|
|
45
|
+
e as s
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=NavigationUtils-CyW0Vo8A.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavigationUtils-CyW0Vo8A.mjs","sources":["../../src/model/NavigationUtils.ts"],"sourcesContent":["/**\n * Pure navigation query functions for block boundary and isolating checks.\n *\n * These utilities inspect the document structure and schema to answer\n * questions about block traversal — whether a block is void, isolating,\n * or whether navigation between two blocks is allowed.\n */\n\nimport type { EditorState } from '../state/EditorState.js';\nimport type { BlockId } from './TypeBrands.js';\n\n/** Returns true if the block with the given ID is a void block (e.g. image, HR). */\nexport function isVoidBlock(state: EditorState, bid: BlockId): boolean {\n\tconst block = state.getBlock(bid);\n\tif (!block) return false;\n\tconst getNodeSpec = state.schema.getNodeSpec;\n\tif (!getNodeSpec) return false;\n\treturn getNodeSpec(block.type)?.isVoid === true;\n}\n\n/** Checks whether two blocks share the same parent in the document tree. */\nexport function sharesParent(state: EditorState, blockIdA: BlockId, blockIdB: BlockId): boolean {\n\tconst pathA = state.getNodePath(blockIdA);\n\tconst pathB = state.getNodePath(blockIdB);\n\tif (!pathA || !pathB) return false;\n\tif (pathA.length !== pathB.length) return false;\n\t// Compare parent paths (all but last element)\n\tfor (let i = 0; i < pathA.length - 1; i++) {\n\t\tif (pathA[i] !== pathB[i]) return false;\n\t}\n\treturn true;\n}\n\n/** Checks whether a block is inside an isolating node (e.g. table_cell). */\nexport function isInsideIsolating(state: EditorState, blockId: BlockId): boolean {\n\tconst getNodeSpec = state.schema.getNodeSpec;\n\tif (!getNodeSpec) return false;\n\tconst path = state.getNodePath(blockId);\n\tif (!path || path.length <= 1) return false;\n\n\t// Check ancestors (not the block itself)\n\tfor (let i = 0; i < path.length - 1; i++) {\n\t\tconst ancestorId = path[i];\n\t\tif (!ancestorId) continue;\n\t\tconst ancestor = state.getBlock(ancestorId);\n\t\tif (!ancestor) continue;\n\t\tconst spec = getNodeSpec(ancestor.type);\n\t\tif (spec?.isolating) return true;\n\t}\n\treturn false;\n}\n\n/** Returns true if the block itself has the isolating property. */\nexport function isIsolatingBlock(state: EditorState, blockId: BlockId): boolean {\n\tconst getNodeSpec = state.schema.getNodeSpec;\n\tif (!getNodeSpec) return false;\n\tconst block = state.getBlock(blockId);\n\tif (!block) return false;\n\treturn getNodeSpec(block.type)?.isolating === true;\n}\n\n/**\n * Checks whether navigation between two blocks is allowed.\n * Prevents crossing isolating boundaries (e.g. table cells).\n */\nexport function canCrossBlockBoundary(state: EditorState, fromId: BlockId, toId: BlockId): boolean {\n\t// A block that is itself isolating cannot be crossed into via arrow keys\n\tif (isIsolatingBlock(state, fromId) || isIsolatingBlock(state, toId)) return false;\n\n\tconst fromInside: boolean = isInsideIsolating(state, fromId);\n\tconst toInside: boolean = isInsideIsolating(state, toId);\n\n\t// One inside isolating, the other not → disallow\n\tif (fromInside !== toInside) return false;\n\n\t// Both inside isolating → must share the same parent\n\tif (fromInside && toInside) return sharesParent(state, fromId, toId);\n\n\treturn true;\n}\n"],"names":["isVoidBlock","state","bid","block","getBlock","getNodeSpec","schema","type","isVoid","sharesParent","blockIdA","blockIdB","pathA","getNodePath","pathB","length","i","isInsideIsolating","blockId","path","ancestorId","ancestor","spec","isolating","isIsolatingBlock","canCrossBlockBoundary","fromId","toId","fromInside","toInside"],"mappings":"AAYO,SAASA,EAAYC,IAAoBC,IAAAA;AAC/C,QAAMC,KAAQF,GAAMG,SAASF;AAC7B,MAAA,CAAKC,GAAO;AACZ,QAAME,KAAcJ,GAAMK,OAAOD;AACjC,SAAA,CAAA,CAAKA,MAAAA,SACEA,GAAYF,GAAMI,IAAAA,GAAOC;AACjC;AAGO,SAASC,EAAaR,IAAoBS,IAAmBC;AACnE,QAAMC,KAAQX,GAAMY,YAAYH,EAAAA,GAC1BI,KAAQb,GAAMY,YAAYF;AAChC,MAAA,CAAKC,MAAAA,CAAUE,GAAO,QAAA;AACtB,MAAIF,GAAMG,WAAWD,GAAMC,OAAQ,QAAA;AAEnC,WAASC,KAAI,GAAGA,KAAIJ,GAAMG,SAAS,GAAGC,KACrC,KAAIJ,GAAMI,EAAAA,MAAOF,GAAME,EAAAA,EAAI,QAAA;AAE5B,SAAA;AACD;AAGO,SAASC,EAAkBhB,IAAoBiB;AACrD,QAAMb,KAAcJ,GAAMK,OAAOD;AACjC,OAAKA,GAAa,QAAA;AAClB,QAAMc,KAAOlB,GAAMY,YAAYK,EAAAA;AAC/B,OAAKC,MAAQA,GAAKJ,UAAU,EAAG,QAAA;AAG/B,WAASC,KAAI,GAAGA,KAAIG,GAAKJ,SAAS,GAAGC,MAAK;AACzC,UAAMI,KAAaD,GAAKH,EAAAA;AACxB,QAAA,CAAKI,GAAY;AACjB,UAAMC,IAAWpB,GAAMG,SAASgB;AAChC,QAAA,CAAKC,EAAU;AACf,UAAMC,IAAOjB,GAAYgB,EAASd,IAAAA;AAClC,QAAIe,GAAMC,UAAW,QAAA;AAAA,EACtB;AACA,SAAA;AACD;AAGO,SAASC,EAAiBvB,IAAoBiB,IAAAA;AACpD,QAAMb,KAAcJ,GAAMK,OAAOD;AACjC,MAAA,CAAKA,GAAa,QAAA;AAClB,QAAMF,KAAQF,GAAMG,SAASc;AAC7B,SAAA,CAAA,CAAKf,MAAAA,SACEE,GAAYF,GAAMI,OAAOgB;AACjC;AAMO,SAASE,EAAsBxB,IAAoByB,IAAiBC,GAAAA;AAE1E,MAAIH,EAAiBvB,IAAOyB,EAAAA,KAAWF,EAAiBvB,IAAO0B,CAAAA,EAAO,QAAA;AAEtE,QAAMC,IAAsBX,EAAkBhB,IAAOyB,EAAAA,GAC/CG,IAAoBZ,EAAkBhB,IAAO0B;AAGnD,SAAIC,MAAeC,OAGfD,KAAAA,CAAcC,KAAiBpB,EAAaR,IAAOyB,IAAQC;AAGhE;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as n } from "./Document-
|
|
1
|
+
import { a as n } from "./Document-CuwE73FF.mjs";
|
|
2
2
|
function i(i2, r2) {
|
|
3
3
|
if (0 === r2.length) return;
|
|
4
4
|
let t2 = i2.children.find((n2) => n2.id === r2[0]);
|
|
@@ -78,4 +78,4 @@ export {
|
|
|
78
78
|
i as r,
|
|
79
79
|
o as w
|
|
80
80
|
};
|
|
81
|
-
//# sourceMappingURL=NodeResolver-
|
|
81
|
+
//# sourceMappingURL=NodeResolver-B-wzthAQ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeResolver-
|
|
1
|
+
{"version":3,"file":"NodeResolver-B-wzthAQ.mjs","sources":["../../src/model/NodeResolver.ts"],"sourcesContent":["/**\n * Utilities for resolving nodes by path within the document tree.\n * Supports both flat and recursive (nested) document structures.\n */\n\nimport type { BlockNode, Document } from './Document.js';\nimport { isBlockNode } from './Document.js';\n\n/**\n * Resolves a node in the document tree by its path (array of block IDs).\n * The path traces the lineage from the root block down to the target.\n */\nexport function resolveNodeByPath(doc: Document, path: readonly string[]): BlockNode | undefined {\n\tif (path.length === 0) return undefined;\n\n\tlet current: BlockNode | undefined = doc.children.find((b) => b.id === path[0]);\n\tif (!current) return undefined;\n\n\tfor (let i = 1; i < path.length; i++) {\n\t\tconst childId = path[i];\n\t\tif (!childId) return undefined;\n\t\tconst next: BlockNode | undefined = current?.children.find(\n\t\t\t(c): c is BlockNode => isBlockNode(c) && c.id === childId,\n\t\t);\n\t\tif (!next) return undefined;\n\t\tcurrent = next;\n\t}\n\n\treturn current;\n}\n\n/**\n * Resolves the parent of a node at the given path.\n * Returns the parent container (Document or BlockNode) and the child's index within it.\n */\nexport function resolveParentByPath(\n\tdoc: Document,\n\tpath: readonly string[],\n): { parent: Document | BlockNode; index: number } | undefined {\n\tif (path.length === 0) return undefined;\n\n\tif (path.length === 1) {\n\t\tconst index = doc.children.findIndex((b) => b.id === path[0]);\n\t\tif (index === -1) return undefined;\n\t\treturn { parent: doc, index };\n\t}\n\n\tconst parentPath = path.slice(0, -1);\n\tconst parent = resolveNodeByPath(doc, parentPath);\n\tif (!parent) return undefined;\n\n\tconst childId = path[path.length - 1];\n\tif (!childId) return undefined;\n\tconst index = parent.children.findIndex(\n\t\t(c): c is BlockNode => isBlockNode(c) && c.id === childId,\n\t);\n\tif (index === -1) return undefined;\n\n\treturn { parent, index };\n}\n\n/**\n * Finds the path (array of block IDs) to a node by its ID.\n * Performs recursive DFS through the document tree.\n */\nexport function findNodePath(doc: Document, nodeId: string): string[] | undefined {\n\tfor (const block of doc.children) {\n\t\tif (block.id === nodeId) return [nodeId];\n\n\t\tconst subPath = findNodePathInBlock(block, nodeId);\n\t\tif (subPath) return [block.id, ...subPath];\n\t}\n\treturn undefined;\n}\n\nfunction findNodePathInBlock(block: BlockNode, nodeId: string): string[] | undefined {\n\tfor (const child of block.children) {\n\t\tif (!isBlockNode(child)) continue;\n\t\tif (child.id === nodeId) return [nodeId];\n\n\t\tconst subPath = findNodePathInBlock(child, nodeId);\n\t\tif (subPath) return [child.id, ...subPath];\n\t}\n\treturn undefined;\n}\n\n/**\n * Walks all block nodes in the document tree in depth-first order.\n * Visits every BlockNode, including nested ones, with their full path.\n */\nexport function walkNodes(\n\tdoc: Document,\n\tcallback: (node: BlockNode, path: string[]) => void,\n): void {\n\tfor (const block of doc.children) {\n\t\twalkNodeRecursive(block, [block.id], callback);\n\t}\n}\n\nfunction walkNodeRecursive(\n\tnode: BlockNode,\n\tpath: string[],\n\tcallback: (node: BlockNode, path: string[]) => void,\n): void {\n\tcallback(node, path);\n\tfor (const child of node.children) {\n\t\tif (isBlockNode(child)) {\n\t\t\twalkNodeRecursive(child, [...path, child.id], callback);\n\t\t}\n\t}\n}\n\n/**\n * Finds a block node by ID anywhere in the document tree (recursive DFS).\n */\nexport function findNode(doc: Document, nodeId: string): BlockNode | undefined {\n\tfor (const block of doc.children) {\n\t\tif (block.id === nodeId) return block;\n\t\tconst found = findNodeInBlock(block, nodeId);\n\t\tif (found) return found;\n\t}\n\treturn undefined;\n}\n\nfunction findNodeInBlock(block: BlockNode, nodeId: string): BlockNode | undefined {\n\tfor (const child of block.children) {\n\t\tif (!isBlockNode(child)) continue;\n\t\tif (child.id === nodeId) return child;\n\t\tconst found = findNodeInBlock(child, nodeId);\n\t\tif (found) return found;\n\t}\n\treturn undefined;\n}\n\n/**\n * Finds a block node and returns it together with its path.\n */\nexport function findNodeWithPath(\n\tdoc: Document,\n\tnodeId: string,\n): { node: BlockNode; path: string[] } | undefined {\n\tconst path = findNodePath(doc, nodeId);\n\tif (!path) return undefined;\n\tconst node = resolveNodeByPath(doc, path);\n\tif (!node) return undefined;\n\treturn { node, path };\n}\n"],"names":["resolveNodeByPath","doc","path","length","current","children","find","b","id","i","childId","next","c","isBlockNode","resolveParentByPath","index","findIndex","parent","slice","findNodePath","nodeId","block","subPath","findNodePathInBlock","child","walkNodes","callback","walkNodeRecursive","node","findNode","found","findNodeInBlock","findNodeWithPath"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
let t, e, n;
|
|
2
|
+
function r() {
|
|
3
|
+
if (void 0 !== t) return t;
|
|
4
|
+
if ("undefined" == typeof navigator) return t = false, false;
|
|
5
|
+
if ("userAgentData" in navigator) {
|
|
6
|
+
const e2 = navigator.userAgentData;
|
|
7
|
+
if (e2.platform) return t = /macOS/i.test(e2.platform), t;
|
|
8
|
+
}
|
|
9
|
+
return t = /Mac|iP(hone|[oa]d)/.test(navigator.platform), t;
|
|
10
|
+
}
|
|
11
|
+
function a() {
|
|
12
|
+
return void 0 !== e ? e : "undefined" == typeof navigator ? (e = false, false) : (e = /Firefox\//.test(navigator.userAgent), e);
|
|
13
|
+
}
|
|
14
|
+
function i() {
|
|
15
|
+
return void 0 !== n ? n : "undefined" == typeof navigator ? (n = false, false) : (n = /AppleWebKit\//.test(navigator.userAgent) && !/Chrome\//.test(navigator.userAgent), n);
|
|
16
|
+
}
|
|
17
|
+
function o(t2) {
|
|
18
|
+
return "rtl" === getComputedStyle(t2).direction ? "rtl" : "ltr";
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
a,
|
|
22
|
+
i as b,
|
|
23
|
+
o as g,
|
|
24
|
+
r as i
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=Platform-S_xqIqcM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Platform-S_xqIqcM.mjs","sources":["../../src/view/Platform.ts"],"sourcesContent":["/**\n * Platform and browser detection utilities.\n *\n * Determines the current platform (macOS/iOS) and browser engine, which\n * affects keyboard shortcut bindings and browser-specific workarounds.\n * Also provides BiDi text direction detection.\n *\n * All detection results are cached after first evaluation for performance.\n */\n\ninterface NavigatorUAData {\n\treadonly platform?: string;\n}\n\nlet cachedIsMac: boolean | undefined;\nlet cachedIsFirefox: boolean | undefined;\nlet cachedIsWebKit: boolean | undefined;\n\n/** Returns `true` when running on macOS or iOS. */\nexport function isMac(): boolean {\n\tif (cachedIsMac !== undefined) return cachedIsMac;\n\tif (typeof navigator === 'undefined') {\n\t\tcachedIsMac = false;\n\t\treturn false;\n\t}\n\tif ('userAgentData' in navigator) {\n\t\tconst uaData: NavigatorUAData = navigator.userAgentData as NavigatorUAData;\n\t\tif (uaData.platform) {\n\t\t\tcachedIsMac = /macOS/i.test(uaData.platform);\n\t\t\treturn cachedIsMac;\n\t\t}\n\t}\n\tcachedIsMac = /Mac|iP(hone|[oa]d)/.test(navigator.platform);\n\treturn cachedIsMac;\n}\n\n/**\n * Returns `true` when running on Firefox (Gecko engine).\n * Only use for genuine browser workarounds, not feature detection.\n */\nexport function isFirefox(): boolean {\n\tif (cachedIsFirefox !== undefined) return cachedIsFirefox;\n\tif (typeof navigator === 'undefined') {\n\t\tcachedIsFirefox = false;\n\t\treturn false;\n\t}\n\tcachedIsFirefox = /Firefox\\//.test(navigator.userAgent);\n\treturn cachedIsFirefox;\n}\n\n/**\n * Returns `true` when running on WebKit (Safari engine).\n * Only use for genuine browser workarounds, not feature detection.\n */\nexport function isWebKit(): boolean {\n\tif (cachedIsWebKit !== undefined) return cachedIsWebKit;\n\tif (typeof navigator === 'undefined') {\n\t\tcachedIsWebKit = false;\n\t\treturn false;\n\t}\n\tcachedIsWebKit =\n\t\t/AppleWebKit\\//.test(navigator.userAgent) && !/Chrome\\//.test(navigator.userAgent);\n\treturn cachedIsWebKit;\n}\n\n/** Returns the computed text direction of the given element. */\nexport function getTextDirection(element: HTMLElement): 'ltr' | 'rtl' {\n\tconst dir: string = getComputedStyle(element).direction;\n\treturn dir === 'rtl' ? 'rtl' : 'ltr';\n}\n\n/**\n * Resets cached detection results.\n * Only intended for use in tests.\n */\nexport function resetPlatformCache(): void {\n\tcachedIsMac = undefined;\n\tcachedIsFirefox = undefined;\n\tcachedIsWebKit = undefined;\n}\n"],"names":["cachedIsMac","cachedIsFirefox","cachedIsWebKit","isMac","navigator","uaData","userAgentData","platform","test","isFirefox","userAgent","isWebKit","getTextDirection","element","getComputedStyle","direction"],"mappings":"AAcA,IAAIA,GACAC,GACAC;AAGG,SAASC;AACf,MAAA,WAAIH,EAA2B,QAAOA;AACtC,MAAyB,eAAA,OAAdI,UAEV,QADAJ,IAAAA,OAAc;AAGf,MAAI,mBAAmBI,WAAW;AACjC,UAAMC,KAA0BD,UAAUE;AAC1C,QAAID,GAAOE,SAEV,QADAP,IAAc,SAASQ,KAAKH,GAAOE,QAAAA,GAC5BP;AAAAA,EAET;AAEA,SADAA,IAAc,qBAAqBQ,KAAKJ,UAAUG,WAC3CP;AACR;AAMO,SAASS,IAAAA;AACf,SAAA,WAAIR,IAAsCA,IACjB,eAAA,OAAdG,aACVH,IAAAA,OAAkB,UAGnBA,IAAkB,YAAYO,KAAKJ,UAAUM,SAAAA,GACtCT;AACR;AAMO,SAASU;AACf,SAAA,WAAIT,IAAqCA,IAChB,eAAA,OAAdE,aACVF,IAAAA,OAAiB,UAGlBA,IACC,gBAAgBM,KAAKJ,UAAUM,SAAAA,KAAAA,CAAe,WAAWF,KAAKJ,UAAUM,SAAAA,GAClER;AACR;AAGO,SAASU,EAAiBC;AAEhC,SAAe,UADKC,iBAAiBD,EAAAA,EAASE,YACvB,QAAQ;AAChC;"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function o(o2, n2) {
|
|
2
|
+
return { type: "node", nodeId: o2, path: n2 };
|
|
3
|
+
}
|
|
4
|
+
function n(o2, n2, e2) {
|
|
5
|
+
return { type: "gap", side: n2, blockId: o2, path: e2 };
|
|
6
|
+
}
|
|
7
|
+
function e(o2) {
|
|
8
|
+
return "type" in o2 && "node" === o2.type;
|
|
9
|
+
}
|
|
10
|
+
function t(o2) {
|
|
11
|
+
return "type" in o2 && "gap" === o2.type;
|
|
12
|
+
}
|
|
13
|
+
function a(o2) {
|
|
14
|
+
return !("type" in o2);
|
|
15
|
+
}
|
|
16
|
+
function c(o2, n2) {
|
|
17
|
+
return e(o2) && e(n2) ? o2.nodeId === n2.nodeId : t(o2) && t(n2) ? o2.blockId === n2.blockId && o2.side === n2.side : !(!a(o2) || !a(n2)) && o2.anchor.blockId === n2.anchor.blockId && o2.anchor.offset === n2.anchor.offset && o2.head.blockId === n2.head.blockId && o2.head.offset === n2.head.offset;
|
|
18
|
+
}
|
|
19
|
+
function d(o2, n2, e2) {
|
|
20
|
+
return e2 ? { blockId: o2, offset: n2, path: e2 } : { blockId: o2, offset: n2 };
|
|
21
|
+
}
|
|
22
|
+
function r(o2, n2) {
|
|
23
|
+
return { anchor: o2, head: n2 };
|
|
24
|
+
}
|
|
25
|
+
function f(o2, n2) {
|
|
26
|
+
const e2 = { blockId: o2, offset: n2 };
|
|
27
|
+
return { anchor: e2, head: e2 };
|
|
28
|
+
}
|
|
29
|
+
function h(o2) {
|
|
30
|
+
return !e(o2) && !t(o2) && o2.anchor.blockId === o2.head.blockId && o2.anchor.offset === o2.head.offset;
|
|
31
|
+
}
|
|
32
|
+
function s(o2, n2) {
|
|
33
|
+
return !(!e(o2) && !t(o2)) || (o2.anchor.blockId === o2.head.blockId ? o2.anchor.offset <= o2.head.offset : !n2 || n2.indexOf(o2.anchor.blockId) <= n2.indexOf(o2.head.blockId));
|
|
34
|
+
}
|
|
35
|
+
function u(o2, n2) {
|
|
36
|
+
return s(o2, n2) ? { from: o2.anchor, to: o2.head } : { from: o2.head, to: o2.anchor };
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
t as a,
|
|
40
|
+
h as b,
|
|
41
|
+
f as c,
|
|
42
|
+
n as d,
|
|
43
|
+
o as e,
|
|
44
|
+
r as f,
|
|
45
|
+
d as g,
|
|
46
|
+
c as h,
|
|
47
|
+
e as i,
|
|
48
|
+
a as j,
|
|
49
|
+
s as k,
|
|
50
|
+
u as s
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=Selection-DmFDwiPJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Selection-DmFDwiPJ.mjs","sources":["../../src/model/Selection.ts"],"sourcesContent":["/**\n * Selection model for the Notectl editor.\n * A selection is defined by an anchor and head position within the document.\n */\n\nimport type { BlockId } from './TypeBrands.js';\n\nexport interface Position {\n\treadonly blockId: BlockId;\n\treadonly offset: number;\n\t/** Path from root block to leaf block (optional, for nested structures). */\n\treadonly path?: readonly BlockId[];\n}\n\nexport interface Selection {\n\treadonly anchor: Position;\n\treadonly head: Position;\n}\n\nexport interface SelectionRange {\n\treadonly from: Position;\n\treadonly to: Position;\n}\n\n/** A selection that selects an entire node (e.g. void blocks, table selection). */\nexport interface NodeSelection {\n\treadonly type: 'node';\n\treadonly nodeId: BlockId;\n\treadonly path: readonly BlockId[];\n}\n\n/** A virtual cursor at the boundary of a void block where no native caret can exist. */\nexport interface GapCursorSelection {\n\treadonly type: 'gap';\n\treadonly side: 'before' | 'after';\n\treadonly blockId: BlockId;\n\treadonly path: readonly BlockId[];\n}\n\n/** Union type representing a text selection, node selection, or gap cursor. */\nexport type EditorSelection = Selection | NodeSelection | GapCursorSelection;\n\n/** Creates a NodeSelection for the given block. */\nexport function createNodeSelection(nodeId: BlockId, path: readonly BlockId[]): NodeSelection {\n\treturn { type: 'node', nodeId, path };\n}\n\n/** Creates a GapCursorSelection at the boundary of a void block. */\nexport function createGapCursor(\n\tblockId: BlockId,\n\tside: 'before' | 'after',\n\tpath: readonly BlockId[],\n): GapCursorSelection {\n\treturn { type: 'gap', side, blockId, path };\n}\n\n/** Type guard: returns true if the selection is a NodeSelection. */\nexport function isNodeSelection(sel: EditorSelection): sel is NodeSelection {\n\treturn 'type' in sel && sel.type === 'node';\n}\n\n/** Type guard: returns true if the selection is a GapCursorSelection. */\nexport function isGapCursor(sel: EditorSelection): sel is GapCursorSelection {\n\treturn 'type' in sel && sel.type === 'gap';\n}\n\n/** Type guard: returns true if the selection is a text Selection. */\nexport function isTextSelection(sel: EditorSelection): sel is Selection {\n\treturn !('type' in sel);\n}\n\n/** Compares two EditorSelections for equality. */\nexport function selectionsEqual(a: EditorSelection, b: EditorSelection): boolean {\n\tif (isNodeSelection(a) && isNodeSelection(b)) {\n\t\treturn a.nodeId === b.nodeId;\n\t}\n\tif (isGapCursor(a) && isGapCursor(b)) {\n\t\treturn a.blockId === b.blockId && a.side === b.side;\n\t}\n\tif (isTextSelection(a) && isTextSelection(b)) {\n\t\treturn (\n\t\t\ta.anchor.blockId === b.anchor.blockId &&\n\t\t\ta.anchor.offset === b.anchor.offset &&\n\t\t\ta.head.blockId === b.head.blockId &&\n\t\t\ta.head.offset === b.head.offset\n\t\t);\n\t}\n\treturn false;\n}\n\n/** Creates a Position, optionally with a path. */\nexport function createPosition(\n\tblockId: BlockId,\n\toffset: number,\n\tpath?: readonly BlockId[],\n): Position {\n\treturn path ? { blockId, offset, path } : { blockId, offset };\n}\n\n/** Creates a selection with distinct anchor and head. */\nexport function createSelection(anchor: Position, head: Position): Selection {\n\treturn { anchor, head };\n}\n\n/** Creates a collapsed selection (cursor) at the given position. */\nexport function createCollapsedSelection(blockId: BlockId, offset: number): Selection {\n\tconst pos: Position = { blockId, offset };\n\treturn { anchor: pos, head: pos };\n}\n\n/** Returns true if the selection is collapsed (cursor with no range). NodeSelection and GapCursor are never collapsed. */\nexport function isCollapsed(sel: EditorSelection): boolean {\n\tif (isNodeSelection(sel) || isGapCursor(sel)) return false;\n\treturn sel.anchor.blockId === sel.head.blockId && sel.anchor.offset === sel.head.offset;\n}\n\n/**\n * Returns true if the selection direction is forward (anchor before head).\n * When anchor and head are in the same block, compares offsets.\n * Cross-block ordering uses document order (not determinable here — caller provides block order).\n * For NodeSelection, always returns true.\n */\nexport function isForward(sel: EditorSelection, blockOrder?: readonly BlockId[]): boolean {\n\tif (isNodeSelection(sel) || isGapCursor(sel)) return true;\n\tif (sel.anchor.blockId === sel.head.blockId) {\n\t\treturn sel.anchor.offset <= sel.head.offset;\n\t}\n\tif (blockOrder) {\n\t\tconst anchorIdx = blockOrder.indexOf(sel.anchor.blockId);\n\t\tconst headIdx = blockOrder.indexOf(sel.head.blockId);\n\t\treturn anchorIdx <= headIdx;\n\t}\n\treturn true;\n}\n\n/**\n * Returns a normalized range where `from` is always before `to`.\n * Throws for NodeSelection — use isNodeSelection() guard first.\n */\nexport function selectionRange(sel: Selection, blockOrder?: readonly BlockId[]): SelectionRange {\n\tif (isForward(sel, blockOrder)) {\n\t\treturn { from: sel.anchor, to: sel.head };\n\t}\n\treturn { from: sel.head, to: sel.anchor };\n}\n"],"names":["createNodeSelection","nodeId","path","type","createGapCursor","blockId","side","isNodeSelection","sel","isGapCursor","isTextSelection","selectionsEqual","a","b","anchor","offset","head","createPosition","createSelection","createCollapsedSelection","pos","isCollapsed","isForward","blockOrder","indexOf","selectionRange","from","to"],"mappings":"AA2CO,SAASA,EAAoBC,IAAiBC,IAAAA;AACpD,SAAO,EAAEC,MAAM,QAAQF,QAAAA,IAAQC,MAAAA,GAAAA;AAChC;AAGO,SAASE,EACfC,IACAC,IACAJ,IAAAA;AAEA,SAAO,EAAEC,MAAM,OAAOG,MAAAA,IAAMD,SAAAA,IAASH,MAAAA,GAAAA;AACtC;AAGO,SAASK,EAAgBC,IAAAA;AAC/B,SAAO,UAAUA,MAAoB,WAAbA,GAAIL;AAC7B;AAGO,SAASM,EAAYD,IAAAA;AAC3B,SAAO,UAAUA,MAAoB,UAAbA,GAAIL;AAC7B;AAGO,SAASO,EAAgBF,IAAAA;AAC/B,SAAA,EAAS,UAAUA;AACpB;AAGO,SAASG,EAAgBC,IAAoBC,IAAAA;AACnD,SAAIN,EAAgBK,OAAML,EAAgBM,EAAAA,IAClCD,GAAEX,WAAWY,GAAEZ,SAEnBQ,EAAYG,EAAAA,KAAMH,EAAYI,EAAAA,IAC1BD,GAAEP,YAAYQ,GAAER,WAAWO,GAAEN,SAASO,GAAEP,OAAAA,EAAAA,CAE5CI,EAAgBE,EAAAA,KAAAA,CAAMF,EAAgBG,EAAAA,MAExCD,GAAEE,OAAOT,YAAYQ,GAAEC,OAAOT,WAC9BO,GAAEE,OAAOC,WAAWF,GAAEC,OAAOC,UAC7BH,GAAEI,KAAKX,YAAYQ,GAAEG,KAAKX,WAC1BO,GAAEI,KAAKD,WAAWF,GAAEG,KAAKD;AAI5B;AAGO,SAASE,EACfZ,IACAU,IACAb,IAAAA;AAEA,SAAOA,KAAO,EAAEG,SAAAA,IAASU,QAAAA,IAAQb,MAAAA,GAAAA,IAAS,EAAEG,SAAAA,IAASU,QAAAA,GAAAA;AACtD;AAGO,SAASG,EAAgBJ,IAAkBE;AACjD,SAAO,EAAEF,QAAAA,IAAQE,MAAAA,GAAAA;AAClB;AAGO,SAASG,EAAyBd,IAAkBU,IAAAA;AAC1D,QAAMK,KAAgB,EAAEf,SAAAA,IAASU,QAAAA,GAAAA;AACjC,SAAO,EAAED,QAAQM,IAAKJ,MAAMI,GAAAA;AAC7B;AAGO,SAASC,EAAYb,IAAAA;AAC3B,SAAA,CAAID,EAAgBC,EAAAA,KAAAA,CAAQC,EAAYD,EAAAA,KACjCA,GAAIM,OAAOT,YAAYG,GAAIQ,KAAKX,WAAWG,GAAIM,OAAOC,WAAWP,GAAIQ,KAAKD;AAClF;AAQO,SAASO,EAAUd,IAAsBe,IAAAA;AAC/C,SAAA,EAAA,CAAIhB,EAAgBC,EAAAA,KAAAA,CAAQC,EAAYD,EAAAA,OACpCA,GAAIM,OAAOT,YAAYG,GAAIQ,KAAKX,UAC5BG,GAAIM,OAAOC,UAAUP,GAAIQ,KAAKD,SAAAA,CAElCQ,MACeA,GAAWC,QAAQhB,GAAIM,OAAOT,OAAAA,KAChCkB,GAAWC,QAAQhB,GAAIQ,KAAKX,OAAAA;AAI9C;AAMO,SAASoB,EAAejB,IAAgBe,IAAAA;AAC9C,SAAID,EAAUd,IAAKe,EAAAA,IACX,EAAEG,MAAMlB,GAAIM,QAAQa,IAAInB,GAAIQ,KAAAA,IAE7B,EAAEU,MAAMlB,GAAIQ,MAAMW,IAAInB,GAAIM,OAAAA;AAClC;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { i as e } from "./Platform-S_xqIqcM.mjs";
|
|
2
|
+
function r(r2) {
|
|
3
|
+
return e() ? r2.replace(/Mod/g, "⌘").replace(/Shift/g, "⇧").replace(/Alt/g, "⌥").replace(/-/g, "") : r2.replace(/Mod/g, "Ctrl").replace(/-/g, "+");
|
|
4
|
+
}
|
|
5
|
+
export {
|
|
6
|
+
r as f
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=ToolbarItem-BpZggUGo.mjs.map
|