@nerd-bible/wordgard 0.3.4 → 0.3.6

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 CHANGED
@@ -4,7 +4,7 @@ import { Paragraph, Heading, Direction, Blockquote, HorizontalRule, Alignment, D
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, Panel, Dialog, Decoration, PointSet, Tooltip } from 'wordgard/editor';
7
+ import { KeyBinding, InputRule, Wordgard, Widget, Decoration, Panel, Dialog, PointSet, Tooltip } from 'wordgard/editor';
8
8
  import cr from 'crelt';
9
9
 
10
10
  function blockDoc() {
@@ -313,6 +313,97 @@ function codeBlock() {
313
313
  };
314
314
  }));
315
315
  ;return codeBlock})(codeBlock);
316
+ function codeBlockLanguage(options = {}) {
317
+ return [
318
+ GardState.schemaElement.of(CodeBlockLanguage),
319
+ options.languages ? [
320
+ languageStyles,
321
+ languageOptions.of(options.languages),
322
+ languageSelect,
323
+ codeBlockLanguage.selectLanguageBinding
324
+ ] : []
325
+ ];
326
+ }
327
+ const languageStyles = /*@__PURE__*/Wordgard.styles({
328
+ pre: {
329
+ position: "relative",
330
+ "& select.wg-code-language": {
331
+ font: "var(--wg-dialog-font)",
332
+ fontSize: "70%",
333
+ position: "absolute",
334
+ top: "2px",
335
+ right: "4px",
336
+ border: "none",
337
+ borderRadius: "4px"
338
+ }
339
+ }
340
+ });
341
+ const languageOptions = /*@__PURE__*/GardState.Facet.define({
342
+ combine: input => input.reduce((a, b) => a.concat(b), [])
343
+ });
344
+ function option(text, selected, value) {
345
+ let node = document.createElement("option");
346
+ node.textContent = text;
347
+ node.value = value;
348
+ if (selected)
349
+ node.selected = true;
350
+ return node;
351
+ }
352
+ const languageWidget = /*@__PURE__*/Widget.define({
353
+ render({ options, value }, wg) {
354
+ let sel = document.createElement("select");
355
+ sel.className = "wg-code-language";
356
+ sel.tabIndex = -1;
357
+ sel.appendChild(option("Plain", value == null, ""));
358
+ let selected = value && options.find(o => o.toLowerCase() == value);
359
+ if (value && !selected)
360
+ sel.appendChild(option(value, true, value));
361
+ for (let opt of options)
362
+ sel.appendChild(option(opt, opt == selected, opt.toLowerCase()));
363
+ sel.onchange = () => {
364
+ let value = sel.value || undefined;
365
+ let pos = wg.posAtDOM(sel.parentNode);
366
+ let block = pos > 0 && wg.state.doc.nodeAt(pos - 1);
367
+ if (block && block.type == CodeBlock.type && block.mark(CodeBlockLanguage) != value) {
368
+ wg.dispatch({
369
+ changes: value ? { from: pos - 1, add: CodeBlockLanguage.of(value) } :
370
+ { from: pos - 1, remove: CodeBlockLanguage.isInSet(block.tag.marks) }
371
+ });
372
+ wg.focus();
373
+ }
374
+ };
375
+ return sel;
376
+ },
377
+ propagateEvent: false,
378
+ eq(a, b) {
379
+ return a.value == b.value && a.options.length == b.options.length &&
380
+ a.options.every((o, i) => o == b.options[i]);
381
+ },
382
+ inFlow: false
383
+ });
384
+ const languageSelect = /*@__PURE__*/Decoration.Tag.widget.dynamic(CodeBlock, "end", state => {
385
+ let options = state.facet(languageOptions);
386
+ return tag => {
387
+ return languageWidget.of({ options, value: tag.mark(CodeBlockLanguage) });
388
+ };
389
+ });
390
+ ;codeBlockLanguage = /*@__PURE__*/(function (codeBlockLanguage) {
391
+ codeBlockLanguage.selectLanguage = wg => {
392
+ let blk = wg.state.sel.head.textblockParent;
393
+ if (!blk || blk.node.type != CodeBlock.type)
394
+ return false;
395
+ let sel = wg.nodeDOM(blk.before)?.querySelector("select.wg-code-language");
396
+ if (!sel)
397
+ return false;
398
+ sel.focus();
399
+ sel.showPicker();
400
+ return true;
401
+ };
402
+ codeBlockLanguage.selectLanguageBinding = KeyBinding.of({
403
+ key: "Shift-Mod-l",
404
+ run: codeBlockLanguage.selectLanguage
405
+ });
406
+ ;return codeBlockLanguage})(codeBlockLanguage);
316
407
 
317
408
  function strong() {
318
409
  return [GardState.schemaElement.of(Strong), strong.button, strong.keyBinding];
@@ -1188,7 +1279,7 @@ function toggleLink(wg) {
1188
1279
  let url = form && form.elements.namedItem("url")?.value;
1189
1280
  if (url)
1190
1281
  wg.dispatch({
1191
- changes: selection.ranges.map(r => ({ from: r.from, to: r.to, add: Link.of(url) })),
1282
+ changes: wg.state.selection.ranges.map(r => ({ from: r.from, to: r.to, add: Link.of(url) })),
1192
1283
  userEvent: "mark.add"
1193
1284
  });
1194
1285
  });
package/dist/state.d.ts CHANGED
@@ -136,6 +136,10 @@ declare abstract class GardSelection {
136
136
  */
137
137
  static cursor(pos: number, side?: -1 | 1, goalColumn?: number): GardSelection.Text;
138
138
  /**
139
+ Find a normal cursor near the given position.
140
+ */
141
+ static near(cx: GardSelection.Context, pos: number, side?: -1 | 1, goalColumn?: number): GardSelection.Text;
142
+ /**
139
143
  Create a text selection.
140
144
  */
141
145
  static range(anchor: number, head?: number, headSide?: -1 | 1, goalColumn?: number): GardSelection.Text;
@@ -161,18 +165,10 @@ declare abstract class GardSelection {
161
165
  */
162
166
  nextNormalCursor(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
163
167
  /**
164
- Get a normal cursor at the start or end of this selection.
165
- */
166
- normalCursorAtBound(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
167
- /**
168
168
  Move across one word starting from this selection's head.
169
169
  */
170
170
  skipWord(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
171
171
  /**
172
- Find a normal selection near the given position.
173
- */
174
- static near(cx: GardSelection.Context, pos: number, bias?: -1 | 1): GardSelection.Text;
175
- /**
176
172
  Find a normal selection at the start of the document or the
177
173
  given textblock.
178
174
  */
package/dist/state.js CHANGED
@@ -384,7 +384,7 @@ class TextblockMap {
384
384
  sectionPos = pos + ch.length;
385
385
  }
386
386
  }
387
- else if (ch.type.spec.cursorInsideBounds) {
387
+ else if (ch.type.cursorInsideBounds) {
388
388
  text += " ";
389
389
  scan(ch, pos + 1);
390
390
  text += " ";
@@ -590,6 +590,10 @@ class GardSelection {
590
590
  static cursor(pos, side, goalColumn) {
591
591
  return GardSelection.Text.createInner(pos, pos, side, goalColumn);
592
592
  }
593
+ static near(cx, pos, side = 1, goalColumn) {
594
+ let norm = findNormalAt(cx, pos, side);
595
+ return GardSelection.cursor(norm.pos, norm.side, goalColumn);
596
+ }
593
597
  static range(anchor, head, headSide, goalColumn) {
594
598
  return GardSelection.Text.createInner(anchor, head ?? anchor, headSide, goalColumn);
595
599
  }
@@ -597,23 +601,13 @@ class GardSelection {
597
601
  return GardSelection.Node.create(pos, node, goalColumn);
598
602
  }
599
603
  nextNormalCursor(cx, forward = true) {
600
- let found = scanNormalFrom(cx, this.head, this.headSide, forward, true);
601
- return found && GardSelection.cursor(found.pos, found.side);
602
- }
603
- normalCursorAtBound(cx, forward = true) {
604
- let found = scanNormalFrom(cx, forward ? this.to : this.from, forward ? -1 : 1, forward, false);
604
+ let found = scanNormalFrom(cx, this.head, this.headSide, forward);
605
605
  return found && GardSelection.cursor(found.pos, found.side);
606
606
  }
607
607
  skipWord(cx, forward = true) {
608
608
  let found = skipWord(cx, this.head, this.headSide, forward);
609
609
  return found && GardSelection.cursor(found.pos, found.side);
610
610
  }
611
- static near(cx, pos, bias = 1) {
612
- let norm = scanNormalFrom(cx, pos, bias, bias > 0, false) ??
613
- scanNormalFrom(cx, pos, -bias, bias < 0, false) ??
614
- { pos: pos, side: -1 };
615
- return GardSelection.cursor(norm.pos, norm.side);
616
- }
617
611
  static atStart(cx, block) {
618
612
  return cursorAtStart(cx, block);
619
613
  }
@@ -621,7 +615,7 @@ class GardSelection {
621
615
  let found = block
622
616
  ? TextblockMap.get(cx, block.start, block.node).visualSide(false)
623
617
  : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(false)
624
- : scanNormalFrom(cx, cx.doc.length, -1, false, false) ?? { pos: cx.doc.length, side: -1 };
618
+ : findNormalAt(cx, cx.doc.length, -1);
625
619
  return GardSelection.cursor(found.pos, found.side);
626
620
  }
627
621
  }
@@ -760,7 +754,7 @@ function cursorAtStart(cx, block) {
760
754
  let found = block
761
755
  ? TextblockMap.get(cx, block.start, block.node).visualSide(true)
762
756
  : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(true)
763
- : scanNormalFrom(cx, 0, 1, true, false) ?? { pos: 0, side: 1 };
757
+ : findNormalAt(cx, 0, 1);
764
758
  return GardSelection.cursor(found.pos, found.side);
765
759
  }
766
760
  function isBarrier(cx, node) {
@@ -769,13 +763,11 @@ function isBarrier(cx, node) {
769
763
  let override = node.type.spec.cursorBarrier;
770
764
  if (override != null)
771
765
  return override;
772
- return node.type.isolating || node.type.preserveWhitespace || node.type.isBlock && cx.config.isAtom(node.type);
766
+ return node.type.isBlock && (node.type.isolating || node.type.preserveWhitespace || cx.config.isAtom(node.type));
773
767
  }
774
- function scanNormalFrom(cx, from, side, forward, mustMove) {
768
+ function scanNormalFrom(cx, from, side, forward) {
775
769
  let pos = cx.doc.resolve(from), pastBarrier = false;
776
770
  if (pos.parent.node.inlineContent) {
777
- if (!mustMove)
778
- return { pos: pos.pos, side };
779
771
  let block = pos.textblockParent;
780
772
  let map = TextblockMap.get(cx, block.start, block.node);
781
773
  let next = cx.config.visualCursorMotion ? map.moveVisually(pos.pos, side, forward) : map.moveLogically(pos.pos, forward);
@@ -813,7 +805,7 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
813
805
  }
814
806
  if (index == (forward ? node.content.length : 0)) {
815
807
  let barrier = !next || isBarrier(cx, node);
816
- if ((bottom != from || !mustMove) && pastBarrier && barrier)
808
+ if ((bottom != from) && pastBarrier && barrier)
817
809
  return { pos: bottom, side: forward ? -1 : 1 };
818
810
  if (!next)
819
811
  return null;
@@ -827,7 +819,7 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
827
819
  else {
828
820
  let nextNode = node.content[index - (forward ? 0 : 1)];
829
821
  let barrier = isBarrier(cx, nextNode);
830
- if (pastBarrier && (bottom != from || !mustMove) && barrier)
822
+ if (pastBarrier && (bottom != from) && barrier)
831
823
  return { pos: bottom, side: forward ? -1 : 1 };
832
824
  if (nextNode.isLeaf || cx.config.isAtom(nextNode.type)) {
833
825
  index += step;
@@ -847,12 +839,52 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
847
839
  }
848
840
  }
849
841
  }
842
+ function findNormalAt(cx, pos, bias) {
843
+ let res = cx.doc.resolve(pos), lowest = pos;
844
+ if (res.inText) {
845
+ let text = res.parent.node.content[res.index].tag.param;
846
+ let off = bias > 0 ? findClusterBreak(text, res.inText - 1) : findClusterBreak(text, res.inText + 1, false);
847
+ pos += off - res.inText;
848
+ if (off > 0 && off < text.length)
849
+ return { pos, side: bias };
850
+ res = Pos.create(res.parent, pos, res.index + (off ? 1 : 0), 0);
851
+ }
852
+ for (let pass = 0; pass < 2; pass++) {
853
+ let dir = !pass ? bias : -bias, { parent, index } = res, curPos = pos;
854
+ for (;;) {
855
+ if (parent.node.inlineContent &&
856
+ (parent.node.type.isBlock || curPos > parent.start && curPos < parent.end || parent.node.type.cursorInsideBounds))
857
+ return { pos: curPos, side: bias };
858
+ if (index == (dir > 0 ? parent.node.content.length : 0)) {
859
+ if ((parent.node.type.isInline ? parent.node.type.cursorInsideBounds : isBarrier(cx, parent.node)) ||
860
+ !parent.parent)
861
+ break;
862
+ lowest = curPos = curPos + dir;
863
+ index = parent.index + (dir > 0 ? 1 : 0);
864
+ parent = parent.parent;
865
+ }
866
+ else {
867
+ let next = parent.node.content[index + (dir > 0 ? 0 : -1)];
868
+ if (next.isLeaf || isBarrier(cx, next))
869
+ break;
870
+ if (!parent.node.inlineContent && next.inlineContent && cx.config.visualCursorMotion) {
871
+ let startPos = curPos - (dir > 0 ? 0 : next.length) + 1;
872
+ return TextblockMap.get(cx, startPos, next).visualSide(dir > 0);
873
+ }
874
+ parent = Pos.Plot.create(parent, next, curPos, index);
875
+ index = dir > 0 ? 0 : next.content.length;
876
+ curPos += dir;
877
+ }
878
+ }
879
+ }
880
+ return { pos: lowest, side: bias };
881
+ }
850
882
  function skipWord(cx, start, side, forward) {
851
883
  let last = null;
852
884
  for (let pos = start, visually = cx.config.visualCursorMotion;;) {
853
885
  let block = cx.doc.resolve(pos).textblockParent;
854
886
  if (!block) {
855
- let next = scanNormalFrom(cx, pos, side, forward, true);
887
+ let next = scanNormalFrom(cx, pos, side, forward);
856
888
  if (!next)
857
889
  return last;
858
890
  ({ pos, side } = next);
@@ -1884,6 +1916,7 @@ function scanChanges(changes, doc, corrections) {
1884
1916
  pos = off;
1885
1917
  break;
1886
1918
  }
1919
+ off = end;
1887
1920
  }
1888
1921
  if (queried.has(pos))
1889
1922
  return;
package/dist/table.js CHANGED
@@ -159,7 +159,7 @@ function sameCell(a, b) {
159
159
  return a != 0 && a == b;
160
160
  }
161
161
  function computeMap(table) {
162
- if (table.tag != Table)
162
+ if (table.type != Table.type)
163
163
  throw new RangeError(`Not a table node: ${table.type.name}`);
164
164
  let width = table.content[0].content.reduce((w, c) => w + (c.mark(ColSpan) ?? 1), 0);
165
165
  let height = table.content.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerd-bible/wordgard",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Semantic rich text editor system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",