@thomasfosterau/effect-richtext 0.0.0-stage → 0.1.0
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/LICENSE +48 -0
- package/README.md +118 -2
- package/dist/_virtual/_rolldown/runtime.js +12 -0
- package/dist/commands/commands.d.ts +323 -0
- package/dist/commands/commands.js +722 -0
- package/dist/commands/internal.js +43 -0
- package/dist/commands/keymaps.d.ts +46 -0
- package/dist/commands/keymaps.js +64 -0
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +3 -0
- package/dist/compat/commands.d.ts +250 -0
- package/dist/compat/commands.js +275 -0
- package/dist/compat/internal/core.d.ts +20 -0
- package/dist/compat/internal/core.js +148 -0
- package/dist/compat/internal/props.js +82 -0
- package/dist/compat/keymap.d.ts +21 -0
- package/dist/compat/keymap.js +38 -0
- package/dist/compat/model.d.ts +893 -0
- package/dist/compat/model.js +1212 -0
- package/dist/compat/orderedmap.d.ts +74 -0
- package/dist/compat/orderedmap.js +124 -0
- package/dist/compat/schema-basic.d.ts +38 -0
- package/dist/compat/schema-basic.js +238 -0
- package/dist/compat/schema-list.d.ts +81 -0
- package/dist/compat/schema-list.js +156 -0
- package/dist/compat/state.d.ts +415 -0
- package/dist/compat/state.js +689 -0
- package/dist/compat/test-builder.d.ts +98 -0
- package/dist/compat/test-builder.js +197 -0
- package/dist/compat/transform.d.ts +458 -0
- package/dist/compat/transform.js +803 -0
- package/dist/compat/view.d.ts +411 -0
- package/dist/compat/view.js +409 -0
- package/dist/dropcursor.d.ts +46 -0
- package/dist/dropcursor.js +176 -0
- package/dist/extensions/defaults.d.ts +716 -0
- package/dist/extensions/defaults.js +209 -0
- package/dist/extensions/editor.d.ts +246 -0
- package/dist/extensions/editor.js +271 -0
- package/dist/extensions/extension.d.ts +375 -0
- package/dist/extensions/extension.js +233 -0
- package/dist/extensions.d.ts +4 -0
- package/dist/extensions.js +4 -0
- package/dist/gapcursor.d.ts +66 -0
- package/dist/gapcursor.js +273 -0
- package/dist/history.d.ts +178 -0
- package/dist/history.js +412 -0
- package/dist/html.d.ts +158 -0
- package/dist/html.js +164 -0
- package/dist/inputrules.d.ts +271 -0
- package/dist/inputrules.js +321 -0
- package/dist/internal/attempt.js +15 -0
- package/dist/internal/json.js +29 -0
- package/dist/keymap/keymap.d.ts +130 -0
- package/dist/keymap/keymap.js +159 -0
- package/dist/keymap.d.ts +2 -0
- package/dist/keymap.js +1 -0
- package/dist/markdown/errors.d.ts +23 -0
- package/dist/markdown/errors.js +26 -0
- package/dist/markdown/from_markdown.d.ts +185 -0
- package/dist/markdown/from_markdown.js +266 -0
- package/dist/markdown/schema.d.ts +522 -0
- package/dist/markdown/schema.js +292 -0
- package/dist/markdown/to_markdown.d.ts +251 -0
- package/dist/markdown/to_markdown.js +413 -0
- package/dist/markdown.d.ts +5 -0
- package/dist/markdown.js +5 -0
- package/dist/model/attrsCodec.js +48 -0
- package/dist/model/comparedeep.js +17 -0
- package/dist/model/content.d.ts +232 -0
- package/dist/model/content.js +630 -0
- package/dist/model/diff.js +66 -0
- package/dist/model/dom.d.ts +3 -0
- package/dist/model/errors.d.ts +90 -0
- package/dist/model/errors.js +76 -0
- package/dist/model/fragment.d.ts +131 -0
- package/dist/model/fragment.js +262 -0
- package/dist/model/from_dom.d.ts +299 -0
- package/dist/model/from_dom.js +618 -0
- package/dist/model/json.d.ts +37 -0
- package/dist/model/json.js +126 -0
- package/dist/model/mark.d.ts +73 -0
- package/dist/model/mark.js +121 -0
- package/dist/model/node.d.ts +267 -0
- package/dist/model/node.js +434 -0
- package/dist/model/replace.d.ts +78 -0
- package/dist/model/replace.js +207 -0
- package/dist/model/resolvedpos.d.ts +196 -0
- package/dist/model/resolvedpos.js +305 -0
- package/dist/model/schema.d.ts +740 -0
- package/dist/model/schema.js +451 -0
- package/dist/model/to_dom.d.ts +169 -0
- package/dist/model/to_dom.js +238 -0
- package/dist/model.d.ts +12 -0
- package/dist/model.js +12 -0
- package/dist/placeholder.d.ts +53 -0
- package/dist/placeholder.js +79 -0
- package/dist/reactive/observer.d.ts +54 -0
- package/dist/reactive/observer.js +61 -0
- package/dist/reactive/selectors.d.ts +22 -0
- package/dist/reactive/selectors.js +61 -0
- package/dist/reactive/stateref.d.ts +72 -0
- package/dist/reactive/stateref.js +71 -0
- package/dist/reactive/streams.d.ts +83 -0
- package/dist/reactive/streams.js +85 -0
- package/dist/reactive.d.ts +16 -0
- package/dist/reactive.js +26 -0
- package/dist/schema-basic.d.ts +448 -0
- package/dist/schema-basic.js +269 -0
- package/dist/schema-list/commands.d.ts +66 -0
- package/dist/schema-list/commands.js +203 -0
- package/dist/schema-list.d.ts +92 -0
- package/dist/schema-list.js +100 -0
- package/dist/state/errors.d.ts +39 -0
- package/dist/state/errors.js +32 -0
- package/dist/state/plugin.d.ts +232 -0
- package/dist/state/plugin.js +82 -0
- package/dist/state/selection.d.ts +297 -0
- package/dist/state/selection.js +486 -0
- package/dist/state/state.d.ts +212 -0
- package/dist/state/state.js +489 -0
- package/dist/state/transaction.d.ts +145 -0
- package/dist/state/transaction.js +195 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.js +6 -0
- package/dist/test-builder.d.ts +3582 -0
- package/dist/test-builder.js +239 -0
- package/dist/transform/attr_step.d.ts +60 -0
- package/dist/transform/attr_step.js +105 -0
- package/dist/transform/errors.d.ts +50 -0
- package/dist/transform/errors.js +35 -0
- package/dist/transform/map.d.ts +182 -0
- package/dist/transform/map.js +280 -0
- package/dist/transform/mark.js +98 -0
- package/dist/transform/mark_set_step.js +69 -0
- package/dist/transform/mark_step.d.ts +113 -0
- package/dist/transform/mark_step.js +225 -0
- package/dist/transform/replace.d.ts +18 -0
- package/dist/transform/replace.js +381 -0
- package/dist/transform/replace_step.d.ts +109 -0
- package/dist/transform/replace_step.js +175 -0
- package/dist/transform/step.d.ts +157 -0
- package/dist/transform/step.js +168 -0
- package/dist/transform/structure.d.ts +89 -0
- package/dist/transform/structure.js +350 -0
- package/dist/transform/transform.d.ts +248 -0
- package/dist/transform/transform.js +373 -0
- package/dist/transform.d.ts +10 -0
- package/dist/transform.js +10 -0
- package/dist/view/browser.js +39 -0
- package/dist/view/capturekeys.js +282 -0
- package/dist/view/clipboard.d.ts +8 -0
- package/dist/view/clipboard.js +245 -0
- package/dist/view/decoration.d.ts +339 -0
- package/dist/view/decoration.js +566 -0
- package/dist/view/dom.d.ts +18 -0
- package/dist/view/dom.js +140 -0
- package/dist/view/domchange.js +295 -0
- package/dist/view/domcoords.d.ts +22 -0
- package/dist/view/domcoords.js +448 -0
- package/dist/view/domobserver.d.ts +47 -0
- package/dist/view/domobserver.js +328 -0
- package/dist/view/errors.d.ts +18 -0
- package/dist/view/errors.js +21 -0
- package/dist/view/input.d.ts +58 -0
- package/dist/view/input.js +704 -0
- package/dist/view/selection.js +198 -0
- package/dist/view/view.d.ts +699 -0
- package/dist/view/view.js +623 -0
- package/dist/view/viewdesc.d.ts +236 -0
- package/dist/view/viewdesc.js +1205 -0
- package/dist/view-css.d.ts +43 -0
- package/dist/view-css.js +124 -0
- package/dist/view.d.ts +6 -0
- package/dist/view.js +4 -0
- package/package.json +206 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { NodeRange } from "../model/resolvedpos.js";
|
|
2
|
+
import { Slice } from "../model/replace.js";
|
|
3
|
+
import { AnySchema, NodeAttrsInput, NodeNames, NodeType } from "../model/schema.js";
|
|
4
|
+
import { Node } from "../model/node.js";
|
|
5
|
+
import "../model.js";
|
|
6
|
+
import "./transform.js";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
/**
|
|
9
|
+
* A node type paired with (optional) attributes, as used by `wrap`,
|
|
10
|
+
* `split` and `findWrapping`. For typed schemas, the attributes are
|
|
11
|
+
* typed according to the node type. A node type whose name isn't known
|
|
12
|
+
* statically can be given without attributes (defaults are used).
|
|
13
|
+
*
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
* @category models
|
|
16
|
+
*/
|
|
17
|
+
export type NodeTypeWithAttrs<Sc extends AnySchema = AnySchema> = { readonly [N in NodeNames<Sc>]: {
|
|
18
|
+
readonly type: NodeType<Sc, N>;
|
|
19
|
+
readonly attrs?: NodeAttrsInput<Sc, N> | null | undefined;
|
|
20
|
+
}; }[NodeNames<Sc>] | {
|
|
21
|
+
readonly type: NodeType<Sc>;
|
|
22
|
+
readonly attrs?: null | undefined;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Try to find a target depth to which the content in the given range
|
|
26
|
+
* can be lifted. Will not go across isolating parent nodes.
|
|
27
|
+
*
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
* @category queries
|
|
30
|
+
*/
|
|
31
|
+
export declare const liftTarget: <Sc extends AnySchema>(range: NodeRange<Sc>) => Option.Option<number>;
|
|
32
|
+
/**
|
|
33
|
+
* Try to find a valid way to wrap the content in the given range in a
|
|
34
|
+
* node of the given type. May introduce extra nodes around and inside
|
|
35
|
+
* the wrapper node, if necessary. Returns `None` if no valid wrapping
|
|
36
|
+
* could be found. When `innerRange` is given, that range's content is
|
|
37
|
+
* used as the content to fit into the wrapping, instead of the content
|
|
38
|
+
* of `range`.
|
|
39
|
+
*
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
* @category queries
|
|
42
|
+
*/
|
|
43
|
+
export declare const findWrapping: <Sc extends AnySchema, N extends NodeNames<Sc>>(range: NodeRange<Sc>, nodeType: NodeType<Sc, N>, attrs?: NodeAttrsInput<Sc, N> | null, innerRange?: NodeRange<Sc>) => Option.Option<ReadonlyArray<NodeTypeWithAttrs<Sc>>>;
|
|
44
|
+
/**
|
|
45
|
+
* Check whether splitting at the given position is allowed. Throws a
|
|
46
|
+
* `PositionError` when `pos` is outside of the document.
|
|
47
|
+
*
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
* @category queries
|
|
50
|
+
*/
|
|
51
|
+
export declare function canSplit<Sc extends AnySchema>(doc: Node<Sc>, pos: number, depth?: number, typesAfter?: ReadonlyArray<NodeTypeWithAttrs<Sc> | null | undefined>): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Test whether the blocks before and after a given position can be
|
|
54
|
+
* joined. Throws a `PositionError` when `pos` is outside of the
|
|
55
|
+
* document.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
* @category queries
|
|
59
|
+
*/
|
|
60
|
+
export declare function canJoin<Sc extends AnySchema>(doc: Node<Sc>, pos: number): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Find an ancestor of the given position that can be joined to the
|
|
63
|
+
* block before (or after if `dir` is positive). Returns the joinable
|
|
64
|
+
* point, if any.
|
|
65
|
+
*
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
* @category queries
|
|
68
|
+
*/
|
|
69
|
+
export declare function joinPoint<Sc extends AnySchema>(doc: Node<Sc>, pos: number, dir?: number): Option.Option<number>;
|
|
70
|
+
/**
|
|
71
|
+
* Try to find a point where a node of the given type can be inserted
|
|
72
|
+
* near `pos`, by searching up the node hierarchy when `pos` itself
|
|
73
|
+
* isn't a valid place but is at the start or end of a node. Returns
|
|
74
|
+
* `None` if no position was found.
|
|
75
|
+
*
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @category queries
|
|
78
|
+
*/
|
|
79
|
+
export declare const insertPoint: <Sc extends AnySchema>(doc: Node<Sc>, pos: number, nodeType: NodeType<Sc, any>) => Option.Option<number>;
|
|
80
|
+
/**
|
|
81
|
+
* Finds a position at or around the given position where the given
|
|
82
|
+
* slice can be inserted. Will look at parent nodes' nearest boundary
|
|
83
|
+
* and try there, even if the original position wasn't directly at the
|
|
84
|
+
* start or end of that node. Returns `None` when no position was found.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
* @category queries
|
|
88
|
+
*/
|
|
89
|
+
export declare function dropPoint<Sc extends AnySchema>(doc: Node<Sc>, pos: number, slice: Slice<Sc>): Option.Option<number>;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { Fragment } from "../model/fragment.js";
|
|
2
|
+
import { Slice } from "../model/replace.js";
|
|
3
|
+
import { TransformError } from "./errors.js";
|
|
4
|
+
import { clearIncompatible } from "./mark.js";
|
|
5
|
+
import { ReplaceAroundStep, ReplaceStep } from "./replace_step.js";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
// src/transform/structure.ts
|
|
8
|
+
/**
|
|
9
|
+
* Structural queries (`liftTarget`, `findWrapping`, `canSplit`, …) and
|
|
10
|
+
* the structure-changing transform implementations.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
function canCut(node, start, end) {
|
|
15
|
+
return (start === 0 || node.canReplace(start, node.childCount)) && (end === node.childCount || node.canReplace(0, end));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Try to find a target depth to which the content in the given range
|
|
19
|
+
* can be lifted. Will not go across isolating parent nodes.
|
|
20
|
+
*
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
* @category queries
|
|
23
|
+
*/
|
|
24
|
+
const liftTarget = (range) => Option.fromNullishOr(liftTargetOrNull(range));
|
|
25
|
+
/** @internal */
|
|
26
|
+
function liftTargetOrNull(range) {
|
|
27
|
+
const content = range.parent.content.cutByIndex(range.startIndex, range.endIndex);
|
|
28
|
+
for (let depth = range.depth, contentBefore = 0, contentAfter = 0;; --depth) {
|
|
29
|
+
const node = range.$from.node(depth);
|
|
30
|
+
const index = range.$from.index(depth) + contentBefore, endIndex = range.$to.indexAfter(depth) - contentAfter;
|
|
31
|
+
if (depth < range.depth && node.canReplace(index, endIndex, content)) return depth;
|
|
32
|
+
if (depth === 0 || node.type.spec.isolating || !canCut(node, index, endIndex)) break;
|
|
33
|
+
if (index) contentBefore = 1;
|
|
34
|
+
if (endIndex < node.childCount) contentAfter = 1;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** @internal */
|
|
39
|
+
function lift(tr, range, target) {
|
|
40
|
+
const { $from, $to, depth } = range;
|
|
41
|
+
const gapStart = $from.beforeUnsafe(depth + 1), gapEnd = $to.afterUnsafe(depth + 1);
|
|
42
|
+
let start = gapStart, end = gapEnd;
|
|
43
|
+
let before = Fragment.empty, openStart = 0;
|
|
44
|
+
for (let d = depth, splitting = false; d > target; d--) if (splitting || $from.index(d) > 0) {
|
|
45
|
+
splitting = true;
|
|
46
|
+
before = Fragment.from($from.node(d).copy(before));
|
|
47
|
+
openStart++;
|
|
48
|
+
} else start--;
|
|
49
|
+
let after = Fragment.empty, openEnd = 0;
|
|
50
|
+
for (let d = depth, splitting = false; d > target; d--) if (splitting || $to.afterUnsafe(d + 1) < $to.end(d)) {
|
|
51
|
+
splitting = true;
|
|
52
|
+
after = Fragment.from($to.node(d).copy(after));
|
|
53
|
+
openEnd++;
|
|
54
|
+
} else end++;
|
|
55
|
+
tr.stepUnsafe(new ReplaceAroundStep(start, end, gapStart, gapEnd, new Slice(before.append(after), openStart, openEnd), before.size - openStart, true));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Try to find a valid way to wrap the content in the given range in a
|
|
59
|
+
* node of the given type. May introduce extra nodes around and inside
|
|
60
|
+
* the wrapper node, if necessary. Returns `None` if no valid wrapping
|
|
61
|
+
* could be found. When `innerRange` is given, that range's content is
|
|
62
|
+
* used as the content to fit into the wrapping, instead of the content
|
|
63
|
+
* of `range`.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
* @category queries
|
|
67
|
+
*/
|
|
68
|
+
const findWrapping = (range, nodeType, attrs = null, innerRange = range) => Option.fromNullishOr(findWrappingOrNull(range, nodeType, attrs, innerRange));
|
|
69
|
+
/** @internal */
|
|
70
|
+
function findWrappingOrNull(range, nodeType, attrs = null, innerRange = range) {
|
|
71
|
+
const around = findWrappingOutside(range, nodeType);
|
|
72
|
+
const inner = around && findWrappingInside(innerRange, nodeType);
|
|
73
|
+
if (!inner) return null;
|
|
74
|
+
return around.map(withAttrs).concat({
|
|
75
|
+
type: nodeType,
|
|
76
|
+
attrs
|
|
77
|
+
}).concat(inner.map(withAttrs));
|
|
78
|
+
}
|
|
79
|
+
const withAttrs = (type) => ({
|
|
80
|
+
type,
|
|
81
|
+
attrs: null
|
|
82
|
+
});
|
|
83
|
+
function findWrappingOutside(range, type) {
|
|
84
|
+
const { parent, startIndex, endIndex } = range;
|
|
85
|
+
const around = parent.contentMatchAtUnsafe(startIndex).findWrappingOrNull(type);
|
|
86
|
+
if (!around) return null;
|
|
87
|
+
const outer = around.length ? around[0] : type;
|
|
88
|
+
return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null;
|
|
89
|
+
}
|
|
90
|
+
function findWrappingInside(range, type) {
|
|
91
|
+
const { endIndex, parent, startIndex } = range;
|
|
92
|
+
const inner = parent.content.content[startIndex];
|
|
93
|
+
const inside = type.contentMatch.findWrappingOrNull(inner.type);
|
|
94
|
+
if (!inside) return null;
|
|
95
|
+
let innerMatch = (inside.length ? inside[inside.length - 1] : type).contentMatch;
|
|
96
|
+
for (let i = startIndex; innerMatch && i < endIndex; i++) innerMatch = innerMatch.matchTypeOrNull(parent.content.content[i].type);
|
|
97
|
+
if (!innerMatch || !innerMatch.validEnd) return null;
|
|
98
|
+
return inside;
|
|
99
|
+
}
|
|
100
|
+
/** @internal */
|
|
101
|
+
function wrap(tr, range, wrappers) {
|
|
102
|
+
let content = Fragment.empty;
|
|
103
|
+
for (let i = wrappers.length - 1; i >= 0; i--) {
|
|
104
|
+
if (content.size) {
|
|
105
|
+
const match = wrappers[i].type.contentMatch.matchFragmentOrNull(content);
|
|
106
|
+
if (!match || !match.validEnd) throw new TransformError({ message: "Wrapper type given to Transform.wrap does not form valid content of its parent wrapper" });
|
|
107
|
+
}
|
|
108
|
+
content = Fragment.from(wrappers[i].type.createUnsafe(wrappers[i].attrs, content));
|
|
109
|
+
}
|
|
110
|
+
const start = range.start, end = range.end;
|
|
111
|
+
tr.stepUnsafe(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true));
|
|
112
|
+
}
|
|
113
|
+
/** @internal */
|
|
114
|
+
function setBlockType(tr, from, to, type, attrs) {
|
|
115
|
+
if (!type.isTextblock) throw new TransformError({ message: "Type given to setBlockType should be a textblock" });
|
|
116
|
+
const mapFrom = tr.steps.length;
|
|
117
|
+
const linebreak = Option.getOrNull(type.schema.linebreakReplacement);
|
|
118
|
+
tr.doc.nodesBetween(from, to, (node, pos) => {
|
|
119
|
+
const attrsHere = typeof attrs === "function" ? attrs(node) : attrs;
|
|
120
|
+
if (node.isTextblock && !node.hasMarkup(type, attrsHere) && canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
|
|
121
|
+
let convertNewlines = null;
|
|
122
|
+
if (linebreak) {
|
|
123
|
+
const pre = type.whitespace === "pre", supportLinebreak = !!type.contentMatch.matchTypeOrNull(linebreak);
|
|
124
|
+
if (pre && !supportLinebreak) convertNewlines = false;
|
|
125
|
+
else if (!pre && supportLinebreak) convertNewlines = true;
|
|
126
|
+
}
|
|
127
|
+
if (convertNewlines === false) replaceLinebreaks(tr, node, pos, mapFrom);
|
|
128
|
+
clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, void 0, convertNewlines === null);
|
|
129
|
+
const mapping = tr.mapping.slice(mapFrom);
|
|
130
|
+
const startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1);
|
|
131
|
+
tr.stepUnsafe(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1, new Slice(Fragment.from(type.createUnsafe(attrsHere, null, node.marks)), 0, 0), 1, true));
|
|
132
|
+
if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function replaceNewlines(tr, node, pos, mapFrom) {
|
|
138
|
+
const replacement = node.type.schema.linebreakReplacement;
|
|
139
|
+
if (Option.isNone(replacement)) return;
|
|
140
|
+
const linebreak = replacement.value;
|
|
141
|
+
node.forEach((child, offset) => {
|
|
142
|
+
if (child.isText) {
|
|
143
|
+
let m;
|
|
144
|
+
const newline = /\r?\n|\r/g;
|
|
145
|
+
while (m = newline.exec(child.text)) {
|
|
146
|
+
const start = tr.mapping.slice(mapFrom).map(pos + 1 + offset + m.index);
|
|
147
|
+
tr.replaceWithUnsafe(start, start + 1, linebreak.createUnsafe());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function replaceLinebreaks(tr, node, pos, mapFrom) {
|
|
153
|
+
const linebreak = Option.getOrNull(node.type.schema.linebreakReplacement);
|
|
154
|
+
node.forEach((child, offset) => {
|
|
155
|
+
if (child.type === linebreak) {
|
|
156
|
+
const start = tr.mapping.slice(mapFrom).map(pos + 1 + offset);
|
|
157
|
+
tr.replaceWithUnsafe(start, start + 1, node.type.schema.textUnsafe("\n"));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function canChangeType(doc, pos, type) {
|
|
162
|
+
const $pos = doc.resolveUnsafe(pos), index = $pos.index();
|
|
163
|
+
return $pos.parent.canReplaceWith(index, index + 1, type);
|
|
164
|
+
}
|
|
165
|
+
/** @internal */
|
|
166
|
+
function setNodeMarkup(tr, pos, type, attrs, marks) {
|
|
167
|
+
const node = tr.doc.nodeAtOrNull(pos);
|
|
168
|
+
if (!node) throw new TransformError({ message: "No node at given position" });
|
|
169
|
+
if (!type) type = node.type;
|
|
170
|
+
const newNode = type.createUnsafe(attrs, null, marks || node.marks);
|
|
171
|
+
if (node.isLeaf) {
|
|
172
|
+
tr.replaceWithUnsafe(pos, pos + node.nodeSize, newNode);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (!type.validContent(node.content)) throw new TransformError({ message: "Invalid content for node type " + type.name });
|
|
176
|
+
tr.stepUnsafe(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1, new Slice(Fragment.from(newNode), 0, 0), 1, true));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check whether splitting at the given position is allowed. Throws a
|
|
180
|
+
* `PositionError` when `pos` is outside of the document.
|
|
181
|
+
*
|
|
182
|
+
* @since 0.1.0
|
|
183
|
+
* @category queries
|
|
184
|
+
*/
|
|
185
|
+
function canSplit(doc, pos, depth = 1, typesAfter) {
|
|
186
|
+
const types = typesAfter;
|
|
187
|
+
const $pos = doc.resolveUnsafe(pos), base = $pos.depth - depth;
|
|
188
|
+
const innerType = types && types[types.length - 1] || $pos.parent;
|
|
189
|
+
if (base < 0 || $pos.parent.type.spec.isolating || !$pos.parent.canReplace($pos.index(), $pos.parent.childCount) || !innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount))) return false;
|
|
190
|
+
for (let d = $pos.depth - 1, i = depth - 2; d > base; d--, i--) {
|
|
191
|
+
const node = $pos.node(d), index = $pos.index(d);
|
|
192
|
+
if (node.type.spec.isolating) return false;
|
|
193
|
+
let rest = node.content.cutByIndex(index, node.childCount);
|
|
194
|
+
const overrideChild = types && types[i + 1];
|
|
195
|
+
if (overrideChild) rest = rest.replaceChild(0, overrideChild.type.createUnsafe(overrideChild.attrs));
|
|
196
|
+
const after = types && types[i] || node;
|
|
197
|
+
if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest)) return false;
|
|
198
|
+
}
|
|
199
|
+
const index = $pos.indexAfter(base);
|
|
200
|
+
const baseType = types && types[0];
|
|
201
|
+
return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type);
|
|
202
|
+
}
|
|
203
|
+
/** @internal */
|
|
204
|
+
function split(tr, pos, depth = 1, typesAfter) {
|
|
205
|
+
const $pos = tr.doc.resolveUnsafe(pos);
|
|
206
|
+
let before = Fragment.empty, after = Fragment.empty;
|
|
207
|
+
for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
|
|
208
|
+
before = Fragment.from($pos.node(d).copy(before));
|
|
209
|
+
const typeAfter = typesAfter && typesAfter[i];
|
|
210
|
+
after = Fragment.from(typeAfter ? typeAfter.type.createUnsafe(typeAfter.attrs, after) : $pos.node(d).copy(after));
|
|
211
|
+
}
|
|
212
|
+
tr.stepUnsafe(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Test whether the blocks before and after a given position can be
|
|
216
|
+
* joined. Throws a `PositionError` when `pos` is outside of the
|
|
217
|
+
* document.
|
|
218
|
+
*
|
|
219
|
+
* @since 0.1.0
|
|
220
|
+
* @category queries
|
|
221
|
+
*/
|
|
222
|
+
function canJoin(doc, pos) {
|
|
223
|
+
const $pos = doc.resolveUnsafe(pos), index = $pos.index();
|
|
224
|
+
return joinable($pos.nodeBeforeOrNull, $pos.nodeAfterOrNull) && $pos.parent.canReplace(index, index + 1);
|
|
225
|
+
}
|
|
226
|
+
function canAppendWithSubstitutedLinebreaks(a, b) {
|
|
227
|
+
if (!b.content.size) a.type.compatibleContent(b.type);
|
|
228
|
+
let match = a.contentMatchAtUnsafe(a.childCount);
|
|
229
|
+
const linebreakReplacement = Option.getOrNull(a.type.schema.linebreakReplacement);
|
|
230
|
+
for (let i = 0; i < b.childCount; i++) {
|
|
231
|
+
const child = b.content.content[i];
|
|
232
|
+
const type = child.type === linebreakReplacement ? a.type.schema.nodes.text : child.type;
|
|
233
|
+
match = match.matchTypeOrNull(type);
|
|
234
|
+
if (!match) return false;
|
|
235
|
+
if (!a.type.allowsMarks(child.marks)) return false;
|
|
236
|
+
}
|
|
237
|
+
return match.validEnd;
|
|
238
|
+
}
|
|
239
|
+
function joinable(a, b) {
|
|
240
|
+
return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebreaks(a, b));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Find an ancestor of the given position that can be joined to the
|
|
244
|
+
* block before (or after if `dir` is positive). Returns the joinable
|
|
245
|
+
* point, if any.
|
|
246
|
+
*
|
|
247
|
+
* @since 0.1.0
|
|
248
|
+
* @category queries
|
|
249
|
+
*/
|
|
250
|
+
function joinPoint(doc, pos, dir = -1) {
|
|
251
|
+
const $pos = doc.resolveUnsafe(pos);
|
|
252
|
+
for (let d = $pos.depth;; d--) {
|
|
253
|
+
let before, after;
|
|
254
|
+
let index = $pos.index(d);
|
|
255
|
+
if (d === $pos.depth) {
|
|
256
|
+
before = $pos.nodeBeforeOrNull;
|
|
257
|
+
after = $pos.nodeAfterOrNull;
|
|
258
|
+
} else if (dir > 0) {
|
|
259
|
+
before = $pos.node(d + 1);
|
|
260
|
+
index++;
|
|
261
|
+
after = $pos.node(d).content.content[index];
|
|
262
|
+
} else {
|
|
263
|
+
before = $pos.node(d).content.content[index - 1];
|
|
264
|
+
after = $pos.node(d + 1);
|
|
265
|
+
}
|
|
266
|
+
if (before && !before.isTextblock && joinable(before, after) && $pos.node(d).canReplace(index, index + 1)) return Option.some(pos);
|
|
267
|
+
if (d === 0) break;
|
|
268
|
+
pos = dir < 0 ? $pos.beforeUnsafe(d) : $pos.afterUnsafe(d);
|
|
269
|
+
}
|
|
270
|
+
return Option.none();
|
|
271
|
+
}
|
|
272
|
+
/** @internal */
|
|
273
|
+
function join(tr, pos, depth) {
|
|
274
|
+
let convertNewlines = null;
|
|
275
|
+
const linebreakReplacement = Option.getOrNull(tr.doc.type.schema.linebreakReplacement);
|
|
276
|
+
const $before = tr.doc.resolveUnsafe(pos - depth), beforeType = $before.node().type;
|
|
277
|
+
if (linebreakReplacement && beforeType.inlineContent) {
|
|
278
|
+
const pre = beforeType.whitespace === "pre";
|
|
279
|
+
const supportLinebreak = !!beforeType.contentMatch.matchTypeOrNull(linebreakReplacement);
|
|
280
|
+
if (pre && !supportLinebreak) convertNewlines = false;
|
|
281
|
+
else if (!pre && supportLinebreak) convertNewlines = true;
|
|
282
|
+
}
|
|
283
|
+
const mapFrom = tr.steps.length;
|
|
284
|
+
if (convertNewlines === false) {
|
|
285
|
+
const $after = tr.doc.resolveUnsafe(pos + depth);
|
|
286
|
+
replaceLinebreaks(tr, $after.node(), $after.beforeUnsafe(), mapFrom);
|
|
287
|
+
}
|
|
288
|
+
if (beforeType.inlineContent) clearIncompatible(tr, pos + depth - 1, beforeType, $before.node().contentMatchAtUnsafe($before.index()), convertNewlines == null);
|
|
289
|
+
const mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth);
|
|
290
|
+
tr.stepUnsafe(new ReplaceStep(start, mapping.map(pos + depth, -1), Slice.empty, true));
|
|
291
|
+
if (convertNewlines === true) {
|
|
292
|
+
const $full = tr.doc.resolveUnsafe(start);
|
|
293
|
+
replaceNewlines(tr, $full.node(), $full.beforeUnsafe(), tr.steps.length);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Try to find a point where a node of the given type can be inserted
|
|
298
|
+
* near `pos`, by searching up the node hierarchy when `pos` itself
|
|
299
|
+
* isn't a valid place but is at the start or end of a node. Returns
|
|
300
|
+
* `None` if no position was found.
|
|
301
|
+
*
|
|
302
|
+
* @since 0.1.0
|
|
303
|
+
* @category queries
|
|
304
|
+
*/
|
|
305
|
+
const insertPoint = (doc, pos, nodeType) => Option.fromNullishOr(insertPointOrNull(doc, pos, nodeType));
|
|
306
|
+
/** @internal */
|
|
307
|
+
function insertPointOrNull(doc, pos, nodeType) {
|
|
308
|
+
const $pos = doc.resolveUnsafe(pos);
|
|
309
|
+
if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType)) return pos;
|
|
310
|
+
if ($pos.parentOffset === 0) for (let d = $pos.depth - 1; d >= 0; d--) {
|
|
311
|
+
const index = $pos.index(d);
|
|
312
|
+
if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.beforeUnsafe(d + 1);
|
|
313
|
+
if (index > 0) return null;
|
|
314
|
+
}
|
|
315
|
+
if ($pos.parentOffset === $pos.parent.content.size) for (let d = $pos.depth - 1; d >= 0; d--) {
|
|
316
|
+
const index = $pos.indexAfter(d);
|
|
317
|
+
if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.afterUnsafe(d + 1);
|
|
318
|
+
if (index < $pos.node(d).childCount) return null;
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Finds a position at or around the given position where the given
|
|
324
|
+
* slice can be inserted. Will look at parent nodes' nearest boundary
|
|
325
|
+
* and try there, even if the original position wasn't directly at the
|
|
326
|
+
* start or end of that node. Returns `None` when no position was found.
|
|
327
|
+
*
|
|
328
|
+
* @since 0.1.0
|
|
329
|
+
* @category queries
|
|
330
|
+
*/
|
|
331
|
+
function dropPoint(doc, pos, slice) {
|
|
332
|
+
const $pos = doc.resolveUnsafe(pos);
|
|
333
|
+
if (!slice.content.size) return Option.some(pos);
|
|
334
|
+
let content = slice.content;
|
|
335
|
+
for (let i = 0; i < slice.openStart; i++) content = content.content[0].content;
|
|
336
|
+
for (let pass = 1; pass <= (slice.openStart === 0 && slice.size ? 2 : 1); pass++) for (let d = $pos.depth; d >= 0; d--) {
|
|
337
|
+
const bias = d === $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1;
|
|
338
|
+
const insertPos = $pos.index(d) + (bias > 0 ? 1 : 0);
|
|
339
|
+
const parent = $pos.node(d);
|
|
340
|
+
let fits = false;
|
|
341
|
+
if (pass === 1) fits = parent.canReplace(insertPos, insertPos, content);
|
|
342
|
+
else {
|
|
343
|
+
const wrapping = parent.contentMatchAtUnsafe(insertPos).findWrappingOrNull(content.content[0].type);
|
|
344
|
+
fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0]);
|
|
345
|
+
}
|
|
346
|
+
if (fits) return Option.some(bias === 0 ? $pos.pos : bias < 0 ? $pos.beforeUnsafe(d + 1) : $pos.afterUnsafe(d + 1));
|
|
347
|
+
}
|
|
348
|
+
return Option.none();
|
|
349
|
+
}
|
|
350
|
+
export { canJoin, canSplit, dropPoint, findWrapping, findWrappingOrNull, insertPoint, insertPointOrNull, join, joinPoint, lift, liftTarget, liftTargetOrNull, setBlockType, setNodeMarkup, split, wrap };
|