ckeditor5-phoenix 1.15.1 → 1.15.2
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/hooks/editor/editor.d.ts.map +1 -1
- package/dist/hooks/editor/plugins/index.d.ts +3 -0
- package/dist/hooks/editor/plugins/index.d.ts.map +1 -0
- package/dist/hooks/editor/plugins/sync-editor-with-input.d.ts +11 -0
- package/dist/hooks/editor/plugins/sync-editor-with-input.d.ts.map +1 -0
- package/dist/hooks/editor/plugins/sync-editor-with-phoenix.d.ts +19 -0
- package/dist/hooks/editor/plugins/sync-editor-with-phoenix.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +318 -257
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/hooks/editor/editor.ts +22 -118
- package/src/hooks/editor/plugins/index.ts +2 -0
- package/src/hooks/editor/plugins/sync-editor-with-input.ts +82 -0
- package/src/hooks/editor/plugins/sync-editor-with-phoenix.ts +120 -0
|
@@ -4,15 +4,14 @@ import type { EditorId, EditorType } from './typings';
|
|
|
4
4
|
import type { EditorCreator } from './utils';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
debounce,
|
|
8
7
|
isEmptyObject,
|
|
9
|
-
isNil,
|
|
10
8
|
mapObjectValues,
|
|
11
9
|
parseIntIfNotNull,
|
|
12
10
|
} from '../../shared';
|
|
13
11
|
import { ClassHook, makeHook } from '../../shared/hook';
|
|
14
12
|
import { ContextsRegistry, getNearestContextParentPromise } from '../context';
|
|
15
13
|
import { EditorsRegistry } from './editors-registry';
|
|
14
|
+
import { createSyncEditorWithInputPlugin, createSyncEditorWithPhoenixPlugin } from './plugins';
|
|
16
15
|
import {
|
|
17
16
|
createEditorInContext,
|
|
18
17
|
isSingleEditingLikeEditor,
|
|
@@ -179,6 +178,25 @@ class EditorHookImpl extends ClassHook {
|
|
|
179
178
|
|
|
180
179
|
const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins);
|
|
181
180
|
|
|
181
|
+
if (isSingleEditingLikeEditor(type)) {
|
|
182
|
+
loadedPlugins.push(
|
|
183
|
+
await createSyncEditorWithInputPlugin({
|
|
184
|
+
editorId,
|
|
185
|
+
saveDebounceMs,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
loadedPlugins.push(
|
|
191
|
+
await createSyncEditorWithPhoenixPlugin({
|
|
192
|
+
editorId,
|
|
193
|
+
saveDebounceMs,
|
|
194
|
+
events,
|
|
195
|
+
pushEvent: this.pushEvent.bind(this),
|
|
196
|
+
handleEvent: this.handleEvent.bind(this),
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
|
|
182
200
|
// Mix custom translations with loaded translations.
|
|
183
201
|
const loadedTranslations = await loadAllEditorTranslations(language, hasPremium);
|
|
184
202
|
const mixedTranslations = [
|
|
@@ -216,126 +234,12 @@ class EditorHookImpl extends ClassHook {
|
|
|
216
234
|
return result.editor;
|
|
217
235
|
})();
|
|
218
236
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (events.blur) {
|
|
224
|
-
this.setupEventPush(editorId, editor, 'blur');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (events.focus) {
|
|
228
|
-
this.setupEventPush(editorId, editor, 'focus');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Handle incoming data from the server.
|
|
232
|
-
this.handleEvent('ckeditor5:set-data', ({ editorId, data }) => {
|
|
233
|
-
if (isNil(editorId) || editorId === this.attrs.editorId) {
|
|
234
|
-
editor.setData(data);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
if (isSingleEditingLikeEditor(type)) {
|
|
239
|
-
const input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;
|
|
240
|
-
|
|
241
|
-
if (input) {
|
|
242
|
-
syncEditorToInput(input, editor, saveDebounceMs);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (editableHeight) {
|
|
246
|
-
setEditorEditableHeight(editor, editableHeight);
|
|
247
|
-
}
|
|
237
|
+
if (isSingleEditingLikeEditor(type) && editableHeight) {
|
|
238
|
+
setEditorEditableHeight(editor, editableHeight);
|
|
248
239
|
}
|
|
249
240
|
|
|
250
241
|
return editor;
|
|
251
242
|
};
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Setups the content push event for the editor.
|
|
255
|
-
*/
|
|
256
|
-
private setupTypingContentPush(editorId: EditorId, editor: Editor, saveDebounceMs: number) {
|
|
257
|
-
const pushContentChange = () => {
|
|
258
|
-
this.pushEvent(
|
|
259
|
-
'ckeditor5:change',
|
|
260
|
-
{
|
|
261
|
-
editorId,
|
|
262
|
-
data: getEditorRootsValues(editor),
|
|
263
|
-
},
|
|
264
|
-
);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
editor.model.document.on('change:data', debounce(saveDebounceMs, pushContentChange));
|
|
268
|
-
pushContentChange();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Setups the event push for the editor.
|
|
273
|
-
*/
|
|
274
|
-
private setupEventPush(editorId: EditorId, editor: Editor, eventType: 'focus' | 'blur') {
|
|
275
|
-
const pushEvent = () => {
|
|
276
|
-
const { isFocused } = editor.ui.focusTracker;
|
|
277
|
-
const currentType = isFocused ? 'focus' : 'blur';
|
|
278
|
-
|
|
279
|
-
if (currentType !== eventType) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
this.pushEvent(
|
|
284
|
-
`ckeditor5:${eventType}`,
|
|
285
|
-
{
|
|
286
|
-
editorId,
|
|
287
|
-
data: getEditorRootsValues(editor),
|
|
288
|
-
},
|
|
289
|
-
);
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
editor.ui.focusTracker.on('change:isFocused', pushEvent);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Gets the values of the editor's roots.
|
|
298
|
-
*
|
|
299
|
-
* @param editor The CKEditor instance.
|
|
300
|
-
* @returns An object mapping root names to their content.
|
|
301
|
-
*/
|
|
302
|
-
function getEditorRootsValues(editor: Editor) {
|
|
303
|
-
const roots = editor.model.document.getRootNames();
|
|
304
|
-
|
|
305
|
-
return roots.reduce<Record<string, string>>((acc, rootName) => {
|
|
306
|
-
acc[rootName] = editor.getData({ rootName });
|
|
307
|
-
return acc;
|
|
308
|
-
}, Object.create({}));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Synchronizes the editor's content with a hidden input field.
|
|
313
|
-
*
|
|
314
|
-
* @param input The input element to synchronize with the editor.
|
|
315
|
-
* @param editor The CKEditor instance.
|
|
316
|
-
*/
|
|
317
|
-
function syncEditorToInput(input: HTMLInputElement, editor: Editor, saveDebounceMs: number) {
|
|
318
|
-
const sync = () => {
|
|
319
|
-
const newValue = editor.getData();
|
|
320
|
-
|
|
321
|
-
input.value = newValue;
|
|
322
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
editor.model.document.on('change:data', debounce(saveDebounceMs, sync));
|
|
326
|
-
getParentFormElement(input)?.addEventListener('submit', sync);
|
|
327
|
-
|
|
328
|
-
sync();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Gets the parent form element of the given HTML element.
|
|
333
|
-
*
|
|
334
|
-
* @param element The HTML element to find the parent form for.
|
|
335
|
-
* @returns The parent form element or null if not found.
|
|
336
|
-
*/
|
|
337
|
-
function getParentFormElement(element: HTMLElement) {
|
|
338
|
-
return element.closest('form') as HTMLFormElement | null;
|
|
339
243
|
}
|
|
340
244
|
|
|
341
245
|
/**
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { PluginConstructor } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import { debounce } from '../../../shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a SyncEditorWithInput plugin class.
|
|
7
|
+
*/
|
|
8
|
+
export async function createSyncEditorWithInputPlugin(
|
|
9
|
+
{
|
|
10
|
+
editorId,
|
|
11
|
+
saveDebounceMs,
|
|
12
|
+
}: Attrs,
|
|
13
|
+
): Promise<PluginConstructor> {
|
|
14
|
+
const { Plugin } = await import('ckeditor5');
|
|
15
|
+
|
|
16
|
+
return class SyncEditorWithInput extends Plugin {
|
|
17
|
+
/**
|
|
18
|
+
* The input element to synchronize with.
|
|
19
|
+
*/
|
|
20
|
+
private input: HTMLInputElement | null = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The form element reference for cleanup.
|
|
24
|
+
*/
|
|
25
|
+
private form: HTMLFormElement | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The name of the plugin.
|
|
29
|
+
*/
|
|
30
|
+
static get pluginName() {
|
|
31
|
+
return 'SyncEditorWithInput' as const;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initializes the plugin.
|
|
36
|
+
*/
|
|
37
|
+
public afterInit(): void {
|
|
38
|
+
const { editor } = this;
|
|
39
|
+
|
|
40
|
+
this.input = document.getElementById(`${editorId}_input`) as HTMLInputElement | null;
|
|
41
|
+
|
|
42
|
+
if (!this.input) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Setup handlers.
|
|
47
|
+
editor.model.document.on('change:data', debounce(saveDebounceMs, () => this.sync()));
|
|
48
|
+
editor.once('ready', this.sync);
|
|
49
|
+
|
|
50
|
+
// Setup form integration.
|
|
51
|
+
this.form = this.input.closest('form');
|
|
52
|
+
this.form?.addEventListener('submit', this.sync);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Synchronizes the editor's content with the input field.
|
|
57
|
+
*/
|
|
58
|
+
private sync = (): void => {
|
|
59
|
+
const newValue = this.editor.getData();
|
|
60
|
+
|
|
61
|
+
this.input!.value = newValue;
|
|
62
|
+
this.input!.dispatchEvent(new Event('input', { bubbles: true }));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Destroys the plugin.
|
|
67
|
+
*/
|
|
68
|
+
public override destroy(): void {
|
|
69
|
+
if (this.form) {
|
|
70
|
+
this.form.removeEventListener('submit', this.sync);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.input = null;
|
|
74
|
+
this.form = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type Attrs = {
|
|
80
|
+
editorId: string;
|
|
81
|
+
saveDebounceMs: number;
|
|
82
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Editor, PluginConstructor } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import type { EditorId } from '../typings';
|
|
4
|
+
|
|
5
|
+
import { debounce, isNil } from '../../../shared';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a SyncEditorWithPhoenix plugin class.
|
|
9
|
+
*/
|
|
10
|
+
export async function createSyncEditorWithPhoenixPlugin(options: Attrs): Promise<PluginConstructor> {
|
|
11
|
+
const { Plugin } = await import('ckeditor5');
|
|
12
|
+
const { editorId, saveDebounceMs, events, pushEvent, handleEvent } = options;
|
|
13
|
+
|
|
14
|
+
return class SyncEditorWithPhoenix extends Plugin {
|
|
15
|
+
/**
|
|
16
|
+
* The name of the plugin.
|
|
17
|
+
*/
|
|
18
|
+
static get pluginName() {
|
|
19
|
+
return 'SyncEditorWithPhoenix' as const;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initializes the plugin.
|
|
24
|
+
*/
|
|
25
|
+
public init(): void {
|
|
26
|
+
const { editor } = this;
|
|
27
|
+
|
|
28
|
+
if (events.change) {
|
|
29
|
+
this.setupTypingContentPush();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (events.blur) {
|
|
33
|
+
this.setupEventPush('blur');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (events.focus) {
|
|
37
|
+
this.setupEventPush('focus');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
handleEvent('ckeditor5:set-data', ({ editorId: targetId, data }) => {
|
|
41
|
+
if (isNil(targetId) || targetId === editorId) {
|
|
42
|
+
editor.setData(data);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Setups the content push event for the editor.
|
|
49
|
+
*/
|
|
50
|
+
private setupTypingContentPush() {
|
|
51
|
+
const { editor } = this;
|
|
52
|
+
|
|
53
|
+
const pushContentChange = () => {
|
|
54
|
+
pushEvent(
|
|
55
|
+
'ckeditor5:change',
|
|
56
|
+
{
|
|
57
|
+
editorId,
|
|
58
|
+
data: getEditorRootsValues(this.editor),
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
editor.model.document.on('change:data', debounce(saveDebounceMs, pushContentChange));
|
|
64
|
+
editor.once('ready', pushContentChange);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Setups the event push for the editor.
|
|
69
|
+
*/
|
|
70
|
+
private setupEventPush(eventType: 'focus' | 'blur') {
|
|
71
|
+
const { editor } = this;
|
|
72
|
+
|
|
73
|
+
const pushEventCallback = () => {
|
|
74
|
+
const { isFocused } = editor.ui.focusTracker;
|
|
75
|
+
const currentType = isFocused ? 'focus' : 'blur';
|
|
76
|
+
|
|
77
|
+
if (currentType !== eventType) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pushEvent(
|
|
82
|
+
`ckeditor5:${eventType}`,
|
|
83
|
+
{
|
|
84
|
+
editorId,
|
|
85
|
+
data: getEditorRootsValues(editor),
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
editor.ui.focusTracker.on('change:isFocused', pushEventCallback);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type Attrs = {
|
|
96
|
+
editorId: EditorId;
|
|
97
|
+
saveDebounceMs: number;
|
|
98
|
+
events: {
|
|
99
|
+
change: boolean;
|
|
100
|
+
focus: boolean;
|
|
101
|
+
blur: boolean;
|
|
102
|
+
};
|
|
103
|
+
pushEvent: (event: string, payload: any) => void;
|
|
104
|
+
handleEvent: (event: string, callback: (payload: any) => void) => void;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Gets the values of the editor's roots.
|
|
109
|
+
*
|
|
110
|
+
* @param editor The CKEditor instance.
|
|
111
|
+
* @returns An object mapping root names to their content.
|
|
112
|
+
*/
|
|
113
|
+
function getEditorRootsValues(editor: Editor) {
|
|
114
|
+
const roots = editor.model.document.getRootNames();
|
|
115
|
+
|
|
116
|
+
return roots.reduce<Record<string, string>>((acc, rootName) => {
|
|
117
|
+
acc[rootName] = editor.getData({ rootName });
|
|
118
|
+
return acc;
|
|
119
|
+
}, Object.create({}));
|
|
120
|
+
}
|