@nerd-bible/wordgard 0.3.5 → 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/command.d.ts +6 -6
- package/dist/command.js +32 -36
- package/dist/doc.d.ts +6 -1
- package/dist/doc.js +17 -7
- package/dist/editor.js +231 -53
- package/dist/state.d.ts +4 -8
- package/dist/state.js +53 -21
- package/package.json +1 -1
package/dist/command.d.ts
CHANGED
|
@@ -511,7 +511,7 @@ declare const insertLineBreak: Command.Pure;
|
|
|
511
511
|
The command that handles enter presses. The default handler will,
|
|
512
512
|
if the selection is not in an inline context, insert an empty
|
|
513
513
|
default textblock in its position. Otherwise it first tries
|
|
514
|
-
`
|
|
514
|
+
`liftEmptyBlock`, then `splitTextblock`.
|
|
515
515
|
*/
|
|
516
516
|
declare const enter: Command.Pure;
|
|
517
517
|
/**
|
|
@@ -705,11 +705,11 @@ selection is empty.
|
|
|
705
705
|
*/
|
|
706
706
|
declare function deleteSelection(state: GardState): Transaction.Spec | false;
|
|
707
707
|
/**
|
|
708
|
-
If the cursor is inside an empty
|
|
709
|
-
|
|
710
|
-
|
|
708
|
+
If the cursor is inside an empty plot, return a transaction that
|
|
709
|
+
deletes the entire plot. `dir` determines which way the cursor
|
|
710
|
+
moves after the deletion.
|
|
711
711
|
*/
|
|
712
|
-
declare function
|
|
712
|
+
declare function deleteEmptyPlot(state: GardState, dir?: -1 | 1): Transaction.Spec | false;
|
|
713
713
|
/**
|
|
714
714
|
If the cursor is at the start of a textblock that can be joined to
|
|
715
715
|
a textblock before it, return a transaction to performs this join.
|
|
@@ -793,4 +793,4 @@ updated to perform those joins.
|
|
|
793
793
|
*/
|
|
794
794
|
declare function autoJoinBlocks(state: GardState, tr: Transaction.Spec): Transaction.Spec;
|
|
795
795
|
|
|
796
|
-
export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward,
|
|
796
|
+
export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
|
package/dist/command.js
CHANGED
|
@@ -150,14 +150,14 @@ function deleteSelection(state) {
|
|
|
150
150
|
userEvent: "delete.selection"
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
|
-
function
|
|
153
|
+
function deleteEmptyPlot(state, dir = -1) {
|
|
154
154
|
if (!state.selection.isCursor)
|
|
155
155
|
return false;
|
|
156
|
-
let
|
|
157
|
-
if (!
|
|
156
|
+
let plot = state.sel.head.parent;
|
|
157
|
+
if (!plot.parent || plot.start < plot.end)
|
|
158
158
|
return false;
|
|
159
159
|
return {
|
|
160
|
-
changes: { from:
|
|
160
|
+
changes: { from: plot.before, to: plot.after, fit: true },
|
|
161
161
|
selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(state.selection.head), dir),
|
|
162
162
|
scrollIntoView: true,
|
|
163
163
|
userEvent: dir < 0 ? "delete.backward" : "delete.forward"
|
|
@@ -289,12 +289,9 @@ function deleteBackward(state, word = false) {
|
|
|
289
289
|
for (;;) {
|
|
290
290
|
if (next.isPlot && next.type.isolating)
|
|
291
291
|
return false;
|
|
292
|
-
if (next.isLeaf || state.isAtom(next.type))
|
|
292
|
+
if (next.isLeaf || state.isAtom(next.type) || !next.content.length)
|
|
293
293
|
break;
|
|
294
|
-
|
|
295
|
-
if (last < 0)
|
|
296
|
-
return false;
|
|
297
|
-
next = next.content[last];
|
|
294
|
+
next = next.content[next.content.length - 1];
|
|
298
295
|
pos--;
|
|
299
296
|
}
|
|
300
297
|
if (next.is(Leaf.Text)) {
|
|
@@ -336,13 +333,14 @@ function deleteBackward(state, word = false) {
|
|
|
336
333
|
}
|
|
337
334
|
let from = pos - next.length, to = pos;
|
|
338
335
|
let parent = state.doc.resolve(pos).parent;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
336
|
+
if (next.type.isBlock)
|
|
337
|
+
while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
|
|
338
|
+
if (!parent.parent)
|
|
339
|
+
return false;
|
|
340
|
+
parent = parent.parent;
|
|
341
|
+
from--;
|
|
342
|
+
to++;
|
|
343
|
+
}
|
|
346
344
|
return {
|
|
347
345
|
changes: { from, to },
|
|
348
346
|
scrollIntoView: true,
|
|
@@ -366,10 +364,8 @@ function deleteForward(state, word = false) {
|
|
|
366
364
|
for (;;) {
|
|
367
365
|
if (next.isPlot && next.type.isolating)
|
|
368
366
|
return false;
|
|
369
|
-
if (next.isLeaf || state.isAtom(next.type))
|
|
367
|
+
if (next.isLeaf || state.isAtom(next.type) || !next.content.length)
|
|
370
368
|
break;
|
|
371
|
-
if (!next.content.length)
|
|
372
|
-
return false;
|
|
373
369
|
next = next.content[0];
|
|
374
370
|
pos++;
|
|
375
371
|
}
|
|
@@ -412,13 +408,14 @@ function deleteForward(state, word = false) {
|
|
|
412
408
|
}
|
|
413
409
|
let from = pos, to = pos + next.length;
|
|
414
410
|
let parent = state.doc.resolve(pos).parent;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
411
|
+
if (next.type.isBlock)
|
|
412
|
+
while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
|
|
413
|
+
if (!parent.parent)
|
|
414
|
+
return false;
|
|
415
|
+
parent = parent.parent;
|
|
416
|
+
from--;
|
|
417
|
+
to++;
|
|
418
|
+
}
|
|
422
419
|
return {
|
|
423
420
|
changes: { from, to },
|
|
424
421
|
scrollIntoView: true,
|
|
@@ -766,15 +763,15 @@ const deleteUnit = ({ state }, dir) => {
|
|
|
766
763
|
if (state.readOnly)
|
|
767
764
|
return false;
|
|
768
765
|
return deleteSelection(state) || (dir == "forward"
|
|
769
|
-
? joinForward(state) || deleteForward(state) ||
|
|
770
|
-
: joinListItems(state) || joinBackward(state) || deleteBackward(state) ||
|
|
766
|
+
? joinForward(state) || deleteForward(state) || deleteEmptyPlot(state, 1)
|
|
767
|
+
: joinListItems(state) || joinBackward(state) || deleteBackward(state) || deleteEmptyPlot(state, -1));
|
|
771
768
|
};
|
|
772
769
|
const deleteWord = ({ state }, dir) => {
|
|
773
770
|
if (state.readOnly)
|
|
774
771
|
return false;
|
|
775
772
|
return deleteSelection(state) || (dir == "forward"
|
|
776
|
-
? joinForward(state) || deleteForward(state, true) ||
|
|
777
|
-
: joinListItems(state) || joinBackward(state) || deleteBackward(state, true) ||
|
|
773
|
+
? joinForward(state) || deleteForward(state, true) || deleteEmptyPlot(state, 1)
|
|
774
|
+
: joinListItems(state) || joinBackward(state) || deleteBackward(state, true) || deleteEmptyPlot(state, -1));
|
|
778
775
|
};
|
|
779
776
|
const deleteToLineEnd = (wg, dir) => {
|
|
780
777
|
if (wg.state.readOnly)
|
|
@@ -1101,8 +1098,7 @@ function extendSel(base, head) {
|
|
|
1101
1098
|
const moveByUnit = ({ state }, { dir, extend }) => {
|
|
1102
1099
|
let forward = isForward(dir, state), selection = asTextSel(state.selection, forward);
|
|
1103
1100
|
if (!selection.empty && !extend) {
|
|
1104
|
-
|
|
1105
|
-
return next ? setSelection(next) : false;
|
|
1101
|
+
return setSelection(GardSelection.near(state, forward ? selection.to : selection.from, forward ? -1 : 1));
|
|
1106
1102
|
}
|
|
1107
1103
|
else {
|
|
1108
1104
|
let next = selection.nextNormalCursor(state, forward);
|
|
@@ -1133,10 +1129,10 @@ function nextVertical(wg, sel, forward, distance, allowNode) {
|
|
|
1133
1129
|
}
|
|
1134
1130
|
const moveByLine = (wg, { dir, extend }) => {
|
|
1135
1131
|
let { state } = wg, { selection } = state, forward = dir == "down";
|
|
1136
|
-
if (
|
|
1137
|
-
let next = !extend && state.selection.
|
|
1132
|
+
if (selection instanceof GardSelection.Node) {
|
|
1133
|
+
let next = !extend && GardSelection.near(state, forward ? selection.to : selection.from, forward ? -1 : 1);
|
|
1138
1134
|
if (next && !state.doc.resolve(next.head).parent.node.inlineContent)
|
|
1139
|
-
return setSelection(GardSelection.cursor(next.head, next.headSide,
|
|
1135
|
+
return setSelection(GardSelection.cursor(next.head, next.headSide, selection.goalColumn));
|
|
1140
1136
|
selection = GardSelection.cursor(forward ? selection.to : selection.from, undefined, selection.goalColumn);
|
|
1141
1137
|
}
|
|
1142
1138
|
else {
|
|
@@ -1448,4 +1444,4 @@ const Menu = /*@__PURE__*/(function (Menu) {
|
|
|
1448
1444
|
Menu.resolve = resolve;
|
|
1449
1445
|
;return Menu})({});
|
|
1450
1446
|
|
|
1451
|
-
export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward,
|
|
1447
|
+
export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
|
package/dist/doc.d.ts
CHANGED
|
@@ -250,7 +250,7 @@ declare class Elt<T = string> {
|
|
|
250
250
|
/**
|
|
251
251
|
Convert an element (with only string content) to a DOM tree.
|
|
252
252
|
*/
|
|
253
|
-
toDOM(this: Elt<string>, doc?: Document): Element
|
|
253
|
+
toDOM(this: Elt<string>, doc?: Document): Element;
|
|
254
254
|
}
|
|
255
255
|
declare namespace Elt {
|
|
256
256
|
/**
|
|
@@ -1685,6 +1685,11 @@ declare namespace Plot {
|
|
|
1685
1685
|
*/
|
|
1686
1686
|
readonly orientation: "row" | "column";
|
|
1687
1687
|
/**
|
|
1688
|
+
True if this is an inline plot with {@link
|
|
1689
|
+
Plot.Spec.cursorInsideBounds `cursorInsideBounds`} set.
|
|
1690
|
+
*/
|
|
1691
|
+
readonly cursorInsideBounds: boolean;
|
|
1692
|
+
/**
|
|
1688
1693
|
The spec used to define this plot type.
|
|
1689
1694
|
*/
|
|
1690
1695
|
readonly spec: Plot.Spec<any>;
|
package/dist/doc.js
CHANGED
|
@@ -1429,6 +1429,7 @@ class Plot {
|
|
|
1429
1429
|
neutral;
|
|
1430
1430
|
preserveWhitespace;
|
|
1431
1431
|
orientation;
|
|
1432
|
+
cursorInsideBounds;
|
|
1432
1433
|
spec;
|
|
1433
1434
|
constructor(name, flags, spec) {
|
|
1434
1435
|
super(name, flags, spec, NodeShape.from(name, false, spec.shape));
|
|
@@ -1440,6 +1441,7 @@ class Plot {
|
|
|
1440
1441
|
this.neutral = spec.neutral ?? !this.defining;
|
|
1441
1442
|
this.preserveWhitespace = spec.preserveWhitespace ?? !!this.hasRole(Node.Role.Code);
|
|
1442
1443
|
this.orientation = flags & 2 ? "row" : spec.orientation || "column";
|
|
1444
|
+
this.cursorInsideBounds = !!((flags & 1) && spec.cursorInsideBounds);
|
|
1443
1445
|
this.default = "defaultParam" in spec ? Plot.Tag.new(this, spec.defaultParam, none) :
|
|
1444
1446
|
(flags & 16) ? Plot.Tag.new(this, null, none) : null;
|
|
1445
1447
|
if (!this.shape.atom && this.isInline && !this.inlineContent)
|
|
@@ -3069,6 +3071,9 @@ function splatContext(top, cx) {
|
|
|
3069
3071
|
for (let ch of cx.children)
|
|
3070
3072
|
top.push(ch);
|
|
3071
3073
|
}
|
|
3074
|
+
function isFitBarrier(plot) {
|
|
3075
|
+
return plot.isolating || plot.cursorInsideBounds;
|
|
3076
|
+
}
|
|
3072
3077
|
function fitReplacement(doc, from, to, slice, context) {
|
|
3073
3078
|
if (!slice.length)
|
|
3074
3079
|
return fitDeletion(doc, from, to);
|
|
@@ -3099,10 +3104,10 @@ function fitReplacement(doc, from, to, slice, context) {
|
|
|
3099
3104
|
let found, foundCost = 1e8;
|
|
3100
3105
|
let neutral = true, toEnd = true;
|
|
3101
3106
|
scan: for (let cxFrom = from.parent, cxTo = to.parent, fromDepth = from.depth, toDepth = to.depth, start = from.pos, end = to.pos; cxFrom.parent; cxFrom = cxFrom.parent, start--, fromDepth--) {
|
|
3102
|
-
if (cxFrom.start != start || cxFrom.node.type
|
|
3107
|
+
if (cxFrom.start != start || isFitBarrier(cxFrom.node.type))
|
|
3103
3108
|
break;
|
|
3104
3109
|
while (toDepth > fromDepth) {
|
|
3105
|
-
if (cxTo.node.type
|
|
3110
|
+
if (isFitBarrier(cxTo.node.type))
|
|
3106
3111
|
break scan;
|
|
3107
3112
|
cxTo = cxTo.parent;
|
|
3108
3113
|
toDepth--;
|
|
@@ -3136,17 +3141,22 @@ function fitReplacement(doc, from, to, slice, context) {
|
|
|
3136
3141
|
if (found)
|
|
3137
3142
|
return found;
|
|
3138
3143
|
if (from.pos == to.pos && !from.inText) {
|
|
3139
|
-
let cx = from.parent
|
|
3140
|
-
for (
|
|
3144
|
+
let cx = from.parent;
|
|
3145
|
+
for (let before = from.index ? -1 : from.pos, after = from.pos == from.parent.end ? from.pos : -1; before > -1 || after > -1;) {
|
|
3141
3146
|
for (let i = -1; i < context.length; i++) {
|
|
3142
3147
|
let type = i >= 0 ? context[i].type : firstType;
|
|
3143
3148
|
if (!type)
|
|
3144
3149
|
continue;
|
|
3145
|
-
if (doc.schema.canContain(cx.
|
|
3146
|
-
let pos = before
|
|
3150
|
+
if (doc.schema.canContain(cx.node.type, type)) {
|
|
3151
|
+
let pos = before > -1 ? before : after;
|
|
3147
3152
|
return { from: pos, to: pos, slice: i >= 0 ? closeSlice(doc.schema, slice, context, i + 1, true) : slice };
|
|
3148
3153
|
}
|
|
3149
3154
|
}
|
|
3155
|
+
if (isFitBarrier(cx.node.type) || !cx.parent)
|
|
3156
|
+
break;
|
|
3157
|
+
before = before == cx.start ? before - 1 : -1;
|
|
3158
|
+
after = after == cx.end ? after + 1 : -1;
|
|
3159
|
+
cx = cx.parent;
|
|
3150
3160
|
}
|
|
3151
3161
|
}
|
|
3152
3162
|
for (let i = 0; i < context.length; i++) {
|
|
@@ -3161,7 +3171,7 @@ function fitDeletion(doc, from, to) {
|
|
|
3161
3171
|
let toDepth = to.depth;
|
|
3162
3172
|
let covered;
|
|
3163
3173
|
for (let cx = from.parent, cxTo = to.parent, depth = from.depth, start = from.pos, end = to.pos; cx.parent; start--, cx = cx.parent, depth--) {
|
|
3164
|
-
if (cx.start != start || cx.node.type
|
|
3174
|
+
if (cx.start != start || isFitBarrier(cx.node.type))
|
|
3165
3175
|
break;
|
|
3166
3176
|
while (toDepth > depth) {
|
|
3167
3177
|
cxTo = cxTo.parent;
|
package/dist/editor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GardState, GardSelection, TextblockMap, BidiSpan, Transaction } from 'wordgard/state';
|
|
2
|
-
import { Attributes, Elt, Node, Leaf, ChangeSet, parse, Slice, Plot, serialize, Pos, ValidationError } from 'wordgard/doc';
|
|
2
|
+
import { Attributes, Elt, Node, Leaf, ChangeSet, parse, Slice, Plot, serialize, Pos, ValidationError, Mark } from 'wordgard/doc';
|
|
3
3
|
import { StyleModule } from 'style-mod';
|
|
4
4
|
import { findClusterBreak } from '@marijn/find-cluster-break';
|
|
5
5
|
import { enter, insertLineBreak, selectAll, undo, redo, transposeChars, Command, deleteUnit, deleteWord, deleteToLineEnd, moveByUnit, moveByLine, moveByWord, moveToLineSide, moveToDocSide, moveByPage, moveToTextblockSide, setAlignment, toggleUnderline, toggleEmphasis, toggleStrong, deleteLine, insertText, setDirection, deleteSelection, Menu, findWrappable, wrapBlockRange, autoJoinBlocks } from 'wordgard/command';
|
|
@@ -229,6 +229,15 @@ const baseTagShape = /*@__PURE__*/memo((tag) => {
|
|
|
229
229
|
return addMarkAttributes(tag.is(Leaf.Text) ? Widget.EditableText.of(tag.param)
|
|
230
230
|
: tag.type.shape.create(tag.param), tag);
|
|
231
231
|
});
|
|
232
|
+
function renderMarks(marks, around) {
|
|
233
|
+
let result = addMarkAttributes(Elt.create("span", Attributes.none, [around]), Leaf.text(around, marks));
|
|
234
|
+
for (let i = marks.length - 1; i >= 0; i--) {
|
|
235
|
+
let mark = marks[i];
|
|
236
|
+
if (mark.type.element)
|
|
237
|
+
result = renderMarkWrapper(mark).fill([result]);
|
|
238
|
+
}
|
|
239
|
+
return result.toDOM();
|
|
240
|
+
}
|
|
232
241
|
class AttributeRangeDecoration extends Decoration.Range {
|
|
233
242
|
attribute;
|
|
234
243
|
value;
|
|
@@ -2610,15 +2619,20 @@ class ContentUpdate {
|
|
|
2610
2619
|
return tile;
|
|
2611
2620
|
}
|
|
2612
2621
|
}
|
|
2613
|
-
|
|
2622
|
+
ensureHackNode() {
|
|
2614
2623
|
let tile = this.new;
|
|
2615
2624
|
if (!tile.isPlotContent)
|
|
2616
2625
|
return;
|
|
2617
2626
|
while (tile.isNodeInner)
|
|
2618
2627
|
tile = tile.parent;
|
|
2619
2628
|
let node = tile.node;
|
|
2620
|
-
if (!node || !node.isPlot || !node.isTextblock)
|
|
2629
|
+
if (!node || !node.isPlot || !(node.isTextblock || node.type.isInline))
|
|
2630
|
+
return;
|
|
2631
|
+
if (node.type.isInline) {
|
|
2632
|
+
if (!this.new.children.length)
|
|
2633
|
+
this.new.addChild(new WidgetTile(imgHack, null, 16 | 64, imgHack.render(this.wg)));
|
|
2621
2634
|
return;
|
|
2635
|
+
}
|
|
2622
2636
|
let hasHack = -1, needsHack = true;
|
|
2623
2637
|
for (let parent = this.new, i = parent.children.length;;) {
|
|
2624
2638
|
if (i > 0) {
|
|
@@ -2656,7 +2670,7 @@ class ContentUpdate {
|
|
|
2656
2670
|
}
|
|
2657
2671
|
}
|
|
2658
2672
|
up() {
|
|
2659
|
-
this.
|
|
2673
|
+
this.ensureHackNode();
|
|
2660
2674
|
this.new = this.new.parent;
|
|
2661
2675
|
}
|
|
2662
2676
|
leaveNode() {
|
|
@@ -2739,7 +2753,7 @@ class ContentUpdate {
|
|
|
2739
2753
|
finish() {
|
|
2740
2754
|
while (!(this.new instanceof DocTile))
|
|
2741
2755
|
this.up();
|
|
2742
|
-
this.
|
|
2756
|
+
this.ensureHackNode();
|
|
2743
2757
|
return this.new;
|
|
2744
2758
|
}
|
|
2745
2759
|
}
|
|
@@ -2784,7 +2798,11 @@ const brHack = /*@__PURE__*/Widget.create({
|
|
|
2784
2798
|
editable: true
|
|
2785
2799
|
});
|
|
2786
2800
|
const imgHack = /*@__PURE__*/Widget.create({
|
|
2787
|
-
render() {
|
|
2801
|
+
render() {
|
|
2802
|
+
let img = document.createElement("img");
|
|
2803
|
+
img.className = "wg-buffer";
|
|
2804
|
+
return img;
|
|
2805
|
+
},
|
|
2788
2806
|
editable: true
|
|
2789
2807
|
});
|
|
2790
2808
|
function separateComposition(sections, comp) {
|
|
@@ -2817,18 +2835,26 @@ function separateComposition(sections, comp) {
|
|
|
2817
2835
|
return result;
|
|
2818
2836
|
}
|
|
2819
2837
|
|
|
2838
|
+
class Coords {
|
|
2839
|
+
ref;
|
|
2840
|
+
rect;
|
|
2841
|
+
constructor(ref, rect) {
|
|
2842
|
+
this.ref = ref;
|
|
2843
|
+
this.rect = rect;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2820
2846
|
function coordsAtPos(wg, pos, assoc) {
|
|
2821
2847
|
let { offset, tile, pos: tilePos } = wg.docTile.resolve(pos, assoc);
|
|
2822
2848
|
if (tile instanceof TextTile) {
|
|
2823
2849
|
let node = tile.dom, len = node.nodeValue.length;
|
|
2824
2850
|
if (!len)
|
|
2825
|
-
return singleRect(textRange(node, 0, 0), 1);
|
|
2851
|
+
return new Coords(tile, singleRect(textRange(node, 0, 0), 1));
|
|
2826
2852
|
let from = offset, to = offset, side = assoc < 0 && from || from == len ? 1 : -1;
|
|
2827
2853
|
if (side < 0)
|
|
2828
2854
|
to++;
|
|
2829
2855
|
else
|
|
2830
2856
|
from--;
|
|
2831
|
-
return flattenV(singleRect(textRange(node, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc));
|
|
2857
|
+
return new Coords(tile, flattenV(singleRect(textRange(node, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc)));
|
|
2832
2858
|
}
|
|
2833
2859
|
let tagTile = tile;
|
|
2834
2860
|
while (!tagTile.node)
|
|
@@ -2839,10 +2865,10 @@ function coordsAtPos(wg, pos, assoc) {
|
|
|
2839
2865
|
if (tile.widget.type.inFlow) {
|
|
2840
2866
|
let rect = singleRect(tile.dom, after ? 1 : -1);
|
|
2841
2867
|
if (rect.width || rect.height)
|
|
2842
|
-
return horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after);
|
|
2868
|
+
return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after));
|
|
2843
2869
|
}
|
|
2844
2870
|
if (!tile.parent)
|
|
2845
|
-
return new DOMRect;
|
|
2871
|
+
return new Coords(tile, new DOMRect);
|
|
2846
2872
|
offset = tile.parent.children.indexOf(tile) + (after ? 1 : 0);
|
|
2847
2873
|
tile = tile.parent;
|
|
2848
2874
|
assoc = after ? 1 : -1;
|
|
@@ -2854,7 +2880,7 @@ function coordsAtPos(wg, pos, assoc) {
|
|
|
2854
2880
|
continue;
|
|
2855
2881
|
let rect = singleRect(before.dom, 1);
|
|
2856
2882
|
if (rect.width || rect.height)
|
|
2857
|
-
return horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1));
|
|
2883
|
+
return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1)));
|
|
2858
2884
|
}
|
|
2859
2885
|
}
|
|
2860
2886
|
else { for (let i = offset; i < tile.children.length; i++) {
|
|
@@ -2863,12 +2889,12 @@ function coordsAtPos(wg, pos, assoc) {
|
|
|
2863
2889
|
continue;
|
|
2864
2890
|
let rect = singleRect(after.dom, -1);
|
|
2865
2891
|
if (rect.width || rect.height)
|
|
2866
|
-
return horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1));
|
|
2892
|
+
return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1)));
|
|
2867
2893
|
}
|
|
2868
2894
|
}
|
|
2869
2895
|
}
|
|
2870
2896
|
let rect = singleRect(tile.dom, -assoc);
|
|
2871
|
-
return horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0);
|
|
2897
|
+
return new Coords(tile, horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0));
|
|
2872
2898
|
}
|
|
2873
2899
|
function flattenV(rect, left) {
|
|
2874
2900
|
return rect.width ? new DOMRect(left ? rect.left : rect.right, rect.top, 0, rect.height) : rect;
|
|
@@ -3141,6 +3167,13 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
|
|
|
3141
3167
|
borderLeft: "1.8px solid currentColor",
|
|
3142
3168
|
marginLeft: "-0.9px",
|
|
3143
3169
|
},
|
|
3170
|
+
".wg-cursor-v.wg-cursor-bold": {
|
|
3171
|
+
borderLeft: "2.4px solid currentColor",
|
|
3172
|
+
marginLeft: "-1.2px",
|
|
3173
|
+
},
|
|
3174
|
+
".wg-cursor-v.wg-cursor-italic": {
|
|
3175
|
+
transform: "rotate(10deg)"
|
|
3176
|
+
},
|
|
3144
3177
|
".wg-cursor-h": {
|
|
3145
3178
|
borderTop: "1.8px solid currentColor",
|
|
3146
3179
|
marginTop: "-0.9px",
|
|
@@ -3151,6 +3184,10 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
|
|
|
3151
3184
|
backgroundColor: "transparent"
|
|
3152
3185
|
}
|
|
3153
3186
|
},
|
|
3187
|
+
".wg-buffer": {
|
|
3188
|
+
verticalAlign: "bottom",
|
|
3189
|
+
height: "1em",
|
|
3190
|
+
},
|
|
3154
3191
|
"wg-placeholder": {
|
|
3155
3192
|
opacity: "0.6",
|
|
3156
3193
|
display: "inline-block",
|
|
@@ -3963,7 +4000,7 @@ class InputState {
|
|
|
3963
4000
|
let inText = node.nodeType == 3;
|
|
3964
4001
|
let ref = this.wg.docTile.posFromDOM(node, inText ? 0 : offset);
|
|
3965
4002
|
let dir = -1;
|
|
3966
|
-
let textBefore = textNodeBefore(node.parentNode, domIndex(node));
|
|
4003
|
+
let textBefore = node.parentNode && textNodeBefore(node.parentNode, domIndex(node));
|
|
3967
4004
|
let prev = textBefore && Tile.get(textBefore);
|
|
3968
4005
|
if (prev instanceof TextTile && prev.length < prev.dom.nodeValue.length)
|
|
3969
4006
|
dir = 1;
|
|
@@ -3986,8 +4023,8 @@ class InputState {
|
|
|
3986
4023
|
let type = event.inputType, range;
|
|
3987
4024
|
let { wg } = this, sel = wg.state.selection;
|
|
3988
4025
|
if (data.domRange) {
|
|
3989
|
-
range = { from: this.domMapping.mapPos(data.domRange.from),
|
|
3990
|
-
to: this.domMapping.mapPos(data.domRange.to) };
|
|
4026
|
+
range = { from: snapToSel(wg.state, this.domMapping.mapPos(data.domRange.from)),
|
|
4027
|
+
to: snapToSel(wg.state, this.domMapping.mapPos(data.domRange.to)) };
|
|
3991
4028
|
if (!this.domMapping.empty && type == "insertText" && !this.composing && range.from == range.to) {
|
|
3992
4029
|
let fromMax = this.domMapping.mapPos(data.domRange.from, 1);
|
|
3993
4030
|
if (range.from <= sel.from && fromMax >= sel.to)
|
|
@@ -3996,7 +4033,7 @@ class InputState {
|
|
|
3996
4033
|
}
|
|
3997
4034
|
let command = inputTypeCommands[type];
|
|
3998
4035
|
if ((type == "deleteContentBackward" || type == "deleteContentForward") && range &&
|
|
3999
|
-
(sel.empty
|
|
4036
|
+
range.from != range.to && (sel.empty
|
|
4000
4037
|
? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
|
|
4001
4038
|
sel.head != (type == "deleteContentBackward" ? range.to : range.from)
|
|
4002
4039
|
: sel.from != range.from || sel.to != range.to)) {
|
|
@@ -4089,6 +4126,39 @@ class InputState {
|
|
|
4089
4126
|
this.mouseSelection.disconnect();
|
|
4090
4127
|
}
|
|
4091
4128
|
}
|
|
4129
|
+
function snapToSel(state, pos) {
|
|
4130
|
+
let { head, anchor } = state.selection;
|
|
4131
|
+
if (pos == head || pos == anchor)
|
|
4132
|
+
return pos;
|
|
4133
|
+
if (onlyInlineNodeBoundsBetween(state.doc, head, pos))
|
|
4134
|
+
return head;
|
|
4135
|
+
if (head != anchor && onlyInlineNodeBoundsBetween(state.doc, head, pos))
|
|
4136
|
+
return anchor;
|
|
4137
|
+
return pos;
|
|
4138
|
+
}
|
|
4139
|
+
function onlyInlineNodeBoundsBetween(doc, a, b) {
|
|
4140
|
+
if (a > b)
|
|
4141
|
+
[a, b] = [b, a];
|
|
4142
|
+
let { parent, index, inText } = doc.resolve(a);
|
|
4143
|
+
if (inText)
|
|
4144
|
+
return false;
|
|
4145
|
+
for (; a < b; a++) {
|
|
4146
|
+
if (index == parent.node.content.length) {
|
|
4147
|
+
if (!parent.node.type.isInline)
|
|
4148
|
+
return false;
|
|
4149
|
+
index = parent.index + 1;
|
|
4150
|
+
parent = parent.parent;
|
|
4151
|
+
}
|
|
4152
|
+
else {
|
|
4153
|
+
let next = parent.node.content[index];
|
|
4154
|
+
if (!next.isPlot || !next.type.isInline)
|
|
4155
|
+
return false;
|
|
4156
|
+
parent = Pos.Plot.create(parent, next, a, index);
|
|
4157
|
+
index = 0;
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
return true;
|
|
4161
|
+
}
|
|
4092
4162
|
function isSingleChar(doc, from, to) {
|
|
4093
4163
|
if (to > from + 10)
|
|
4094
4164
|
return false;
|
|
@@ -4712,9 +4782,34 @@ class ViewState {
|
|
|
4712
4782
|
const cursorBlinkRate = /*@__PURE__*/GardState.Facet.define({
|
|
4713
4783
|
combine: inputs => inputs.length ? Math.min(...inputs) : 1200
|
|
4714
4784
|
});
|
|
4715
|
-
class
|
|
4785
|
+
class CursorStyle {
|
|
4786
|
+
height;
|
|
4787
|
+
align;
|
|
4788
|
+
color;
|
|
4789
|
+
bold;
|
|
4790
|
+
italic;
|
|
4791
|
+
constructor(height, align, color, bold, italic) {
|
|
4792
|
+
this.height = height;
|
|
4793
|
+
this.align = align;
|
|
4794
|
+
this.color = color;
|
|
4795
|
+
this.bold = bold;
|
|
4796
|
+
this.italic = italic;
|
|
4797
|
+
}
|
|
4798
|
+
static read(node, height) {
|
|
4799
|
+
let win = node.ownerDocument.defaultView || window;
|
|
4800
|
+
let elt = node.nodeType == 1 ? node : node.parentNode;
|
|
4801
|
+
let style = win.getComputedStyle(elt);
|
|
4802
|
+
return new CursorStyle(height, style.verticalAlign, style.color, +style.fontWeight > 400, style.fontStyle == "italic");
|
|
4803
|
+
}
|
|
4804
|
+
eq(other) {
|
|
4805
|
+
return other && this.height == other.height && this.align == other.align &&
|
|
4806
|
+
this.color == other.color && this.bold == other.bold && this.italic == other.italic;
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
class CursorLayer {
|
|
4716
4810
|
layer;
|
|
4717
|
-
|
|
4811
|
+
info = null;
|
|
4812
|
+
cached = null;
|
|
4718
4813
|
constructor(wg) {
|
|
4719
4814
|
this.layer = wg.scrollDOM.appendChild(document.createElement("wg-cursor-layer"));
|
|
4720
4815
|
this.positionCursor = this.positionCursor.bind(this);
|
|
@@ -4737,48 +4832,131 @@ class cursorLayer {
|
|
|
4737
4832
|
this.layer.remove();
|
|
4738
4833
|
}
|
|
4739
4834
|
positionCursor(wg) {
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4835
|
+
getCursorInfo(wg, this, info => {
|
|
4836
|
+
let cur = this.info;
|
|
4837
|
+
if (!info ? cur : !cur || cur.left != info.left || cur.top != info.top || cur.size != info.size ||
|
|
4838
|
+
(cur.style ? !cur.style.eq(info.style) : info.style)) {
|
|
4839
|
+
this.info = info;
|
|
4840
|
+
wg.scheduleDOMWrite(() => {
|
|
4841
|
+
let cursor = this.layer.firstChild;
|
|
4842
|
+
if (!info) {
|
|
4843
|
+
if (cursor)
|
|
4844
|
+
cursor.remove();
|
|
4845
|
+
}
|
|
4846
|
+
else {
|
|
4847
|
+
if (!cursor)
|
|
4848
|
+
cursor = this.layer.appendChild(document.createElement("wg-cursor"));
|
|
4849
|
+
cursor.className = "wg-cursor-" + (info.horiz ? "h" : "v");
|
|
4850
|
+
cursor.style.top = info.top + "px";
|
|
4851
|
+
cursor.style.left = info.left + "px";
|
|
4852
|
+
cursor.style.width = info.horiz ? info.size + "px" : "";
|
|
4853
|
+
cursor.style.height = info.horiz ? "" : info.size + "px";
|
|
4854
|
+
cursor.style.borderLeftColor = info.style ? info.style.color : "";
|
|
4855
|
+
cursor.classList.toggle("wg-cursor-bold", info.style?.bold ?? false);
|
|
4856
|
+
cursor.classList.toggle("wg-cursor-italic", info.style?.italic ?? false);
|
|
4857
|
+
}
|
|
4858
|
+
});
|
|
4859
|
+
}
|
|
4860
|
+
});
|
|
4760
4861
|
}
|
|
4761
4862
|
}
|
|
4863
|
+
function alignOffset(align, height) {
|
|
4864
|
+
if (align == "super")
|
|
4865
|
+
return height * 0.36;
|
|
4866
|
+
if (align == "sub")
|
|
4867
|
+
return height * -0.17;
|
|
4868
|
+
if (align.endsWith("px"))
|
|
4869
|
+
return +align.slice(0, align.length - 2);
|
|
4870
|
+
if (align.endsWith("%"))
|
|
4871
|
+
return height * (+align.slice(0, align.length - 1)) * 100;
|
|
4872
|
+
return 0;
|
|
4873
|
+
}
|
|
4762
4874
|
const VertWidth = 30, VertGap = 5;
|
|
4763
|
-
function
|
|
4875
|
+
function getCursorInfo(wg, plugin, cont) {
|
|
4764
4876
|
let { state } = wg;
|
|
4765
4877
|
if (!state.selection.isCursor)
|
|
4766
|
-
return null;
|
|
4767
|
-
let { head, headSide } = state.selection;
|
|
4768
|
-
let {
|
|
4769
|
-
let
|
|
4770
|
-
if (
|
|
4771
|
-
|
|
4772
|
-
if (!wg.state.textLTR)
|
|
4773
|
-
left = right - size;
|
|
4878
|
+
return cont(null);
|
|
4879
|
+
let { head, headSide } = state.selection, { sel } = wg.state;
|
|
4880
|
+
let { ref, rect } = coordsAtPos(wg, head, headSide);
|
|
4881
|
+
let doc = wg.contentDOM.getBoundingClientRect();
|
|
4882
|
+
if (!sel.head.parent.node.inlineContent) {
|
|
4883
|
+
let width = Math.max(VertWidth, rect.width), top = rect.top;
|
|
4774
4884
|
let other = wg.coordsAtPos(head, headSide > 0 ? -1 : 1);
|
|
4775
4885
|
if (other.top == other.bottom && other.top != top) {
|
|
4776
4886
|
let move = Math.min(VertGap, Math.abs(other.top - top) / 2);
|
|
4777
|
-
top =
|
|
4887
|
+
top = top + move * (other.top < top ? -1 : 1);
|
|
4778
4888
|
}
|
|
4889
|
+
return cont({
|
|
4890
|
+
left: (wg.state.textLTR ? rect.left : rect.right - width) - doc.left,
|
|
4891
|
+
top: top - doc.top,
|
|
4892
|
+
size: width,
|
|
4893
|
+
horiz: true, style: null
|
|
4894
|
+
});
|
|
4779
4895
|
}
|
|
4780
|
-
let
|
|
4781
|
-
|
|
4896
|
+
let finish = (style, vertRect) => {
|
|
4897
|
+
if (style && (!plugin.cached || plugin.cached.style != style))
|
|
4898
|
+
plugin.cached = { style, marks, parent: sel.head.parent.node.tag };
|
|
4899
|
+
let height = style ? style.height : rect.height;
|
|
4900
|
+
let bot = rect.bottom;
|
|
4901
|
+
if (vertRect && (vertRect.top < rect.bottom && vertRect.bottom > rect.top)) {
|
|
4902
|
+
bot = vertRect.bottom;
|
|
4903
|
+
}
|
|
4904
|
+
else {
|
|
4905
|
+
let win = ref.dom.ownerDocument.defaultView || window;
|
|
4906
|
+
let refAlign = win.getComputedStyle((ref.dom.nodeType == 1 ? ref.dom : ref.dom.parentNode)).verticalAlign;
|
|
4907
|
+
if (style && refAlign != style.align) {
|
|
4908
|
+
bot += alignOffset(refAlign, rect.height) - alignOffset(style.align, style.height);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
cont({
|
|
4912
|
+
left: rect.left - doc.left,
|
|
4913
|
+
top: bot - height - doc.top,
|
|
4914
|
+
size: height,
|
|
4915
|
+
horiz: false, style
|
|
4916
|
+
});
|
|
4917
|
+
};
|
|
4918
|
+
let marks = sel.activeMarks;
|
|
4919
|
+
if (ref instanceof TextTile) {
|
|
4920
|
+
let node = ref.posBefore < head ? state.sel.head.nodeBefore : state.sel.head.nodeAfter;
|
|
4921
|
+
if (node && node.isText && Mark.sameSet(node.marks, marks))
|
|
4922
|
+
return finish(CursorStyle.read(ref.dom, rect.height), rect);
|
|
4923
|
+
}
|
|
4924
|
+
let pos = sel.head.parent.start, foundRect, foundNode;
|
|
4925
|
+
for (let sibling of sel.head.parent.node.content) {
|
|
4926
|
+
if (sibling.isText && Mark.sameSet(sibling.marks, marks)) {
|
|
4927
|
+
let { tile } = wg.docTile.resolve(pos, 1);
|
|
4928
|
+
if (tile instanceof TextTile) {
|
|
4929
|
+
let rects = textRange(tile.dom, 0, tile.length).getClientRects();
|
|
4930
|
+
foundNode = tile.dom;
|
|
4931
|
+
for (let i = 0; i < rects.length; i++) {
|
|
4932
|
+
foundRect = rects[i];
|
|
4933
|
+
if (foundRect.top < rect.bottom && foundRect.bottom > rect.top)
|
|
4934
|
+
break;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
pos += sibling.length;
|
|
4939
|
+
}
|
|
4940
|
+
if (foundNode)
|
|
4941
|
+
return finish(CursorStyle.read(foundNode, foundRect.height), foundRect);
|
|
4942
|
+
if (plugin.cached && Mark.sameSet(plugin.cached.marks, marks) && plugin.cached.parent.eq(sel.head.parent.node.tag))
|
|
4943
|
+
return finish(plugin.cached.style);
|
|
4944
|
+
if (!marks.length)
|
|
4945
|
+
return finish(null);
|
|
4946
|
+
let target = sel.head.parent.node.isDoc ? wg.contentDOM : wg.nodeDOM(sel.head.parent.before);
|
|
4947
|
+
wg.scheduleDOMWrite(() => {
|
|
4948
|
+
let temp = renderMarks(marks, "M");
|
|
4949
|
+
temp.style.position = "absolute";
|
|
4950
|
+
wg.observer.ignore(() => target.insertBefore(temp, target.firstChild));
|
|
4951
|
+
wg.scheduleDOMRead(() => {
|
|
4952
|
+
wg.scheduleDOMWrite(() => wg.observer.ignore(() => temp.remove()));
|
|
4953
|
+
let inner = temp;
|
|
4954
|
+
while (inner.firstChild)
|
|
4955
|
+
inner = inner.firstChild;
|
|
4956
|
+
let r = textRange(inner, 0, 1).getClientRects()[0];
|
|
4957
|
+
finish(CursorStyle.read(inner, r.height), r);
|
|
4958
|
+
});
|
|
4959
|
+
});
|
|
4782
4960
|
}
|
|
4783
4961
|
function setBlinkRate(state, dom) {
|
|
4784
4962
|
dom.style.animationDuration = state.facet(cursorBlinkRate) + "ms";
|
|
@@ -5132,7 +5310,7 @@ class Wordgard {
|
|
|
5132
5310
|
}
|
|
5133
5311
|
coordsAtPos(pos, assoc = -1) {
|
|
5134
5312
|
this.ensureFlushed();
|
|
5135
|
-
return coordsAtPos(this, pos, assoc);
|
|
5313
|
+
return coordsAtPos(this, pos, assoc).rect;
|
|
5136
5314
|
}
|
|
5137
5315
|
coordsForElement(pos) {
|
|
5138
5316
|
this.ensureFlushed();
|
|
@@ -5381,7 +5559,7 @@ function attrsFromFacet(wg, facet, base) {
|
|
|
5381
5559
|
Wordgard.Update = Update;
|
|
5382
5560
|
;return Wordgard})(Wordgard);
|
|
5383
5561
|
const editorPlugin = /*@__PURE__*/GardState.Facet.define();
|
|
5384
|
-
const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(
|
|
5562
|
+
const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(CursorLayer);
|
|
5385
5563
|
class PluginInstance {
|
|
5386
5564
|
spec;
|
|
5387
5565
|
mustUpdate = null;
|
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.
|
|
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
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
887
|
+
let next = scanNormalFrom(cx, pos, side, forward);
|
|
856
888
|
if (!next)
|
|
857
889
|
return last;
|
|
858
890
|
({ pos, side } = next);
|