Skip to content

Commit 997c091

Browse files
TINY-11907: add support for 'disabled' property (#414)
* TINY-11907: add support for 'disabled' property * TINY-11907: add dependency * TINY-11907: add 'readonly' property * TINY-11907: add changelog * TINY-11907: cleanup tiny versions * TINY-11907: apply codereview comment * TINY-11907: remove dependency * TINY-11907: apply codereview comment * retrigger checks
1 parent 63ee891 commit 997c091

File tree

10 files changed

+203
-12
lines changed

10 files changed

+203
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88

9+
### Added
10+
- Added 'readonly' property. #TINY-11907
11+
912
### Fixed
1013
- Updated dependencies. #INT-3324
1114

1215
### Changed
1316
- Moved tinymce dependency to be a optional peer dependency. #INT-3324
1417
- Updated tinymce dev dependency to version ^7 from 5.10.7 so now all internal tinymce types point to version 7. #INT-3324
18+
- The 'disabled' property is now mapped to editor's 'disabled' option if Tiny >= 7.6.0 is used. #TINY-11907
1519

1620
## 8.0.1 - 2024-07-12
1721

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"tinymce-5": "npm:tinymce@^5",
6868
"tinymce-6": "npm:tinymce@^6",
6969
"tinymce-7": "npm:tinymce@^7",
70+
"tinymce-7.5.0": "npm:[email protected]",
7071
"to-string-loader": "^1.1.5",
7172
"tslib": "^2.6.2",
7273
"typescript": "~5.5.4",

stories/Editor.stories.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { MatTabsModule } from '@angular/material/tabs';
1515
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
1616
import { ContainerComponent, ContentProjectionComponent } from './contentprojection/ContentProjection.component';
1717
import { BindingComponent } from './data-binding/DataBinding.component';
18+
import { ReadonlyComponent } from './readonly/Readonly.component';
1819

1920
const meta: Meta = {
2021
component: EditorComponent,
@@ -137,6 +138,20 @@ export const DisablingStory: StoryObj<EditorComponent> = {
137138
}
138139
};
139140

141+
export const ReadonlyStory: StoryObj<EditorComponent> = {
142+
name: 'Readonly',
143+
render: () => ({
144+
moduleMetadata: {
145+
imports: [ ReactiveFormsModule, FormsModule ],
146+
declarations: [ ReadonlyComponent ],
147+
},
148+
template: `<readonly/>`
149+
}),
150+
parameters: {
151+
notes: 'Example of toggling readonly state in the editor component'
152+
}
153+
};
154+
140155
export const ViewQueryStory: StoryObj<EditorComponent> = {
141156
name: 'View Query',
142157
render: () => ({
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<button (click)="toggleReadonly()">{{ isReadonly ? 'Escape readonly' : 'Enter readonly' }}</button>
2+
<editor
3+
[apiKey]="apiKey"
4+
[readonly]="isReadonly"
5+
[initialValue]="initialValue"
6+
[init]="{ height: 300 }"
7+
/>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Component } from '@angular/core';
2+
import { apiKey, sampleContent } from '../Settings';
3+
4+
@Component({
5+
selector: 'readonly',
6+
templateUrl: './Readonly.component.html',
7+
})
8+
export class ReadonlyComponent {
9+
public isReadonly = false;
10+
public apiKey = apiKey;
11+
public initialValue = sampleContent;
12+
public toggleReadonly = () => (this.isReadonly = !this.isReadonly);
13+
}

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
1919
import { Subject, takeUntil } from 'rxjs';
2020
import { getTinymce } from '../TinyMCE';
21-
import { listenTinyMCEEvent, bindHandlers, isTextarea, mergePlugins, uuid, noop, isNullOrUndefined } from '../utils/Utils';
21+
import { listenTinyMCEEvent, bindHandlers, isTextarea, mergePlugins, uuid, noop, isNullOrUndefined, setMode } from '../utils/Utils';
22+
import * as DisabledUtils from '../utils/DisabledUtils';
2223
import { EventObj, Events } from './Events';
2324
import { ScriptLoader } from '../utils/ScriptLoader';
2425
import type { Editor as TinyMCEEditor, TinyMCE } from 'tinymce';
@@ -64,14 +65,26 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
6465
@Input() public modelEvents = 'change input undo redo';
6566
@Input() public allowedEvents?: string | string[];
6667
@Input() public ignoreEvents?: string | string[];
68+
@Input()
69+
public set readonly(val) {
70+
this._readonly = val;
71+
if (this._editor && this._editor.initialized) {
72+
setMode(this._editor, val ? 'readonly' : 'design');
73+
}
74+
}
75+
76+
public get readonly() {
77+
return this._readonly;
78+
}
79+
6780
@Input()
6881
public set disabled(val) {
6982
this._disabled = val;
7083
if (this._editor && this._editor.initialized) {
71-
if (typeof this._editor.mode?.set === 'function') {
72-
this._editor.mode.set(val ? 'readonly' : 'design');
73-
} else if ('setMode' in this._editor && typeof this._editor.setMode === 'function') {
74-
this._editor.setMode(val ? 'readonly' : 'design');
84+
if (DisabledUtils.isDisabledOptionSupported()) {
85+
this._editor.options.set('disabled', val ?? false);
86+
} else {
87+
setMode(this._editor, val ? 'readonly' : 'design');
7588
}
7689
}
7790
}
@@ -89,6 +102,7 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
89102
private _elementRef: ElementRef;
90103
private _element?: HTMLElement;
91104
private _disabled?: boolean;
105+
private _readonly?: boolean;
92106
private _editor?: TinyMCEEditor;
93107

94108
private onTouchedCallback = noop;
@@ -176,7 +190,10 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
176190
selector: undefined,
177191
target: this._element,
178192
inline: this.inline,
179-
readonly: this.disabled,
193+
...( DisabledUtils.isDisabledOptionSupported()
194+
? { disabled: this.disabled, readonly: this.readonly }
195+
: { readonly: this.disabled || this.readonly }
196+
),
180197
license_key: this.licenseKey,
181198
plugins: mergePlugins((this.init && this.init.plugins) as string, this.plugins),
182199
toolbar: this.toolbar || (this.init && this.init.toolbar),
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { getTinymce } from '../TinyMCE';
2+
import { TinyMCE } from 'tinymce';
3+
4+
const isDisabledOptionSupported = () => {
5+
const tiny: TinyMCE = getTinymce();
6+
// Disabled option is supported since Tiny 7.6.0
7+
return Number(tiny.majorVersion) > 7 || (Number(tiny.majorVersion) === 7 && Number(tiny.minorVersion) >= 6);
8+
};
9+
10+
export {
11+
isDisabledOptionSupported
12+
};

tinymce-angular-component/src/main/ts/utils/Utils.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { HasEventTargetAddRemove } from 'rxjs/internal/observable/fromEvent';
1212

1313
import { EditorComponent } from '../editor/editor.component';
1414
import { validEvents, Events } from '../editor/Events';
15+
import { Editor } from 'tinymce';
1516

1617
// Caretaker note: `fromEvent` supports passing JQuery-style event targets, the editor has `on` and `off` methods which
1718
// will be invoked upon subscription and teardown.
@@ -47,10 +48,10 @@ const getValidEvents = (ctx: EditorComponent): (keyof Events)[] => {
4748
};
4849

4950
const parseStringProperty = (property: string | string[] | undefined, defaultValue: (keyof Events)[]): string[] => {
50-
if ( typeof property === 'string') {
51+
if (typeof property === 'string') {
5152
return property.split(',').map((value) => value.trim());
5253
}
53-
if ( Array.isArray(property)) {
54+
if (Array.isArray(property)) {
5455
return property;
5556
}
5657
return defaultValue;
@@ -91,6 +92,14 @@ const isObserved = (o: Subject<unknown>): boolean =>
9192
// checking if a subject has observers.
9293
o.observed || o.observers?.length > 0;
9394

95+
const setMode = (editor: Editor, mode: 'readonly' | 'design') => {
96+
if (typeof editor.mode?.set === 'function') {
97+
editor.mode.set(mode);
98+
} else if ('setMode' in editor && typeof editor.setMode === 'function') {
99+
editor.setMode(mode);
100+
}
101+
};
102+
94103
export {
95104
listenTinyMCEEvent,
96105
bindHandlers,
@@ -99,5 +108,6 @@ export {
99108
normalizePluginArray,
100109
mergePlugins,
101110
noop,
102-
isNullOrUndefined
111+
isNullOrUndefined,
112+
setMode
103113
};
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Assertions } from '@ephox/agar';
2+
import '../alien/InitTestEnvironment';
3+
4+
import { EditorComponent } from '../../../main/ts/public_api';
5+
import { describe, it } from '@ephox/bedrock-client';
6+
import { eachVersionContext, editorHook } from '../alien/TestHooks';
7+
import { Editor } from 'tinymce';
8+
9+
describe('DisabledPropertyTest', () => {
10+
const getMode = (editor: Editor) => {
11+
if (typeof editor.mode?.get === 'function') {
12+
return editor.mode.get();
13+
}
14+
return editor.readonly ? 'readonly' : 'design';
15+
};
16+
const assertDesignMode = (editor: Editor) => Assertions.assertEq('TinyMCE should be in design mode', 'design', getMode(editor));
17+
const assertReadonlyMode = (editor: Editor) => Assertions.assertEq('TinyMCE should be in readonly mode', 'readonly', getMode(editor));
18+
const assertDisabledOption = (editor: Editor, expected: boolean) =>
19+
Assertions.assertEq(`TinyMCE should have disabled option set to ${expected}`, expected, editor.options.get('disabled'));
20+
21+
eachVersionContext([ '7.5.0' ], () => {
22+
const createFixture = editorHook(EditorComponent);
23+
24+
it(`Component 'disabled' property is mapped to editor 'readonly' property`, async () => {
25+
const { editor } = await createFixture({ disabled: true });
26+
assertReadonlyMode(editor);
27+
});
28+
29+
it(`Toggling component's 'disabled' property is mapped to editor 'readonly' property`, async () => {
30+
const fixture = await createFixture();
31+
const { editor } = fixture;
32+
33+
assertDesignMode(editor);
34+
35+
fixture.componentRef.setInput('disabled', true);
36+
fixture.detectChanges();
37+
assertReadonlyMode(editor);
38+
39+
fixture.componentRef.setInput('disabled', false);
40+
fixture.detectChanges();
41+
assertDesignMode(editor);
42+
});
43+
44+
it(`[disabled]=true [readonly]=false triggers readonly mode`, async () => {
45+
const { editor } = await createFixture({ disabled: true, readonly: false });
46+
assertReadonlyMode(editor);
47+
});
48+
49+
it(`[disabled]=false [readonly]=true triggers readonly mode`, async () => {
50+
const { editor } = await createFixture({ disabled: false, readonly: true });
51+
assertReadonlyMode(editor);
52+
});
53+
});
54+
55+
eachVersionContext([ '7' ], () => {
56+
const createFixture = editorHook(EditorComponent);
57+
58+
it(`Component 'disabled' property is mapped to editor 'disabled' property`, async () => {
59+
const { editor } = await createFixture({ disabled: true });
60+
61+
Assertions.assertEq('TinyMCE should have disabled option set to true', true, editor.options.get('disabled'));
62+
assertDesignMode(editor);
63+
});
64+
65+
it(`Toggling component's 'disabled' property is mapped to editor 'disabled' property`, async () => {
66+
const fixture = await createFixture();
67+
const { editor } = fixture;
68+
69+
assertDesignMode(editor);
70+
assertDisabledOption(editor, false);
71+
72+
fixture.componentRef.setInput('disabled', true);
73+
fixture.detectChanges();
74+
assertDesignMode(editor);
75+
assertDisabledOption(editor, true);
76+
77+
fixture.componentRef.setInput('disabled', false);
78+
fixture.detectChanges();
79+
assertDesignMode(editor);
80+
assertDisabledOption(editor, false);
81+
});
82+
});
83+
84+
eachVersionContext([ '4', '5', '6', '7' ], () => {
85+
const createFixture = editorHook(EditorComponent);
86+
87+
it(`Setting the 'readonly' property causing readonly mode`, async () => {
88+
const { editor } = await createFixture({ readonly: true });
89+
assertReadonlyMode(editor);
90+
});
91+
92+
it(`Toggling component's 'readonly' property is mapped to editor 'readonly' mode`, async () => {
93+
const fixture = await createFixture();
94+
const { editor } = fixture;
95+
96+
assertDesignMode(editor);
97+
98+
fixture.componentRef.setInput('readonly', true);
99+
fixture.detectChanges();
100+
assertReadonlyMode(editor);
101+
102+
fixture.componentRef.setInput('readonly', false);
103+
fixture.detectChanges();
104+
assertDesignMode(editor);
105+
});
106+
});
107+
});

yarn.lock

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13082,10 +13082,15 @@ tiny-invariant@^1.3.1, tiny-invariant@^1.3.3:
1308213082
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.3.tgz#0025a4aaa4c24dc2a3e32e83dfda705d196fd802"
1308313083
integrity sha512-3fCHKAeqT+xNwBVESf6iDbDV0VNwZNmfrkx9c/6Gz5iB8piMfaO6s7FvoiTrj1hf1gVbfyLTnz1DooI6DhgINQ==
1308413084

13085+
"tinymce-7.5.0@npm:[email protected]":
13086+
version "7.5.0"
13087+
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.5.0.tgz#56388d314399c288a100df4aaf468153f29477f1"
13088+
integrity sha512-A7iuQPIfeze5rO6bvnnPwP7TiWnPA9AGr8U/9ssLwrEG+FMYPzvLPt3RT8ktVn/wPSJkVBBSLCAZX2dAHb8YEA==
13089+
1308513090
"tinymce-7@npm:tinymce@^7":
13086-
version "7.1.2"
13087-
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.1.2.tgz#cb40e527dc03d6a0547a23c91231a946e50dae03"
13088-
integrity sha512-I/M5WRyEJjwIhyIv6FhkvZS1mWNbb0sIEvDkP8akBnuV1X78mkNhi6Kz9FBBbHzy61U3pmXgzyCSaDZfdQbCSg==
13091+
version "7.8.0"
13092+
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.8.0.tgz#d57a597aecdc2108f2dd68fe74c6099c0a0ef66f"
13093+
integrity sha512-MUER5MWV9mkOB4expgbWknh/C5ZJvOXQlMVSx4tJxTuYtcUCDB6bMZ34fWNOIc8LvrnXmGHGj0eGQuxjQyRgrA==
1308913094

1309013095
tinymce@^7:
1309113096
version "7.2.1"

0 commit comments

Comments
 (0)