@kerebron/editor 0.4.27 → 0.4.29
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/esm/CoreEditor.d.ts +40 -0
- package/esm/CoreEditor.d.ts.map +1 -0
- package/esm/CoreEditor.js +224 -0
- package/esm/CoreEditor.js.map +1 -0
- package/esm/DummyEditorView.d.ts +60 -0
- package/esm/DummyEditorView.d.ts.map +1 -0
- package/esm/DummyEditorView.js +243 -0
- package/esm/DummyEditorView.js.map +1 -0
- package/esm/Extension.d.ts +31 -0
- package/esm/Extension.d.ts.map +1 -0
- package/esm/Extension.js +35 -0
- package/esm/Extension.js.map +1 -0
- package/esm/ExtensionManager.d.ts +32 -0
- package/esm/ExtensionManager.d.ts.map +1 -0
- package/esm/ExtensionManager.js +223 -0
- package/esm/ExtensionManager.js.map +1 -0
- package/esm/Mark.d.ts +23 -0
- package/esm/Mark.d.ts.map +1 -0
- package/esm/Mark.js +28 -0
- package/esm/Mark.js.map +1 -0
- package/esm/Node.d.ts +32 -0
- package/esm/Node.d.ts.map +1 -0
- package/esm/Node.js +37 -0
- package/esm/Node.js.map +1 -0
- package/esm/commands/CommandManager.d.ts +24 -0
- package/esm/commands/CommandManager.d.ts.map +1 -0
- package/esm/commands/CommandManager.js +101 -0
- package/esm/commands/CommandManager.js.map +1 -0
- package/esm/commands/baseCommandFactories.d.ts +3 -0
- package/esm/commands/baseCommandFactories.d.ts.map +1 -0
- package/esm/commands/baseCommandFactories.js +862 -0
- package/esm/commands/baseCommandFactories.js.map +1 -0
- package/esm/commands/createChainableState.d.ts +3 -0
- package/esm/commands/createChainableState.d.ts.map +1 -0
- package/esm/commands/createChainableState.js +30 -0
- package/esm/commands/createChainableState.js.map +1 -0
- package/esm/commands/keyCommandFactories.d.ts +3 -0
- package/esm/commands/keyCommandFactories.d.ts.map +1 -0
- package/esm/commands/keyCommandFactories.js +11 -0
- package/esm/commands/keyCommandFactories.js.map +1 -0
- package/esm/commands/mod.d.ts +7 -0
- package/esm/commands/mod.d.ts.map +1 -0
- package/esm/commands/mod.js +82 -0
- package/esm/commands/mod.js.map +1 -0
- package/esm/commands/replaceCommandFactories.d.ts +3 -0
- package/esm/commands/replaceCommandFactories.d.ts.map +1 -0
- package/esm/commands/replaceCommandFactories.js +95 -0
- package/esm/commands/replaceCommandFactories.js.map +1 -0
- package/esm/commands/types.d.ts +22 -0
- package/esm/commands/types.d.ts.map +1 -0
- package/esm/commands/types.js +2 -0
- package/esm/commands/types.js.map +1 -0
- package/esm/mod.d.ts +9 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +9 -0
- package/esm/mod.js.map +1 -0
- package/esm/nodeToTreeString.d.ts +10 -0
- package/esm/nodeToTreeString.d.ts.map +1 -0
- package/esm/nodeToTreeString.js +75 -0
- package/esm/nodeToTreeString.js.map +1 -0
- package/esm/package.json +3 -0
- package/esm/plugins/TrackSelecionPlugin.d.ts +6 -0
- package/esm/plugins/TrackSelecionPlugin.d.ts.map +1 -0
- package/esm/plugins/TrackSelecionPlugin.js +25 -0
- package/esm/plugins/TrackSelecionPlugin.js.map +1 -0
- package/esm/plugins/input-rules/InputRulesPlugin.d.ts +25 -0
- package/esm/plugins/input-rules/InputRulesPlugin.d.ts.map +1 -0
- package/esm/plugins/input-rules/InputRulesPlugin.js +169 -0
- package/esm/plugins/input-rules/InputRulesPlugin.js.map +1 -0
- package/esm/plugins/input-rules/mod.d.ts +3 -0
- package/esm/plugins/input-rules/mod.d.ts.map +1 -0
- package/esm/plugins/input-rules/mod.js +3 -0
- package/esm/plugins/input-rules/mod.js.map +1 -0
- package/esm/plugins/input-rules/rulebuilders.d.ts +6 -0
- package/esm/plugins/input-rules/rulebuilders.d.ts.map +1 -0
- package/esm/plugins/input-rules/rulebuilders.js +65 -0
- package/esm/plugins/input-rules/rulebuilders.js.map +1 -0
- package/esm/plugins/keymap/keymap.d.ts +12 -0
- package/esm/plugins/keymap/keymap.d.ts.map +1 -0
- package/esm/plugins/keymap/keymap.js +126 -0
- package/esm/plugins/keymap/keymap.js.map +1 -0
- package/esm/plugins/keymap/mod.d.ts +2 -0
- package/esm/plugins/keymap/mod.d.ts.map +1 -0
- package/esm/plugins/keymap/mod.js +2 -0
- package/esm/plugins/keymap/mod.js.map +1 -0
- package/esm/plugins/keymap/w3c-keyname.d.ts +4 -0
- package/esm/plugins/keymap/w3c-keyname.d.ts.map +1 -0
- package/esm/plugins/keymap/w3c-keyname.js +125 -0
- package/esm/plugins/keymap/w3c-keyname.js.map +1 -0
- package/esm/search/mod.d.ts +3 -0
- package/esm/search/mod.d.ts.map +1 -0
- package/esm/search/mod.js +3 -0
- package/esm/search/mod.js.map +1 -0
- package/esm/search/query.d.ts +45 -0
- package/esm/search/query.d.ts.map +1 -0
- package/esm/search/query.js +335 -0
- package/esm/search/query.js.map +1 -0
- package/esm/search/search.d.ts +32 -0
- package/esm/search/search.d.ts.map +1 -0
- package/esm/search/search.js +200 -0
- package/esm/search/search.js.map +1 -0
- package/esm/types.d.ts +59 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +2 -0
- package/esm/types.js.map +1 -0
- package/esm/ui.d.ts +15 -0
- package/esm/ui.d.ts.map +1 -0
- package/esm/ui.js +17 -0
- package/esm/ui.js.map +1 -0
- package/esm/utilities/SmartOutput.d.ts +41 -0
- package/esm/utilities/SmartOutput.d.ts.map +1 -0
- package/esm/utilities/SmartOutput.js +202 -0
- package/esm/utilities/SmartOutput.js.map +1 -0
- package/esm/utilities/createNodeFromContent.d.ts +9 -0
- package/esm/utilities/createNodeFromContent.d.ts.map +1 -0
- package/esm/utilities/createNodeFromContent.js +33 -0
- package/esm/utilities/createNodeFromContent.js.map +1 -0
- package/esm/utilities/getHtmlAttributes.d.ts +9 -0
- package/esm/utilities/getHtmlAttributes.d.ts.map +1 -0
- package/esm/utilities/getHtmlAttributes.js +48 -0
- package/esm/utilities/getHtmlAttributes.js.map +1 -0
- package/esm/utilities/getShadowRoot.d.ts +2 -0
- package/esm/utilities/getShadowRoot.d.ts.map +1 -0
- package/esm/utilities/getShadowRoot.js +17 -0
- package/esm/utilities/getShadowRoot.js.map +1 -0
- package/esm/utilities/mod.d.ts +6 -0
- package/esm/utilities/mod.d.ts.map +1 -0
- package/esm/utilities/mod.js +6 -0
- package/esm/utilities/mod.js.map +1 -0
- package/esm/utilities/toRawTextResult.d.ts +3 -0
- package/esm/utilities/toRawTextResult.d.ts.map +1 -0
- package/esm/utilities/toRawTextResult.js +22 -0
- package/esm/utilities/toRawTextResult.js.map +1 -0
- package/package.json +5 -2
- package/src/CoreEditor.ts +277 -0
- package/src/DummyEditorView.ts +403 -0
- package/src/Extension.ts +63 -0
- package/src/ExtensionManager.ts +328 -0
- package/src/Mark.ts +47 -0
- package/src/Node.ts +66 -0
- package/src/commands/CommandManager.ts +145 -0
- package/src/commands/baseCommandFactories.ts +1103 -0
- package/src/commands/createChainableState.ts +36 -0
- package/src/commands/keyCommandFactories.ts +26 -0
- package/src/commands/mod.ts +104 -0
- package/src/commands/replaceCommandFactories.ts +129 -0
- package/src/commands/types.ts +30 -0
- package/src/mod.ts +8 -0
- package/src/nodeToTreeString.ts +100 -0
- package/src/plugins/TrackSelecionPlugin.ts +27 -0
- package/src/plugins/input-rules/InputRulesPlugin.ts +242 -0
- package/src/plugins/input-rules/mod.ts +2 -0
- package/src/plugins/input-rules/rulebuilders.ts +88 -0
- package/src/plugins/keymap/keymap.ts +117 -0
- package/src/plugins/keymap/mod.ts +1 -0
- package/src/plugins/keymap/w3c-keyname.ts +123 -0
- package/src/search/mod.ts +2 -0
- package/src/search/query.ts +412 -0
- package/src/search/search.ts +284 -0
- package/src/types.ts +71 -0
- package/src/ui.ts +35 -0
- package/src/utilities/SmartOutput.ts +284 -0
- package/src/utilities/createNodeFromContent.ts +66 -0
- package/src/utilities/getHtmlAttributes.ts +68 -0
- package/src/utilities/getShadowRoot.ts +18 -0
- package/src/utilities/mod.ts +5 -0
- package/src/utilities/toRawTextResult.ts +27 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { EditorView } from 'prosemirror-view';
|
|
2
|
+
import { Node as ProseMirrorNode, Schema } from 'prosemirror-model';
|
|
3
|
+
|
|
4
|
+
import { ExtensionManager } from './ExtensionManager.js';
|
|
5
|
+
import type { EditorConfig, JSONContent } from './types.js';
|
|
6
|
+
import { EditorState, Transaction } from 'prosemirror-state';
|
|
7
|
+
import { CommandManager } from './commands/CommandManager.js';
|
|
8
|
+
import { nodeToTreeString } from './nodeToTreeString.js';
|
|
9
|
+
import { DummyEditorView } from './DummyEditorView.js';
|
|
10
|
+
import { ChainedCommands } from './commands/mod.js';
|
|
11
|
+
import { createNodeFromObject } from './utilities/createNodeFromContent.js';
|
|
12
|
+
import { Extension } from './Extension.js';
|
|
13
|
+
import { defaultUi, EditorUi } from './ui.js';
|
|
14
|
+
|
|
15
|
+
function ensureDocSchema(
|
|
16
|
+
doc: ProseMirrorNode,
|
|
17
|
+
schema: Schema,
|
|
18
|
+
): ProseMirrorNode {
|
|
19
|
+
if (doc.type.schema === schema) {
|
|
20
|
+
return doc;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const json = doc.toJSON();
|
|
24
|
+
return ProseMirrorNode.fromJSON(schema, json);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class CoreEditor extends EventTarget {
|
|
28
|
+
public readonly config: Partial<EditorConfig> = {
|
|
29
|
+
element: undefined,
|
|
30
|
+
extensions: [],
|
|
31
|
+
};
|
|
32
|
+
private extensionManager: ExtensionManager;
|
|
33
|
+
private commandManager: CommandManager;
|
|
34
|
+
public view!: EditorView | DummyEditorView;
|
|
35
|
+
public state!: EditorState;
|
|
36
|
+
public ui: EditorUi = defaultUi(this);
|
|
37
|
+
|
|
38
|
+
constructor(config: Partial<EditorConfig> = {}) {
|
|
39
|
+
super();
|
|
40
|
+
this.config = {
|
|
41
|
+
...this.config,
|
|
42
|
+
...config,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this.commandManager = new CommandManager(
|
|
46
|
+
this,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
this.extensionManager = new ExtensionManager(
|
|
50
|
+
this.config.extensions || [],
|
|
51
|
+
this,
|
|
52
|
+
this.commandManager,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
this.extensionManager.created();
|
|
56
|
+
|
|
57
|
+
// const content = this.options.content ? this.options.content : {
|
|
58
|
+
// type: this.extensionManager.schema.topNodeType.name,
|
|
59
|
+
// content: this.extensionManager.schema.topNodeType.spec.EMPTY_DOC,
|
|
60
|
+
// };
|
|
61
|
+
const content = this.config.content
|
|
62
|
+
? this.config.content
|
|
63
|
+
: this.extensionManager.schema.topNodeType.spec.EMPTY_DOC;
|
|
64
|
+
|
|
65
|
+
this.createView(content);
|
|
66
|
+
this.setupPlugins();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getExtension<T extends Extension>(name: string): T | undefined {
|
|
70
|
+
return this.extensionManager.getExtension<T>(name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public get schema() {
|
|
74
|
+
return this.extensionManager.schema;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public get run() {
|
|
78
|
+
return this.commandManager.run;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public get commandFactories() {
|
|
82
|
+
return this.commandManager.commandFactories;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public chain(): ChainedCommands {
|
|
86
|
+
return this.commandManager.createChain();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public can(): ChainedCommands {
|
|
90
|
+
return this.commandManager.createCan();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private createView(content: any) {
|
|
94
|
+
const doc = createNodeFromObject(content, this.schema);
|
|
95
|
+
|
|
96
|
+
this.state = EditorState.create({ doc });
|
|
97
|
+
|
|
98
|
+
if (this.config.element) {
|
|
99
|
+
const view = new EditorView(this.config.element, {
|
|
100
|
+
state: this.state,
|
|
101
|
+
attributes: {
|
|
102
|
+
class: 'kb-editor',
|
|
103
|
+
},
|
|
104
|
+
dispatchTransaction: (tx: Transaction) => this.dispatchTransaction(tx),
|
|
105
|
+
editable: () => !this.config.readOnly,
|
|
106
|
+
});
|
|
107
|
+
this.view = view;
|
|
108
|
+
|
|
109
|
+
const parent = this.config.element.parentNode;
|
|
110
|
+
if (parent) {
|
|
111
|
+
const observer = new MutationObserver((mutations) => {
|
|
112
|
+
for (const mutation of mutations) {
|
|
113
|
+
for (const removedNode of mutation.removedNodes) {
|
|
114
|
+
if (removedNode.contains(view.dom)) {
|
|
115
|
+
// Editor DOM was removed
|
|
116
|
+
observer.disconnect(); // Prevent multiple calls
|
|
117
|
+
view.destroy();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
this.view = new DummyEditorView({
|
|
126
|
+
state: this.state,
|
|
127
|
+
dispatchTransaction: (tx: Transaction) => this.dispatchTransaction(tx),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const event = new CustomEvent('doc:loaded', {
|
|
132
|
+
detail: {
|
|
133
|
+
editor: this,
|
|
134
|
+
doc,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
this.dispatchEvent(event);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public dispatchTransaction(transaction: Transaction) {
|
|
141
|
+
this.state = this.state.apply(transaction);
|
|
142
|
+
if (this.view) {
|
|
143
|
+
this.view.updateState(this.state);
|
|
144
|
+
const event = new CustomEvent('transaction', {
|
|
145
|
+
detail: {
|
|
146
|
+
editor: this,
|
|
147
|
+
transaction,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
this.dispatchEvent(event);
|
|
151
|
+
}
|
|
152
|
+
if (transaction.docChanged) {
|
|
153
|
+
const event = new CustomEvent('changed', {
|
|
154
|
+
detail: {
|
|
155
|
+
editor: this,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
this.dispatchEvent(event);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private setupPlugins() {
|
|
163
|
+
this.state = this.state.reconfigure({
|
|
164
|
+
plugins: this.extensionManager.plugins,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (this.view) {
|
|
168
|
+
this.view.updateState(this.state);
|
|
169
|
+
|
|
170
|
+
this.view.setProps({
|
|
171
|
+
nodeViews: this.extensionManager.nodeViews,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public clearDocument() {
|
|
177
|
+
const content = {
|
|
178
|
+
type: this.extensionManager.schema.topNodeType.name,
|
|
179
|
+
content: this.extensionManager.schema.topNodeType.spec.EMPTY_DOC.content,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
this.setDocument(content);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public setDocument(content: any) {
|
|
186
|
+
let doc = createNodeFromObject(content, this.schema, {
|
|
187
|
+
errorOnInvalidContent: true,
|
|
188
|
+
});
|
|
189
|
+
doc = ensureDocSchema(doc, this.schema);
|
|
190
|
+
|
|
191
|
+
this.state = EditorState.create({
|
|
192
|
+
doc,
|
|
193
|
+
plugins: this.state.plugins,
|
|
194
|
+
storedMarks: this.state.storedMarks,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (this.view) {
|
|
198
|
+
this.view.updateState(this.state);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const event = new CustomEvent('doc:loaded', {
|
|
202
|
+
detail: {
|
|
203
|
+
editor: this,
|
|
204
|
+
doc,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
this.dispatchEvent(event);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public getDocument() {
|
|
211
|
+
return this.state.doc;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async loadDocument(mediaType: string, content: Uint8Array) {
|
|
215
|
+
const converter = this.extensionManager.converters[mediaType];
|
|
216
|
+
if (!converter) {
|
|
217
|
+
throw new Error('Converter not found for: ' + mediaType);
|
|
218
|
+
}
|
|
219
|
+
const doc = await converter.toDoc(content);
|
|
220
|
+
|
|
221
|
+
this.state = EditorState.create({
|
|
222
|
+
doc,
|
|
223
|
+
plugins: this.state.plugins,
|
|
224
|
+
storedMarks: this.state.storedMarks,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (this.view) {
|
|
228
|
+
this.view.updateState(this.state);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const event = new CustomEvent('doc:loaded', {
|
|
232
|
+
detail: {
|
|
233
|
+
editor: this,
|
|
234
|
+
doc,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
this.dispatchEvent(event);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public async saveDocument(mediaType: string): Promise<Uint8Array> {
|
|
241
|
+
const converter = this.extensionManager.converters[mediaType];
|
|
242
|
+
if (!converter) {
|
|
243
|
+
throw new Error('Converter not found for: ' + mediaType);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const json = this.state.doc.toJSON();
|
|
247
|
+
const clonedDoc = ProseMirrorNode.fromJSON(this.state.schema, json);
|
|
248
|
+
|
|
249
|
+
return await converter.fromDoc(clonedDoc);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public getJSON(): JSONContent {
|
|
253
|
+
return this.state.doc.toJSON();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public clone(options: Partial<EditorConfig> = {}): CoreEditor {
|
|
257
|
+
return new CoreEditor({
|
|
258
|
+
...options,
|
|
259
|
+
extensions: [...(Array.from(this.extensionManager.extensions) || [])],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public debug(doc?: ProseMirrorNode) {
|
|
264
|
+
if (!doc) {
|
|
265
|
+
doc = this.state.doc;
|
|
266
|
+
}
|
|
267
|
+
console.debug(nodeToTreeString(doc));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public destroy() {
|
|
271
|
+
const event = new CustomEvent('beforeDestroy', {
|
|
272
|
+
detail: {},
|
|
273
|
+
});
|
|
274
|
+
this.dispatchEvent(event);
|
|
275
|
+
this.view.destroy();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// Headless View to be used on Server-side
|
|
2
|
+
// TODO: remove all unnecessary props and methods
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
EditorState,
|
|
6
|
+
Plugin,
|
|
7
|
+
PluginView,
|
|
8
|
+
Transaction,
|
|
9
|
+
} from 'prosemirror-state';
|
|
10
|
+
import { Mark, Node } from 'prosemirror-model';
|
|
11
|
+
|
|
12
|
+
import { EditorView, MarkView, NodeView } from 'prosemirror-view';
|
|
13
|
+
|
|
14
|
+
import { Decoration, DecorationSource } from 'prosemirror-view';
|
|
15
|
+
|
|
16
|
+
/// An editor view manages the DOM structure that represents an
|
|
17
|
+
/// editable document. Its state and behavior are determined by its
|
|
18
|
+
/// [props](#view.DirectEditorProps).
|
|
19
|
+
export class DummyEditorView {
|
|
20
|
+
/// @internal
|
|
21
|
+
private _props: DirectEditorProps;
|
|
22
|
+
private directPlugins: readonly Plugin[];
|
|
23
|
+
/// @internal
|
|
24
|
+
private nodeViews: NodeViewSet;
|
|
25
|
+
private prevDirectPlugins: readonly Plugin[] = [];
|
|
26
|
+
private pluginViews: PluginView[] = [];
|
|
27
|
+
|
|
28
|
+
/// The view's current [state](#state.EditorState).
|
|
29
|
+
public state: EditorState;
|
|
30
|
+
|
|
31
|
+
/// Create a view. `place` may be a DOM node that the editor should
|
|
32
|
+
/// be appended to, a function that will place it into the document,
|
|
33
|
+
/// or an object whose `mount` property holds the node to use as the
|
|
34
|
+
/// document container. If it is `null`, the editor will not be
|
|
35
|
+
/// added to the document.
|
|
36
|
+
constructor(props: DirectEditorProps) {
|
|
37
|
+
this._props = props;
|
|
38
|
+
this.state = props.state;
|
|
39
|
+
this.directPlugins = props.plugins || [];
|
|
40
|
+
this.directPlugins.forEach(checkStateComponent);
|
|
41
|
+
|
|
42
|
+
this.dispatch = this.dispatch.bind(this);
|
|
43
|
+
|
|
44
|
+
this.editable = getEditable(this);
|
|
45
|
+
this.nodeViews = buildNodeViews(this);
|
|
46
|
+
// TODO initInput(this)
|
|
47
|
+
this.updatePluginViews();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Indicates whether the editor is currently [editable](#view.EditorProps.editable).
|
|
51
|
+
editable: boolean;
|
|
52
|
+
|
|
53
|
+
/// Holds `true` when a
|
|
54
|
+
/// [composition](https://w3c.github.io/uievents/#events-compositionevents)
|
|
55
|
+
/// is active.
|
|
56
|
+
get composing() {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get dom() {
|
|
61
|
+
return {
|
|
62
|
+
addEventListener() {},
|
|
63
|
+
removeEventListener() {},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// The view's current [props](#view.EditorProps).
|
|
68
|
+
get props() {
|
|
69
|
+
if (this._props.state != this.state) {
|
|
70
|
+
let prev = this._props;
|
|
71
|
+
this._props = {} as any;
|
|
72
|
+
for (let name in prev) (this._props as any)[name] = (prev as any)[name];
|
|
73
|
+
this._props.state = this.state;
|
|
74
|
+
}
|
|
75
|
+
return this._props;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Update the view's props. Will immediately cause an update to
|
|
79
|
+
/// the DOM.
|
|
80
|
+
update(props: DirectEditorProps) {
|
|
81
|
+
let prevProps = this._props;
|
|
82
|
+
this._props = props;
|
|
83
|
+
if (props.plugins) {
|
|
84
|
+
props.plugins.forEach(checkStateComponent);
|
|
85
|
+
this.directPlugins = props.plugins;
|
|
86
|
+
}
|
|
87
|
+
this.updateStateInner(props.state, prevProps);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Update the view by updating existing props object with the object
|
|
91
|
+
/// given as argument. Equivalent to `view.update(Object.assign({},
|
|
92
|
+
/// view.props, props))`.
|
|
93
|
+
setProps(props: Partial<DirectEditorProps>) {
|
|
94
|
+
let updated = {} as DirectEditorProps;
|
|
95
|
+
for (let name in this._props) {
|
|
96
|
+
(updated as any)[name] = (this._props as any)[name];
|
|
97
|
+
}
|
|
98
|
+
updated.state = this.state;
|
|
99
|
+
for (let name in props) (updated as any)[name] = (props as any)[name];
|
|
100
|
+
this.update(updated);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Update the editor's `state` prop, without touching any of the
|
|
104
|
+
/// other props.
|
|
105
|
+
updateState(state: EditorState) {
|
|
106
|
+
this.updateStateInner(state, this._props);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private updateStateInner(state: EditorState, prevProps: DirectEditorProps) {
|
|
110
|
+
let prev = this.state, redraw = false, updateSel = false;
|
|
111
|
+
// When stored marks are added, stop composition, so that they can
|
|
112
|
+
// be displayed.
|
|
113
|
+
if (state.storedMarks && this.composing) {
|
|
114
|
+
// TODO clearComposition(this)
|
|
115
|
+
updateSel = true;
|
|
116
|
+
}
|
|
117
|
+
this.state = state;
|
|
118
|
+
let pluginsChanged = prev.plugins != state.plugins ||
|
|
119
|
+
this._props.plugins != prevProps.plugins;
|
|
120
|
+
if (
|
|
121
|
+
pluginsChanged || this._props.plugins != prevProps.plugins ||
|
|
122
|
+
this._props.nodeViews != prevProps.nodeViews
|
|
123
|
+
) {
|
|
124
|
+
let nodeViews = buildNodeViews(this);
|
|
125
|
+
if (changedNodeViews(nodeViews, this.nodeViews)) {
|
|
126
|
+
this.nodeViews = nodeViews;
|
|
127
|
+
redraw = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.editable = getEditable(this);
|
|
132
|
+
|
|
133
|
+
let updateDoc = redraw;
|
|
134
|
+
if (updateDoc || !state.selection.eq(prev.selection)) updateSel = true;
|
|
135
|
+
|
|
136
|
+
if (updateSel) {
|
|
137
|
+
// Work around an issue in Chrome, IE, and Edge where changing
|
|
138
|
+
// the DOM around an active selection puts it into a broken
|
|
139
|
+
// state where the thing the user sees differs from the
|
|
140
|
+
// selection reported by the Selection object (#710, #973,
|
|
141
|
+
// #1011, #1013, #1035).
|
|
142
|
+
let forceSelUpdate = false;
|
|
143
|
+
if (updateDoc) {
|
|
144
|
+
// If the node that the selection points into is written to,
|
|
145
|
+
// Chrome sometimes starts misreporting the selection, so this
|
|
146
|
+
// tracks that and forces a selection reset when our update
|
|
147
|
+
// did write to the node.
|
|
148
|
+
// TODO if (this.composing) this.input.compositionNode = findCompositionNode(this)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.updatePluginViews(prev);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// @internal
|
|
156
|
+
scrollToSelection() {
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private destroyPluginViews() {
|
|
160
|
+
let view;
|
|
161
|
+
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private updatePluginViews(prevState?: EditorState) {
|
|
165
|
+
if (
|
|
166
|
+
!prevState || prevState.plugins != this.state.plugins ||
|
|
167
|
+
this.directPlugins != this.prevDirectPlugins
|
|
168
|
+
) {
|
|
169
|
+
this.prevDirectPlugins = this.directPlugins;
|
|
170
|
+
this.destroyPluginViews();
|
|
171
|
+
for (let i = 0; i < this.directPlugins.length; i++) {
|
|
172
|
+
let plugin = this.directPlugins[i];
|
|
173
|
+
if (plugin.spec.view) {
|
|
174
|
+
this.pluginViews.push(plugin.spec.view(<any> this));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (let i = 0; i < this.state.plugins.length; i++) {
|
|
178
|
+
let plugin = this.state.plugins[i];
|
|
179
|
+
if (plugin.spec.view) {
|
|
180
|
+
this.pluginViews.push(plugin.spec.view(<any> this));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
for (let i = 0; i < this.pluginViews.length; i++) {
|
|
185
|
+
let pluginView = this.pluginViews[i];
|
|
186
|
+
if (pluginView.update) pluginView.update(<any> this, prevState);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Goes over the values of a prop, first those provided directly,
|
|
192
|
+
/// then those from plugins given to the view, then from plugins in
|
|
193
|
+
/// the state (in order), and calls `f` every time a non-undefined
|
|
194
|
+
/// value is found. When `f` returns a truthy value, that is
|
|
195
|
+
/// immediately returned. When `f` isn't provided, it is treated as
|
|
196
|
+
/// the identity function (the prop value is returned directly).
|
|
197
|
+
someProp<PropName extends keyof EditorProps, Result>(
|
|
198
|
+
propName: PropName,
|
|
199
|
+
f: (value: NonNullable<EditorProps[PropName]>) => Result,
|
|
200
|
+
): Result | undefined;
|
|
201
|
+
someProp<PropName extends keyof EditorProps>(
|
|
202
|
+
propName: PropName,
|
|
203
|
+
): NonNullable<EditorProps[PropName]> | undefined;
|
|
204
|
+
someProp<PropName extends keyof EditorProps, Result>(
|
|
205
|
+
propName: PropName,
|
|
206
|
+
f?: (value: NonNullable<EditorProps[PropName]>) => Result,
|
|
207
|
+
): Result | undefined {
|
|
208
|
+
let prop = this._props && this._props[propName], value;
|
|
209
|
+
if (prop != null && (value = f ? f(prop as any) : prop)) {
|
|
210
|
+
return value as any;
|
|
211
|
+
}
|
|
212
|
+
for (let i = 0; i < this.directPlugins.length; i++) {
|
|
213
|
+
let prop = this.directPlugins[i].props[propName];
|
|
214
|
+
if (prop != null && (value = f ? f(prop as any) : prop)) {
|
|
215
|
+
return value as any;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
let plugins = this.state.plugins;
|
|
219
|
+
if (plugins) {
|
|
220
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
221
|
+
let prop = plugins[i].props[propName];
|
|
222
|
+
if (
|
|
223
|
+
prop != null && (value = f ? f(prop as any) : prop)
|
|
224
|
+
) return value as any;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Query whether the view has focus.
|
|
230
|
+
hasFocus() {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// Focus the editor.
|
|
235
|
+
focus() {
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// Removes the editor from the DOM and destroys all [node
|
|
239
|
+
/// views](#view.NodeView).
|
|
240
|
+
destroy() {
|
|
241
|
+
this.destroyPluginViews();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// This is true when the view has been
|
|
245
|
+
/// [destroyed](#view.DummyEditorView.destroy) (and thus should not be
|
|
246
|
+
/// used anymore).
|
|
247
|
+
get isDestroyed() {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Used for testing.
|
|
252
|
+
dispatchEvent(event: Event) {
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// Dispatch a transaction. Will call
|
|
256
|
+
/// [`dispatchTransaction`](#view.DirectEditorProps.dispatchTransaction)
|
|
257
|
+
/// when given, and otherwise defaults to applying the transaction to
|
|
258
|
+
/// the current state and calling
|
|
259
|
+
/// [`updateState`](#view.DummyEditorView.updateState) with the result.
|
|
260
|
+
/// This method is bound to the view instance, so that it can be
|
|
261
|
+
/// easily passed around.
|
|
262
|
+
declare dispatch: (tr: Transaction) => void;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
DummyEditorView.prototype.dispatch = function (tr: Transaction) {
|
|
266
|
+
let dispatchTransaction = this.props.dispatchTransaction;
|
|
267
|
+
if (dispatchTransaction) dispatchTransaction.call(this, tr);
|
|
268
|
+
else this.updateState(this.state.apply(tr));
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
function getEditable(view: DummyEditorView) {
|
|
272
|
+
return !view.someProp('editable', (value) => value(view.state) === false);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildNodeViews(view: DummyEditorView) {
|
|
276
|
+
let result: NodeViewSet = Object.create(null);
|
|
277
|
+
function add(obj: NodeViewSet) {
|
|
278
|
+
for (let prop in obj) {
|
|
279
|
+
if (!Object.prototype.hasOwnProperty.call(result, prop)) {
|
|
280
|
+
result[prop] = obj[prop];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
view.someProp('nodeViews', add);
|
|
285
|
+
view.someProp('markViews', add);
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function changedNodeViews(a: NodeViewSet, b: NodeViewSet) {
|
|
290
|
+
let nA = 0, nB = 0;
|
|
291
|
+
for (let prop in a) {
|
|
292
|
+
if (a[prop] != b[prop]) return true;
|
|
293
|
+
nA++;
|
|
294
|
+
}
|
|
295
|
+
for (let _ in b) nB++;
|
|
296
|
+
return nA != nB;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function checkStateComponent(plugin: Plugin) {
|
|
300
|
+
if (
|
|
301
|
+
plugin.spec.state || plugin.spec.filterTransaction ||
|
|
302
|
+
plugin.spec.appendTransaction
|
|
303
|
+
) {
|
|
304
|
+
throw new RangeError(
|
|
305
|
+
'Plugins passed directly to the view must not have a state component',
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// The type of function [provided](#view.EditorProps.nodeViews) to
|
|
311
|
+
/// create [node views](#view.NodeView).
|
|
312
|
+
export type NodeViewConstructor = (
|
|
313
|
+
node: Node,
|
|
314
|
+
view: EditorView,
|
|
315
|
+
getPos: () => number | undefined,
|
|
316
|
+
decorations: readonly Decoration[],
|
|
317
|
+
innerDecorations: DecorationSource,
|
|
318
|
+
) => NodeView;
|
|
319
|
+
|
|
320
|
+
/// The function types [used](#view.EditorProps.markViews) to create
|
|
321
|
+
/// mark views.
|
|
322
|
+
export type MarkViewConstructor = (
|
|
323
|
+
mark: Mark,
|
|
324
|
+
view: EditorView,
|
|
325
|
+
inline: boolean,
|
|
326
|
+
) => MarkView;
|
|
327
|
+
|
|
328
|
+
type NodeViewSet = {
|
|
329
|
+
[name: string]: NodeViewConstructor | MarkViewConstructor;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
/// Helper type that maps event names to event object types, but
|
|
333
|
+
/// includes events that TypeScript's HTMLElementEventMap doesn't know
|
|
334
|
+
/// about.
|
|
335
|
+
export interface DOMEventMap extends HTMLElementEventMap {
|
|
336
|
+
[event: string]: any;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// Props are configuration values that can be passed to an editor view
|
|
340
|
+
/// or included in a plugin. This interface lists the supported props.
|
|
341
|
+
export interface EditorProps<P = any> {
|
|
342
|
+
/// Allows you to pass custom rendering and behavior logic for
|
|
343
|
+
/// nodes. Should map node names to constructor functions that
|
|
344
|
+
/// produce a [`NodeView`](#view.NodeView) object implementing the
|
|
345
|
+
/// node's display behavior. The third argument `getPos` is a
|
|
346
|
+
/// function that can be called to get the node's current position,
|
|
347
|
+
/// which can be useful when creating transactions to update it.
|
|
348
|
+
/// Note that if the node is not in the document, the position
|
|
349
|
+
/// returned by this function will be `undefined`.
|
|
350
|
+
///
|
|
351
|
+
/// (For backwards compatibility reasons, [mark
|
|
352
|
+
/// views](#view.EditorProps.markViews) can also be included in this
|
|
353
|
+
/// object.)
|
|
354
|
+
nodeViews?: { [node: string]: NodeViewConstructor };
|
|
355
|
+
|
|
356
|
+
/// Pass custom mark rendering functions. Note that these cannot
|
|
357
|
+
/// provide the kind of dynamic behavior that [node
|
|
358
|
+
/// views](#view.NodeView) can—they just provide custom rendering
|
|
359
|
+
/// logic. The third argument indicates whether the mark's content
|
|
360
|
+
/// is inline.
|
|
361
|
+
markViews?: { [mark: string]: MarkViewConstructor };
|
|
362
|
+
|
|
363
|
+
/// When this returns false, the content of the view is not directly
|
|
364
|
+
/// editable.
|
|
365
|
+
editable?: (this: P, state: EditorState) => boolean;
|
|
366
|
+
|
|
367
|
+
/// Control the DOM attributes of the editable element. May be either
|
|
368
|
+
/// an object or a function going from an editor state to an object.
|
|
369
|
+
/// By default, the element will get a class `"ProseMirror"`, and
|
|
370
|
+
/// will have its `contentEditable` attribute determined by the
|
|
371
|
+
/// [`editable` prop](#view.EditorProps.editable). Additional classes
|
|
372
|
+
/// provided here will be added to the class. For other attributes,
|
|
373
|
+
/// the value provided first (as in
|
|
374
|
+
/// [`someProp`](#view.DummyEditorView.someProp)) will be used.
|
|
375
|
+
attributes?:
|
|
376
|
+
| { [name: string]: string }
|
|
377
|
+
| ((state: EditorState) => { [name: string]: string });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// The props object given directly to the editor view supports some
|
|
381
|
+
/// fields that can't be used in plugins:
|
|
382
|
+
export interface DirectEditorProps extends EditorProps {
|
|
383
|
+
/// The current state of the editor.
|
|
384
|
+
state: EditorState;
|
|
385
|
+
|
|
386
|
+
/// A set of plugins to use in the view, applying their [plugin
|
|
387
|
+
/// view](#state.PluginSpec.view) and
|
|
388
|
+
/// [props](#state.PluginSpec.props). Passing plugins with a state
|
|
389
|
+
/// component (a [state field](#state.PluginSpec.state) field or a
|
|
390
|
+
/// [transaction](#state.PluginSpec.filterTransaction) filter or
|
|
391
|
+
/// appender) will result in an error, since such plugins must be
|
|
392
|
+
/// present in the state to work.
|
|
393
|
+
plugins?: readonly Plugin[];
|
|
394
|
+
|
|
395
|
+
/// The callback over which to send transactions (state updates)
|
|
396
|
+
/// produced by the view. If you specify this, you probably want to
|
|
397
|
+
/// make sure this ends up calling the view's
|
|
398
|
+
/// [`updateState`](#view.DummyEditorView.updateState) method with a new
|
|
399
|
+
/// state that has the transaction
|
|
400
|
+
/// [applied](#state.EditorState.apply). The callback will be bound to have
|
|
401
|
+
/// the view instance as its `this` binding.
|
|
402
|
+
dispatchTransaction?: (tr: Transaction) => void;
|
|
403
|
+
}
|