Skip to content

Commit 8dca388

Browse files
authored
INT-3292: Support for TinyMCE v7 (#378)
* INT-3292: Added tinymce v7 to possible deps * INT-3292: Add input and composition related events * INT-3292: Remove unnecessary ng-template * INT-3292: Added Version type and default `cloudChannel` is now set to 7 * INT-3292: Updated README * INT-3292: Use `?` instead of `| undefined` * INT-3292: Added missing apiKey props to stories * INT-3292: Added the `licenseKey` prop * INT-3292: Added a JSDoc link to the TinyMCE Angular technical reference doc page * INT-3292: Test against TinyMCE v7 as well * INT-3292: Updated package version * INT-3292: Updated changelog * INT-3292: Add missing `validEvents` * INT-3292: Edited readme, changelog and package peer deps to state the support for Angular 16+ only
1 parent b5cda49 commit 8dca388

15 files changed

+135
-44
lines changed

CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88

9-
## 7.1.1 - 2024-04-04
9+
## 8.0.0 - 2024-04-24
1010
### Fixed
1111
- Updated CI library to latest
1212
- Updated dependencies. #INT-3274
1313
- Usage of RxJS deprecated operators. #INT-3274
1414
### Added
15-
- Support for Angular 15, 16 & 17. #INT-3274
15+
- Support for Angular 16 & 17. Angular 15 is still supported via ^7.0.0 #INT-3274
1616
- Support for the OnPush change detection strategy. #INT-2974
17+
- Support for TinyMCE version 7. #INT-3292
18+
- Added `licenseKey` prop. #INT-3292
19+
- Added events `onInput`, `onCompositionEnd`, `onCompositionStart` & `onCompositionUpdate`. #INT-3292
20+
- Added a JSDoc link to the TinyMCE 7 Angular Technical Reference docs page. #INT-3292
1721
### Improved
1822
- Updated Storybook to v8, as well as now using CSFv3 components. #INT-3274
23+
- Improved `cloudChannel` type. #INT-3292
1924

2025
## 7.0.0 - 2022-06-27
2126
### Added

README.md

+11-7
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,32 @@
44

55
This package is a thin wrapper around [TinyMCE](https://github.com/tinymce/tinymce) to make it easier to use in an Angular application.
66

7-
* If you need detailed documentation on TinyMCE, see: [TinyMCE Documentation](https://www.tiny.cloud/docs/tinymce/6/).
8-
* For the TinyMCE Angular Quick Start, see: [TinyMCE Documentation - Angular Integration](https://www.tiny.cloud/docs/tinymce/6/angular-cloud/).
9-
* For the TinyMCE Angular Technical Reference, see: [TinyMCE Documentation - TinyMCE Angular Technical Reference](https://www.tiny.cloud/docs/tinymce/6/angular-ref/).
7+
* If you need detailed documentation on TinyMCE, see: [TinyMCE Documentation](https://www.tiny.cloud/docs/tinymce/7/).
8+
* For the TinyMCE Angular Quick Start, see: [TinyMCE Documentation - Angular Integration](https://www.tiny.cloud/docs/tinymce/7/angular-cloud/).
9+
* For the TinyMCE Angular Technical Reference, see: [TinyMCE Documentation - TinyMCE Angular Technical Reference](https://www.tiny.cloud/docs/tinymce/7/angular-ref/).
1010
* For our quick demos, check out the TinyMCE Angular [Storybook](https://tinymce.github.io/tinymce-angular/).
1111

1212
### Support
1313

14+
For Angular 16+, use integration version 8.x:
15+
16+
`npm install @tinymce/tinymce-angular@^8`
17+
1418
For Angular 14+, use integration version 7.x:
1519

16-
`npm install @tinymce/tinymce-angular@^7.0.0`
20+
`npm install @tinymce/tinymce-angular@^7`
1721

1822
For Angular 13+, use integration version 6.x:
1923

20-
`npm install @tinymce/tinymce-angular@^6.0.0`
24+
`npm install @tinymce/tinymce-angular@^6`
2125

2226
For Angular 9+, use integration version 4.x:
2327

24-
`npm install @tinymce/tinymce-angular@^4.0.0`
28+
`npm install @tinymce/tinymce-angular@^4`
2529

2630
For Angular 8 and below use integration version 3.x:
2731

28-
`npm install @tinymce/tinymce-angular@^3.0.0`
32+
`npm install @tinymce/tinymce-angular@^3`
2933

3034
Versions below Angular 5 are not supported.
3135

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"tinymce-4": "npm:tinymce@^4",
6363
"tinymce-5": "npm:tinymce@^5",
6464
"tinymce-6": "npm:tinymce@^6",
65+
"tinymce-7": "npm:tinymce@^7",
6566
"to-string-loader": "^1.1.5",
6667
"tslib": "^2.6.2",
6768
"typescript": "~5.4.3",
@@ -71,6 +72,6 @@
7172
"dependencies": {
7273
"tinymce": "^5.10.7"
7374
},
74-
"version": "7.1.1-rc.2",
75+
"version": "8.0.0-rc",
7576
"name": "@tinymce/tinymce-angular"
7677
}

stories/form-control/FormControl.component.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/* eslint-disable no-console */
22
import { Component } from '@angular/core';
33
import { FormBuilder, FormControl } from '@angular/forms';
4+
import { apiKey } from 'stories/Settings';
45

56
@Component({
67
selector: 'form-control',
78
templateUrl: './FormControl.component.html',
89
})
910
export class FormControlComponent {
11+
public apiKey = apiKey;
1012
public formControl: FormControl<string | null>;
1113

1214
// eslint-disable-next-line @typescript-eslint/no-parameter-properties

stories/form-with-on-push/form-with-on-push.component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import {
77
} from '@angular/core';
88
import { FormControl, FormGroup, Validators } from '@angular/forms';
99
import type { EditorComponent } from '../../tinymce-angular-component/src/main/ts/public_api';
10+
import { apiKey } from 'stories/Settings';
1011

1112
@Component({
1213
selector: 'form-with-on-push',
1314
templateUrl: './form-with-on-push.html',
1415
changeDetection: ChangeDetectionStrategy.OnPush,
1516
})
1617
export class FormWithOnPushComponent {
17-
@Input() public apiKey = '';
18+
@Input() public apiKey = apiKey;
1819
public readonly initialValue = '';
1920
public readonly init: EditorComponent['init'] = {
2021
plugins: [ 'help' ],

stories/form-with-on-push/form-with-on-push.html

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<form [formGroup]="form" style="display: flex; gap: 0.2em; flex-direction: column;">
22
<editor
3+
[apiKey]="apiKey"
34
formControlName="tiny"
45
plugins="table"
56
init="{ width: '200px', }"

tinymce-angular-component/package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
"license": "MIT",
88
"private": false,
99
"peerDependencies": {
10-
"@angular/core": ">=14.0.0",
11-
"@angular/common": ">=14.0.0",
12-
"@angular/forms": ">=14.0.0"
10+
"@angular/core": ">=16.0.0",
11+
"@angular/common": ">=16.0.0",
12+
"@angular/forms": ">=16.0.0"
1313
},
1414
"dependencies": {
15-
"tinymce": "^6.0.0 || ^5.5.0"
15+
"tinymce": "^7.0.0 || ^6.0.0 || ^5.5.0"
1616
}
1717
}

tinymce-angular-component/src/main/ts/editor/Events.ts

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export class Events {
1111
@Output() public onBeforePaste: EventEmitter<EventObj<ClipboardEvent>> = new EventEmitter();
1212
@Output() public onBlur: EventEmitter<EventObj<FocusEvent>> = new EventEmitter();
1313
@Output() public onClick: EventEmitter<EventObj<MouseEvent>> = new EventEmitter();
14+
@Output() public onCompositionEnd: EventEmitter<EventObj<CompositionEvent>> = new EventEmitter();
15+
@Output() public onCompositionStart: EventEmitter<EventObj<CompositionEvent>> = new EventEmitter();
16+
@Output() public onCompositionUpdate: EventEmitter<EventObj<CompositionEvent>> = new EventEmitter();
1417
@Output() public onContextMenu: EventEmitter<EventObj<MouseEvent>> = new EventEmitter();
1518
@Output() public onCopy: EventEmitter<EventObj<ClipboardEvent>> = new EventEmitter();
1619
@Output() public onCut: EventEmitter<EventObj<ClipboardEvent>> = new EventEmitter();
@@ -51,6 +54,7 @@ export class Events {
5154
@Output() public onGetContent: EventEmitter<EventObj<any>> = new EventEmitter();
5255
@Output() public onHide: EventEmitter<EventObj<any>> = new EventEmitter();
5356
@Output() public onInit: EventEmitter<EventObj<any>> = new EventEmitter();
57+
@Output() public onInput: EventEmitter<EventObj<any>> = new EventEmitter();
5458
@Output() public onInitNgModel: EventEmitter<EventObj<any>> = new EventEmitter();
5559
@Output() public onLoadContent: EventEmitter<EventObj<any>> = new EventEmitter();
5660
@Output() public onNodeChange: EventEmitter<EventObj<any>> = new EventEmitter();
@@ -88,6 +92,9 @@ export const validEvents: (keyof Events)[] = [
8892
'onChange',
8993
'onClearUndos',
9094
'onClick',
95+
'onCompositionEnd',
96+
'onCompositionStart',
97+
'onCompositionUpdate',
9198
'onContextMenu',
9299
'onCopy',
93100
'onCut',
@@ -107,6 +114,7 @@ export const validEvents: (keyof Events)[] = [
107114
'onGetContent',
108115
'onHide',
109116
'onInit',
117+
'onInput',
110118
'onKeyDown',
111119
'onKeyPress',
112120
'onKeyUp',

tinymce-angular-component/src/main/ts/editor/editor.component.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,37 @@ const EDITOR_COMPONENT_VALUE_ACCESSOR = {
3333
multi: true
3434
};
3535

36+
export type Version = `${'4' | '5' | '6' | '7'}${'' | '-dev' | '-testing' | `.${number}` | `.${number}.${number}`}`;
37+
3638
@Component({
3739
selector: 'editor',
38-
template: '<ng-template></ng-template>',
40+
template: '',
3941
styles: [ ':host { display: block; }' ],
4042
providers: [ EDITOR_COMPONENT_VALUE_ACCESSOR ],
4143
standalone: true,
4244
imports: [ CommonModule, FormsModule ],
4345
changeDetection: ChangeDetectionStrategy.OnPush
4446
})
47+
48+
/**
49+
* @see {@link https://www.tiny.cloud/docs/tinymce/7/angular-ref/} for the TinyMCE Angular Technical Reference
50+
*/
4551
export class EditorComponent extends Events implements AfterViewInit, ControlValueAccessor, OnDestroy {
4652

47-
@Input() public cloudChannel = '6';
53+
@Input() public cloudChannel: Version = '7';
4854
@Input() public apiKey = 'no-api-key';
49-
@Input() public init: EditorOptions | undefined;
55+
@Input() public licenseKey?: string;
56+
@Input() public init?: EditorOptions;
5057
@Input() public id = '';
51-
@Input() public initialValue: string | undefined;
52-
@Input() public outputFormat: 'html' | 'text' | undefined;
53-
@Input() public inline: boolean | undefined;
54-
@Input() public tagName: string | undefined;
55-
@Input() public plugins: string | undefined;
56-
@Input() public toolbar: string | string[] | undefined;
58+
@Input() public initialValue?: string;
59+
@Input() public outputFormat?: 'html' | 'text';
60+
@Input() public inline?: boolean;
61+
@Input() public tagName?: string;
62+
@Input() public plugins?: string;
63+
@Input() public toolbar?: string | string[];
5764
@Input() public modelEvents = 'change input undo redo';
58-
@Input() public allowedEvents: string | string[] | undefined;
59-
@Input() public ignoreEvents: string | string[] | undefined;
65+
@Input() public allowedEvents?: string | string[];
66+
@Input() public ignoreEvents?: string | string[];
6067
@Input()
6168
public set disabled(val) {
6269
this._disabled = val;
@@ -80,9 +87,9 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
8087
public ngZone: NgZone;
8188

8289
private _elementRef: ElementRef;
83-
private _element: HTMLElement | undefined;
84-
private _disabled: boolean | undefined;
85-
private _editor: TinyMCEEditor | undefined;
90+
private _element?: HTMLElement;
91+
private _disabled?: boolean;
92+
private _editor?: TinyMCEEditor;
8693

8794
private onTouchedCallback = noop;
8895
private onChangeCallback: any;
@@ -169,6 +176,7 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
169176
target: this._element,
170177
inline: this.inline,
171178
readonly: this.disabled,
179+
license_key: this.licenseKey,
172180
plugins: mergePlugins((this.init && this.init.plugins) as string, this.plugins),
173181
toolbar: this.toolbar || (this.init && this.init.toolbar),
174182
setup: (editor: TinyMCEEditor) => {

tinymce-angular-component/src/test/ts/browser/EventBlacklistingTest.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { VersionLoader } from '@tinymce/miniature';
88

99
import { EditorComponent } from '../../../main/ts/public_api';
1010
import { TestStore } from '../alien/TestStore';
11+
import { Version } from '../../../main/ts/editor/editor.component';
1112

1213
UnitTest.asynctest('EventBlacklistingTest', (success, failure) => {
1314

@@ -37,7 +38,7 @@ UnitTest.asynctest('EventBlacklistingTest', (success, failure) => {
3738
});
3839
});
3940

40-
const sTestVersion = (version: '4' | '5' | '6') => VersionLoader.sWithVersion(
41+
const sTestVersion = (version: Version) => VersionLoader.sWithVersion(
4142
version,
4243
Log.chainsAsStep('', 'Events should be bound when allowed',
4344
[
@@ -70,6 +71,7 @@ UnitTest.asynctest('EventBlacklistingTest', (success, failure) => {
7071
Pipeline.async({}, [
7172
sTestVersion('4'),
7273
sTestVersion('5'),
73-
sTestVersion('6')
74+
sTestVersion('6'),
75+
sTestVersion('7')
7476
], success, failure);
7577
});

tinymce-angular-component/src/test/ts/browser/FormControlTest.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { UnitTest } from '@ephox/bedrock-client';
99
import { VersionLoader } from '@tinymce/miniature';
1010

1111
import { EditorComponent, EditorModule } from '../../../main/ts/public_api';
12+
import { Version } from '../../../main/ts/editor/editor.component';
1213

1314
UnitTest.asynctest('FormControlTest', (success, failure) => {
1415
@Component({
@@ -53,7 +54,7 @@ UnitTest.asynctest('FormControlTest', (success, failure) => {
5354
TestBed.resetTestingModule();
5455
});
5556

56-
const sTestVersion = (version: '4' | '5' | '6') => VersionLoader.sWithVersion(
57+
const sTestVersion = (version: Version) => VersionLoader.sWithVersion(
5758
version,
5859
Log.chainsAsStep('', 'FormControl interaction ', [
5960
cSetupEditorWithFormControl,
@@ -89,6 +90,7 @@ UnitTest.asynctest('FormControlTest', (success, failure) => {
8990
Pipeline.async({}, [
9091
sTestVersion('4'),
9192
sTestVersion('5'),
92-
sTestVersion('6')
93+
sTestVersion('6'),
94+
sTestVersion('7')
9395
], success, failure);
9496
});

tinymce-angular-component/src/test/ts/browser/LoadTinyTest.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SelectorFilter, Attribute, SugarElement, Remove } from '@ephox/sugar';
1111

1212
import { EditorModule, EditorComponent, TINYMCE_SCRIPT_SRC } from '../../../main/ts/public_api';
1313
import { ScriptLoader } from '../../../main/ts/utils/ScriptLoader';
14+
import { Version } from '../../../main/ts/editor/editor.component';
1415

1516
UnitTest.asynctest('LoadTinyTest', (success, failure) => {
1617
const cSetupEditor = (attributes: string[], providers: any[]) => Chain.async<void, void>((_, next) => {
@@ -60,14 +61,19 @@ UnitTest.asynctest('LoadTinyTest', (success, failure) => {
6061
TestBed.resetTestingModule();
6162
});
6263

63-
const cAssertTinymceVersion = (version: '4' | '5' | '6' | '7') => Chain.op(() => {
64+
const cAssertTinymceVersion = (version: Version) => Chain.op(() => {
6465
Assertions.assertEq(`Loaded version of TinyMCE should be ${version}`, version, Global.tinymce.majorVersion);
6566
});
6667

6768
Pipeline.async({}, [
6869
Log.chainsAsStep('Should be able to load local version of TinyMCE specified via depdendency injection', '', [
6970
cDeleteTinymce,
7071

72+
cSetupEditor([], [{ provide: TINYMCE_SCRIPT_SRC, useValue: '/project/node_modules/tinymce-7/tinymce.min.js' }]),
73+
cAssertTinymceVersion('7'),
74+
cTeardown,
75+
cDeleteTinymce,
76+
7177
cSetupEditor([], [{ provide: TINYMCE_SCRIPT_SRC, useValue: '/project/node_modules/tinymce-6/tinymce.min.js' }]),
7278
cAssertTinymceVersion('6'),
7379
cTeardown,
@@ -83,7 +89,20 @@ UnitTest.asynctest('LoadTinyTest', (success, failure) => {
8389
cTeardown,
8490
cDeleteTinymce,
8591
]),
86-
Log.chainsAsStep('Should be able to load TinyMCE from Cloud', '', [
92+
Log.chainsAsStep('Should be able to load TinyMCE 7 from Cloud', '', [
93+
cSetupEditor([ 'apiKey="a-fake-api-key"', 'cloudChannel="7"' ], []),
94+
cAssertTinymceVersion('7'),
95+
Chain.op(() => {
96+
Assertions.assertEq(
97+
'TinyMCE should have been loaded from Cloud',
98+
'https://cdn.tiny.cloud/1/a-fake-api-key/tinymce/7',
99+
Global.tinymce.baseURI.source
100+
);
101+
}),
102+
cTeardown,
103+
cDeleteTinymce
104+
]),
105+
Log.chainsAsStep('Should be able to load TinyMCE 6 from Cloud', '', [
87106
cSetupEditor([ 'apiKey="a-fake-api-key"', 'cloudChannel="6"' ], []),
88107
cAssertTinymceVersion('6'),
89108
Chain.op(() => {

tinymce-angular-component/src/test/ts/browser/NgModelTest.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { VersionLoader } from '@tinymce/miniature';
1111
import { SugarElement } from '@ephox/sugar';
1212

1313
import { EditorModule, EditorComponent } from '../../../main/ts/public_api';
14+
import { Version } from '../../../main/ts/editor/editor.component';
1415

1516
UnitTest.asynctest('NgModelTest', (success, failure) => {
1617
class Base {
@@ -70,7 +71,7 @@ UnitTest.asynctest('NgModelTest', (success, failure) => {
7071
context.fixture.detectChanges();
7172
});
7273

73-
const sTestVersion = (version: '4' | '5' | '6') => VersionLoader.sWithVersion(version, GeneralSteps.sequence([
74+
const sTestVersion = (version: Version) => VersionLoader.sWithVersion(version, GeneralSteps.sequence([
7475
Log.chainsAsStep('', 'should be pristine, untouched, and valid initially', [
7576
cSetupEditorWithNgModel(),
7677
cAssertNgModelState('valid', true),
@@ -144,6 +145,7 @@ UnitTest.asynctest('NgModelTest', (success, failure) => {
144145
Pipeline.async({}, [
145146
sTestVersion('4'),
146147
sTestVersion('5'),
147-
sTestVersion('6')
148+
sTestVersion('6'),
149+
sTestVersion('7')
148150
], success, failure);
149151
});

tinymce-angular-component/src/test/ts/browser/NgZoneTest.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { UnitTest } from '@ephox/bedrock-client';
77
import { VersionLoader } from '@tinymce/miniature';
88

99
import { EditorComponent } from '../../../main/ts/public_api';
10+
import { Version } from '../../../main/ts/editor/editor.component';
1011

1112
UnitTest.asynctest('NgZoneTest', (success, failure) => {
1213
const createComponent = <T>(componentType: Type<T>) => {
@@ -20,7 +21,7 @@ UnitTest.asynctest('NgZoneTest', (success, failure) => {
2021
TestBed.resetTestingModule();
2122
});
2223

23-
const sTestVersion = (version: '4' | '5' | '6') => VersionLoader.sWithVersion(
24+
const sTestVersion = (version: Version) => VersionLoader.sWithVersion(
2425
version,
2526
Log.chainsAsStep('', 'Subscribers to events should rune within NgZone', [
2627
Chain.async<void, ComponentFixture<EditorComponent>>((_, next) => {
@@ -56,6 +57,7 @@ UnitTest.asynctest('NgZoneTest', (success, failure) => {
5657
Pipeline.async({}, [
5758
sTestVersion('4'),
5859
sTestVersion('5'),
59-
sTestVersion('6')
60+
sTestVersion('6'),
61+
sTestVersion('7')
6062
], success, failure);
6163
});

0 commit comments

Comments
 (0)