@nerd-bible/wordgard 0.3.3

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/schema.js ADDED
@@ -0,0 +1,1341 @@
1
+ import { ChangeSet, Leaf, Plot, Elt } from 'wordgard/doc';
2
+ import { GardState, GardSelection, BidiSpan, Transaction } from 'wordgard/state';
3
+ import { Paragraph, Heading, Direction, Blockquote, HorizontalRule, Alignment, Doc, InlineDoc, BulletList, OrderedList, InlineListItem, ListItem, CodeBlock, CodeBlockLanguage, Strong, Emphasis, Code, Underline, Strikethrough, Superscript, Subscript, ImageSize, ImageAlt, Image, Figure, CaptionedFigure, Color, BackgroundColor, Link, LineBreak } from 'wordgard/types';
4
+ import { phrases, imagePhrases, colorNames } from 'wordgard/phrases';
5
+ import { Command, setTextblockType, Menu, setAlignment, setDirection, toggleBlock, listIsActive, toggleList, enter, toggleMark } from 'wordgard/command';
6
+ import { history } from 'wordgard/history';
7
+ import { KeyBinding, InputRule, Wordgard, Panel, Dialog, Decoration, PointSet, Tooltip } from 'wordgard/editor';
8
+ import cr from 'crelt';
9
+
10
+ function blockDoc() {
11
+ return GardState.schemaElement.of(Doc);
12
+ }
13
+ function inlineDoc() {
14
+ return GardState.schemaElement.of(InlineDoc);
15
+ }
16
+ function selectionInType(tag) {
17
+ return (state) => {
18
+ let { sel } = state, block = sel.head.textblockParent;
19
+ return !!block && block.start == sel.anchor.textblockParent?.start && block.node.tag.eq(tag);
20
+ };
21
+ }
22
+ function paragraph() {
23
+ return [GardState.schemaElement.of(Paragraph), paragraph.button, paragraph.keyBinding];
24
+ }
25
+ ;paragraph = /*@__PURE__*/(function (paragraph) {
26
+ paragraph.keyBinding = KeyBinding.of({
27
+ key: "Ctrl-Shift-0",
28
+ run: Command.bind(setTextblockType, Paragraph)
29
+ });
30
+ paragraph.button = Menu.Button.define({
31
+ run: Command.bind(setTextblockType, Paragraph),
32
+ active: selectionInType(Paragraph),
33
+ label: phrases.ref("paragraph"),
34
+ enable: s => !s.readOnly,
35
+ parent: Menu.Submenu.textblockStyle,
36
+ rank: 10
37
+ });
38
+ ;return paragraph})(paragraph);
39
+ function heading() {
40
+ return [GardState.schemaElement.of(Heading),
41
+ heading.button1, heading.button2, heading.button3,
42
+ heading.keyBindings, heading.createOnHash];
43
+ }
44
+ ;heading = /*@__PURE__*/(function (heading) {
45
+ heading.keyBindings = [
46
+ KeyBinding.of({ key: "Ctrl-Shift-1", run: Command.bind(setTextblockType, Heading.of(1)) }),
47
+ KeyBinding.of({ key: "Ctrl-Shift-2", run: Command.bind(setTextblockType, Heading.of(2)) }),
48
+ KeyBinding.of({ key: "Ctrl-Shift-3", run: Command.bind(setTextblockType, Heading.of(3)) }),
49
+ KeyBinding.of({ key: "Ctrl-Shift-4", run: Command.bind(setTextblockType, Heading.of(4)) }),
50
+ KeyBinding.of({ key: "Ctrl-Shift-5", run: Command.bind(setTextblockType, Heading.of(5)) }),
51
+ KeyBinding.of({ key: "Ctrl-Shift-6", run: Command.bind(setTextblockType, Heading.of(6)) })
52
+ ];
53
+ heading.button1 = Menu.Button.define({
54
+ run: Command.bind(setTextblockType, Heading.of(1)),
55
+ active: selectionInType(Heading.of(1)),
56
+ label: phrases.ref("heading_1"),
57
+ enable: s => !s.readOnly,
58
+ parent: Menu.Submenu.textblockStyle,
59
+ rank: 50
60
+ });
61
+ heading.button2 = Menu.Button.define({
62
+ run: Command.bind(setTextblockType, Heading.of(2)),
63
+ active: selectionInType(Heading.of(2)),
64
+ label: phrases.ref("heading_2"),
65
+ enable: s => !s.readOnly,
66
+ parent: Menu.Submenu.textblockStyle,
67
+ rank: 51
68
+ });
69
+ heading.button3 = Menu.Button.define({
70
+ run: Command.bind(setTextblockType, Heading.of(3)),
71
+ active: selectionInType(Heading.of(3)),
72
+ label: phrases.ref("heading_3"),
73
+ enable: s => !s.readOnly,
74
+ parent: Menu.Submenu.textblockStyle,
75
+ rank: 52
76
+ });
77
+ heading.createOnHash = InputRule.textblockType(/^(#{1,6}) $/, m => Heading.of(m[1].to.pos - m[1].from.pos), true);
78
+ ;return heading})(heading);
79
+ function alignment() {
80
+ return [GardState.schemaElement.of(Alignment), alignment.button, alignment.keyBindings];
81
+ }
82
+ function alignmentAtCursor(state) {
83
+ let block = state.sel.head.textblockParent;
84
+ return (block && block.node.tag.mark(Alignment)) || null;
85
+ }
86
+ ;alignment = /*@__PURE__*/(function (alignment) {
87
+ alignment.keyBindings = [
88
+ KeyBinding.of({ key: "Mod-Shift-l", run: Command.bind(setAlignment, "left") }),
89
+ KeyBinding.of({ key: "Mod-Shift-r", run: Command.bind(setAlignment, "right") }),
90
+ KeyBinding.of({ key: "Mod-Shift-e", run: Command.bind(setAlignment, "center") })
91
+ ];
92
+ alignment.buttonStart = Menu.Button.define({
93
+ run: Command.bind(setAlignment, null),
94
+ active: state => alignmentAtCursor(state) == null,
95
+ label: {
96
+ icon: "M16 81a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m0-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69m0-19a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m0-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69",
97
+ directional: true
98
+ },
99
+ description: phrases.ref("align_start"),
100
+ });
101
+ alignment.buttonEnd = Menu.Button.define({
102
+ run: Command.bind(setAlignment, "end"),
103
+ active: state => alignmentAtCursor(state) == "end",
104
+ label: {
105
+ icon: "M41 81a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m-25-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69m25-19a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m-25-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69",
106
+ directional: true
107
+ },
108
+ description: phrases.ref("align_end"),
109
+ });
110
+ alignment.buttonCenter = Menu.Button.define({
111
+ run: Command.bind(setAlignment, "center"),
112
+ active: state => alignmentAtCursor(state) == "center",
113
+ label: {
114
+ icon: "M29 81a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m-13-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69m13-19a3 3 0 0 1 0-6h44a3 3 0 0 1 0 6h-44m-13-19a3 3 0 0 1 0-6h69a3 3 0 0 1 0 6h-69"
115
+ },
116
+ description: phrases.ref("align_center"),
117
+ });
118
+ alignment.button = Menu.Submenu.define({
119
+ description: phrases.ref("alignment"),
120
+ parent: Menu.Group.block,
121
+ arrow: false,
122
+ enable: s => !s.readOnly,
123
+ rank: 10,
124
+ content: [alignment.buttonStart, alignment.buttonEnd, alignment.buttonCenter]
125
+ });
126
+ ;return alignment})(alignment);
127
+ function direction() {
128
+ return [GardState.schemaElement.of(Direction), direction.textblockDir, direction.button];
129
+ }
130
+ function autoDir(plot) {
131
+ for (let ch of plot.content)
132
+ if (ch.is(Leaf.Text)) {
133
+ for (let i = 0; i < ch.param.length; i++) {
134
+ let dir = BidiSpan.strongDir(ch.param.charCodeAt(i));
135
+ if (dir != null)
136
+ return dir;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ function directionAtCursor(state) {
142
+ let block = state.sel.head.textblockParent;
143
+ return (block && block.node.mark(Direction)) || (state.textLTR ? "ltr" : "rtl");
144
+ }
145
+ ;direction = /*@__PURE__*/(function (direction) {
146
+ direction.textblockDir = GardState.textblockLTR.of(plot => {
147
+ let dir = plot.mark(Direction);
148
+ return !dir ? null : dir == "auto" ? autoDir(plot) : dir == "ltr";
149
+ });
150
+ direction.buttonLTR = Menu.Button.define({
151
+ run: Command.bind(setDirection, "ltr"),
152
+ active: state => directionAtCursor(state) == "ltr",
153
+ label: {
154
+ icon: "M70 35l20 15l-20 15l0-30M45 83v-63h-5v63a3 3 0 0 1-6 0v-28h-4a20 20 0 1 1 0-40h28a3 3 0 0 1 0 6h-7v62a3 3 0 0 1-6 0"
155
+ },
156
+ description: phrases.ref("text_dir_ltr")
157
+ });
158
+ direction.buttonRTL = Menu.Button.define({
159
+ run: Command.bind(setDirection, "rtl"),
160
+ active: state => directionAtCursor(state) == "rtl",
161
+ label: {
162
+ icon: "M30 35l-20 15l20 15l0-30M75 83v-63h-5v63a3 3 0 0 1-6 0v-28h-4a20 20 0 1 1 0-40h28a3 3 0 0 1 0 6h-7v62a3 3 0 0 1-6 0"
163
+ },
164
+ description: phrases.ref("text_dir_rtl")
165
+ });
166
+ direction.buttonAuto = Menu.Button.define({
167
+ run: Command.bind(setDirection, "auto"),
168
+ active: state => directionAtCursor(state) == "auto",
169
+ label: {
170
+ icon: "M35 30l-23 20l23 20l0-40M60 30l23 20l-23 20l0-40"
171
+ },
172
+ description: phrases.ref("text_dir_auto")
173
+ });
174
+ direction.button = Menu.Submenu.define({
175
+ description: phrases.ref("text_dir"),
176
+ parent: Menu.Group.block,
177
+ arrow: false,
178
+ enable: s => !s.readOnly,
179
+ rank: 20,
180
+ content: [direction.buttonLTR, direction.buttonRTL, direction.buttonAuto]
181
+ });
182
+ ;return direction})(direction);
183
+ function blockquote() {
184
+ return [GardState.schemaElement.of(Blockquote), blockquote.button, blockquote.createOnGT, blockquote.theme];
185
+ }
186
+ ;blockquote = /*@__PURE__*/(function (blockquote) {
187
+ blockquote.button = Menu.Button.define({
188
+ run: Command.bind(toggleBlock, Blockquote),
189
+ active: state => {
190
+ for (let cur = state.sel.head.parent; cur; cur = cur.parent)
191
+ if (cur.node.type == Blockquote.type)
192
+ return true;
193
+ return false;
194
+ },
195
+ label: {
196
+ icon: "M75 75a6 6 0 0 0 6-6V53a6 6 0 0 0-6-6h-9q0-3 0-7 1-3 2-6t3-4q2-2 5-2V19q-5 0-9 2a21 21 0 0 0-7 6 31 31 0 0 0-4 9A48 48 0 0 0 56 47V69a5 5 0 0 0 6 6zm-37 0a6 6 0 0 0 6-6V53a6 6 0 0 0-6-6H29q0-3 0-7 1-3 2-6 1-3 3-4 2-2 5-2V19q-5 0-9 2a21 21 0 0 0-7 6 31 31 0 0 0-4 9A48 48 0 0 0 19 47V69a6 6 0 0 0 6 6z"
197
+ },
198
+ description: phrases.ref("toggle_quote"),
199
+ enable: s => !s.readOnly,
200
+ parent: Menu.Group.block,
201
+ rank: 40
202
+ });
203
+ blockquote.createOnGT = InputRule.wrapping(/^> $/, Blockquote, true);
204
+ blockquote.theme = Wordgard.theme({
205
+ blockquote: {
206
+ marginInline: "3px",
207
+ paddingInlineStart: "12px",
208
+ borderInlineStart: "4px solid silver"
209
+ }
210
+ });
211
+ ;return blockquote})(blockquote);
212
+ function horizontalRule() {
213
+ return [GardState.schemaElement.of(HorizontalRule), horizontalRule.createOnDashes];
214
+ }
215
+ ;horizontalRule = /*@__PURE__*/(function (horizontalRule) {
216
+ horizontalRule.createOnDashes = InputRule.define({
217
+ expr: /^---$/,
218
+ lookahead: /^$/,
219
+ apply: (state, m) => {
220
+ let changes = ChangeSet.create(state.doc, {
221
+ from: m[0].from.pos, to: m[0].to.pos,
222
+ insert: [HorizontalRule],
223
+ fit: true
224
+ });
225
+ let hr = changes.findInserted(t => t == HorizontalRule);
226
+ if (hr == null)
227
+ return null;
228
+ return {
229
+ changes,
230
+ selection: cx => GardSelection.near(cx, hr + 1, 1),
231
+ annotations: history.isolate.of(true),
232
+ userEvent: "insert.horizontalrule"
233
+ };
234
+ }
235
+ });
236
+ ;return horizontalRule})(horizontalRule);
237
+
238
+ function bulletList(config = {}) {
239
+ return [GardState.schemaElement.of(BulletList),
240
+ GardState.schemaElement.of(config.blockItems == false ? InlineListItem : ListItem),
241
+ bulletList.toggleButton, bulletList.createOnDash];
242
+ }
243
+ ;bulletList = /*@__PURE__*/(function (bulletList) {
244
+ bulletList.createOnDash = InputRule.wrapping(/^ ?- $/, BulletList, true);
245
+ bulletList.toggleButton = Menu.Button.define({
246
+ run: Command.bind(toggleList, BulletList),
247
+ active: listIsActive(BulletList),
248
+ label: {
249
+ icon: "M34 75a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56m0-25a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56m0-25a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56m-22 3a6 6 0 1 0 0-12 6 6 0 0 0 0 12m0 25a6 6 0 1 0 0-12 6 6 0 0 0 0 12m0 25a6 6 0 1 0 0-12 6 6 0 0 0 0 12",
250
+ directional: true
251
+ },
252
+ description: phrases.ref("toggle_bullet_list"),
253
+ enable: s => !s.readOnly,
254
+ parent: Menu.Group.block,
255
+ rank: 20
256
+ });
257
+ ;return bulletList})(bulletList);
258
+ function orderedList(config = {}) {
259
+ return [GardState.schemaElement.of(OrderedList),
260
+ GardState.schemaElement.of(config.blockItems == false ? InlineListItem : ListItem),
261
+ orderedList.toggleButton, orderedList.createOnNumber];
262
+ }
263
+ ;orderedList = /*@__PURE__*/(function (orderedList) {
264
+ orderedList.createOnNumber = InputRule.wrapping(/^ ?(\d+)\. $/, match => OrderedList.of(+match[1].text), true);
265
+ orderedList.toggleButton = Menu.Button.define({
266
+ run: Command.bind(toggleList, OrderedList.default),
267
+ active: listIsActive(OrderedList.default),
268
+ label: {
269
+ icon: "M34 75a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56m0-25a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56m0-25a3 3 0 0 1 0-6h56a3 3 0 0 1 0 6h-56M11 74v-3H13c1 0 2-1 2-2 0-1-1-2-2-2-1 0-2 1-2 2h-4c0-3 2-5 6-5 4 0 6 2 6 4a4 4 0 0 1-3 4v0a4 4 0 0 1 4 4c0 3-3 5-7 5-4 0-6-2-6-5h4c0 1 1 2 3 2 2 0 3-1 3-2 0-1-1-2-3-2h-2zm0-29h-4v0c0-3 2-5 6-5 4 0 6 2 6 5 0 2-2 4-3 5l-3 4h7V57H7v-3l6-6c1-1 2-2 2-3 0-1-1-2-2-2a2 2 0 0 0-2 2zM16 31h-4V18h0l-4 3v-4l4-3h4z",
270
+ directional: true
271
+ },
272
+ description: phrases.ref("toggle_ordered_list"),
273
+ enable: s => !s.readOnly,
274
+ parent: Menu.Group.block,
275
+ rank: 30
276
+ });
277
+ ;return orderedList})(orderedList);
278
+
279
+ function codeBlock() {
280
+ return [GardState.schemaElement.of(CodeBlock),
281
+ codeBlock.button, codeBlock.keyBinding, codeBlock.createOnBackticks];
282
+ }
283
+ ;codeBlock = /*@__PURE__*/(function (codeBlock) {
284
+ codeBlock.keyBinding = KeyBinding.of({
285
+ key: "Ctrl-Shift-\\",
286
+ run: Command.bind(setTextblockType, CodeBlock)
287
+ });
288
+ codeBlock.button = Menu.Button.define({
289
+ run: Command.bind(setTextblockType, CodeBlock),
290
+ active: selectionInType(CodeBlock),
291
+ label: phrases.ref("code_block"),
292
+ enable: s => !s.readOnly,
293
+ parent: Menu.Submenu.textblockStyle,
294
+ rank: 30
295
+ });
296
+ codeBlock.createOnBackticks = GardState.prec.high(Command.handler(enter, wg => {
297
+ let { sel } = wg.state, parent = sel.head.parent;
298
+ if (!sel.selection.isCursor || !parent.node.isTextblock || sel.head.pos != parent.end ||
299
+ parent.node.length > 20 || parent.node.type == CodeBlock.type || !parent.parent ||
300
+ !wg.state.doc.schema.canContain(parent.parent.node.type, CodeBlock.type))
301
+ return false;
302
+ let text = wg.state.textblockMap(sel.head.parent);
303
+ let match = /^```\s*([^\s`]+\s*)?$/.exec(text.text);
304
+ if (!match)
305
+ return false;
306
+ let tag = CodeBlock;
307
+ if (match[1] && wg.state.schema.has(CodeBlockLanguage))
308
+ tag = tag.withMarks([CodeBlockLanguage.of(match[1])]);
309
+ return {
310
+ changes: { from: parent.before, to: parent.end, insert: [tag] },
311
+ selection: { anchor: parent.start },
312
+ scrollIntoView: true
313
+ };
314
+ }));
315
+ ;return codeBlock})(codeBlock);
316
+
317
+ function strong() {
318
+ return [GardState.schemaElement.of(Strong), strong.button, strong.keyBinding];
319
+ }
320
+ ;strong = /*@__PURE__*/(function (strong) {
321
+ strong.keyBinding = KeyBinding.of({
322
+ key: "Mod-b",
323
+ run: Command.bind(toggleMark, Strong),
324
+ });
325
+ strong.button = Menu.Button.toggleMark({
326
+ mark: Strong,
327
+ parent: Menu.Group.inline,
328
+ rank: 10,
329
+ description: phrases.ref("toggle_strong"),
330
+ label: {
331
+ icon: "M51 81c13 0 21-7 21-18 0-8-6-14-14-15v0a14 14 0 0 0 12-13c0-9-7-15-19-15H24V81zM37 29h11c6 0 10 3 10 8 0 5-4 8-11 8H37V29zm0 42V54h11c8 0 12 3 12 9 0 6-4 9-11 9H37z"
332
+ }
333
+ });
334
+ ;return strong})(strong);
335
+ function emphasis() {
336
+ return [GardState.schemaElement.of(Emphasis), emphasis.button, emphasis.keyBinding];
337
+ }
338
+ ;emphasis = /*@__PURE__*/(function (emphasis) {
339
+ emphasis.keyBinding = KeyBinding.of({
340
+ key: "Mod-i",
341
+ run: Command.bind(toggleMark, Emphasis),
342
+ });
343
+ emphasis.button = Menu.Button.toggleMark({
344
+ mark: Emphasis,
345
+ parent: Menu.Group.inline,
346
+ rank: 15,
347
+ description: phrases.ref("toggle_em"),
348
+ label: {
349
+ icon: "M50 73 60 28c1-4 2-4 8-5l1-3H45l-1 3c7 1 7 1 6 5L41 73c-1 4-2 4-8 5l-1 3h24l1-3c-7-1-7-1-7-5z"
350
+ }
351
+ });
352
+ ;return emphasis})(emphasis);
353
+ function code() {
354
+ return [GardState.schemaElement.of(Code), code.button, code.keyBinding];
355
+ }
356
+ ;code = /*@__PURE__*/(function (code) {
357
+ code.keyBinding = KeyBinding.of({
358
+ key: "Mod-`",
359
+ run: Command.bind(toggleMark, Code),
360
+ });
361
+ code.button = Menu.Button.toggleMark({
362
+ mark: Code,
363
+ parent: Menu.Group.inline,
364
+ rank: 30,
365
+ description: phrases.ref("toggle_code"),
366
+ label: {
367
+ icon: "M37 30a2 2 0 1 0-4-4l-22 22a3 3 0 0 0 0 4l22 22a2 2 0 0 0 4-4L17 50zm27 0a2 2 0 0 1 4-4l22 22a3 3 0 0 1 0 4l-22 22a2 2 0 0 1-4-4L83 50z"
368
+ }
369
+ });
370
+ ;return code})(code);
371
+ function underline() {
372
+ return [GardState.schemaElement.of(Underline), underline.button, underline.keyBinding];
373
+ }
374
+ ;underline = /*@__PURE__*/(function (underline) {
375
+ underline.keyBinding = KeyBinding.of({
376
+ key: "Mod-u",
377
+ run: Command.bind(toggleMark, Underline)
378
+ });
379
+ underline.button = Menu.Button.toggleMark({
380
+ mark: Underline,
381
+ parent: Menu.Group.inline,
382
+ rank: 60,
383
+ description: phrases.ref("toggle_underline"),
384
+ label: {
385
+ icon: "M33 20h-8V60c0 13 9 23 24 23s24-9 24-23V20h-8v40c0 9-6 16-17 16s-15-7-15-16M78 94h-56v-6h56z"
386
+ }
387
+ });
388
+ ;return underline})(underline);
389
+ function strikethrough() {
390
+ return [GardState.schemaElement.of(Strikethrough), strikethrough.button, strikethrough.keyBinding];
391
+ }
392
+ ;strikethrough = /*@__PURE__*/(function (strikethrough) {
393
+ strikethrough.keyBinding = KeyBinding.of({
394
+ key: "Mod-/",
395
+ run: Command.bind(toggleMark, Strikethrough),
396
+ });
397
+ strikethrough.button = Menu.Button.toggleMark({
398
+ mark: Strikethrough,
399
+ parent: Menu.Group.inline,
400
+ rank: 65,
401
+ description: phrases.ref("toggle_strikethrough"),
402
+ label: {
403
+ icon: "M38 37c0 2 0 3 2 5H31a17 17 0 0 1-1-5c0-10 9-16 21-16 12 0 20 7 20 17h-7c-1-6-6-10-13-10-7 0-13 4-13 10zm13 44c-13 0-21-7-22-17h7c1 6 7 10 15 10c8 0 14-4 14-10c0-5-3-8-11-10L48 53h20c3 3 4 6 4 10 0 11-9 18-22 18M11 50v-6h75v6H11"
404
+ }
405
+ });
406
+ ;return strikethrough})(strikethrough);
407
+ function superscript() {
408
+ return [GardState.schemaElement.of(Superscript), superscript.button, superscript.keyBinding];
409
+ }
410
+ ;superscript = /*@__PURE__*/(function (superscript) {
411
+ superscript.keyBinding = KeyBinding.of({
412
+ key: "Mod-.",
413
+ run: Command.bind(toggleMark, Superscript),
414
+ });
415
+ superscript.button = Menu.Button.toggleMark({
416
+ mark: Superscript,
417
+ parent: Menu.Group.inline,
418
+ rank: 70,
419
+ description: phrases.ref("toggle_super"),
420
+ label: {
421
+ icon: "m27 78 6-18H55l6 18H69L48 19H40L19 78zm17-50 9 26h-18l9-26zm32-11v0c4 -10 12 0 5 6l-11 11V38h22v-6h-12v0l6-6c3-3 5-5 5-10 0-5-4-9-11-9C72 6 69 11 69 16v0z"
422
+ }
423
+ });
424
+ ;return superscript})(superscript);
425
+ function subscript() {
426
+ return [GardState.schemaElement.of(Subscript), subscript.button, subscript.keyBinding];
427
+ }
428
+ ;subscript = /*@__PURE__*/(function (subscript) {
429
+ subscript.keyBinding = KeyBinding.of({
430
+ key: "Mod-,",
431
+ run: Command.bind(toggleMark, Subscript),
432
+ });
433
+ subscript.button = Menu.Button.toggleMark({
434
+ mark: Subscript,
435
+ parent: Menu.Group.inline,
436
+ rank: 75,
437
+ description: phrases.ref("toggle_sub"),
438
+ label: {
439
+ icon: "m21 78 6-18H49l6 18H63L41 19H34L13 78zm17-50 9 26h-18l9-26zm38 45v0c4 -10 12 0 5 6l-11 11V94h22v-6h-12v0l6-6c3-3 5-5 5-10 0-5-4-9-11-9-8 0-11 5-11 10v0z"
440
+ }
441
+ });
442
+ ;return subscript})(subscript);
443
+
444
+ const imageUploader = /*@__PURE__*/GardState.Facet.define();
445
+ const imageTypes = [Image, Figure, CaptionedFigure];
446
+ function activeImage(sel) {
447
+ if (sel.selection instanceof GardSelection.Node && imageTypes.includes(sel.selection.node.type))
448
+ return sel.selection.node.tag;
449
+ if (sel.head.parent.start == sel.anchor.parent.start && sel.head.parent.node.type == CaptionedFigure)
450
+ return sel.head.parent.node.tag;
451
+ return null;
452
+ }
453
+ const svg = "http://www.w3.org/2000/svg";
454
+ function rect(x, y, w, h, cls) {
455
+ let elt = document.createElementNS(svg, "rect");
456
+ elt.setAttribute("x", String(x));
457
+ elt.setAttribute("y", String(y));
458
+ elt.setAttribute("width", String(w));
459
+ elt.setAttribute("height", String(h));
460
+ elt.setAttribute("class", cls);
461
+ return elt;
462
+ }
463
+ function imageTypeButtons(state, active) {
464
+ let hasImg = state.schema.has(Image), hasFig = state.schema.has(Figure), hasCap = state.schema.has(CaptionedFigure);
465
+ let align = (hasFig || hasCap) && state.schema.markAllowed(Alignment, hasFig ? Figure : CaptionedFigure);
466
+ if (!align && !(hasImg && (hasFig || hasCap)))
467
+ return null;
468
+ let buttons = [];
469
+ function button(type, label, active) {
470
+ let labelText = imagePhrases.get(state, label);
471
+ let icon = document.createElementNS(svg, "svg");
472
+ icon.setAttribute("viewbox", "0 0 24 22");
473
+ icon.setAttribute("width", "24");
474
+ icon.setAttribute("height", "22");
475
+ icon.appendChild(rect(1, 1, 22, 3, "wg-img-icon-text"));
476
+ let flip = !state.textLTR;
477
+ icon.appendChild(rect(type == "start" ? (flip ? 12 : 2) : type == "end" ? (flip ? 2 : 12) : 7, 6, 10, 10, "wg-img-icon-image"));
478
+ if (type == "inline") {
479
+ icon.appendChild(rect(1, 12, 5, 3, "wg-img-icon-text"));
480
+ icon.appendChild(rect(18, 12, 5, 3, "wg-img-icon-text"));
481
+ }
482
+ icon.appendChild(rect(1, 18, 22, 3, "wg-img-icon-text"));
483
+ return cr("label", { class: "wg-img-radio", title: labelText }, cr("input", { type: "radio", "aria-label": labelText,
484
+ name: "type", value: type, checked: active ? "checked" : null }), icon);
485
+ }
486
+ let aligned = !active || active.type == Image ? null : active.mark(Alignment) || "start";
487
+ if (hasImg)
488
+ buttons.push(button("inline", "inline", aligned == null));
489
+ buttons.push(button("start", "figure", aligned == "start"));
490
+ if (align) {
491
+ buttons.push(button("center", "figure_center", aligned == "center"));
492
+ buttons.push(button("end", "figure_end", aligned == "end"));
493
+ }
494
+ if (hasFig && hasCap) {
495
+ let caption = cr("label", " ", cr("input", { type: "checkbox", name: "caption",
496
+ checked: active && active.type == CaptionedFigure ? "checked" : null }), " ", imagePhrases.get(state, "captioned"));
497
+ if (hasImg) {
498
+ let imageRadio = buttons[0].querySelector("input");
499
+ for (let b of buttons)
500
+ b.querySelector("input").addEventListener("change", () => {
501
+ caption.style.display = imageRadio.checked ? "none" : "";
502
+ });
503
+ if (!aligned)
504
+ caption.style.display = "none";
505
+ }
506
+ buttons.push(caption);
507
+ }
508
+ return [cr("span", { class: "wg-label" }, imagePhrases.get(state, "image_style"), ":"), cr("span", buttons)];
509
+ }
510
+ const setImageDialog = /*@__PURE__*/Transaction.Effect.define();
511
+ const createImagePanel = wg => {
512
+ let dom = buildImagePanel(wg), mustFocus = true;
513
+ return {
514
+ top: true,
515
+ dom,
516
+ connect() {
517
+ if (mustFocus) {
518
+ mustFocus = false;
519
+ let target = dom.querySelector("input[name=src]");
520
+ if (target)
521
+ target.focus();
522
+ }
523
+ }
524
+ };
525
+ };
526
+ function startUpload(wg, file, set) {
527
+ let imageFile = file.files?.[0], handler = wg.state.facet(imageUploader)[0];
528
+ if (!imageFile || !handler)
529
+ return;
530
+ let promise = handler(imageFile, wg, percent => {
531
+ progress.lastChild.textContent = Math.round(percent) + "%";
532
+ });
533
+ let progress = cr("span", { class: "wg-img-upload", style: `width: ${file.offsetWidth}px` }, imagePhrases.get(wg.state, "uploading"), " ", cr("span"));
534
+ file.parentNode.replaceChild(progress, file);
535
+ function reset() {
536
+ if (progress.parentNode)
537
+ progress.parentNode.replaceChild(file, progress);
538
+ }
539
+ promise.then(url => {
540
+ reset();
541
+ set(url);
542
+ }, err => {
543
+ reset();
544
+ Dialog.show(wg, { label: imagePhrases.get(wg.state, "upload_failed") + ": " + err });
545
+ });
546
+ }
547
+ function buildImagePanel(wg) {
548
+ let { state } = wg;
549
+ let sel = (state.field(imageDialog) || state.selection).resolve(state.doc);
550
+ let active = activeImage(sel);
551
+ let size = !wg.state.schema.has(ImageSize) ? null :
552
+ [cr("label", { for: "wg-img-size" }, imagePhrases.get(state, "width"), ":"),
553
+ cr("input", { type: "number", id: "wg-img-size", name: "size", value: active && active.mark(ImageSize) || "",
554
+ placeholder: imagePhrases.get(state, "auto") })];
555
+ let src = cr("input", { type: "text", id: "wg-img-src", name: "src", required: "required",
556
+ value: active ? active.param : "", placeholder: "https://..." });
557
+ let file = null;
558
+ if (wg.state.facet(imageUploader).length) {
559
+ file = cr("input", { type: "file", id: "wg-img-file", name: "file", "aria-label": imagePhrases.get(state, "upload_image"),
560
+ onchange: (e) => startUpload(wg, e.target, url => src.value = url) });
561
+ }
562
+ let form = cr("form", { class: "wg-img-form", onkeydown }, cr("div", { class: "wg-dialog-title" }, imagePhrases.get(state, active ? "update_image" : "insert_image")), cr("label", { for: "wg-img-src" }, imagePhrases.get(state, "image_source"), ":"), cr("span", { class: "wg-img-src-line" }, src, file), cr("label", { for: "wg-img-alt" }, imagePhrases.get(state, "alt_text"), ":"), cr("input", { type: "text", id: "wg-img-alt", name: "alt",
563
+ value: active && active.mark(ImageAlt) || "",
564
+ placeholder: imagePhrases.get(state, "describe_image") }), imageTypeButtons(state, active), size, cr("div", { class: "wg-img-buttons" }, cr("button", { type: "submit", class: "wg-dialog-button" }, imagePhrases.get(state, active ? "update" : "insert")), " ", cr("button", { type: "button", class: "wg-dialog-button", onclick: close }, imagePhrases.get(state, "cancel"))));
565
+ function onsubmit(e) {
566
+ e.preventDefault();
567
+ let { state } = wg, sel = (state.field(imageDialog) || state.selection).resolve(state.doc);
568
+ let data = new FormData(form);
569
+ let src = data.get("src");
570
+ if (!src)
571
+ return;
572
+ let type = data.get("type") ?? (state.schema.has(Image) ? "inline" : "start");
573
+ let cap = !!data.get("caption") || !state.schema.has(Figure);
574
+ let marks = [];
575
+ if (type == "center" || type == "end")
576
+ marks = Alignment.of(type).addToSet(marks);
577
+ if (data.get("alt"))
578
+ marks = ImageAlt.of(data.get("alt")).addToSet(marks);
579
+ if (data.get("size"))
580
+ marks = ImageSize.of(Number(data.get("size"))).addToSet(marks);
581
+ let tag = type == "inline" ? Image.of(src, marks) : cap ? CaptionedFigure.of(src, marks) : Figure.of(src, marks);
582
+ let change;
583
+ if (sel.from.parent.node.type == CaptionedFigure && sel.to.parent.start == sel.from.parent.start) {
584
+ let from = sel.from.parent.before;
585
+ if (tag instanceof Plot.Tag)
586
+ change = { from, to: from + 1, insert: [tag] };
587
+ else
588
+ change = { from, to: sel.from.parent.after, insert: [tag], fit: true };
589
+ }
590
+ else {
591
+ change = { from: sel.from.pos, to: sel.to.pos, insert: [tag instanceof Plot.Tag ? tag.create() : tag], fit: true };
592
+ }
593
+ wg.focus();
594
+ let changes = ChangeSet.create(state.doc, change), pos = changes.findInserted(t => t == tag) ?? change.from;
595
+ wg.dispatch({
596
+ changes: change,
597
+ effects: setImageDialog.of(false),
598
+ userEvent: "insert.image",
599
+ selection: tag instanceof Plot.Tag ? { anchor: pos + 1 } : GardSelection.node(pos, tag)
600
+ });
601
+ }
602
+ function close() {
603
+ wg.focus();
604
+ wg.dispatch({ effects: setImageDialog.of(false) });
605
+ }
606
+ function onkeydown(e) {
607
+ if (e.key == "Escape") {
608
+ e.preventDefault();
609
+ close();
610
+ }
611
+ }
612
+ return cr("wg-dialog", { class: "wg-img-dialog", onsubmit }, form);
613
+ }
614
+ const insertImage = wg => {
615
+ let val = wg.state.field(imageDialog, false);
616
+ if (val) {
617
+ wg.dispatch({ effects: setImageDialog.of(false) });
618
+ }
619
+ else {
620
+ let effects = [setImageDialog.of(true)];
621
+ if (val === undefined)
622
+ effects.push(GardState.appendConfig.of(imageDialog));
623
+ wg.dispatch({ effects });
624
+ }
625
+ return true;
626
+ };
627
+ const imageDialogTheme = /*@__PURE__*/Wordgard.styles({
628
+ ".wg-img-dialog": {
629
+ borderBottom: "1px solid var(--wg-border-color)"
630
+ },
631
+ ".wg-img-form": {
632
+ padding: "5px 3px",
633
+ display: "grid",
634
+ gap: "8px",
635
+ alignItems: "center",
636
+ gridTemplateColumns: "max-content auto",
637
+ "& label, & .wg-label": {
638
+ textAlign: "right"
639
+ }
640
+ },
641
+ ".wg-dialog-title": {
642
+ gridColumn: "span 2",
643
+ fontSize: "90%",
644
+ fontWeight: "bold",
645
+ textAlign: "center"
646
+ },
647
+ ".wg-img-buttons": {
648
+ gridColumn: "2"
649
+ },
650
+ ".wg-img-src-line": {
651
+ display: "flex",
652
+ gap: "7px",
653
+ "& [type=text]": {
654
+ flex: "1"
655
+ }
656
+ },
657
+ ".wg-img-radio": {
658
+ display: "inline-block",
659
+ verticalAlign: "middle",
660
+ "& input[type=radio]": {
661
+ opacity: "0",
662
+ position: "absolute",
663
+ pointerEvents: "none"
664
+ },
665
+ "& svg": {
666
+ marginRight: "6px",
667
+ width: "24px",
668
+ "& .wg-img-icon-text": { fill: "#bbb" },
669
+ "& .wg-img-icon-image": { fill: "#888" },
670
+ },
671
+ "& input:checked + svg .wg-img-icon-image": {
672
+ fill: "var(--wg-highlight-color)"
673
+ },
674
+ "& input:focus + svg": {
675
+ borderRadius: "2px",
676
+ outline: "2px solid var(--wg-highlight-color)",
677
+ },
678
+ },
679
+ ".wg-img-upload": {
680
+ boxSizing: "border-box",
681
+ padding: "4px",
682
+ fontSize: "80%"
683
+ }
684
+ });
685
+ const imageDialog = /*@__PURE__*/GardState.Field.define({
686
+ create: () => null,
687
+ update(value, tr) {
688
+ for (let e of tr.effects)
689
+ if (e.is(setImageDialog))
690
+ return e.value ? tr.state.selection : null;
691
+ return value && value.map(tr.changes, tr.state);
692
+ },
693
+ provide: f => [
694
+ GardState.prec.lowest(Panel.show.from(f, val => val && createImagePanel)),
695
+ imageDialogTheme
696
+ ]
697
+ });
698
+
699
+ function baseSupport() {
700
+ return [GardState.schemaElement.of(ImageAlt), image.button, imageDialog, image.keyBinding, image.dropHandler];
701
+ }
702
+ function image() {
703
+ return [GardState.schemaElement.of(Image), baseSupport()];
704
+ }
705
+ function figure(conf = {}) {
706
+ return [GardState.schemaElement.of(Figure), conf?.captioned ? [GardState.schemaElement.of(CaptionedFigure)] : [], baseSupport()];
707
+ }
708
+ function imageResizing() {
709
+ return [GardState.schemaElement.of(ImageSize), imageResizing.keyBindings, imageResizing.dragHandle];
710
+ }
711
+ const resizeTheme = /*@__PURE__*/Wordgard.theme({
712
+ ".wg-resize-hover": {
713
+ display: "inline-block",
714
+ lineHeight: "0.1",
715
+ position: "relative"
716
+ },
717
+ ".wg-resize-handle": {
718
+ position: "absolute",
719
+ right: "1px", bottom: "1px",
720
+ width: "min(60%, 20px)",
721
+ height: "min(60%, 20px)",
722
+ },
723
+ ".wg-resize-handle-active": {
724
+ cursor: "nwse-resize"
725
+ }
726
+ });
727
+ const setResizing = /*@__PURE__*/Transaction.Effect.define({
728
+ map: (value, mapping) => {
729
+ let newPos = mapping.mapPos(value.target, 1, "after");
730
+ return newPos == null ? undefined : { target: newPos, resizing: value.resizing };
731
+ }
732
+ });
733
+ const handleElt = /*@__PURE__*/(() => Elt.mk("svg:svg", { class: "wg-resize-handle", viewBox: "0 0 20 20" }, [
734
+ Elt.mk("svg:path", { d: "M20 0L0 20M20 5L5 20M20 10L10 20", stroke: "#000000aa", "stroke-width": "1.5" }),
735
+ Elt.mk("svg:polygon", { points: "0,20 20,20 20,0", fill: "transparent", class: "wg-resize-handle-active" })
736
+ ]))();
737
+ const resizeWrapper = /*@__PURE__*/(() => Decoration.Point.wrapper(Elt.mk("span", { class: "wg-resize-hover" }, [handleElt, 0]), { target: "img" }))();
738
+ const resizeState = /*@__PURE__*/GardState.Field.define({
739
+ create: () => ({ target: -1, resizing: -1, deco: PointSet.empty }),
740
+ update: (value, tr) => {
741
+ for (let e of tr.effects) {
742
+ if (e.is(setResizing)) {
743
+ let { target, resizing } = e.value;
744
+ if (target < 0)
745
+ return { target: -1, resizing: -1, deco: PointSet.empty };
746
+ let deco = [[target, resizeWrapper]];
747
+ if (resizing > -1)
748
+ deco.push([target, Decoration.Point.attributes({ style: `width: ${resizing}px` }, { target: "img" })]);
749
+ return { target, resizing, deco: PointSet.create(deco) };
750
+ }
751
+ }
752
+ return value.target < 0 || !tr.docChanged ? value
753
+ : { target: value.target, resizing: value.resizing, deco: value.deco.map(tr.changes) };
754
+ }
755
+ });
756
+ const MIN_SIZE = 10;
757
+ function imageNode(wg, pos) {
758
+ let dom = wg.nodeDOM(pos);
759
+ return dom.nodeName == "IMG" ? dom : dom.querySelector("img[src]");
760
+ }
761
+ const resizeHandlers = /*@__PURE__*/(() => [
762
+ Wordgard.domEventHandler("mousedown", (event, wg) => {
763
+ let resizing = wg.state.field(resizeState);
764
+ if (resizing.target < 0)
765
+ return;
766
+ for (let dom = event.target;;) {
767
+ if (dom.classList.contains("wg-resize-handle-active"))
768
+ break;
769
+ let next = dom.parentNode;
770
+ if (!next || next == wg.contentDOM)
771
+ return;
772
+ dom = next;
773
+ }
774
+ let node = wg.state.doc.nodeAt(resizing.target);
775
+ let width = node.tag.mark(ImageSize) ?? imageNode(wg, resizing.target).getBoundingClientRect().width;
776
+ wg.dispatch({ effects: setResizing.of({ target: resizing.target, resizing: width }) });
777
+ event.preventDefault();
778
+ }),
779
+ Wordgard.domEventHandler("mousemove", (event, wg) => {
780
+ let resizing = wg.state.field(resizeState);
781
+ if (resizing.resizing > -1) {
782
+ let dom = imageNode(wg, resizing.target);
783
+ let width = event.clientX - dom.getBoundingClientRect().left;
784
+ if (width >= MIN_SIZE && Math.abs(width - resizing.resizing) >= 1)
785
+ wg.dispatch({ effects: setResizing.of({ target: resizing.target, resizing: width }) });
786
+ }
787
+ else {
788
+ let elt = event.target.closest("img, .wg-resize-handle");
789
+ let node = elt && wg.nodeFromDOM(elt);
790
+ let target = node && wg.state.schema.markAllowed(ImageSize, node.node.type) ? node.pos : -1;
791
+ if (target != resizing.target)
792
+ wg.dispatch({ effects: setResizing.of({ target, resizing: -1 }) });
793
+ }
794
+ }),
795
+ Wordgard.domEventHandler("mouseup", (event, wg) => {
796
+ let resizing = wg.state.field(resizeState);
797
+ if (resizing.resizing < 0)
798
+ return;
799
+ wg.dispatch({
800
+ effects: setResizing.of({ target: resizing.target, resizing: -1 }),
801
+ changes: { from: resizing.target, add: ImageSize.of(Math.round(resizing.resizing)) }
802
+ });
803
+ })
804
+ ])();
805
+ ;imageResizing = /*@__PURE__*/(function (imageResizing) {
806
+ imageResizing.resizeCommand = (by, relative = false) => wg => {
807
+ let { selection } = wg.state;
808
+ if (selection instanceof GardSelection.Node && wg.state.schema.markAllowed(ImageSize, selection.node.type)) {
809
+ let curWidth = selection.node.mark(ImageSize) ?? imageNode(wg, wg.state.selection.from).getBoundingClientRect().width;
810
+ let newWidth = Math.max(MIN_SIZE, relative ? curWidth * by : curWidth + by);
811
+ if (newWidth != curWidth) {
812
+ wg.dispatch({
813
+ changes: { from: wg.state.selection.from, add: ImageSize.of(newWidth) },
814
+ userEvent: "image.resize"
815
+ });
816
+ return true;
817
+ }
818
+ }
819
+ return false;
820
+ };
821
+ imageResizing.keyBindings = [
822
+ KeyBinding.of({ key: "Ctrl-Alt-l", mac: "Ctrl-Cmd-l", run: imageResizing.resizeCommand(1.1, true) }),
823
+ KeyBinding.of({ key: "Ctrl-Alt-k", mac: "Ctrl-Cmd-k", run: imageResizing.resizeCommand(0.9091, true) }),
824
+ ];
825
+ imageResizing.dragHandle = [
826
+ GardState.prec.high(resizeHandlers),
827
+ resizeState,
828
+ Decoration.Point.source.of(s => s.field(resizeState).deco),
829
+ resizeTheme
830
+ ];
831
+ ;return imageResizing})(imageResizing);
832
+ ;image = /*@__PURE__*/(function (image) {
833
+ image.keyBinding = KeyBinding.of({ key: "Ctrl-Alt-i", mac: "Ctrl-Cmd-i", run: insertImage });
834
+ image.button = Menu.Button.define({
835
+ run: insertImage,
836
+ active: state => !!activeImage(state.sel),
837
+ label: {
838
+ icon: "M38 34a9 9 0 1 1-19 0 9 9 0 0 1 19 0M9 13A9 9 0 0 0 0 22v56A9 9 0 0 0 9 88h81a9 9 0 0 0 9-9v-56A9 9 0 0 0 91 13zm81 6a3 3 0 0 1 3 3v38l-24-12a3 3 0 0 0-4 1l-23 23-17-11a3 3 0 0 0-4 0L6 75v3L6 78v-56a3 3 0 0 1 3-3z"
839
+ },
840
+ description: imagePhrases.ref("insert_image"),
841
+ enable: s => !s.readOnly,
842
+ parent: Menu.Group.insert,
843
+ rank: 30,
844
+ });
845
+ image.dropHandler = GardState.prec.lowest(Wordgard.domEventHandler("drop", (event, wg) => {
846
+ let { state } = wg, upload = state.facet(imageUploader)[0];
847
+ const type = state.schema.has(Image) ? Image : state.schema.has(Figure) ? Figure : null;
848
+ if (state.readOnly || !type || !upload || !event.dataTransfer)
849
+ return false;
850
+ let files = event.dataTransfer.files, uploads = [];
851
+ for (let i = 0; i < files.length; i++) {
852
+ let file = files[i];
853
+ if (/^image\//.test(file.type))
854
+ uploads.push(upload(file, wg, () => { }));
855
+ }
856
+ if (!uploads.length)
857
+ return false;
858
+ let dropPos = { x: event.clientX, y: event.clientY };
859
+ Promise.all(uploads).then(urls => {
860
+ wg.dispatch({
861
+ changes: { from: wg.posAtCoords(dropPos).pos, insert: urls.map(u => type.of(u)), fit: true },
862
+ userEvent: "drop.image"
863
+ });
864
+ }, err => {
865
+ Wordgard.logException(state, err, "Dropped image upload");
866
+ });
867
+ return true;
868
+ }));
869
+ image.insert = insertImage;
870
+ image.uploader = imageUploader;
871
+ ;return image})(image);
872
+
873
+ function setColor(wg, mark, value) {
874
+ let { state } = wg, { selection } = state;
875
+ if (state.readOnly)
876
+ return;
877
+ if (selection instanceof GardSelection.Text && selection.empty) {
878
+ let selMarks = selection.marks || state.sel.head.marks();
879
+ let newMarks = value ? mark.of(value).addToSet(selMarks) : mark.removeFromSet(selMarks);
880
+ wg.dispatch({
881
+ selection: GardSelection.Text.create({ anchor: selection.anchor, headSide: selection.headSide,
882
+ goalColumn: selection.goalColumn, marks: newMarks }),
883
+ userEvent: value ? "mark.add" : "mark.remove"
884
+ });
885
+ }
886
+ else if (value) {
887
+ wg.dispatch({
888
+ changes: selection.ranges.map(r => ({ from: r.from, to: r.to, add: mark.of(value) })),
889
+ userEvent: "mark.add"
890
+ });
891
+ }
892
+ else {
893
+ let changes = [];
894
+ for (let { from, to } of selection.ranges) {
895
+ state.doc.iterate(from, to, (node, pos) => {
896
+ let has = mark.isInSet(node.marks);
897
+ if (has)
898
+ changes.push({ from: Math.max(from, pos), to: Math.min(to, pos + node.length), remove: has });
899
+ });
900
+ }
901
+ wg.dispatch({ changes, userEvent: "mark.remove" });
902
+ }
903
+ }
904
+ class ColorPicker {
905
+ wg;
906
+ finish;
907
+ dom;
908
+ width;
909
+ selPos = 0;
910
+ options;
911
+ constructor(
912
+ wg,
913
+ finish) {
914
+ this.wg = wg;
915
+ this.finish = finish;
916
+ this.width = wg.state.facet(ColorPicker.width);
917
+ this.dom = document.createElement("wg-color-picker");
918
+ this.dom.role = "listbox";
919
+ this.dom.style.gridTemplateColumns = `repeat(${this.width}, max-content)`;
920
+ this.options = wg.state.facet(ColorPicker.options).map(({ name, detail, value }, i) => {
921
+ let option = this.dom.appendChild(document.createElement("wg-color-picker-color"));
922
+ let label = name(wg.state);
923
+ if (detail)
924
+ label += ` (${detail(wg.state)})`;
925
+ option.role = "option";
926
+ option.setAttribute("aria-label", label);
927
+ option.title = label;
928
+ if (i == this.selPos)
929
+ option.setAttribute("aria-selected", "true");
930
+ if (value)
931
+ option.style.backgroundColor = value;
932
+ else
933
+ option.className = "wg-no-color";
934
+ option.setAttribute("data-value", value);
935
+ return option;
936
+ });
937
+ this.dom.addEventListener("mousedown", e => {
938
+ if (e.button == 0) {
939
+ let target = e.target.closest("wg-color-picker-color");
940
+ if (target)
941
+ this.finish(target.getAttribute("data-value"));
942
+ }
943
+ });
944
+ this.dom.addEventListener("keydown", e => {
945
+ let ltr = this.wg.state.textLTR;
946
+ if (e.key == (ltr ? "ArrowLeft" : "ArrowRight") && this.selPos > 0) {
947
+ this.move(this.selPos - 1);
948
+ }
949
+ else if (e.key == (ltr ? "ArrowRight" : "ArrowLeft") && this.selPos < this.options.length - 1) {
950
+ this.move(this.selPos + 1);
951
+ }
952
+ else if (e.key == "ArrowUp" || e.key == "ArrowDown") {
953
+ let next = e.key == "ArrowUp" ? this.selPos - this.width : this.selPos + this.width;
954
+ if (next < 0 || next >= this.options.length - 1) {
955
+ let col = this.selPos % this.width;
956
+ if (next < 0)
957
+ next = (Math.ceil(this.options.length / this.width) - 1) * this.width + col - 1;
958
+ else
959
+ next = col + 1;
960
+ }
961
+ this.move(Math.max(0, Math.min(this.options.length - 1, next)));
962
+ }
963
+ else if (e.key == " " || e.key == "Enter") {
964
+ this.finish(this.options[this.selPos].getAttribute("data-value"));
965
+ }
966
+ else {
967
+ return;
968
+ }
969
+ e.preventDefault();
970
+ });
971
+ }
972
+ static create(wg, finish) {
973
+ return new ColorPicker(wg, finish);
974
+ }
975
+ move(selPos) {
976
+ if (selPos != this.selPos) {
977
+ let prev = this.options[this.selPos];
978
+ let cur = this.options[this.selPos = selPos];
979
+ prev.removeAttribute("aria-selected");
980
+ cur.setAttribute("aria-selected", "true");
981
+ }
982
+ }
983
+ }
984
+ ;ColorPicker = /*@__PURE__*/(function (ColorPicker) {
985
+ function col(rgb, name, mod) {
986
+ let detail = mod == 3 ? colorNames.ref("lightest") : mod == 2 ? colorNames.ref("lighter") :
987
+ mod == 1 ? colorNames.ref("light") : mod == -1 ? colorNames.ref("dark") :
988
+ mod == -2 ? colorNames.ref("darker") : mod ? colorNames.ref("darkest") : undefined;
989
+ return { name: colorNames.ref(name), detail, value: rgb };
990
+ }
991
+ function defaultColors() {
992
+ return [
993
+ col("", "none"),
994
+ col("#000000", "black"),
995
+ col("#434343", "grey", -3),
996
+ col("#666666", "grey", -2),
997
+ col("#999999", "grey", -1),
998
+ col("#cccccc", "grey"),
999
+ col("#d9d9d9", "grey", 1),
1000
+ col("#efefef", "grey", 2),
1001
+ col("#f3f3f3", "grey", 3),
1002
+ col("#ffffff", "white"),
1003
+ col("#980000", "red_berry"),
1004
+ col("#ff0000", "red"),
1005
+ col("#ff9900", "orange"),
1006
+ col("#ffff00", "yellow"),
1007
+ col("#00ff00", "green"),
1008
+ col("#00ffff", "cyan"),
1009
+ col("#4a86e8", "cornflower"),
1010
+ col("#0000ff", "blue"),
1011
+ col("#9900ff", "purple"),
1012
+ col("#ff00ff", "magenta"),
1013
+ col("#e6b8af", "red_berry", 3),
1014
+ col("#f4cccc", "red", 3),
1015
+ col("#fce5cd", "orange", 3),
1016
+ col("#fff2cc", "yellow", 3),
1017
+ col("#d9ead3", "green", 3),
1018
+ col("#d0e0e3", "cyan", 3),
1019
+ col("#c9daf8", "cornflower", 3),
1020
+ col("#cfe2f3", "blue", 3),
1021
+ col("#d9d2e9", "purple", 3),
1022
+ col("#ead1dc", "magenta", 3),
1023
+ col("#dd7e6b", "red_berry", 2),
1024
+ col("#ea9999", "red", 2),
1025
+ col("#f9cb9c", "orange", 2),
1026
+ col("#ffe599", "yellow", 2),
1027
+ col("#b6d7a8", "green", 2),
1028
+ col("#a2c4c9", "cyan", 2),
1029
+ col("#a4c2f4", "cornflower", 2),
1030
+ col("#9fc5e8", "blue", 2),
1031
+ col("#b4a7d6", "purple", 2),
1032
+ col("#d5a6bd", "magenta", 2),
1033
+ col("#cc4125", "red_berry", 1),
1034
+ col("#e06666", "red", 1),
1035
+ col("#f6b26b", "orange", 1),
1036
+ col("#ffd966", "yellow", 1),
1037
+ col("#93c47d", "green", 1),
1038
+ col("#76a5af", "cyan", 1),
1039
+ col("#6d9eeb", "cornflower", 1),
1040
+ col("#6fa8dc", "blue", 1),
1041
+ col("#8e7cc3", "purple", 1),
1042
+ col("#c27ba0", "magenta", 1),
1043
+ col("#a61c00", "red_berry", -1),
1044
+ col("#cc0000", "red", -1),
1045
+ col("#e69138", "orange", -1),
1046
+ col("#f1c232", "yellow", -1),
1047
+ col("#6aa84f", "green", -1),
1048
+ col("#45818e", "cyan", -1),
1049
+ col("#3c78d8", "cornflower", -1),
1050
+ col("#3d85c6", "blue", -1),
1051
+ col("#674ea7", "purple", -1),
1052
+ col("#a64d79", "magenta", -1),
1053
+ col("#85200c", "red_berry", -2),
1054
+ col("#990000", "red", -2),
1055
+ col("#b45f06", "orange", -2),
1056
+ col("#bf9000", "yellow", -2),
1057
+ col("#38761d", "green", -2),
1058
+ col("#134f5c", "cyan", -2),
1059
+ col("#1155cc", "cornflower", -2),
1060
+ col("#0b5394", "blue", -2),
1061
+ col("#351c75", "purple", -2),
1062
+ col("#741b47", "magenta", -2),
1063
+ col("#5b0f00", "red_berry", -3),
1064
+ col("#660000", "red", -3),
1065
+ col("#783f04", "orange", -3),
1066
+ col("#7f6000", "yellow", -3),
1067
+ col("#274e13", "green", -3),
1068
+ col("#0c343d", "cyan", -3),
1069
+ col("#1c4587", "cornflower", -3),
1070
+ col("#073763", "blue", -3),
1071
+ col("#20124d", "purple", -3),
1072
+ col("#4c1130", "magenta", -3),
1073
+ ];
1074
+ }
1075
+ ColorPicker.width = GardState.Facet.define({
1076
+ combine: values => values.length ? values[0] : 10
1077
+ });
1078
+ ColorPicker.options = GardState.Facet.define({
1079
+ combine: values => values.length ? values[0] : defaultColors()
1080
+ });
1081
+ ColorPicker.theme = Wordgard.styles({
1082
+ "wg-color-picker": {
1083
+ display: "grid",
1084
+ gap: "4px",
1085
+ padding: "3px",
1086
+ },
1087
+ "wg-color-picker-color": {
1088
+ borderRadius: "50%",
1089
+ border: "1px solid var(--wg-border-color)",
1090
+ width: "12px",
1091
+ height: "12px",
1092
+ "wg-color-picker:focus &[aria-selected], &:hover": {
1093
+ outline: "2px solid var(--wg-highlight-color)"
1094
+ },
1095
+ "&.wg-no-color": {
1096
+ border: "none",
1097
+ background: `${crossGradient(45)}, ${crossGradient(135)}`
1098
+ }
1099
+ },
1100
+ });
1101
+ ;return ColorPicker})(ColorPicker);
1102
+ function crossGradient(angle) {
1103
+ return `linear-gradient(${angle}deg, transparent, transparent 44%, currentColor 44%, currentColor 56%, transparent 56%)`;
1104
+ }
1105
+ const colorPicker = /*@__PURE__*/Menu.CustomControl.define({
1106
+ render(wg, done) {
1107
+ return ColorPicker.create(wg, color => {
1108
+ done();
1109
+ setColor(wg, Color, color);
1110
+ wg.focus();
1111
+ });
1112
+ }
1113
+ });
1114
+ function color() {
1115
+ return [GardState.schemaElement.of(Color), color.button, ColorPicker.theme];
1116
+ }
1117
+ ;color = /*@__PURE__*/(function (color) {
1118
+ color.button = Menu.Submenu.define({
1119
+ label: {
1120
+ icon: "M5 8A3 3 0 0 1 8 5h28a3 3 0 0 1 3 3v30l23-23a3 3 0 0 1 4 0l20 20a3 3 0 0 1 0 4L63 61H92a3 3 0 0 1 3 3v28a3 3 0 0 1-3 3H22a17 17 0 0 1-12-5A17 17 0 0 1 5 78m34-1 41-41-16-16L39 45zM30 78a8 8 0 1 0-17 0 8 8 0 0 0 17 0M89 89v-22H57l-23 23zM5 8v70zm0 70V78z",
1121
+ },
1122
+ description: phrases.ref("text_color"),
1123
+ arrow: false,
1124
+ parent: Menu.Group.inline,
1125
+ enable: s => !s.readOnly,
1126
+ rank: 80,
1127
+ content: [colorPicker]
1128
+ });
1129
+ ;return color})(color);
1130
+ function backgroundColor() {
1131
+ return [GardState.schemaElement.of(BackgroundColor), backgroundColor.button, ColorPicker.theme];
1132
+ }
1133
+ const backgroundPicker = /*@__PURE__*/Menu.CustomControl.define({
1134
+ render(wg, done) {
1135
+ return ColorPicker.create(wg, color => {
1136
+ done();
1137
+ setColor(wg, BackgroundColor, color);
1138
+ wg.focus();
1139
+ });
1140
+ }
1141
+ });
1142
+ ;backgroundColor = /*@__PURE__*/(function (backgroundColor) {
1143
+ backgroundColor.button = Menu.Submenu.define({
1144
+ label: {
1145
+ icon: "M67 9a11 11 0 0 1 16 0l8 8a11 11 0 0 1 0 16l-2 2-45 51a3 3 0 0 1-2 1h-17a3 3 0 0 1-1 0l-2 2A3 3 0 0 1 19 89h-11a3 3 0 0 1-2-5l8-8A3 3 0 0 1 13 75v-17a3 3 0 0 1 1-2l51-45zm-1 8L20 59l21 21 42-46zm20 12 0 0a6 6 0 0 0 0-8L79 13a6 6 0 0 0-8 0l0 0zM35 81 19 65v9L26 81z"
1146
+ },
1147
+ description: phrases.ref("background_color"),
1148
+ arrow: false,
1149
+ parent: Menu.Group.inline,
1150
+ enable: s => !s.readOnly,
1151
+ rank: 85,
1152
+ content: [backgroundPicker]
1153
+ });
1154
+ ;return backgroundColor})(backgroundColor);
1155
+
1156
+ function toggleLink(wg) {
1157
+ if (wg.state.readOnly)
1158
+ return false;
1159
+ let open = Dialog.get(wg, "wg-link-dialog");
1160
+ if (open) {
1161
+ if (open.dom.contains(wg.contentDOM.ownerDocument.activeElement))
1162
+ wg.focus();
1163
+ Dialog.close(wg, "wg-link-dialog");
1164
+ return true;
1165
+ }
1166
+ let { selection, doc } = wg.state;
1167
+ if (selection.empty)
1168
+ return false;
1169
+ let remove = [];
1170
+ for (let { from, to } of selection.ranges)
1171
+ doc.iterate(from, to, (node, pos) => {
1172
+ let has = Link.isInSet(node.marks);
1173
+ if (has)
1174
+ remove.push({ from: pos, to: pos + node.length, remove: has });
1175
+ });
1176
+ if (remove.length) {
1177
+ wg.dispatch({ changes: remove, userEvent: "mark.remove" });
1178
+ }
1179
+ else {
1180
+ Dialog.show(wg, {
1181
+ label: phrases.get(wg.state, "link_target"),
1182
+ input: { type: "text", name: "url" },
1183
+ submitLabel: phrases.get(wg.state, "create_link"),
1184
+ class: "wg-link-dialog",
1185
+ focus: true
1186
+ }).result.then(form => {
1187
+ wg.focus();
1188
+ let url = form && form.elements.namedItem("url")?.value;
1189
+ if (url)
1190
+ wg.dispatch({
1191
+ changes: selection.ranges.map(r => ({ from: r.from, to: r.to, add: Link.of(url) })),
1192
+ userEvent: "mark.add"
1193
+ });
1194
+ });
1195
+ }
1196
+ return true;
1197
+ }
1198
+ function computeLinkTooltip(state) {
1199
+ if (!state.selection.isCursor)
1200
+ return null;
1201
+ let { head } = state.sel, before = head.nodeBefore, link = before && Link.isInSet(before.marks);
1202
+ if (!link)
1203
+ return null;
1204
+ let start = head.pos - before.length, end = head.pos, siblings = head.parent.node.content;
1205
+ for (let index = head.index - 1; index > 0 && link.isInSet(siblings[index - 1].marks);)
1206
+ start -= siblings[--index].length;
1207
+ for (let index = head.index; index < siblings.length && link.isInSet(siblings[index].marks);)
1208
+ end += siblings[index++].length;
1209
+ return {
1210
+ pos: start,
1211
+ end,
1212
+ above: false,
1213
+ create: () => renderLinkTooltip(link.value)
1214
+ };
1215
+ }
1216
+ const closeLinkTooltip = /*@__PURE__*/Transaction.Effect.define();
1217
+ const linkTooltipField = /*@__PURE__*/GardState.Field.define({
1218
+ create: computeLinkTooltip,
1219
+ update(value, tr) {
1220
+ if (tr.effects.some(e => e.is(closeLinkTooltip)))
1221
+ return null;
1222
+ let sel = tr.selection;
1223
+ if (!tr.docChanged && (!sel || value && sel.isCursor && sel.head >= value.pos && sel.head <= value.end))
1224
+ return value;
1225
+ return computeLinkTooltip(tr.state);
1226
+ },
1227
+ provide: f => Tooltip.show.from(f)
1228
+ });
1229
+ function renderLinkTooltip(target) {
1230
+ let dom = document.createElement("wg-link-tooltip");
1231
+ let link = dom.appendChild(document.createElement("a"));
1232
+ link.href = target;
1233
+ link.textContent = target;
1234
+ return { dom };
1235
+ }
1236
+ const linkTooltipTheme = /*@__PURE__*/Wordgard.styles({
1237
+ "wg-link-tooltip": {
1238
+ maxWidth: "30em",
1239
+ fontSize: "90%",
1240
+ textOverflow: "ellipsis",
1241
+ whiteSpace: "pre",
1242
+ overflow: "hidden",
1243
+ borderRadius: "3px",
1244
+ padding: "2px 5px",
1245
+ marginTop: "1px",
1246
+ "& a": {
1247
+ textDecoration: "none",
1248
+ color: "inherit"
1249
+ }
1250
+ }
1251
+ });
1252
+ function link() {
1253
+ return [GardState.schemaElement.of(Link), link.button, link.keyBinding, link.tooltip, link.pasteOver];
1254
+ }
1255
+ ;link = /*@__PURE__*/(function (link_1) {
1256
+ link_1.keyBinding = KeyBinding.of({
1257
+ key: "Mod-k",
1258
+ run: toggleLink,
1259
+ });
1260
+ link_1.button = Menu.Button.define({
1261
+ run: toggleLink,
1262
+ active(state) {
1263
+ let { selection, doc } = state, found = false;
1264
+ if (!selection.empty)
1265
+ for (let { from, to } of selection.ranges)
1266
+ doc.iterate(from, to, node => {
1267
+ if (found)
1268
+ return false;
1269
+ if (Link.isInSet(node.marks))
1270
+ found = true;
1271
+ });
1272
+ return found;
1273
+ },
1274
+ enable(state) {
1275
+ return !state.readOnly && !state.selection.empty;
1276
+ },
1277
+ label: {
1278
+ icon: "M29 41 21 49a19 19 0 1 0 27 27l11-11A19 19 0 0 0 54 34L50 38a6 6 0 0 0-1 1 13 13 0 0 1 5 22L43 72a12 12 0 1 1-18-18l5-5a25 25 0 0 1-1-8zM41 29A19 19 0 0 0 46 59l5-5a13 13 0 0 1-6-21L57 22a12 12 0 1 1 18 18l-5 5c1 3 1 5 1 8l9-9a19 19 0 1 0-27-27z"
1279
+ },
1280
+ description: phrases.ref("create_link"),
1281
+ parent: Menu.Group.inline,
1282
+ rank: 50,
1283
+ });
1284
+ link_1.tooltip = [
1285
+ linkTooltipField,
1286
+ GardState.prec.low(KeyBinding.of({
1287
+ key: "Escape",
1288
+ run: wg => {
1289
+ if (!wg.state.field(linkTooltipField))
1290
+ return false;
1291
+ wg.dispatch({ effects: closeLinkTooltip.of(null) });
1292
+ return true;
1293
+ }
1294
+ })),
1295
+ linkTooltipTheme
1296
+ ];
1297
+ link_1.pasteOver = Wordgard.pasteHandler.of((wg, event) => {
1298
+ let { selection } = wg.state, data = event.clipboardData;
1299
+ if (!data || selection.empty)
1300
+ return false;
1301
+ let text = data.getData("text/plain") || data.getData("Text") || data.getData("text/uri-list");
1302
+ if (!text || !/^(https?|mailto|xmpp|data):[^ ]+$/.test(text))
1303
+ return false;
1304
+ let link = Link.of(text);
1305
+ let changes = ChangeSet.create(wg.state.doc, { from: selection.from, to: selection.to, add: link });
1306
+ if (changes.empty)
1307
+ return false;
1308
+ wg.dispatch({
1309
+ changes,
1310
+ userEvent: "paste.link",
1311
+ scrollIntoView: true
1312
+ });
1313
+ return true;
1314
+ });
1315
+ ;return link})(link);
1316
+
1317
+ function lineBreak() {
1318
+ return GardState.schemaElement.of(LineBreak);
1319
+ }
1320
+ function basicMarks() {
1321
+ return [strong(), emphasis(), link()];
1322
+ }
1323
+ function inlineMarks() {
1324
+ return [basicMarks(), code(), underline(), strikethrough(), superscript(), subscript(), color(), backgroundColor()];
1325
+ }
1326
+ function basicSchema() {
1327
+ return [blockDoc(), basicMarks(), paragraph(), heading(), lineBreak()];
1328
+ }
1329
+ function inlineSchema() {
1330
+ return [inlineDoc(), basicMarks(), image(), lineBreak()];
1331
+ }
1332
+ function fullSchema() {
1333
+ return [
1334
+ blockDoc(), paragraph(), heading(), lineBreak(),
1335
+ codeBlock(), alignment(), direction(), blockquote(), horizontalRule(),
1336
+ bulletList(), orderedList(),
1337
+ inlineMarks(), image(), figure(), imageResizing()
1338
+ ];
1339
+ }
1340
+
1341
+ export { ColorPicker, alignment, backgroundColor, basicMarks, basicSchema, blockDoc, blockquote, bulletList, code, codeBlock, color, direction, emphasis, figure, fullSchema, heading, horizontalRule, image, imageResizing, inlineDoc, inlineMarks, inlineSchema, lineBreak, link, orderedList, paragraph, strikethrough, strong, subscript, superscript, underline };