@depup/prosemirror-state 1.4.4-depup.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/.tern-project +8 -0
- package/CHANGELOG.md +296 -0
- package/CONTRIBUTING.md +104 -0
- package/LICENSE +19 -0
- package/README.md +33 -0
- package/changes.json +18 -0
- package/dist/index.cjs +884 -0
- package/dist/index.d.cts +709 -0
- package/dist/index.d.ts +709 -0
- package/dist/index.js +1002 -0
- package/package.json +67 -0
- package/src/README.md +42 -0
- package/src/index.ts +7 -0
- package/src/plugin.ts +142 -0
- package/src/selection.ts +462 -0
- package/src/state.ts +266 -0
- package/src/transaction.ts +215 -0
package/src/state.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import {Node, Mark, Schema} from "prosemirror-model"
|
|
2
|
+
|
|
3
|
+
import {Selection, TextSelection} from "./selection"
|
|
4
|
+
import {Transaction} from "./transaction"
|
|
5
|
+
import {Plugin, StateField} from "./plugin"
|
|
6
|
+
|
|
7
|
+
function bind<T extends Function>(f: T, self: any): T {
|
|
8
|
+
return !self || !f ? f : f.bind(self)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class FieldDesc<T> {
|
|
12
|
+
init: (config: EditorStateConfig, instance: EditorState) => T
|
|
13
|
+
apply: (tr: Transaction, value: T, oldState: EditorState, newState: EditorState) => T
|
|
14
|
+
|
|
15
|
+
constructor(readonly name: string, desc: StateField<any>, self?: any) {
|
|
16
|
+
this.init = bind(desc.init, self)
|
|
17
|
+
this.apply = bind(desc.apply, self)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const baseFields = [
|
|
22
|
+
new FieldDesc<Node>("doc", {
|
|
23
|
+
init(config) { return config.doc || config.schema!.topNodeType.createAndFill() },
|
|
24
|
+
apply(tr) { return tr.doc }
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
new FieldDesc<Selection>("selection", {
|
|
28
|
+
init(config, instance) { return config.selection || Selection.atStart(instance.doc) },
|
|
29
|
+
apply(tr) { return tr.selection }
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
new FieldDesc<readonly Mark[] | null>("storedMarks", {
|
|
33
|
+
init(config) { return config.storedMarks || null },
|
|
34
|
+
apply(tr, _marks, _old, state) { return (state.selection as TextSelection).$cursor ? tr.storedMarks : null }
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
new FieldDesc<number>("scrollToSelection", {
|
|
38
|
+
init() { return 0 },
|
|
39
|
+
apply(tr, prev) { return tr.scrolledIntoView ? prev + 1 : prev }
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
// Object wrapping the part of a state object that stays the same
|
|
44
|
+
// across transactions. Stored in the state's `config` property.
|
|
45
|
+
class Configuration {
|
|
46
|
+
fields: FieldDesc<any>[]
|
|
47
|
+
plugins: Plugin[] = []
|
|
48
|
+
pluginsByKey: {[key: string]: Plugin} = Object.create(null)
|
|
49
|
+
|
|
50
|
+
constructor(readonly schema: Schema, plugins?: readonly Plugin[]) {
|
|
51
|
+
this.fields = baseFields.slice()
|
|
52
|
+
if (plugins) plugins.forEach(plugin => {
|
|
53
|
+
if (this.pluginsByKey[plugin.key])
|
|
54
|
+
throw new RangeError("Adding different instances of a keyed plugin (" + plugin.key + ")")
|
|
55
|
+
this.plugins.push(plugin)
|
|
56
|
+
this.pluginsByKey[plugin.key] = plugin
|
|
57
|
+
if (plugin.spec.state)
|
|
58
|
+
this.fields.push(new FieldDesc<any>(plugin.key, plugin.spec.state, plugin))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// The type of object passed to
|
|
64
|
+
/// [`EditorState.create`](#state.EditorState^create).
|
|
65
|
+
export interface EditorStateConfig {
|
|
66
|
+
/// The schema to use (only relevant if no `doc` is specified).
|
|
67
|
+
schema?: Schema
|
|
68
|
+
|
|
69
|
+
/// The starting document. Either this or `schema` _must_ be
|
|
70
|
+
/// provided.
|
|
71
|
+
doc?: Node
|
|
72
|
+
|
|
73
|
+
/// A valid selection in the document.
|
|
74
|
+
selection?: Selection
|
|
75
|
+
|
|
76
|
+
/// The initial set of [stored marks](#state.EditorState.storedMarks).
|
|
77
|
+
storedMarks?: readonly Mark[] | null
|
|
78
|
+
|
|
79
|
+
/// The plugins that should be active in this state.
|
|
80
|
+
plugins?: readonly Plugin[]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// The state of a ProseMirror editor is represented by an object of
|
|
84
|
+
/// this type. A state is a persistent data structure—it isn't
|
|
85
|
+
/// updated, but rather a new state value is computed from an old one
|
|
86
|
+
/// using the [`apply`](#state.EditorState.apply) method.
|
|
87
|
+
///
|
|
88
|
+
/// A state holds a number of built-in fields, and plugins can
|
|
89
|
+
/// [define](#state.PluginSpec.state) additional fields.
|
|
90
|
+
export class EditorState {
|
|
91
|
+
/// @internal
|
|
92
|
+
constructor(
|
|
93
|
+
/// @internal
|
|
94
|
+
readonly config: Configuration
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
/// The current document.
|
|
98
|
+
declare doc: Node
|
|
99
|
+
|
|
100
|
+
/// The selection.
|
|
101
|
+
declare selection: Selection
|
|
102
|
+
|
|
103
|
+
/// A set of marks to apply to the next input. Will be null when
|
|
104
|
+
/// no explicit marks have been set.
|
|
105
|
+
declare storedMarks: readonly Mark[] | null
|
|
106
|
+
|
|
107
|
+
/// The schema of the state's document.
|
|
108
|
+
get schema(): Schema {
|
|
109
|
+
return this.config.schema
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// The plugins that are active in this state.
|
|
113
|
+
get plugins(): readonly Plugin[] {
|
|
114
|
+
return this.config.plugins
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Apply the given transaction to produce a new state.
|
|
118
|
+
apply(tr: Transaction): EditorState {
|
|
119
|
+
return this.applyTransaction(tr).state
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// @internal
|
|
123
|
+
filterTransaction(tr: Transaction, ignore = -1) {
|
|
124
|
+
for (let i = 0; i < this.config.plugins.length; i++) if (i != ignore) {
|
|
125
|
+
let plugin = this.config.plugins[i]
|
|
126
|
+
if (plugin.spec.filterTransaction && !plugin.spec.filterTransaction.call(plugin, tr, this))
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Verbose variant of [`apply`](#state.EditorState.apply) that
|
|
133
|
+
/// returns the precise transactions that were applied (which might
|
|
134
|
+
/// be influenced by the [transaction
|
|
135
|
+
/// hooks](#state.PluginSpec.filterTransaction) of
|
|
136
|
+
/// plugins) along with the new state.
|
|
137
|
+
applyTransaction(rootTr: Transaction): {state: EditorState, transactions: readonly Transaction[]} {
|
|
138
|
+
if (!this.filterTransaction(rootTr)) return {state: this, transactions: []}
|
|
139
|
+
|
|
140
|
+
let trs = [rootTr], newState = this.applyInner(rootTr), seen = null
|
|
141
|
+
// This loop repeatedly gives plugins a chance to respond to
|
|
142
|
+
// transactions as new transactions are added, making sure to only
|
|
143
|
+
// pass the transactions the plugin did not see before.
|
|
144
|
+
for (;;) {
|
|
145
|
+
let haveNew = false
|
|
146
|
+
for (let i = 0; i < this.config.plugins.length; i++) {
|
|
147
|
+
let plugin = this.config.plugins[i]
|
|
148
|
+
if (plugin.spec.appendTransaction) {
|
|
149
|
+
let n = seen ? seen[i].n : 0, oldState = seen ? seen[i].state : this
|
|
150
|
+
let tr = n < trs.length &&
|
|
151
|
+
plugin.spec.appendTransaction.call(plugin, n ? trs.slice(n) : trs, oldState, newState)
|
|
152
|
+
if (tr && newState.filterTransaction(tr, i)) {
|
|
153
|
+
tr.setMeta("appendedTransaction", rootTr)
|
|
154
|
+
if (!seen) {
|
|
155
|
+
seen = []
|
|
156
|
+
for (let j = 0; j < this.config.plugins.length; j++)
|
|
157
|
+
seen.push(j < i ? {state: newState, n: trs.length} : {state: this, n: 0})
|
|
158
|
+
}
|
|
159
|
+
trs.push(tr)
|
|
160
|
+
newState = newState.applyInner(tr)
|
|
161
|
+
haveNew = true
|
|
162
|
+
}
|
|
163
|
+
if (seen) seen[i] = {state: newState, n: trs.length}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!haveNew) return {state: newState, transactions: trs}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// @internal
|
|
171
|
+
applyInner(tr: Transaction) {
|
|
172
|
+
if (!tr.before.eq(this.doc)) throw new RangeError("Applying a mismatched transaction")
|
|
173
|
+
let newInstance = new EditorState(this.config), fields = this.config.fields
|
|
174
|
+
for (let i = 0; i < fields.length; i++) {
|
|
175
|
+
let field = fields[i]
|
|
176
|
+
;(newInstance as any)[field.name] = field.apply(tr, (this as any)[field.name], this, newInstance)
|
|
177
|
+
}
|
|
178
|
+
return newInstance
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Accessor that constructs and returns a new [transaction](#state.Transaction) from this state.
|
|
182
|
+
get tr(): Transaction { return new Transaction(this) }
|
|
183
|
+
|
|
184
|
+
/// Create a new state.
|
|
185
|
+
static create(config: EditorStateConfig) {
|
|
186
|
+
let $config = new Configuration(config.doc ? config.doc.type.schema : config.schema!, config.plugins)
|
|
187
|
+
let instance = new EditorState($config)
|
|
188
|
+
for (let i = 0; i < $config.fields.length; i++)
|
|
189
|
+
(instance as any)[$config.fields[i].name] = $config.fields[i].init(config, instance)
|
|
190
|
+
return instance
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Create a new state based on this one, but with an adjusted set
|
|
194
|
+
/// of active plugins. State fields that exist in both sets of
|
|
195
|
+
/// plugins are kept unchanged. Those that no longer exist are
|
|
196
|
+
/// dropped, and those that are new are initialized using their
|
|
197
|
+
/// [`init`](#state.StateField.init) method, passing in the new
|
|
198
|
+
/// configuration object..
|
|
199
|
+
reconfigure(config: {
|
|
200
|
+
/// New set of active plugins.
|
|
201
|
+
plugins?: readonly Plugin[]
|
|
202
|
+
}) {
|
|
203
|
+
let $config = new Configuration(this.schema, config.plugins)
|
|
204
|
+
let fields = $config.fields, instance = new EditorState($config)
|
|
205
|
+
for (let i = 0; i < fields.length; i++) {
|
|
206
|
+
let name = fields[i].name
|
|
207
|
+
;(instance as any)[name] = this.hasOwnProperty(name) ? (this as any)[name] : fields[i].init(config, instance)
|
|
208
|
+
}
|
|
209
|
+
return instance
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Serialize this state to JSON. If you want to serialize the state
|
|
213
|
+
/// of plugins, pass an object mapping property names to use in the
|
|
214
|
+
/// resulting JSON object to plugin objects. The argument may also be
|
|
215
|
+
/// a string or number, in which case it is ignored, to support the
|
|
216
|
+
/// way `JSON.stringify` calls `toString` methods.
|
|
217
|
+
toJSON(pluginFields?: {[propName: string]: Plugin}): any {
|
|
218
|
+
let result: any = {doc: this.doc.toJSON(), selection: this.selection.toJSON()}
|
|
219
|
+
if (this.storedMarks) result.storedMarks = this.storedMarks.map(m => m.toJSON())
|
|
220
|
+
if (pluginFields && typeof pluginFields == 'object') for (let prop in pluginFields) {
|
|
221
|
+
if (prop == "doc" || prop == "selection")
|
|
222
|
+
throw new RangeError("The JSON fields `doc` and `selection` are reserved")
|
|
223
|
+
let plugin = pluginFields[prop], state = plugin.spec.state
|
|
224
|
+
if (state && state.toJSON) result[prop] = state.toJSON.call(plugin, (this as any)[plugin.key])
|
|
225
|
+
}
|
|
226
|
+
return result
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Deserialize a JSON representation of a state. `config` should
|
|
230
|
+
/// have at least a `schema` field, and should contain array of
|
|
231
|
+
/// plugins to initialize the state with. `pluginFields` can be used
|
|
232
|
+
/// to deserialize the state of plugins, by associating plugin
|
|
233
|
+
/// instances with the property names they use in the JSON object.
|
|
234
|
+
static fromJSON(config: {
|
|
235
|
+
/// The schema to use.
|
|
236
|
+
schema: Schema
|
|
237
|
+
/// The set of active plugins.
|
|
238
|
+
plugins?: readonly Plugin[]
|
|
239
|
+
}, json: any, pluginFields?: {[propName: string]: Plugin}) {
|
|
240
|
+
if (!json) throw new RangeError("Invalid input for EditorState.fromJSON")
|
|
241
|
+
if (!config.schema) throw new RangeError("Required config field 'schema' missing")
|
|
242
|
+
let $config = new Configuration(config.schema, config.plugins)
|
|
243
|
+
let instance = new EditorState($config)
|
|
244
|
+
$config.fields.forEach(field => {
|
|
245
|
+
if (field.name == "doc") {
|
|
246
|
+
instance.doc = Node.fromJSON(config.schema, json.doc)
|
|
247
|
+
} else if (field.name == "selection") {
|
|
248
|
+
instance.selection = Selection.fromJSON(instance.doc, json.selection)
|
|
249
|
+
} else if (field.name == "storedMarks") {
|
|
250
|
+
if (json.storedMarks) instance.storedMarks = json.storedMarks.map(config.schema.markFromJSON)
|
|
251
|
+
} else {
|
|
252
|
+
if (pluginFields) for (let prop in pluginFields) {
|
|
253
|
+
let plugin = pluginFields[prop], state = plugin.spec.state
|
|
254
|
+
if (plugin.key == field.name && state && state.fromJSON &&
|
|
255
|
+
Object.prototype.hasOwnProperty.call(json, prop)) {
|
|
256
|
+
// This field belongs to a plugin mapped to a JSON field, read it from there.
|
|
257
|
+
;(instance as any)[field.name] = state.fromJSON.call(plugin, config, json[prop], instance)
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
;(instance as any)[field.name] = field.init(config, instance)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
return instance
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {Transform, Step} from "prosemirror-transform"
|
|
2
|
+
import {Mark, MarkType, Node, Slice} from "prosemirror-model"
|
|
3
|
+
import {type EditorView} from "prosemirror-view"
|
|
4
|
+
import {Selection} from "./selection"
|
|
5
|
+
import {Plugin, PluginKey} from "./plugin"
|
|
6
|
+
import {EditorState} from "./state"
|
|
7
|
+
|
|
8
|
+
/// Commands are functions that take a state and a an optional
|
|
9
|
+
/// transaction dispatch function and...
|
|
10
|
+
///
|
|
11
|
+
/// - determine whether they apply to this state
|
|
12
|
+
/// - if not, return false
|
|
13
|
+
/// - if `dispatch` was passed, perform their effect, possibly by
|
|
14
|
+
/// passing a transaction to `dispatch`
|
|
15
|
+
/// - return true
|
|
16
|
+
///
|
|
17
|
+
/// In some cases, the editor view is passed as a third argument.
|
|
18
|
+
export type Command = (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => boolean
|
|
19
|
+
|
|
20
|
+
const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4
|
|
21
|
+
|
|
22
|
+
/// An editor state transaction, which can be applied to a state to
|
|
23
|
+
/// create an updated state. Use
|
|
24
|
+
/// [`EditorState.tr`](#state.EditorState.tr) to create an instance.
|
|
25
|
+
///
|
|
26
|
+
/// Transactions track changes to the document (they are a subclass of
|
|
27
|
+
/// [`Transform`](#transform.Transform)), but also other state changes,
|
|
28
|
+
/// like selection updates and adjustments of the set of [stored
|
|
29
|
+
/// marks](#state.EditorState.storedMarks). In addition, you can store
|
|
30
|
+
/// metadata properties in a transaction, which are extra pieces of
|
|
31
|
+
/// information that client code or plugins can use to describe what a
|
|
32
|
+
/// transaction represents, so that they can update their [own
|
|
33
|
+
/// state](#state.StateField) accordingly.
|
|
34
|
+
///
|
|
35
|
+
/// The [editor view](#view.EditorView) uses a few metadata
|
|
36
|
+
/// properties: it will attach a property `"pointer"` with the value
|
|
37
|
+
/// `true` to selection transactions directly caused by mouse or touch
|
|
38
|
+
/// input, a `"composition"` property holding an ID identifying the
|
|
39
|
+
/// composition that caused it to transactions caused by composed DOM
|
|
40
|
+
/// input, and a `"uiEvent"` property of that may be `"paste"`,
|
|
41
|
+
/// `"cut"`, or `"drop"`.
|
|
42
|
+
export class Transaction extends Transform {
|
|
43
|
+
/// The timestamp associated with this transaction, in the same
|
|
44
|
+
/// format as `Date.now()`.
|
|
45
|
+
time: number
|
|
46
|
+
|
|
47
|
+
private curSelection: Selection
|
|
48
|
+
// The step count for which the current selection is valid.
|
|
49
|
+
private curSelectionFor = 0
|
|
50
|
+
// Bitfield to track which aspects of the state were updated by
|
|
51
|
+
// this transaction.
|
|
52
|
+
private updated = 0
|
|
53
|
+
// Object used to store metadata properties for the transaction.
|
|
54
|
+
private meta: {[name: string]: any} = Object.create(null)
|
|
55
|
+
|
|
56
|
+
/// The stored marks set by this transaction, if any.
|
|
57
|
+
storedMarks: readonly Mark[] | null
|
|
58
|
+
|
|
59
|
+
/// @internal
|
|
60
|
+
constructor(state: EditorState) {
|
|
61
|
+
super(state.doc)
|
|
62
|
+
this.time = Date.now()
|
|
63
|
+
this.curSelection = state.selection
|
|
64
|
+
this.storedMarks = state.storedMarks
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// The transaction's current selection. This defaults to the editor
|
|
68
|
+
/// selection [mapped](#state.Selection.map) through the steps in the
|
|
69
|
+
/// transaction, but can be overwritten with
|
|
70
|
+
/// [`setSelection`](#state.Transaction.setSelection).
|
|
71
|
+
get selection(): Selection {
|
|
72
|
+
if (this.curSelectionFor < this.steps.length) {
|
|
73
|
+
this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionFor))
|
|
74
|
+
this.curSelectionFor = this.steps.length
|
|
75
|
+
}
|
|
76
|
+
return this.curSelection
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Update the transaction's current selection. Will determine the
|
|
80
|
+
/// selection that the editor gets when the transaction is applied.
|
|
81
|
+
setSelection(selection: Selection): this {
|
|
82
|
+
if (selection.$from.doc != this.doc)
|
|
83
|
+
throw new RangeError("Selection passed to setSelection must point at the current document")
|
|
84
|
+
this.curSelection = selection
|
|
85
|
+
this.curSelectionFor = this.steps.length
|
|
86
|
+
this.updated = (this.updated | UPDATED_SEL) & ~UPDATED_MARKS
|
|
87
|
+
this.storedMarks = null
|
|
88
|
+
return this
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Whether the selection was explicitly updated by this transaction.
|
|
92
|
+
get selectionSet() {
|
|
93
|
+
return (this.updated & UPDATED_SEL) > 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Set the current stored marks.
|
|
97
|
+
setStoredMarks(marks: readonly Mark[] | null): this {
|
|
98
|
+
this.storedMarks = marks
|
|
99
|
+
this.updated |= UPDATED_MARKS
|
|
100
|
+
return this
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Make sure the current stored marks or, if that is null, the marks
|
|
104
|
+
/// at the selection, match the given set of marks. Does nothing if
|
|
105
|
+
/// this is already the case.
|
|
106
|
+
ensureMarks(marks: readonly Mark[]): this {
|
|
107
|
+
if (!Mark.sameSet(this.storedMarks || this.selection.$from.marks(), marks))
|
|
108
|
+
this.setStoredMarks(marks)
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Add a mark to the set of stored marks.
|
|
113
|
+
addStoredMark(mark: Mark): this {
|
|
114
|
+
return this.ensureMarks(mark.addToSet(this.storedMarks || this.selection.$head.marks()))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Remove a mark or mark type from the set of stored marks.
|
|
118
|
+
removeStoredMark(mark: Mark | MarkType): this {
|
|
119
|
+
return this.ensureMarks(mark.removeFromSet(this.storedMarks || this.selection.$head.marks()))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Whether the stored marks were explicitly set for this transaction.
|
|
123
|
+
get storedMarksSet() {
|
|
124
|
+
return (this.updated & UPDATED_MARKS) > 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// @internal
|
|
128
|
+
addStep(step: Step, doc: Node) {
|
|
129
|
+
super.addStep(step, doc)
|
|
130
|
+
this.updated = this.updated & ~UPDATED_MARKS
|
|
131
|
+
this.storedMarks = null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Update the timestamp for the transaction.
|
|
135
|
+
setTime(time: number): this {
|
|
136
|
+
this.time = time
|
|
137
|
+
return this
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Replace the current selection with the given slice.
|
|
141
|
+
replaceSelection(slice: Slice): this {
|
|
142
|
+
this.selection.replace(this, slice)
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Replace the selection with the given node. When `inheritMarks` is
|
|
147
|
+
/// true and the content is inline, it inherits the marks from the
|
|
148
|
+
/// place where it is inserted.
|
|
149
|
+
replaceSelectionWith(node: Node, inheritMarks = true): this {
|
|
150
|
+
let selection = this.selection
|
|
151
|
+
if (inheritMarks)
|
|
152
|
+
node = node.mark(this.storedMarks || (selection.empty ? selection.$from.marks() : (selection.$from.marksAcross(selection.$to) || Mark.none)))
|
|
153
|
+
selection.replaceWith(this, node)
|
|
154
|
+
return this
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Delete the selection.
|
|
158
|
+
deleteSelection(): this {
|
|
159
|
+
this.selection.replace(this)
|
|
160
|
+
return this
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Replace the given range, or the selection if no range is given,
|
|
164
|
+
/// with a text node containing the given string.
|
|
165
|
+
insertText(text: string, from?: number, to?: number): this {
|
|
166
|
+
let schema = this.doc.type.schema
|
|
167
|
+
if (from == null) {
|
|
168
|
+
if (!text) return this.deleteSelection()
|
|
169
|
+
return this.replaceSelectionWith(schema.text(text), true)
|
|
170
|
+
} else {
|
|
171
|
+
if (to == null) to = from
|
|
172
|
+
if (!text) return this.deleteRange(from, to)
|
|
173
|
+
let marks = this.storedMarks
|
|
174
|
+
if (!marks) {
|
|
175
|
+
let $from = this.doc.resolve(from)
|
|
176
|
+
marks = to == from ? $from.marks() : $from.marksAcross(this.doc.resolve(to))
|
|
177
|
+
}
|
|
178
|
+
this.replaceRangeWith(from, to, schema.text(text, marks))
|
|
179
|
+
if (!this.selection.empty && this.selection.to == from + text.length)
|
|
180
|
+
this.setSelection(Selection.near(this.selection.$to))
|
|
181
|
+
return this
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Store a metadata property in this transaction, keyed either by
|
|
186
|
+
/// name or by plugin.
|
|
187
|
+
setMeta(key: string | Plugin | PluginKey, value: any): this {
|
|
188
|
+
this.meta[typeof key == "string" ? key : key.key] = value
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Retrieve a metadata property for a given name or plugin.
|
|
193
|
+
getMeta(key: string | Plugin | PluginKey) {
|
|
194
|
+
return this.meta[typeof key == "string" ? key : key.key]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Returns true if this transaction doesn't contain any metadata,
|
|
198
|
+
/// and can thus safely be extended.
|
|
199
|
+
get isGeneric() {
|
|
200
|
+
for (let _ in this.meta) return false
|
|
201
|
+
return true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Indicate that the editor should scroll the selection into view
|
|
205
|
+
/// when updated to the state produced by this transaction.
|
|
206
|
+
scrollIntoView(): this {
|
|
207
|
+
this.updated |= UPDATED_SCROLL
|
|
208
|
+
return this
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// True when this transaction has had `scrollIntoView` called on it.
|
|
212
|
+
get scrolledIntoView() {
|
|
213
|
+
return (this.updated & UPDATED_SCROLL) > 0
|
|
214
|
+
}
|
|
215
|
+
}
|