ckeditor5-symfony 1.0.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/dist/ckeditor5-symfony-error.d.ts +7 -0
- package/dist/ckeditor5-symfony-error.d.ts.map +1 -0
- package/dist/elements/context/context.d.ts +18 -0
- package/dist/elements/context/context.d.ts.map +1 -0
- package/dist/elements/context/contexts-registry.d.ts +9 -0
- package/dist/elements/context/contexts-registry.d.ts.map +1 -0
- package/dist/elements/context/index.d.ts +4 -0
- package/dist/elements/context/index.d.ts.map +1 -0
- package/dist/elements/context/typings.d.ts +34 -0
- package/dist/elements/context/typings.d.ts.map +1 -0
- package/dist/elements/editable.d.ts +18 -0
- package/dist/elements/editable.d.ts.map +1 -0
- package/dist/elements/editor/custom-editor-plugins.d.ts +54 -0
- package/dist/elements/editor/custom-editor-plugins.d.ts.map +1 -0
- package/dist/elements/editor/editor.d.ts +23 -0
- package/dist/elements/editor/editor.d.ts.map +1 -0
- package/dist/elements/editor/editors-registry.d.ts +9 -0
- package/dist/elements/editor/editors-registry.d.ts.map +1 -0
- package/dist/elements/editor/index.d.ts +3 -0
- package/dist/elements/editor/index.d.ts.map +1 -0
- package/dist/elements/editor/plugins/index.d.ts +2 -0
- package/dist/elements/editor/plugins/index.d.ts.map +1 -0
- package/dist/elements/editor/plugins/sync-editor-with-input.d.ts +6 -0
- package/dist/elements/editor/plugins/sync-editor-with-input.d.ts.map +1 -0
- package/dist/elements/editor/typings.d.ts +99 -0
- package/dist/elements/editor/typings.d.ts.map +1 -0
- package/dist/elements/editor/utils/create-editor-in-context.d.ts +44 -0
- package/dist/elements/editor/utils/create-editor-in-context.d.ts.map +1 -0
- package/dist/elements/editor/utils/index.d.ts +12 -0
- package/dist/elements/editor/utils/index.d.ts.map +1 -0
- package/dist/elements/editor/utils/is-single-root-editor.d.ts +9 -0
- package/dist/elements/editor/utils/is-single-root-editor.d.ts.map +1 -0
- package/dist/elements/editor/utils/load-editor-constructor.d.ts +9 -0
- package/dist/elements/editor/utils/load-editor-constructor.d.ts.map +1 -0
- package/dist/elements/editor/utils/load-editor-plugins.d.ts +20 -0
- package/dist/elements/editor/utils/load-editor-plugins.d.ts.map +1 -0
- package/dist/elements/editor/utils/load-editor-translations.d.ts +14 -0
- package/dist/elements/editor/utils/load-editor-translations.d.ts.map +1 -0
- package/dist/elements/editor/utils/normalize-custom-translations.d.ts +11 -0
- package/dist/elements/editor/utils/normalize-custom-translations.d.ts.map +1 -0
- package/dist/elements/editor/utils/query-all-editor-ids.d.ts +5 -0
- package/dist/elements/editor/utils/query-all-editor-ids.d.ts.map +1 -0
- package/dist/elements/editor/utils/query-editor-editables.d.ts +25 -0
- package/dist/elements/editor/utils/query-editor-editables.d.ts.map +1 -0
- package/dist/elements/editor/utils/resolve-editor-config-elements-references.d.ts +9 -0
- package/dist/elements/editor/utils/resolve-editor-config-elements-references.d.ts.map +1 -0
- package/dist/elements/editor/utils/set-editor-editable-height.d.ts +9 -0
- package/dist/elements/editor/utils/set-editor-editable-height.d.ts.map +1 -0
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts +24 -0
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts.map +1 -0
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/register-custom-elements.d.ts +5 -0
- package/dist/elements/register-custom-elements.d.ts.map +1 -0
- package/dist/elements/ui-part.d.ts +18 -0
- package/dist/elements/ui-part.d.ts.map +1 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1089 -0
- package/dist/index.mjs.map +1 -0
- package/dist/shared/async-registry.d.ts +136 -0
- package/dist/shared/async-registry.d.ts.map +1 -0
- package/dist/shared/camel-case.d.ts +8 -0
- package/dist/shared/camel-case.d.ts.map +1 -0
- package/dist/shared/debounce.d.ts +2 -0
- package/dist/shared/debounce.d.ts.map +1 -0
- package/dist/shared/deep-camel-case-keys.d.ts +8 -0
- package/dist/shared/deep-camel-case-keys.d.ts.map +1 -0
- package/dist/shared/filter-object-values.d.ts +9 -0
- package/dist/shared/filter-object-values.d.ts.map +1 -0
- package/dist/shared/index.d.ts +15 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/is-empty-object.d.ts +2 -0
- package/dist/shared/is-empty-object.d.ts.map +1 -0
- package/dist/shared/is-plain-object.d.ts +8 -0
- package/dist/shared/is-plain-object.d.ts.map +1 -0
- package/dist/shared/map-object-values.d.ts +11 -0
- package/dist/shared/map-object-values.d.ts.map +1 -0
- package/dist/shared/once.d.ts +2 -0
- package/dist/shared/once.d.ts.map +1 -0
- package/dist/shared/shallow-equal.d.ts +9 -0
- package/dist/shared/shallow-equal.d.ts.map +1 -0
- package/dist/shared/timeout.d.ts +8 -0
- package/dist/shared/timeout.d.ts.map +1 -0
- package/dist/shared/uid.d.ts +7 -0
- package/dist/shared/uid.d.ts.map +1 -0
- package/dist/shared/wait-for-dom-ready.d.ts +5 -0
- package/dist/shared/wait-for-dom-ready.d.ts.map +1 -0
- package/dist/shared/wait-for.d.ts +20 -0
- package/dist/shared/wait-for.d.ts.map +1 -0
- package/dist/types/can-be-promise.type.d.ts +2 -0
- package/dist/types/can-be-promise.type.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/required-by.type.d.ts +2 -0
- package/dist/types/required-by.type.d.ts.map +1 -0
- package/package.json +40 -0
- package/src/ckeditor5-symfony-error.ts +9 -0
- package/src/elements/context/context.test.ts +291 -0
- package/src/elements/context/context.ts +99 -0
- package/src/elements/context/contexts-registry.test.ts +10 -0
- package/src/elements/context/contexts-registry.ts +10 -0
- package/src/elements/context/index.ts +3 -0
- package/src/elements/context/typings.ts +39 -0
- package/src/elements/editable.test.ts +334 -0
- package/src/elements/editable.ts +114 -0
- package/src/elements/editor/custom-editor-plugins.test.ts +103 -0
- package/src/elements/editor/custom-editor-plugins.ts +86 -0
- package/src/elements/editor/editor.test.ts +438 -0
- package/src/elements/editor/editor.ts +279 -0
- package/src/elements/editor/editors-registry.test.ts +10 -0
- package/src/elements/editor/editors-registry.ts +10 -0
- package/src/elements/editor/index.ts +2 -0
- package/src/elements/editor/plugins/index.ts +1 -0
- package/src/elements/editor/plugins/sync-editor-with-input.ts +78 -0
- package/src/elements/editor/typings.ts +114 -0
- package/src/elements/editor/utils/create-editor-in-context.ts +90 -0
- package/src/elements/editor/utils/index.ts +11 -0
- package/src/elements/editor/utils/is-single-root-editor.test.ts +40 -0
- package/src/elements/editor/utils/is-single-root-editor.ts +11 -0
- package/src/elements/editor/utils/load-editor-constructor.test.ts +62 -0
- package/src/elements/editor/utils/load-editor-constructor.ts +29 -0
- package/src/elements/editor/utils/load-editor-plugins.test.ts +100 -0
- package/src/elements/editor/utils/load-editor-plugins.ts +73 -0
- package/src/elements/editor/utils/load-editor-translations.ts +233 -0
- package/src/elements/editor/utils/normalize-custom-translations.test.ts +152 -0
- package/src/elements/editor/utils/normalize-custom-translations.ts +18 -0
- package/src/elements/editor/utils/query-all-editor-ids.ts +9 -0
- package/src/elements/editor/utils/query-editor-editables.ts +101 -0
- package/src/elements/editor/utils/resolve-editor-config-elements-references.test.ts +93 -0
- package/src/elements/editor/utils/resolve-editor-config-elements-references.ts +36 -0
- package/src/elements/editor/utils/set-editor-editable-height.test.ts +131 -0
- package/src/elements/editor/utils/set-editor-editable-height.ts +15 -0
- package/src/elements/editor/utils/wrap-with-watchdog.test.ts +45 -0
- package/src/elements/editor/utils/wrap-with-watchdog.ts +51 -0
- package/src/elements/index.ts +14 -0
- package/src/elements/register-custom-elements.ts +24 -0
- package/src/elements/ui-part.test.ts +142 -0
- package/src/elements/ui-part.ts +80 -0
- package/src/index.ts +6 -0
- package/src/shared/async-registry.test.ts +737 -0
- package/src/shared/async-registry.ts +353 -0
- package/src/shared/camel-case.test.ts +35 -0
- package/src/shared/camel-case.ts +11 -0
- package/src/shared/debounce.test.ts +72 -0
- package/src/shared/debounce.ts +16 -0
- package/src/shared/deep-camel-case-keys.test.ts +34 -0
- package/src/shared/deep-camel-case-keys.ts +26 -0
- package/src/shared/filter-object-values.test.ts +25 -0
- package/src/shared/filter-object-values.ts +17 -0
- package/src/shared/index.ts +14 -0
- package/src/shared/is-empty-object.test.ts +78 -0
- package/src/shared/is-empty-object.ts +3 -0
- package/src/shared/is-plain-object.test.ts +38 -0
- package/src/shared/is-plain-object.ts +15 -0
- package/src/shared/map-object-values.test.ts +29 -0
- package/src/shared/map-object-values.ts +19 -0
- package/src/shared/once.test.ts +116 -0
- package/src/shared/once.ts +12 -0
- package/src/shared/shallow-equal.test.ts +51 -0
- package/src/shared/shallow-equal.ts +30 -0
- package/src/shared/timeout.test.ts +65 -0
- package/src/shared/timeout.ts +13 -0
- package/src/shared/uid.test.ts +25 -0
- package/src/shared/uid.ts +8 -0
- package/src/shared/wait-for-dom-ready.test.ts +87 -0
- package/src/shared/wait-for-dom-ready.ts +21 -0
- package/src/shared/wait-for.test.ts +24 -0
- package/src/shared/wait-for.ts +56 -0
- package/src/types/can-be-promise.type.ts +1 -0
- package/src/types/index.ts +2 -0
- package/src/types/required-by.type.ts +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ckeditor5-symfony",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CKEditor 5 integration for Symfony",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/index.cjs",
|
|
13
|
+
"module": "dist/index.mjs",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"prepare": "pnpm run build",
|
|
22
|
+
"build": "pnpm run clean && vite build && node ./scripts/check-dist-imports.mjs",
|
|
23
|
+
"watch": "vite build --watch",
|
|
24
|
+
"dev": "vite build --watch",
|
|
25
|
+
"typecheck": "tsc --noEmit --pretty",
|
|
26
|
+
"test": "vitest --coverage",
|
|
27
|
+
"test:watch": "vitest --watch --coverage"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
31
|
+
"ckeditor5": "^45.2.2",
|
|
32
|
+
"ckeditor5-premium-features": "^45.2.1",
|
|
33
|
+
"happy-dom": "^20.0.1",
|
|
34
|
+
"typescript": "^5.5.4",
|
|
35
|
+
"vite": "^7.1.11",
|
|
36
|
+
"vite-plugin-dts": "^4.5.4",
|
|
37
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
38
|
+
"vitest": "^3.2.3"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { Context, ContextPlugin, ContextWatchdog } from 'ckeditor5';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createContextSnapshot,
|
|
6
|
+
DEFAULT_TEST_CONTEXT_ID,
|
|
7
|
+
renderTestContext,
|
|
8
|
+
renderTestEditor,
|
|
9
|
+
waitForTestContext,
|
|
10
|
+
waitForTestEditor,
|
|
11
|
+
} from '~/test-utils';
|
|
12
|
+
|
|
13
|
+
import { timeout } from '../../shared';
|
|
14
|
+
import { CustomEditorPluginsRegistry } from '../editor/custom-editor-plugins';
|
|
15
|
+
import { EditorsRegistry } from '../editor/editors-registry';
|
|
16
|
+
import { registerCustomElements } from '../register-custom-elements';
|
|
17
|
+
import { ContextsRegistry } from './contexts-registry';
|
|
18
|
+
|
|
19
|
+
describe('context component', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
document.body.innerHTML = '';
|
|
22
|
+
registerCustomElements();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
CustomEditorPluginsRegistry.the.unregisterAll();
|
|
27
|
+
EditorsRegistry.the.reset();
|
|
28
|
+
ContextsRegistry.the.reset();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('mount', () => {
|
|
32
|
+
it('should save the context instance in the registry with provided id', async () => {
|
|
33
|
+
renderTestContext({ contextId: 'my-context' });
|
|
34
|
+
|
|
35
|
+
const watchdog = await ContextsRegistry.the.waitFor('my-context');
|
|
36
|
+
|
|
37
|
+
expect(watchdog).toBeInstanceOf(ContextWatchdog);
|
|
38
|
+
expect(watchdog.context).toBeInstanceOf(Context);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should initialize context with empty creator config', async () => {
|
|
42
|
+
renderTestContext(createContextSnapshot(DEFAULT_TEST_CONTEXT_ID, {
|
|
43
|
+
config: {},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
expect(await waitForTestContext()).toBeInstanceOf(ContextWatchdog);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should print console error if `itemError` event is fired', async () => {
|
|
50
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
51
|
+
|
|
52
|
+
renderTestContext();
|
|
53
|
+
|
|
54
|
+
const watchdog = await waitForTestContext();
|
|
55
|
+
|
|
56
|
+
(watchdog as any)._fire('itemError', 'test-error', { some: 'data' });
|
|
57
|
+
|
|
58
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Context item error:', null, 'test-error', { some: 'data' });
|
|
59
|
+
|
|
60
|
+
consoleErrorSpy.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should initialize custom plugins passed to context', async () => {
|
|
64
|
+
class CustomPlugin extends ContextPlugin {
|
|
65
|
+
static get pluginName() {
|
|
66
|
+
return 'CustomPlugin';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
|
|
71
|
+
|
|
72
|
+
renderTestContext(createContextSnapshot(DEFAULT_TEST_CONTEXT_ID, {
|
|
73
|
+
config: {
|
|
74
|
+
plugins: ['CustomPlugin'],
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
const { context } = await waitForTestContext();
|
|
79
|
+
|
|
80
|
+
expect(context?.plugins.get('CustomPlugin')).toBeInstanceOf(CustomPlugin);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('registered plugins should support custom translations', async () => {
|
|
84
|
+
class CustomPlugin extends ContextPlugin {
|
|
85
|
+
static get pluginName() {
|
|
86
|
+
return 'CustomPlugin';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getHelloTitle() {
|
|
90
|
+
return this.context.t('HELLO');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
|
|
95
|
+
|
|
96
|
+
renderTestContext(createContextSnapshot(DEFAULT_TEST_CONTEXT_ID, {
|
|
97
|
+
customTranslations: {
|
|
98
|
+
en: {
|
|
99
|
+
HELLO: 'Hello from CustomPlugin',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
config: {
|
|
103
|
+
plugins: ['CustomPlugin'],
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const { context } = await waitForTestContext();
|
|
108
|
+
const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
|
|
109
|
+
|
|
110
|
+
expect(plugin.getHelloTitle()).toBe('Hello from CustomPlugin');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should support custom language for context translations', async () => {
|
|
114
|
+
class CustomPlugin extends ContextPlugin {
|
|
115
|
+
static get pluginName() {
|
|
116
|
+
return 'CustomPlugin';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getHelloTitle() {
|
|
120
|
+
return this.context.t('HELLO');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
CustomEditorPluginsRegistry.the.register('CustomPlugin', () => CustomPlugin);
|
|
125
|
+
|
|
126
|
+
renderTestContext(createContextSnapshot(
|
|
127
|
+
DEFAULT_TEST_CONTEXT_ID,
|
|
128
|
+
{
|
|
129
|
+
customTranslations: {
|
|
130
|
+
en: {
|
|
131
|
+
HELLO: 'Hello from CustomPlugin',
|
|
132
|
+
},
|
|
133
|
+
pl: {
|
|
134
|
+
HELLO: 'Witaj z CustomPlugin',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
config: {
|
|
138
|
+
plugins: ['CustomPlugin'],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
ui: 'pl',
|
|
143
|
+
content: 'pl',
|
|
144
|
+
},
|
|
145
|
+
));
|
|
146
|
+
|
|
147
|
+
const { context } = await waitForTestContext();
|
|
148
|
+
const plugin = context?.plugins.get('CustomPlugin') as CustomPlugin;
|
|
149
|
+
|
|
150
|
+
expect(plugin.getHelloTitle()).toBe('Witaj z CustomPlugin');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('attaching editor', () => {
|
|
155
|
+
it('should not attach editor to the context (editor has no specified context, context initialized)', async () => {
|
|
156
|
+
renderTestContext();
|
|
157
|
+
|
|
158
|
+
const { context } = await waitForTestContext();
|
|
159
|
+
|
|
160
|
+
renderTestEditor();
|
|
161
|
+
|
|
162
|
+
await waitForTestEditor();
|
|
163
|
+
|
|
164
|
+
expect(context?.editors.first).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should attach editor to the context (editor has specified context, context initialized)', async () => {
|
|
168
|
+
renderTestContext();
|
|
169
|
+
|
|
170
|
+
const { context } = await waitForTestContext();
|
|
171
|
+
|
|
172
|
+
renderTestEditor({
|
|
173
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const editor = await waitForTestEditor();
|
|
177
|
+
|
|
178
|
+
expect(context?.editors.first).toEqual(editor);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should pause editor initialization when context is not yet initialized', async () => {
|
|
182
|
+
renderTestEditor({
|
|
183
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await timeout(50);
|
|
187
|
+
|
|
188
|
+
renderTestContext();
|
|
189
|
+
|
|
190
|
+
const { context } = await waitForTestContext();
|
|
191
|
+
const editor = await waitForTestEditor();
|
|
192
|
+
|
|
193
|
+
expect(context?.editors.first).toEqual(editor);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should be possible to attach multiple editors to the same context', async () => {
|
|
197
|
+
renderTestContext();
|
|
198
|
+
|
|
199
|
+
const { context } = await waitForTestContext();
|
|
200
|
+
|
|
201
|
+
renderTestEditor({
|
|
202
|
+
editorId: 'editor-1',
|
|
203
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
renderTestEditor({
|
|
207
|
+
editorId: 'editor-2',
|
|
208
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const editors = await Promise.all([
|
|
212
|
+
waitForTestEditor('editor-1'),
|
|
213
|
+
waitForTestEditor('editor-2'),
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
expect(context?.editors.length).toBe(2);
|
|
217
|
+
expect([...context!.editors]).toEqual(editors);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('destroy', () => {
|
|
222
|
+
it('destroyed editor is removed from the context editors collection', async () => {
|
|
223
|
+
renderTestContext();
|
|
224
|
+
|
|
225
|
+
const { context } = await waitForTestContext();
|
|
226
|
+
|
|
227
|
+
const editorEl = renderTestEditor({
|
|
228
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const editor = await waitForTestEditor();
|
|
232
|
+
|
|
233
|
+
expect(context?.editors.first).toEqual(editor);
|
|
234
|
+
|
|
235
|
+
editorEl.remove();
|
|
236
|
+
|
|
237
|
+
await expect.poll(() => context?.editors.first).toBeNull();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should remove the context instance from the registry on destroy', async () => {
|
|
241
|
+
const el = renderTestContext({ contextId: 'my-context' });
|
|
242
|
+
|
|
243
|
+
const watchdog = await ContextsRegistry.the.waitFor('my-context');
|
|
244
|
+
|
|
245
|
+
expect(watchdog).toBeInstanceOf(ContextWatchdog);
|
|
246
|
+
|
|
247
|
+
el.remove();
|
|
248
|
+
|
|
249
|
+
await expect.poll(() => ContextsRegistry.the.hasItem('my-context')).toBe(false);
|
|
250
|
+
expect(watchdog.state).toBe('destroyed');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should hide element during destruction', async () => {
|
|
254
|
+
const el = renderTestContext();
|
|
255
|
+
|
|
256
|
+
el.remove();
|
|
257
|
+
|
|
258
|
+
expect(el.style.display).toBe('none');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should handle destruction when mounted promise is not resolved yet', async () => {
|
|
262
|
+
const el = renderTestContext();
|
|
263
|
+
|
|
264
|
+
el.remove();
|
|
265
|
+
|
|
266
|
+
expect(el.style.display).toBe('none');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('destroying context should destroy all attached editors', async () => {
|
|
270
|
+
const contextEl = renderTestContext();
|
|
271
|
+
|
|
272
|
+
await waitForTestContext();
|
|
273
|
+
|
|
274
|
+
const editorEl = renderTestEditor({
|
|
275
|
+
contextId: DEFAULT_TEST_CONTEXT_ID,
|
|
276
|
+
});
|
|
277
|
+
const editorId = editorEl.getAttribute('data-cke-editor-id')!;
|
|
278
|
+
|
|
279
|
+
const editor = await waitForTestEditor();
|
|
280
|
+
|
|
281
|
+
expect(editor.state).toBe('ready');
|
|
282
|
+
|
|
283
|
+
contextEl.remove();
|
|
284
|
+
|
|
285
|
+
await expect.poll(() => editor.state).toBe('destroyed');
|
|
286
|
+
|
|
287
|
+
expect(ContextsRegistry.the.hasItem(DEFAULT_TEST_CONTEXT_ID)).toBe(false);
|
|
288
|
+
await expect.poll(() => EditorsRegistry.the.hasItem(editorId)).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Context, ContextWatchdog } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import type { EditorLanguage } from '../editor';
|
|
4
|
+
import type { ContextConfig } from './typings';
|
|
5
|
+
|
|
6
|
+
import { isEmptyObject, waitForDOMReady } from '../../shared';
|
|
7
|
+
import {
|
|
8
|
+
loadAllEditorTranslations,
|
|
9
|
+
loadEditorPlugins,
|
|
10
|
+
normalizeCustomTranslations,
|
|
11
|
+
} from '../editor/utils';
|
|
12
|
+
import { ContextsRegistry } from './contexts-registry';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The Symfony hook that mounts CKEditor context instances.
|
|
16
|
+
*/
|
|
17
|
+
export class ContextComponentElement extends HTMLElement {
|
|
18
|
+
/**
|
|
19
|
+
* The promise that resolves to the context instance.
|
|
20
|
+
*/
|
|
21
|
+
private contextPromise: Promise<ContextWatchdog<Context>> | null = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mounts the context component.
|
|
25
|
+
*/
|
|
26
|
+
async connectedCallback() {
|
|
27
|
+
await waitForDOMReady();
|
|
28
|
+
|
|
29
|
+
const contextId = this.getAttribute('data-cke-context-id')!;
|
|
30
|
+
const language = JSON.parse(this.getAttribute('data-cke-language')!) as EditorLanguage;
|
|
31
|
+
const contextConfig = JSON.parse(this.getAttribute('data-cke-context')!) as ContextConfig;
|
|
32
|
+
|
|
33
|
+
const { customTranslations, watchdogConfig, config: { plugins, ...config } } = contextConfig;
|
|
34
|
+
const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins ?? []);
|
|
35
|
+
|
|
36
|
+
// Mix custom translations with loaded translations.
|
|
37
|
+
const loadedTranslations = await loadAllEditorTranslations(language, hasPremium);
|
|
38
|
+
const mixedTranslations = [
|
|
39
|
+
...loadedTranslations,
|
|
40
|
+
normalizeCustomTranslations(customTranslations || {}),
|
|
41
|
+
]
|
|
42
|
+
.filter(translations => !isEmptyObject(translations));
|
|
43
|
+
|
|
44
|
+
// Initialize context with watchdog.
|
|
45
|
+
this.contextPromise = (async () => {
|
|
46
|
+
const { ContextWatchdog, Context } = await import('ckeditor5');
|
|
47
|
+
|
|
48
|
+
const instance = new ContextWatchdog(Context, {
|
|
49
|
+
crashNumberLimit: 10,
|
|
50
|
+
...watchdogConfig,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await instance.create({
|
|
54
|
+
...config,
|
|
55
|
+
language,
|
|
56
|
+
plugins: loadedPlugins,
|
|
57
|
+
...mixedTranslations.length && {
|
|
58
|
+
translations: mixedTranslations,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
instance.on('itemError', (...args) => {
|
|
63
|
+
console.error('Context item error:', ...args);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return instance;
|
|
67
|
+
})();
|
|
68
|
+
|
|
69
|
+
const context = await this.contextPromise;
|
|
70
|
+
|
|
71
|
+
if (this.isConnected) {
|
|
72
|
+
ContextsRegistry.the.register(contextId, context);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Destroys the context component. Unmounts root from the editor.
|
|
78
|
+
*/
|
|
79
|
+
async disconnectedCallback() {
|
|
80
|
+
const contextId = this.getAttribute('data-cke-context-id');
|
|
81
|
+
|
|
82
|
+
// Let's hide the element during destruction to prevent flickering.
|
|
83
|
+
this.style.display = 'none';
|
|
84
|
+
|
|
85
|
+
// Let's wait for the mounted promise to resolve before proceeding with destruction.
|
|
86
|
+
try {
|
|
87
|
+
const context = await this.contextPromise;
|
|
88
|
+
|
|
89
|
+
await context?.destroy();
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
this.contextPromise = null;
|
|
93
|
+
|
|
94
|
+
if (contextId && ContextsRegistry.the.hasItem(contextId)) {
|
|
95
|
+
ContextsRegistry.the.unregister(contextId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { AsyncRegistry } from '../../shared/async-registry';
|
|
4
|
+
import { ContextsRegistry } from './contexts-registry';
|
|
5
|
+
|
|
6
|
+
describe('contexts registry', () => {
|
|
7
|
+
it('should be singleton of async registry', () => {
|
|
8
|
+
expect(ContextsRegistry.the).toBeInstanceOf(AsyncRegistry);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context, ContextWatchdog } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import { AsyncRegistry } from '../../shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* It provides a way to register contexts and execute callbacks on them when they are available.
|
|
7
|
+
*/
|
|
8
|
+
export class ContextsRegistry extends AsyncRegistry<ContextWatchdog<Context>> {
|
|
9
|
+
static readonly the = new ContextsRegistry();
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WatchdogConfig } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import type { EditorCustomTranslationsDictionary, EditorPlugin } from '../editor';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration object for CKEditor5 context instance.
|
|
7
|
+
*/
|
|
8
|
+
export type ContextConfig = {
|
|
9
|
+
/**
|
|
10
|
+
* Optional custom translations for the editor.
|
|
11
|
+
* This allows for localization of the editor interface.
|
|
12
|
+
*/
|
|
13
|
+
customTranslations?: EditorCustomTranslationsDictionary | null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Watchdog configuration for the context.
|
|
17
|
+
*/
|
|
18
|
+
watchdogConfig: WatchdogConfig | null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for the context instance.
|
|
22
|
+
*/
|
|
23
|
+
config: ContextCreatorConfig;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration options for the context instance.
|
|
28
|
+
*/
|
|
29
|
+
export type ContextCreatorConfig = {
|
|
30
|
+
/**
|
|
31
|
+
* Array of plugin identifiers to be loaded by the editor.
|
|
32
|
+
*/
|
|
33
|
+
plugins?: EditorPlugin[];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Other configuration options are flexible and can be any key-value pairs.
|
|
37
|
+
*/
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
};
|