@nerd-bible/wordgard 0.5.3-beta7 → 0.6.2-beta10

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.d.ts CHANGED
@@ -240,6 +240,42 @@ declare namespace codeBlock {
240
240
  language, converts the block into a code block.
241
241
  */
242
242
  const createOnBackticks: GardState.Extension;
243
+ /**
244
+ Simple style that gives code blocks a background and slightly
245
+ rounded corners.
246
+ */
247
+ const theme: GardState.Extension;
248
+ }
249
+ /**
250
+ Add support for the code block {@link CodeBlockLanguage language
251
+ mark}.
252
+ */
253
+ declare function codeBlockLanguage(options?: {
254
+ /**
255
+ When given, show a drop-down menu in the corner of code blocks
256
+ that allows the user to choose a language for block from the
257
+ given list of strings. The actual mark value will be the
258
+ lower-case form of the selected language name.
259
+ */
260
+ languages?: readonly string[];
261
+ }): GardState.Extension;
262
+ declare namespace codeBlockLanguage {
263
+ /**
264
+ If the selection is in a code block, and language selection is
265
+ enabled, this command focuses the language field to allow
266
+ selection via the keyboard.
267
+ */
268
+ const selectLanguage: Command;
269
+ /**
270
+ Key binding that binds Shift-Ctrl-l (Shift-Cmd-l on MacOS) to
271
+ {@link codeBlockLanguage.selectLanguage `selectLanguage`}.
272
+ */
273
+ const selectLanguageBinding: KeyBinding;
274
+ /**
275
+ A facet that allows you to add options to the language selection
276
+ drop-down.
277
+ */
278
+ const options: GardState.Facet<readonly string[], readonly string[]>;
243
279
  }
244
280
 
245
281
  /**
@@ -576,4 +612,4 @@ declare namespace image {
576
612
  const uploader: GardState.Facet<(file: File, wg: Wordgard, progress: (percent: number) => void) => Promise<string>, readonly ((file: File, wg: Wordgard, progress: (percent: number) => void) => Promise<string>)[]>;
577
613
  }
578
614
 
579
- 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 };
615
+ export { ColorPicker, alignment, backgroundColor, basicMarks, basicSchema, blockDoc, blockquote, bulletList, code, codeBlock, codeBlockLanguage, color, direction, emphasis, figure, fullSchema, heading, horizontalRule, image, imageResizing, inlineDoc, inlineMarks, inlineSchema, lineBreak, link, orderedList, paragraph, strikethrough, strong, subscript, superscript, underline };
package/dist/schema.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { ChangeSet, Leaf, Plot, Elt } from 'wordgard/doc';
2
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';
3
+ import { Paragraph, Heading, Direction, Blockquote, HorizontalRule, Alignment, Doc, InlineDoc, BulletList, OrderedList, InlineListItem, ListItem, CodeBlock, CodeBlockLanguage, Strong, Emphasis, Code, Underline, Strikethrough, Superscript, Subscript, Image, Figure, CaptionedFigure, ImageSize, ImageAlt, Color, BackgroundColor, Link, LineBreak } from 'wordgard/types';
4
4
  import { phrases, imagePhrases, colorNames } from 'wordgard/phrases';
5
5
  import { Command, setTextblockType, Menu, setAlignment, setDirection, toggleBlock, listIsActive, toggleList, enter, toggleMark } from 'wordgard/command';
6
6
  import { history } from 'wordgard/history';
7
- import { KeyBinding, InputRule, Wordgard, Widget, Decoration, Panel, Dialog, Tooltip } from 'wordgard/editor';
7
+ import { KeyBinding, InputRule, Wordgard, Widget, Decoration, Dialog, Tooltip } from 'wordgard/editor';
8
8
  import cr from 'crelt';
9
9
 
10
10
  function blockDoc() {
@@ -25,6 +25,8 @@ function paragraph() {
25
25
  ;paragraph = /*@__PURE__*/(function (paragraph) {
26
26
  paragraph.keyBinding = KeyBinding.of({
27
27
  key: "Ctrl-Shift-0",
28
+ description: phrases.ref("paragraph"),
29
+ group: KeyBinding.Group.Block,
28
30
  run: Command.bind(setTextblockType, Paragraph)
29
31
  });
30
32
  paragraph.button = Menu.Button.define({
@@ -43,12 +45,18 @@ function heading() {
43
45
  }
44
46
  ;heading = /*@__PURE__*/(function (heading) {
45
47
  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)) })
48
+ KeyBinding.of({ key: "Ctrl-Shift-1", run: Command.bind(setTextblockType, Heading.of(1)),
49
+ description: phrases.ref("heading_1"), group: KeyBinding.Group.Block }),
50
+ KeyBinding.of({ key: "Ctrl-Shift-2", run: Command.bind(setTextblockType, Heading.of(2)),
51
+ description: phrases.ref("heading_2"), group: KeyBinding.Group.Block }),
52
+ KeyBinding.of({ key: "Ctrl-Shift-3", run: Command.bind(setTextblockType, Heading.of(3)),
53
+ description: phrases.ref("heading_3"), group: KeyBinding.Group.Block }),
54
+ KeyBinding.of({ key: "Ctrl-Shift-4", run: Command.bind(setTextblockType, Heading.of(4)),
55
+ description: phrases.ref("heading_4"), group: KeyBinding.Group.Block }),
56
+ KeyBinding.of({ key: "Ctrl-Shift-5", run: Command.bind(setTextblockType, Heading.of(5)),
57
+ description: phrases.ref("heading_5"), group: KeyBinding.Group.Block }),
58
+ KeyBinding.of({ key: "Ctrl-Shift-6", run: Command.bind(setTextblockType, Heading.of(6)),
59
+ description: phrases.ref("heading_6"), group: KeyBinding.Group.Block })
52
60
  ];
53
61
  heading.button1 = Menu.Button.define({
54
62
  run: Command.bind(setTextblockType, Heading.of(1)),
@@ -85,9 +93,12 @@ function alignmentAtCursor(state) {
85
93
  }
86
94
  ;alignment = /*@__PURE__*/(function (alignment) {
87
95
  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") })
96
+ KeyBinding.of({ key: "Mod-Shift-l", run: Command.bind(setAlignment, "left"),
97
+ description: phrases.ref("align_left"), group: KeyBinding.Group.Block }),
98
+ KeyBinding.of({ key: "Mod-Shift-r", run: Command.bind(setAlignment, "right"),
99
+ description: phrases.ref("align_right"), group: KeyBinding.Group.Block }),
100
+ KeyBinding.of({ key: "Mod-Shift-e", run: Command.bind(setAlignment, "center"),
101
+ description: phrases.ref("align_center"), group: KeyBinding.Group.Block })
91
102
  ];
92
103
  alignment.buttonStart = Menu.Button.define({
93
104
  run: Command.bind(setAlignment, null),
@@ -277,13 +288,20 @@ function orderedList(config = {}) {
277
288
  ;return orderedList})(orderedList);
278
289
 
279
290
  function codeBlock() {
280
- return [GardState.schemaElement.of(CodeBlock),
281
- codeBlock.button, codeBlock.keyBinding, codeBlock.createOnBackticks];
291
+ return [
292
+ GardState.schemaElement.of(CodeBlock),
293
+ codeBlock.button,
294
+ codeBlock.keyBinding,
295
+ codeBlock.createOnBackticks,
296
+ codeBlock.theme
297
+ ];
282
298
  }
283
299
  ;codeBlock = /*@__PURE__*/(function (codeBlock) {
284
300
  codeBlock.keyBinding = KeyBinding.of({
285
301
  key: "Ctrl-Shift-\\",
286
- run: Command.bind(setTextblockType, CodeBlock)
302
+ run: Command.bind(setTextblockType, CodeBlock),
303
+ description: phrases.ref("code_block"),
304
+ group: KeyBinding.Group.Block
287
305
  });
288
306
  codeBlock.button = Menu.Button.define({
289
307
  run: Command.bind(setTextblockType, CodeBlock),
@@ -312,6 +330,13 @@ function codeBlock() {
312
330
  scrollIntoView: true
313
331
  };
314
332
  }));
333
+ codeBlock.theme = Wordgard.theme({
334
+ pre: {
335
+ padding: "8px 12px",
336
+ borderRadius: "4px",
337
+ background: "#88888819"
338
+ }
339
+ });
315
340
  ;return codeBlock})(codeBlock);
316
341
  function codeBlockLanguage(options = {}) {
317
342
  return [
@@ -331,15 +356,24 @@ const languageStyles = /*@__PURE__*/Wordgard.styles({
331
356
  font: "var(--wg-dialog-font)",
332
357
  fontSize: "70%",
333
358
  position: "absolute",
334
- top: "2px",
335
- right: "4px",
359
+ top: "5px",
360
+ right: "5px",
336
361
  border: "none",
337
- borderRadius: "4px"
362
+ borderRadius: "4px",
363
+ background: "#88888827"
338
364
  }
339
365
  }
340
366
  });
341
367
  const languageOptions = /*@__PURE__*/GardState.Facet.define({
342
- combine: input => input.reduce((a, b) => a.concat(b), [])
368
+ name: "languageOptions",
369
+ combine: input => {
370
+ let options = [];
371
+ for (let arr of input)
372
+ for (let opt of arr)
373
+ if (!options.includes(opt))
374
+ options.push(opt);
375
+ return options;
376
+ }
343
377
  });
344
378
  function option(text, selected, value) {
345
379
  let node = document.createElement("option");
@@ -381,7 +415,7 @@ const languageWidget = /*@__PURE__*/Widget.define({
381
415
  },
382
416
  inFlow: false
383
417
  });
384
- const languageSelect = /*@__PURE__*/Decoration.Tag.widget.dynamic(CodeBlock, "end", state => {
418
+ const languageSelect = /*@__PURE__*/Decoration.Tag.widget.derived(CodeBlock, "end", state => {
385
419
  let options = state.facet(languageOptions);
386
420
  return tag => {
387
421
  return languageWidget.of({ options, value: tag.mark(CodeBlockLanguage) });
@@ -401,8 +435,11 @@ const languageSelect = /*@__PURE__*/Decoration.Tag.widget.dynamic(CodeBlock, "en
401
435
  };
402
436
  codeBlockLanguage.selectLanguageBinding = KeyBinding.of({
403
437
  key: "Shift-Mod-l",
404
- run: codeBlockLanguage.selectLanguage
438
+ run: codeBlockLanguage.selectLanguage,
439
+ description: phrases.ref("code_block_language"),
440
+ group: KeyBinding.Group.Block
405
441
  });
442
+ codeBlockLanguage.options = languageOptions;
406
443
  ;return codeBlockLanguage})(codeBlockLanguage);
407
444
 
408
445
  function strong() {
@@ -412,6 +449,8 @@ function strong() {
412
449
  strong.keyBinding = KeyBinding.of({
413
450
  key: "Mod-b",
414
451
  run: Command.bind(toggleMark, Strong),
452
+ description: phrases.ref("toggle_strong"),
453
+ group: KeyBinding.Group.Inline
415
454
  });
416
455
  strong.button = Menu.Button.toggleMark({
417
456
  mark: Strong,
@@ -430,6 +469,8 @@ function emphasis() {
430
469
  emphasis.keyBinding = KeyBinding.of({
431
470
  key: "Mod-i",
432
471
  run: Command.bind(toggleMark, Emphasis),
472
+ description: phrases.ref("toggle_em"),
473
+ group: KeyBinding.Group.Inline
433
474
  });
434
475
  emphasis.button = Menu.Button.toggleMark({
435
476
  mark: Emphasis,
@@ -448,6 +489,8 @@ function code() {
448
489
  code.keyBinding = KeyBinding.of({
449
490
  key: "Mod-`",
450
491
  run: Command.bind(toggleMark, Code),
492
+ description: phrases.ref("toggle_code"),
493
+ group: KeyBinding.Group.Inline
451
494
  });
452
495
  code.button = Menu.Button.toggleMark({
453
496
  mark: Code,
@@ -465,7 +508,9 @@ function underline() {
465
508
  ;underline = /*@__PURE__*/(function (underline) {
466
509
  underline.keyBinding = KeyBinding.of({
467
510
  key: "Mod-u",
468
- run: Command.bind(toggleMark, Underline)
511
+ run: Command.bind(toggleMark, Underline),
512
+ description: phrases.ref("toggle_underline"),
513
+ group: KeyBinding.Group.Inline
469
514
  });
470
515
  underline.button = Menu.Button.toggleMark({
471
516
  mark: Underline,
@@ -484,6 +529,8 @@ function strikethrough() {
484
529
  strikethrough.keyBinding = KeyBinding.of({
485
530
  key: "Mod-/",
486
531
  run: Command.bind(toggleMark, Strikethrough),
532
+ description: phrases.ref("toggle_strikethrough"),
533
+ group: KeyBinding.Group.Inline
487
534
  });
488
535
  strikethrough.button = Menu.Button.toggleMark({
489
536
  mark: Strikethrough,
@@ -502,6 +549,8 @@ function superscript() {
502
549
  superscript.keyBinding = KeyBinding.of({
503
550
  key: "Mod-.",
504
551
  run: Command.bind(toggleMark, Superscript),
552
+ description: phrases.ref("toggle_super"),
553
+ group: KeyBinding.Group.Inline
505
554
  });
506
555
  superscript.button = Menu.Button.toggleMark({
507
556
  mark: Superscript,
@@ -520,6 +569,8 @@ function subscript() {
520
569
  subscript.keyBinding = KeyBinding.of({
521
570
  key: "Mod-,",
522
571
  run: Command.bind(toggleMark, Subscript),
572
+ description: phrases.ref("toggle_sub"),
573
+ group: KeyBinding.Group.Inline
523
574
  });
524
575
  subscript.button = Menu.Button.toggleMark({
525
576
  mark: Subscript,
@@ -532,7 +583,7 @@ function subscript() {
532
583
  });
533
584
  ;return subscript})(subscript);
534
585
 
535
- const imageUploader = /*@__PURE__*/GardState.Facet.define();
586
+ const imageUploader = /*@__PURE__*/GardState.Facet.define({ name: "imageUploader" });
536
587
  const imageTypes = [Image, Figure, CaptionedFigure];
537
588
  function activeImage(sel) {
538
589
  if (sel.selection instanceof GardSelection.Node && imageTypes.includes(sel.selection.node.type))
@@ -598,22 +649,6 @@ function imageTypeButtons(state, active) {
598
649
  }
599
650
  return [cr("span", { class: "wg-label" }, imagePhrases.get(state, "image_style"), ":"), cr("span", buttons)];
600
651
  }
601
- const setImageDialog = /*@__PURE__*/Transaction.Effect.define();
602
- const createImagePanel = wg => {
603
- let dom = buildImagePanel(wg), mustFocus = true;
604
- return {
605
- top: true,
606
- dom,
607
- connect() {
608
- if (mustFocus) {
609
- mustFocus = false;
610
- let target = dom.querySelector("input[name=src]");
611
- if (target)
612
- target.focus();
613
- }
614
- }
615
- };
616
- };
617
652
  function startUpload(wg, file, set) {
618
653
  let imageFile = file.files?.[0], handler = wg.state.facet(imageUploader)[0];
619
654
  if (!imageFile || !handler)
@@ -635,9 +670,8 @@ function startUpload(wg, file, set) {
635
670
  Dialog.show(wg, { label: imagePhrases.get(wg.state, "upload_failed") + ": " + err });
636
671
  });
637
672
  }
638
- function buildImagePanel(wg) {
639
- let { state } = wg;
640
- let sel = (state.field(imageDialog) || state.selection).resolve(state.doc);
673
+ function buildImageDialog(wg, close) {
674
+ let { state } = wg, { sel } = state;
641
675
  let active = activeImage(sel);
642
676
  let size = !wg.state.schema.has(ImageSize) ? null :
643
677
  [cr("label", { for: "wg-img-size" }, imagePhrases.get(state, "width"), ":"),
@@ -650,90 +684,78 @@ function buildImagePanel(wg) {
650
684
  file = cr("input", { type: "file", id: "wg-img-file", name: "file", "aria-label": imagePhrases.get(state, "upload_image"),
651
685
  onchange: (e) => startUpload(wg, e.target, url => src.value = url) });
652
686
  }
653
- 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",
687
+ return cr("form", { class: "wg-img-form", onkeydown }, cr("h3", { 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",
654
688
  value: active && active.mark(ImageAlt) || "",
655
689
  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"))));
656
- function onsubmit(e) {
657
- e.preventDefault();
658
- let { state } = wg, sel = (state.field(imageDialog) || state.selection).resolve(state.doc);
659
- let data = new FormData(form);
660
- let src = data.get("src");
661
- if (!src)
662
- return;
663
- let type = data.get("type") ?? (state.schema.has(Image) ? "inline" : "start");
664
- let cap = !!data.get("caption") || !state.schema.has(Figure);
665
- let marks = [];
666
- if (type == "center" || type == "end")
667
- marks = Alignment.of(type).addToSet(marks);
668
- if (data.get("alt"))
669
- marks = ImageAlt.of(data.get("alt")).addToSet(marks);
670
- if (data.get("size"))
671
- marks = ImageSize.of(Number(data.get("size"))).addToSet(marks);
672
- let tag = type == "inline" ? Image.of(src, marks) : cap ? CaptionedFigure.of(src, marks) : Figure.of(src, marks);
673
- let change;
674
- if (sel.from.parent.node.type == CaptionedFigure && sel.to.parent.start == sel.from.parent.start) {
675
- let from = sel.from.parent.before;
676
- if (tag instanceof Plot.Tag)
677
- change = { from, to: from + 1, insert: [tag] };
678
- else
679
- change = { from, to: sel.from.parent.after, insert: [tag], fit: true };
680
- }
681
- else {
682
- change = { from: sel.from.pos, to: sel.to.pos, insert: [tag instanceof Plot.Tag ? tag.create() : tag], fit: true };
683
- }
684
- wg.focus();
685
- let changes = ChangeSet.create(state.doc, change), pos = changes.findInserted(t => t == tag) ?? change.from;
686
- wg.dispatch({
687
- changes: change,
688
- effects: setImageDialog.of(false),
689
- userEvent: "insert.image",
690
- selection: tag instanceof Plot.Tag ? { anchor: pos + 1 } : GardSelection.node(pos, tag)
691
- });
692
- }
693
- function close() {
694
- wg.focus();
695
- wg.dispatch({ effects: setImageDialog.of(false) });
690
+ }
691
+ function finishInsertImage(wg, form) {
692
+ if (!form)
693
+ return;
694
+ let { state } = wg, { sel } = state;
695
+ let data = new FormData(form);
696
+ let src = data.get("src");
697
+ if (!src)
698
+ return;
699
+ let type = data.get("type") ?? (state.schema.has(Image) ? "inline" : "start");
700
+ let cap = !!data.get("caption") || !state.schema.has(Figure);
701
+ let marks = [];
702
+ if (type == "center" || type == "end")
703
+ marks = Alignment.of(type).addToSet(marks);
704
+ if (data.get("alt"))
705
+ marks = ImageAlt.of(data.get("alt")).addToSet(marks);
706
+ if (data.get("size"))
707
+ marks = ImageSize.of(Number(data.get("size"))).addToSet(marks);
708
+ let tag = type == "inline" ? Image.of(src, marks) : cap ? CaptionedFigure.of(src, marks) : Figure.of(src, marks);
709
+ let change;
710
+ if (sel.from.parent.node.type == CaptionedFigure && sel.to.parent.start == sel.from.parent.start) {
711
+ let from = sel.from.parent.before;
712
+ if (tag instanceof Plot.Tag)
713
+ change = { from, to: from + 1, insert: [tag] };
714
+ else
715
+ change = { from, to: sel.from.parent.after, insert: [tag], fit: true };
696
716
  }
697
- function onkeydown(e) {
698
- if (e.key == "Escape") {
699
- e.preventDefault();
700
- close();
701
- }
717
+ else {
718
+ change = { from: sel.from.pos, to: sel.to.pos, insert: [tag instanceof Plot.Tag ? tag.create() : tag], fit: true };
702
719
  }
703
- return cr("wg-dialog", { class: "wg-img-dialog", onsubmit }, form);
720
+ let changes = ChangeSet.create(state.doc, change), pos = changes.findInserted(t => t == tag) ?? change.from;
721
+ wg.dispatch({
722
+ changes: change,
723
+ userEvent: "insert.image",
724
+ selection: tag instanceof Plot.Tag ? { anchor: pos + 1 } : GardSelection.node(pos, tag)
725
+ });
704
726
  }
705
727
  const insertImage = wg => {
706
- let val = wg.state.field(imageDialog, false);
707
- if (val) {
708
- wg.dispatch({ effects: setImageDialog.of(false) });
728
+ let open = Dialog.get(wg, insertImage);
729
+ if (open) {
730
+ open.close();
709
731
  }
710
732
  else {
711
- let effects = [setImageDialog.of(true)];
712
- if (val === undefined)
713
- effects.push(GardState.appendConfig.of(imageDialog));
714
- wg.dispatch({ effects });
733
+ Dialog.show(wg, {
734
+ key: insertImage,
735
+ content: buildImageDialog,
736
+ closedBy: tr => !!tr.selection,
737
+ focus: "input[name=src]",
738
+ class: "wg-img-dialog",
739
+ tooltip: { pos: wg.state.selection.from },
740
+ positioningHint: true
741
+ }).result.then(form => finishInsertImage(wg, form));
715
742
  }
716
743
  return true;
717
744
  };
718
745
  const imageDialogTheme = /*@__PURE__*/Wordgard.styles({
719
- ".wg-img-dialog": {
720
- borderBottom: "1px solid var(--wg-border-color)"
721
- },
746
+ ".wg-img-dialog": {},
722
747
  ".wg-img-form": {
723
- padding: "5px 3px",
724
748
  display: "grid",
725
749
  gap: "8px",
726
750
  alignItems: "center",
727
751
  gridTemplateColumns: "max-content auto",
728
752
  "& label, & .wg-label": {
729
- textAlign: "right"
753
+ textAlign: "end"
730
754
  }
731
755
  },
732
- ".wg-dialog-title": {
756
+ ".wg-img-form .wg-dialog-title": {
733
757
  gridColumn: "span 2",
734
- fontSize: "90%",
735
- fontWeight: "bold",
736
- textAlign: "center"
758
+ margin: "0"
737
759
  },
738
760
  ".wg-img-buttons": {
739
761
  gridColumn: "2"
@@ -773,23 +795,11 @@ const imageDialogTheme = /*@__PURE__*/Wordgard.styles({
773
795
  fontSize: "80%"
774
796
  }
775
797
  });
776
- const imageDialog = /*@__PURE__*/GardState.Field.define({
777
- create: () => null,
778
- update(value, tr) {
779
- for (let e of tr.effects)
780
- if (e.is(setImageDialog))
781
- return e.value ? tr.state.selection : null;
782
- return value && value.map(tr.changes, tr.state);
783
- },
784
- provide: f => [
785
- GardState.prec.lowest(Panel.show.from(f, val => val && createImagePanel)),
786
- imageDialogTheme
787
- ]
788
- });
789
798
 
790
799
  function baseSupport() {
791
- return [GardState.schemaElement.of(ImageAlt), image.button, imageDialog, image.keyBinding, image.dropHandler];
800
+ return [GardState.schemaElement.of(ImageAlt), image.button, imageDialogTheme, image.keyBinding, image.dropHandler];
792
801
  }
802
+ const ImageGroup = /*@__PURE__*/(() => KeyBinding.Group.define({ description: imagePhrases.ref("group_image"), rank: 90 }))();
793
803
  function image() {
794
804
  return [GardState.schemaElement.of(Image), baseSupport()];
795
805
  }
@@ -816,6 +826,7 @@ const resizeTheme = /*@__PURE__*/Wordgard.theme({
816
826
  }
817
827
  });
818
828
  const setResizing = /*@__PURE__*/Transaction.Effect.define({
829
+ name: "setImageResize",
819
830
  map: (value, mapping) => {
820
831
  let newPos = mapping.mapPos(value.target, 1, "after");
821
832
  return newPos == null ? undefined : { target: newPos, resizing: value.resizing };
@@ -827,6 +838,7 @@ const handleElt = /*@__PURE__*/(() => Elt.mk("svg:svg", { class: "wg-resize-hand
827
838
  ]))();
828
839
  const resizeWrapper = /*@__PURE__*/(() => Decoration.Point.wrapper(Elt.mk("span", { class: "wg-resize-hover" }, [handleElt, 0]), { target: "img" }))();
829
840
  const resizeState = /*@__PURE__*/GardState.Field.define({
841
+ name: "imageResize",
830
842
  create: () => ({ target: -1, resizing: -1, deco: Decoration.Point.none }),
831
843
  update: (value, tr) => {
832
844
  for (let e of tr.effects) {
@@ -910,8 +922,18 @@ const resizeHandlers = /*@__PURE__*/(() => [
910
922
  return false;
911
923
  };
912
924
  imageResizing.keyBindings = [
913
- KeyBinding.of({ key: "Ctrl-Alt-l", mac: "Ctrl-Cmd-l", run: imageResizing.resizeCommand(1.1, true) }),
914
- KeyBinding.of({ key: "Ctrl-Alt-k", mac: "Ctrl-Cmd-k", run: imageResizing.resizeCommand(0.9091, true) }),
925
+ KeyBinding.of({
926
+ key: "Ctrl-Alt-l", mac: "Ctrl-Cmd-l",
927
+ run: imageResizing.resizeCommand(1.1, true),
928
+ description: imagePhrases.ref("resize_up"),
929
+ group: ImageGroup
930
+ }),
931
+ KeyBinding.of({
932
+ key: "Ctrl-Alt-k", mac: "Ctrl-Cmd-k",
933
+ run: imageResizing.resizeCommand(0.9091, true),
934
+ description: imagePhrases.ref("resize_down"),
935
+ group: ImageGroup
936
+ }),
915
937
  ];
916
938
  imageResizing.dragHandle = [
917
939
  GardState.prec.high(resizeHandlers),
@@ -921,7 +943,12 @@ const resizeHandlers = /*@__PURE__*/(() => [
921
943
  ];
922
944
  ;return imageResizing})(imageResizing);
923
945
  ;image = /*@__PURE__*/(function (image) {
924
- image.keyBinding = KeyBinding.of({ key: "Ctrl-Alt-i", mac: "Ctrl-Cmd-i", run: insertImage });
946
+ image.keyBinding = KeyBinding.of({
947
+ key: "Ctrl-Alt-i", mac: "Ctrl-Cmd-i",
948
+ run: insertImage,
949
+ description: imagePhrases.ref("insert_image"),
950
+ group: ImageGroup
951
+ });
925
952
  image.button = Menu.Button.define({
926
953
  run: insertImage,
927
954
  active: state => !!activeImage(state.sel),
@@ -1164,9 +1191,11 @@ class ColorPicker {
1164
1191
  ];
1165
1192
  }
1166
1193
  ColorPicker.width = GardState.Facet.define({
1194
+ name: "colorPickerWidth",
1167
1195
  combine: values => values.length ? values[0] : 10
1168
1196
  });
1169
1197
  ColorPicker.options = GardState.Facet.define({
1198
+ name: "colorPickerOptions",
1170
1199
  combine: values => values.length ? values[0] : defaultColors()
1171
1200
  });
1172
1201
  ColorPicker.theme = Wordgard.styles({
@@ -1247,11 +1276,9 @@ const backgroundPicker = /*@__PURE__*/Menu.CustomControl.define({
1247
1276
  function toggleLink(wg) {
1248
1277
  if (wg.state.readOnly)
1249
1278
  return false;
1250
- let open = Dialog.get(wg, "wg-link-dialog");
1279
+ let open = Dialog.get(wg, toggleLink);
1251
1280
  if (open) {
1252
- if (open.dom.contains(wg.contentDOM.ownerDocument.activeElement))
1253
- wg.focus();
1254
- Dialog.close(wg, "wg-link-dialog");
1281
+ open.close();
1255
1282
  return true;
1256
1283
  }
1257
1284
  let { selection, doc } = wg.state;
@@ -1269,11 +1296,14 @@ function toggleLink(wg) {
1269
1296
  }
1270
1297
  else {
1271
1298
  Dialog.show(wg, {
1299
+ key: toggleLink,
1272
1300
  label: phrases.get(wg.state, "link_target"),
1273
1301
  input: { type: "text", name: "url" },
1274
1302
  submitLabel: phrases.get(wg.state, "create_link"),
1275
1303
  class: "wg-link-dialog",
1276
- focus: true
1304
+ focus: true,
1305
+ closedBy: tr => !!tr.selection,
1306
+ tooltip: { pos: wg.state.selection.from }
1277
1307
  }).result.then(form => {
1278
1308
  wg.focus();
1279
1309
  let url = form && form.elements.namedItem("url")?.value;
@@ -1304,8 +1334,9 @@ function computeLinkTooltip(state) {
1304
1334
  create: () => renderLinkTooltip(link.value)
1305
1335
  };
1306
1336
  }
1307
- const closeLinkTooltip = /*@__PURE__*/Transaction.Effect.define();
1337
+ const closeLinkTooltip = /*@__PURE__*/Transaction.Effect.define({ name: "closeLinkTooltip" });
1308
1338
  const linkTooltipField = /*@__PURE__*/GardState.Field.define({
1339
+ name: "linkTooltip",
1309
1340
  create: computeLinkTooltip,
1310
1341
  update(value, tr) {
1311
1342
  if (tr.effects.some(e => e.is(closeLinkTooltip)))
@@ -1347,6 +1378,8 @@ function link() {
1347
1378
  link_1.keyBinding = KeyBinding.of({
1348
1379
  key: "Mod-k",
1349
1380
  run: toggleLink,
1381
+ description: phrases.ref("create_link"),
1382
+ group: KeyBinding.Group.Inline
1350
1383
  });
1351
1384
  link_1.button = Menu.Button.define({
1352
1385
  run: toggleLink,
@@ -1385,12 +1418,9 @@ function link() {
1385
1418
  })),
1386
1419
  linkTooltipTheme
1387
1420
  ];
1388
- link_1.pasteOver = Wordgard.pasteHandler.of((wg, event) => {
1389
- let { selection } = wg.state, data = event.clipboardData;
1390
- if (!data || selection.empty)
1391
- return false;
1392
- let text = data.getData("text/plain") || data.getData("Text") || data.getData("text/uri-list");
1393
- if (!text || !/^(https?|mailto|xmpp|data):[^ ]+$/.test(text))
1421
+ link_1.pasteOver = Wordgard.pasteHandler.of((wg, event, slice, html, text) => {
1422
+ let { selection } = wg.state;
1423
+ if (selection.empty || !/^(https?|mailto|xmpp|data):[^ ]+$/.test(text))
1394
1424
  return false;
1395
1425
  let link = Link.of(text);
1396
1426
  let changes = ChangeSet.create(wg.state.doc, { from: selection.from, to: selection.to, add: link });
@@ -1429,4 +1459,4 @@ function fullSchema() {
1429
1459
  ];
1430
1460
  }
1431
1461
 
1432
- 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 };
1462
+ export { ColorPicker, alignment, backgroundColor, basicMarks, basicSchema, blockDoc, blockquote, bulletList, code, codeBlock, codeBlockLanguage, color, direction, emphasis, figure, fullSchema, heading, horizontalRule, image, imageResizing, inlineDoc, inlineMarks, inlineSchema, lineBreak, link, orderedList, paragraph, strikethrough, strong, subscript, superscript, underline };
package/dist/set.d.ts CHANGED
@@ -195,9 +195,10 @@ declare class PointSet<T extends PointSet.Value> implements Set<T> {
195
195
  /**
196
196
  Get the value at the given position, if any. If there's multiple
197
197
  values at that position, the one with the lowest side is
198
- returned.
198
+ returned. If a predicate is given, only points that satisfy it
199
+ are returned.
199
200
  */
200
- at(pos: number): T | undefined;
201
+ at(pos: number, pred?: (value: T) => boolean): T | undefined;
201
202
  /**
202
203
  Adjust the points for a set of document changes. Returns a new
203
204
  set with the adjusted points. May delete points when the content