@dotcms/angular 0.0.1-alpha.40 → 0.0.1-alpha.41
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/README.md +10 -10
- package/dotcms-angular.d.ts.map +1 -0
- package/esm2022/dotcms-angular.mjs +5 -0
- package/esm2022/index.mjs +5 -0
- package/esm2022/lib/components/dot-editable-text/dot-editable-text.component.mjs +225 -0
- package/esm2022/lib/components/dot-editable-text/utils.mjs +43 -0
- package/esm2022/lib/components/no-component/no-component.component.mjs +32 -0
- package/esm2022/lib/layout/column/column.component.mjs +45 -0
- package/esm2022/lib/layout/container/container.component.mjs +126 -0
- package/esm2022/lib/layout/contentlet/contentlet.component.mjs +120 -0
- package/esm2022/lib/layout/dotcms-layout/dotcms-layout.component.mjs +100 -0
- package/esm2022/lib/layout/row/row.component.mjs +29 -0
- package/esm2022/lib/models/dotcms.model.mjs +3 -0
- package/esm2022/lib/models/index.mjs +3 -0
- package/esm2022/lib/services/dotcms-context/page-context.service.mjs +75 -0
- package/esm2022/lib/utils/index.mjs +79 -0
- package/fesm2022/dotcms-angular.mjs +858 -0
- package/fesm2022/dotcms-angular.mjs.map +1 -0
- package/{src/index.ts → index.d.ts} +1 -0
- package/index.d.ts.map +1 -0
- package/lib/components/dot-editable-text/dot-editable-text.component.d.ts +129 -0
- package/lib/components/dot-editable-text/dot-editable-text.component.d.ts.map +1 -0
- package/lib/components/dot-editable-text/utils.d.ts +7 -0
- package/lib/components/dot-editable-text/utils.d.ts.map +1 -0
- package/lib/components/no-component/no-component.component.d.ts +22 -0
- package/lib/components/no-component/no-component.component.d.ts.map +1 -0
- package/lib/layout/column/column.component.d.ts +29 -0
- package/lib/layout/column/column.component.d.ts.map +1 -0
- package/lib/layout/container/container.component.d.ts +88 -0
- package/lib/layout/container/container.component.d.ts.map +1 -0
- package/{src/lib/layout/contentlet/contentlet.component.ts → lib/layout/contentlet/contentlet.component.d.ts} +17 -32
- package/lib/layout/contentlet/contentlet.component.d.ts.map +1 -0
- package/lib/layout/dotcms-layout/dotcms-layout.component.d.ts +67 -0
- package/lib/layout/dotcms-layout/dotcms-layout.component.d.ts.map +1 -0
- package/lib/layout/row/row.component.d.ts +20 -0
- package/lib/layout/row/row.component.d.ts.map +1 -0
- package/{src/lib/models/dotcms.model.ts → lib/models/dotcms.model.d.ts} +3 -21
- package/lib/models/dotcms.model.d.ts.map +1 -0
- package/{src/lib/models/index.ts → lib/models/index.d.ts} +1 -8
- package/lib/models/index.d.ts.map +1 -0
- package/{src/lib/services/dotcms-context/page-context.service.ts → lib/services/dotcms-context/page-context.service.d.ts} +12 -41
- package/lib/services/dotcms-context/page-context.service.d.ts.map +1 -0
- package/lib/utils/index.d.ts +63 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/package.json +22 -5
- package/.eslintrc.json +0 -18
- package/jest.config.ts +0 -22
- package/ng-package.json +0 -7
- package/project.json +0 -33
- package/src/lib/components/dot-editable-text/dot-editable-text.component.css +0 -4
- package/src/lib/components/dot-editable-text/dot-editable-text.component.html +0 -8
- package/src/lib/components/dot-editable-text/dot-editable-text.component.spec.ts +0 -424
- package/src/lib/components/dot-editable-text/dot-editable-text.component.ts +0 -269
- package/src/lib/components/dot-editable-text/utils.ts +0 -51
- package/src/lib/components/no-component/no-component.component.css +0 -3
- package/src/lib/components/no-component/no-component.component.spec.ts +0 -24
- package/src/lib/components/no-component/no-component.component.ts +0 -31
- package/src/lib/layout/column/column.component.css +0 -99
- package/src/lib/layout/column/column.component.spec.ts +0 -33
- package/src/lib/layout/column/column.component.ts +0 -49
- package/src/lib/layout/container/container.component.css +0 -9
- package/src/lib/layout/container/container.component.html +0 -26
- package/src/lib/layout/container/container.component.spec.ts +0 -205
- package/src/lib/layout/container/container.component.ts +0 -140
- package/src/lib/layout/contentlet/contentlet.component.spec.ts +0 -22
- package/src/lib/layout/dotcms-layout/dotcms-layout.component.css +0 -3
- package/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts +0 -195
- package/src/lib/layout/dotcms-layout/dotcms-layout.component.ts +0 -150
- package/src/lib/layout/row/row.component.css +0 -6
- package/src/lib/layout/row/row.component.spec.ts +0 -28
- package/src/lib/layout/row/row.component.ts +0 -32
- package/src/lib/services/dotcms-context/page-context.spec.ts +0 -80
- package/src/lib/utils/index.ts +0 -92
- package/src/lib/utils/testing.utils.ts +0 -1019
- package/src/test-setup.ts +0 -8
- package/tsconfig.json +0 -29
- package/tsconfig.lib.json +0 -12
- package/tsconfig.lib.prod.json +0 -9
- package/tsconfig.spec.json +0 -11
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import { SpyObject } from '@ngneat/spectator';
|
|
2
|
-
import { Spectator, createComponentFactory } from '@ngneat/spectator/jest';
|
|
3
|
-
import { EditorComponent, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
|
|
4
|
-
import { MockComponent } from 'ng-mocks';
|
|
5
|
-
import { Editor } from 'tinymce';
|
|
6
|
-
|
|
7
|
-
import { DebugElement, ElementRef, Renderer2, SecurityContext } from '@angular/core';
|
|
8
|
-
import { By, DomSanitizer } from '@angular/platform-browser';
|
|
9
|
-
|
|
10
|
-
import * as dotcmsClient from '@dotcms/client';
|
|
11
|
-
|
|
12
|
-
import { DotEditableTextComponent } from './dot-editable-text.component';
|
|
13
|
-
import { TINYMCE_CONFIG } from './utils';
|
|
14
|
-
|
|
15
|
-
import { dotcmsContentletMock } from '../../utils/testing.utils';
|
|
16
|
-
|
|
17
|
-
const { CUSTOMER_ACTIONS, postMessageToEditor } = dotcmsClient;
|
|
18
|
-
|
|
19
|
-
// Mock @dotcms/client module
|
|
20
|
-
jest.mock('@dotcms/client', () => ({
|
|
21
|
-
...jest.requireActual('@dotcms/client'),
|
|
22
|
-
isInsideEditor: jest.fn().mockImplementation(() => true),
|
|
23
|
-
postMessageToEditor: jest.fn(),
|
|
24
|
-
DotCmsClient: {
|
|
25
|
-
dotcmsUrl: 'http://localhost:8080'
|
|
26
|
-
}
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
const mockedDotcmsClient = dotcmsClient as jest.Mocked<typeof dotcmsClient>;
|
|
30
|
-
|
|
31
|
-
const TINYMCE_EDITOR_MOCK: unknown = {
|
|
32
|
-
focus: jest.fn(),
|
|
33
|
-
getContent: (_data: unknown) => '',
|
|
34
|
-
isDirty: () => false,
|
|
35
|
-
hasFocus: () => false,
|
|
36
|
-
setContent: jest.fn()
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const TINYMCE_EDITOR_PROPERTY_MOCK = {
|
|
40
|
-
get: jest.fn(() => TINYMCE_EDITOR_MOCK as Editor)
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const mockEditorFn = (spectator: Spectator<DotEditableTextComponent>) => {
|
|
44
|
-
// Mock the editor property of the EditorComponent
|
|
45
|
-
// We need to test that the methods of the editor are called
|
|
46
|
-
// We do not care about how the editor handles the calls under the hood
|
|
47
|
-
Object.defineProperty(
|
|
48
|
-
spectator.component.editorComponent,
|
|
49
|
-
'editor',
|
|
50
|
-
TINYMCE_EDITOR_PROPERTY_MOCK
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
describe('DotEditableTextComponent', () => {
|
|
55
|
-
let spectator: Spectator<DotEditableTextComponent>;
|
|
56
|
-
|
|
57
|
-
const createComponent = createComponentFactory({
|
|
58
|
-
component: DotEditableTextComponent,
|
|
59
|
-
declarations: [MockComponent(EditorComponent)],
|
|
60
|
-
providers: [
|
|
61
|
-
Renderer2,
|
|
62
|
-
{
|
|
63
|
-
provide: DomSanitizer,
|
|
64
|
-
useValue: {
|
|
65
|
-
bypassSecurityTrustHtml: () => '',
|
|
66
|
-
sanitize: () => ''
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
provide: ElementRef,
|
|
71
|
-
useValue: {
|
|
72
|
-
nativeElement: document.createElement('div')
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
provide: TINYMCE_SCRIPT_SRC,
|
|
77
|
-
useValue: 'tinymce/tinymce.min.js'
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
beforeEach(() => {
|
|
83
|
-
spectator = createComponent({
|
|
84
|
-
props: {
|
|
85
|
-
contentlet: dotcmsContentletMock,
|
|
86
|
-
fieldName: 'title'
|
|
87
|
-
},
|
|
88
|
-
detectChanges: false
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('Outside Editor', () => {
|
|
93
|
-
let renderer2: SpyObject<Renderer2>;
|
|
94
|
-
let elementRef: SpyObject<ElementRef>;
|
|
95
|
-
let sanitizer: SpyObject<DomSanitizer>;
|
|
96
|
-
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
jest.spyOn(mockedDotcmsClient, 'isInsideEditor').mockReturnValue(false);
|
|
99
|
-
renderer2 = spectator.inject(Renderer2, true);
|
|
100
|
-
elementRef = spectator.inject(ElementRef, true);
|
|
101
|
-
sanitizer = spectator.inject(DomSanitizer, true);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe('Template', () => {
|
|
105
|
-
it('Should insert safe HTML content using innerHTML', () => {
|
|
106
|
-
const safeHtml = dotcmsContentletMock.title + 'Safe';
|
|
107
|
-
const spyRender2 = jest.spyOn(renderer2, 'setProperty');
|
|
108
|
-
const spybypassSecurityTrustHtml = jest
|
|
109
|
-
.spyOn(sanitizer, 'bypassSecurityTrustHtml')
|
|
110
|
-
.mockReturnValue(safeHtml);
|
|
111
|
-
const spySanitze = jest.spyOn(sanitizer, 'sanitize').mockReturnValue(safeHtml);
|
|
112
|
-
|
|
113
|
-
spectator.detectChanges();
|
|
114
|
-
|
|
115
|
-
const nativeElement = elementRef.nativeElement;
|
|
116
|
-
const componentHTML = spectator.debugElement.nativeElement.innerHTML;
|
|
117
|
-
|
|
118
|
-
expect(componentHTML).toBe(safeHtml);
|
|
119
|
-
expect(spybypassSecurityTrustHtml).toHaveBeenCalledWith(dotcmsContentletMock.title);
|
|
120
|
-
expect(spySanitze).toHaveBeenCalledWith(SecurityContext.HTML, safeHtml);
|
|
121
|
-
expect(spyRender2).toHaveBeenCalledWith(nativeElement, 'innerHTML', safeHtml);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('Should not initialize the editor', () => {
|
|
125
|
-
spectator.detectChanges();
|
|
126
|
-
|
|
127
|
-
const editorComponent = spectator.query(EditorComponent);
|
|
128
|
-
expect(editorComponent).toBeNull();
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('Inside Editor', () => {
|
|
134
|
-
beforeEach(() => {
|
|
135
|
-
jest.spyOn(mockedDotcmsClient, 'isInsideEditor').mockReturnValue(true);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should set content with the right format when the contentlet changes', () => {
|
|
139
|
-
spectator.detectChanges();
|
|
140
|
-
mockEditorFn(spectator);
|
|
141
|
-
|
|
142
|
-
const editorComponent = spectator.query(EditorComponent) as EditorComponent;
|
|
143
|
-
const spySetContent = jest.spyOn(editorComponent.editor, 'setContent');
|
|
144
|
-
|
|
145
|
-
spectator.setInput('contentlet', {
|
|
146
|
-
...dotcmsContentletMock,
|
|
147
|
-
title: 'New title'
|
|
148
|
-
});
|
|
149
|
-
spectator.detectChanges();
|
|
150
|
-
expect(spySetContent).toHaveBeenCalledWith('New title', { format: 'text' });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe('Configuration', () => {
|
|
154
|
-
describe('Editor Configuration', () => {
|
|
155
|
-
it('should set a plain mode by default', () => {
|
|
156
|
-
spectator.setInput('mode', 'plain');
|
|
157
|
-
spectator.detectChanges();
|
|
158
|
-
const editorComponent = spectator.query(EditorComponent);
|
|
159
|
-
|
|
160
|
-
spectator.detectChanges();
|
|
161
|
-
expect(spectator.component.mode).toBe('plain');
|
|
162
|
-
expect(editorComponent?.init).toEqual({
|
|
163
|
-
...TINYMCE_CONFIG['plain'],
|
|
164
|
-
base_url: 'http://localhost:8080/ext/tinymcev7'
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should set a minimal mode when the mode is set to minimal', () => {
|
|
169
|
-
spectator.setInput('mode', 'minimal');
|
|
170
|
-
spectator.detectChanges();
|
|
171
|
-
|
|
172
|
-
const editorComponent = spectator.query(EditorComponent);
|
|
173
|
-
|
|
174
|
-
expect(spectator.component.mode).toBe('minimal');
|
|
175
|
-
expect(editorComponent?.init).toEqual({
|
|
176
|
-
...TINYMCE_CONFIG['minimal'],
|
|
177
|
-
base_url: 'http://localhost:8080/ext/tinymcev7'
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('should set a full mode when the mode is set to full', () => {
|
|
182
|
-
spectator.setInput('mode', 'full');
|
|
183
|
-
spectator.detectChanges();
|
|
184
|
-
|
|
185
|
-
const editorComponent = spectator.query(EditorComponent);
|
|
186
|
-
|
|
187
|
-
expect(spectator.component.mode).toBe('full');
|
|
188
|
-
expect(editorComponent?.init).toEqual({
|
|
189
|
-
...TINYMCE_CONFIG['full'],
|
|
190
|
-
base_url: 'http://localhost:8080/ext/tinymcev7'
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
describe('format', () => {
|
|
196
|
-
let getContentSpy: jest.SpyInstance;
|
|
197
|
-
let editorDebugElement: DebugElement;
|
|
198
|
-
let customEvent: {
|
|
199
|
-
event: FocusEvent;
|
|
200
|
-
editor: unknown;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
beforeEach(() => {
|
|
204
|
-
spectator.detectChanges();
|
|
205
|
-
mockEditorFn(spectator);
|
|
206
|
-
|
|
207
|
-
const editor = spectator.component.editorComponent.editor;
|
|
208
|
-
getContentSpy = jest.spyOn(editor, 'getContent');
|
|
209
|
-
customEvent = {
|
|
210
|
-
event: new FocusEvent('focusout'),
|
|
211
|
-
editor: TINYMCE_EDITOR_MOCK
|
|
212
|
-
};
|
|
213
|
-
editorDebugElement = spectator.debugElement.query(
|
|
214
|
-
By.directive(EditorComponent)
|
|
215
|
-
);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should get the content as text by default', () => {
|
|
219
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
220
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' });
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should get the content as text when format is set to text', () => {
|
|
224
|
-
spectator.setInput('format', 'text');
|
|
225
|
-
spectator.detectChanges();
|
|
226
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
227
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' });
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should get the content as html when format is set to html', () => {
|
|
231
|
-
spectator.setInput('format', 'html');
|
|
232
|
-
spectator.detectChanges();
|
|
233
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
234
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'html' });
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe('events', () => {
|
|
240
|
-
beforeEach(() => {
|
|
241
|
-
spectator.detectChanges();
|
|
242
|
-
mockEditorFn(spectator);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('Window Message', () => {
|
|
246
|
-
let focusSpy: jest.SpyInstance;
|
|
247
|
-
beforeEach(() => {
|
|
248
|
-
focusSpy = jest.spyOn(spectator.component.editorComponent.editor, 'focus');
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("should focus on the editor when the message is 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS'", () => {
|
|
252
|
-
window.dispatchEvent(
|
|
253
|
-
new MessageEvent('message', {
|
|
254
|
-
data: {
|
|
255
|
-
name: 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS',
|
|
256
|
-
payload: {
|
|
257
|
-
oldInode: dotcmsContentletMock.inode,
|
|
258
|
-
inode: dotcmsContentletMock.inode
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
);
|
|
263
|
-
spectator.detectChanges();
|
|
264
|
-
expect(focusSpy).toHaveBeenCalled();
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("should not focus on the editor when the message is not 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS'", () => {
|
|
268
|
-
window.dispatchEvent(
|
|
269
|
-
new MessageEvent('message', {
|
|
270
|
-
data: { name: 'ANOTHER_EVENT' }
|
|
271
|
-
})
|
|
272
|
-
);
|
|
273
|
-
spectator.detectChanges();
|
|
274
|
-
expect(focusSpy).not.toHaveBeenCalled();
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
describe('mousedown', () => {
|
|
279
|
-
let event: MouseEvent;
|
|
280
|
-
let editorDebugElement: DebugElement;
|
|
281
|
-
let customEvent: {
|
|
282
|
-
event: MouseEvent;
|
|
283
|
-
editor: unknown;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
beforeEach(() => {
|
|
287
|
-
spectator.setInput('contentlet', {
|
|
288
|
-
...dotcmsContentletMock,
|
|
289
|
-
onNumberOfPages: 2 // This will need to be overridden in tests where a different value is required
|
|
290
|
-
});
|
|
291
|
-
spectator.detectChanges();
|
|
292
|
-
|
|
293
|
-
event = new MouseEvent('mousedown');
|
|
294
|
-
customEvent = { event, editor: TINYMCE_EDITOR_MOCK };
|
|
295
|
-
editorDebugElement = spectator.debugElement.query(
|
|
296
|
-
By.directive(EditorComponent)
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
jest.spyOn(event, 'stopPropagation');
|
|
300
|
-
jest.spyOn(event, 'preventDefault');
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should postMessage the UVE if the content is in multiple pages', () => {
|
|
304
|
-
spectator.triggerEventHandler(editorDebugElement, 'onMouseDown', customEvent);
|
|
305
|
-
|
|
306
|
-
const payload = {
|
|
307
|
-
dataset: {
|
|
308
|
-
fieldName: 'title',
|
|
309
|
-
inode: dotcmsContentletMock.inode,
|
|
310
|
-
language: dotcmsContentletMock.languageId
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
expect(postMessageToEditor).toHaveBeenCalledWith({
|
|
315
|
-
action: CUSTOMER_ACTIONS.COPY_CONTENTLET_INLINE_EDITING,
|
|
316
|
-
payload
|
|
317
|
-
});
|
|
318
|
-
expect(event.stopPropagation).toHaveBeenCalled();
|
|
319
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('should not postMessage the UVE if the content is in a single page', () => {
|
|
323
|
-
spectator.setInput('contentlet', {
|
|
324
|
-
...dotcmsContentletMock,
|
|
325
|
-
onNumberOfPages: 1
|
|
326
|
-
});
|
|
327
|
-
spectator.detectChanges();
|
|
328
|
-
spectator.triggerEventHandler(editorDebugElement, 'onMouseDown', customEvent);
|
|
329
|
-
expect(postMessageToEditor).not.toHaveBeenCalled();
|
|
330
|
-
expect(event.stopPropagation).not.toHaveBeenCalled();
|
|
331
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it('should not postMessage the UVE if the editor is already focus', () => {
|
|
335
|
-
const hasFocusSpy = jest
|
|
336
|
-
.spyOn(spectator.component.editorComponent.editor, 'hasFocus')
|
|
337
|
-
.mockReturnValue(true);
|
|
338
|
-
|
|
339
|
-
spectator.detectChanges();
|
|
340
|
-
spectator.triggerEventHandler(editorDebugElement, 'onMouseDown', customEvent);
|
|
341
|
-
expect(postMessageToEditor).not.toHaveBeenCalled();
|
|
342
|
-
expect(event.stopPropagation).not.toHaveBeenCalled();
|
|
343
|
-
expect(event.preventDefault).not.toHaveBeenCalled();
|
|
344
|
-
expect(hasFocusSpy).toHaveBeenCalled();
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
describe('focusout', () => {
|
|
349
|
-
let isDirtySpy: jest.SpyInstance;
|
|
350
|
-
let getContentSpy: jest.SpyInstance;
|
|
351
|
-
let event: FocusEvent;
|
|
352
|
-
let editorDebugElement: DebugElement;
|
|
353
|
-
let customEvent: {
|
|
354
|
-
event: FocusEvent;
|
|
355
|
-
editor: unknown;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
beforeEach(() => {
|
|
359
|
-
const editor = spectator.component.editorComponent.editor;
|
|
360
|
-
isDirtySpy = jest.spyOn(editor, 'isDirty');
|
|
361
|
-
getContentSpy = jest.spyOn(editor, 'getContent');
|
|
362
|
-
|
|
363
|
-
event = new FocusEvent('focusout');
|
|
364
|
-
customEvent = { event, editor: TINYMCE_EDITOR_MOCK };
|
|
365
|
-
|
|
366
|
-
editorDebugElement = spectator.debugElement.query(
|
|
367
|
-
By.directive(EditorComponent)
|
|
368
|
-
);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('should not postMessage the UVE if the editor is not dirty', () => {
|
|
372
|
-
isDirtySpy.mockReturnValue(false);
|
|
373
|
-
getContentSpy.mockReturnValue("I'm not dirty");
|
|
374
|
-
|
|
375
|
-
spectator.detectChanges();
|
|
376
|
-
|
|
377
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
378
|
-
|
|
379
|
-
expect(isDirtySpy).toHaveBeenCalled();
|
|
380
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' });
|
|
381
|
-
expect(postMessageToEditor).not.toHaveBeenCalled();
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('should not postMessage the UVE if the content did not change', () => {
|
|
385
|
-
isDirtySpy.mockReturnValue(true);
|
|
386
|
-
getContentSpy.mockReturnValue(dotcmsContentletMock.title);
|
|
387
|
-
|
|
388
|
-
spectator.detectChanges();
|
|
389
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
390
|
-
|
|
391
|
-
expect(isDirtySpy).toHaveBeenCalled();
|
|
392
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' });
|
|
393
|
-
expect(postMessageToEditor).not.toHaveBeenCalled();
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it('should postMessage the UVE if the content changed', () => {
|
|
397
|
-
isDirtySpy.mockReturnValue(true);
|
|
398
|
-
getContentSpy.mockReturnValue('New content');
|
|
399
|
-
|
|
400
|
-
spectator.detectChanges();
|
|
401
|
-
spectator.triggerEventHandler(editorDebugElement, 'onFocusOut', customEvent);
|
|
402
|
-
|
|
403
|
-
const postMessageData = {
|
|
404
|
-
action: CUSTOMER_ACTIONS.UPDATE_CONTENTLET_INLINE_EDITING,
|
|
405
|
-
payload: {
|
|
406
|
-
content: 'New content',
|
|
407
|
-
dataset: {
|
|
408
|
-
inode: dotcmsContentletMock.inode,
|
|
409
|
-
langId: dotcmsContentletMock.languageId,
|
|
410
|
-
fieldName: 'title'
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
expect(isDirtySpy).toHaveBeenCalled();
|
|
416
|
-
expect(getContentSpy).toHaveBeenCalledWith({ format: 'text' });
|
|
417
|
-
expect(postMessageToEditor).toHaveBeenCalledWith(postMessageData);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
afterEach(() => jest.clearAllMocks()); // Clear all mocks to avoid side effects from other tests
|
|
424
|
-
});
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import { EditorComponent, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
|
|
2
|
-
import { EventObj } from '@tinymce/tinymce-angular/editor/Events';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Component,
|
|
6
|
-
ElementRef,
|
|
7
|
-
HostListener,
|
|
8
|
-
inject,
|
|
9
|
-
Input,
|
|
10
|
-
OnChanges,
|
|
11
|
-
OnInit,
|
|
12
|
-
Renderer2,
|
|
13
|
-
SecurityContext,
|
|
14
|
-
ViewChild
|
|
15
|
-
} from '@angular/core';
|
|
16
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
CUSTOMER_ACTIONS,
|
|
20
|
-
DotCmsClient,
|
|
21
|
-
isInsideEditor,
|
|
22
|
-
postMessageToEditor
|
|
23
|
-
} from '@dotcms/client';
|
|
24
|
-
|
|
25
|
-
import { TINYMCE_CONFIG, DOT_EDITABLE_TEXT_FORMAT, DOT_EDITABLE_TEXT_MODE } from './utils';
|
|
26
|
-
|
|
27
|
-
import { DotCMSContentlet } from '../../models';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Dot editable text component.
|
|
31
|
-
* This component is responsible to render a text field that can be edited inline.
|
|
32
|
-
*
|
|
33
|
-
* @export
|
|
34
|
-
* @class DotEditableTextComponent
|
|
35
|
-
* @implements {OnInit}
|
|
36
|
-
* @implements {OnChanges}
|
|
37
|
-
*/
|
|
38
|
-
@Component({
|
|
39
|
-
selector: 'dot-editable-text',
|
|
40
|
-
standalone: true,
|
|
41
|
-
templateUrl: './dot-editable-text.component.html',
|
|
42
|
-
styleUrl: './dot-editable-text.component.css',
|
|
43
|
-
imports: [EditorComponent],
|
|
44
|
-
providers: [
|
|
45
|
-
{
|
|
46
|
-
provide: TINYMCE_SCRIPT_SRC,
|
|
47
|
-
useFactory: () => {
|
|
48
|
-
return `${DotCmsClient.dotcmsUrl}/ext/tinymcev7/tinymce.min.js`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
})
|
|
53
|
-
export class DotEditableTextComponent implements OnInit, OnChanges {
|
|
54
|
-
@ViewChild(EditorComponent) editorComponent!: EditorComponent;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Represents the mode of the editor which can be `plain`, `minimal`, or `full`
|
|
58
|
-
*
|
|
59
|
-
* @type {DOT_EDITABLE_TEXT_MODE}
|
|
60
|
-
* @memberof DotEditableTextComponent
|
|
61
|
-
*/
|
|
62
|
-
@Input() mode: DOT_EDITABLE_TEXT_MODE = 'plain';
|
|
63
|
-
/**
|
|
64
|
-
* Represents the format of the editor which can be `text` or `html`
|
|
65
|
-
*
|
|
66
|
-
* @type {DOT_EDITABLE_TEXT_FORMAT}
|
|
67
|
-
* @memberof DotEditableTextComponent
|
|
68
|
-
*/
|
|
69
|
-
@Input() format: DOT_EDITABLE_TEXT_FORMAT = 'text';
|
|
70
|
-
/**
|
|
71
|
-
* Represents the `contentlet` that can be inline edited
|
|
72
|
-
*
|
|
73
|
-
* @type {DotCMSContentlet}
|
|
74
|
-
* @memberof DotEditableTextComponent
|
|
75
|
-
*/
|
|
76
|
-
@Input() contentlet!: DotCMSContentlet;
|
|
77
|
-
/**
|
|
78
|
-
* Represents the field name of the `contentlet` that can be edited
|
|
79
|
-
*
|
|
80
|
-
* @memberof DotEditableTextComponent
|
|
81
|
-
*/
|
|
82
|
-
@Input() fieldName = '';
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Represents the content of the `contentlet` that can be edited
|
|
86
|
-
*
|
|
87
|
-
* @protected
|
|
88
|
-
* @memberof DotEditableTextComponent
|
|
89
|
-
*/
|
|
90
|
-
protected content = '';
|
|
91
|
-
/**
|
|
92
|
-
* Represents the configuration of the editor
|
|
93
|
-
*
|
|
94
|
-
* @protected
|
|
95
|
-
* @type {EditorComponent['init']}
|
|
96
|
-
* @memberof DotEditableTextComponent
|
|
97
|
-
*/
|
|
98
|
-
protected init!: EditorComponent['init'];
|
|
99
|
-
/**
|
|
100
|
-
* Represents if the component is inside the editor
|
|
101
|
-
*
|
|
102
|
-
* @protected
|
|
103
|
-
* @type {boolean}
|
|
104
|
-
* @memberof DotEditableTextComponent
|
|
105
|
-
*/
|
|
106
|
-
protected isInsideEditor!: boolean;
|
|
107
|
-
|
|
108
|
-
readonly #sanitizer = inject<DomSanitizer>(DomSanitizer);
|
|
109
|
-
readonly #renderer = inject<Renderer2>(Renderer2);
|
|
110
|
-
readonly #elementRef = inject<ElementRef>(ElementRef);
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* The TinyMCE editor
|
|
114
|
-
*
|
|
115
|
-
* @readonly
|
|
116
|
-
* @memberof DotEditableTextComponent
|
|
117
|
-
*/
|
|
118
|
-
get editor() {
|
|
119
|
-
return this.editorComponent?.editor;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Returns the number of pages the contentlet is on
|
|
124
|
-
*
|
|
125
|
-
* @readonly
|
|
126
|
-
* @memberof DotEditableTextComponent
|
|
127
|
-
*/
|
|
128
|
-
get onNumberOfPages() {
|
|
129
|
-
return this.contentlet['onNumberOfPages'] || 1;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Handle copy contentlet inline editing success event
|
|
134
|
-
*
|
|
135
|
-
* @param {MessageEvent} { data }
|
|
136
|
-
* @return {*}
|
|
137
|
-
* @memberof DotEditableTextComponent
|
|
138
|
-
*/
|
|
139
|
-
@HostListener('window:message', ['$event'])
|
|
140
|
-
onMessage({ data }: MessageEvent) {
|
|
141
|
-
const { name, payload } = data;
|
|
142
|
-
if (name !== 'COPY_CONTENTLET_INLINE_EDITING_SUCCESS') {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const { oldInode, inode } = payload;
|
|
147
|
-
const currentInode = this.contentlet.inode;
|
|
148
|
-
|
|
149
|
-
if (currentInode === oldInode || currentInode === inode) {
|
|
150
|
-
this.editorComponent.editor.focus();
|
|
151
|
-
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
ngOnInit() {
|
|
157
|
-
this.isInsideEditor = isInsideEditor();
|
|
158
|
-
|
|
159
|
-
if (!this.isInsideEditor) {
|
|
160
|
-
this.innerHTMLToElement();
|
|
161
|
-
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.init = {
|
|
166
|
-
...TINYMCE_CONFIG[this.mode],
|
|
167
|
-
base_url: `${DotCmsClient.dotcmsUrl}/ext/tinymcev7`
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
ngOnChanges() {
|
|
172
|
-
this.content = this.contentlet[this.fieldName] || '';
|
|
173
|
-
if (this.editor) {
|
|
174
|
-
this.editor.setContent(this.content, { format: this.format });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Handle mouse down event
|
|
180
|
-
*
|
|
181
|
-
* @param {EventObj<MouseEvent>} { event }
|
|
182
|
-
* @return {*}
|
|
183
|
-
* @memberof DotEditableTextComponent
|
|
184
|
-
*/
|
|
185
|
-
onMouseDown({ event }: EventObj<MouseEvent>) {
|
|
186
|
-
if (this.onNumberOfPages <= 1 || this.editorComponent.editor.hasFocus()) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const { inode, languageId: language } = this.contentlet;
|
|
191
|
-
|
|
192
|
-
event.stopPropagation();
|
|
193
|
-
event.preventDefault();
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
postMessageToEditor({
|
|
197
|
-
action: CUSTOMER_ACTIONS.COPY_CONTENTLET_INLINE_EDITING,
|
|
198
|
-
payload: {
|
|
199
|
-
dataset: {
|
|
200
|
-
inode,
|
|
201
|
-
language,
|
|
202
|
-
fieldName: this.fieldName
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
} catch (error) {
|
|
207
|
-
console.error('Failed to post message to editor:', error);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Handle focus out event
|
|
212
|
-
*
|
|
213
|
-
* @return {*}
|
|
214
|
-
* @memberof DotEditableTextComponent
|
|
215
|
-
*/
|
|
216
|
-
onFocusOut() {
|
|
217
|
-
const content = this.editor.getContent({ format: this.format });
|
|
218
|
-
|
|
219
|
-
if (!this.editor.isDirty() || !this.didContentChange(content)) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const { inode, languageId: langId } = this.contentlet;
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
postMessageToEditor({
|
|
227
|
-
action: CUSTOMER_ACTIONS.UPDATE_CONTENTLET_INLINE_EDITING,
|
|
228
|
-
payload: {
|
|
229
|
-
content,
|
|
230
|
-
dataset: {
|
|
231
|
-
inode,
|
|
232
|
-
langId,
|
|
233
|
-
fieldName: this.fieldName
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('Failed to post message to editor:', error);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* inner HTML to element
|
|
244
|
-
*
|
|
245
|
-
* @private
|
|
246
|
-
* @param {string} editedContent
|
|
247
|
-
* @return {*}
|
|
248
|
-
* @memberof DotEditableTextComponent
|
|
249
|
-
*/
|
|
250
|
-
private innerHTMLToElement() {
|
|
251
|
-
const element = this.#elementRef.nativeElement;
|
|
252
|
-
const safeHtml = this.#sanitizer.bypassSecurityTrustHtml(this.content);
|
|
253
|
-
const content = this.#sanitizer.sanitize(SecurityContext.HTML, safeHtml) || '';
|
|
254
|
-
|
|
255
|
-
this.#renderer.setProperty(element, 'innerHTML', content);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Check if the content has changed
|
|
260
|
-
*
|
|
261
|
-
* @private
|
|
262
|
-
* @param {string} editedContent
|
|
263
|
-
* @return {*}
|
|
264
|
-
* @memberof DotEditableTextComponent
|
|
265
|
-
*/
|
|
266
|
-
private didContentChange(editedContent: string) {
|
|
267
|
-
return this.content !== editedContent;
|
|
268
|
-
}
|
|
269
|
-
}
|