Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added
- Added 'readonly' property. #TINY-11907

### Fixed
- Updated dependencies. #INT-3324

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

## 8.0.1 - 2024-07-12

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"tinymce-5": "npm:tinymce@^5",
"tinymce-6": "npm:tinymce@^6",
"tinymce-7": "npm:tinymce@^7",
"tinymce-7.5.0": "npm:tinymce@7.5.0",
"to-string-loader": "^1.1.5",
"tslib": "^2.6.2",
"typescript": "~5.5.4",
Expand Down
15 changes: 15 additions & 0 deletions stories/Editor.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { MatTabsModule } from '@angular/material/tabs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ContainerComponent, ContentProjectionComponent } from './contentprojection/ContentProjection.component';
import { BindingComponent } from './data-binding/DataBinding.component';
import { ReadonlyComponent } from './readonly/Readonly.component';

const meta: Meta = {
component: EditorComponent,
Expand Down Expand Up @@ -137,6 +138,20 @@ export const DisablingStory: StoryObj<EditorComponent> = {
}
};

export const ReadonlyStory: StoryObj<EditorComponent> = {
name: 'Readonly',
render: () => ({
moduleMetadata: {
imports: [ ReactiveFormsModule, FormsModule ],
declarations: [ ReadonlyComponent ],
},
template: `<readonly/>`
}),
parameters: {
notes: 'Example of toggling readonly state in the editor component'
}
};

export const ViewQueryStory: StoryObj<EditorComponent> = {
name: 'View Query',
render: () => ({
Expand Down
7 changes: 7 additions & 0 deletions stories/readonly/Readonly.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<button (click)="toggleReadonly()">{{ isReadonly ? 'Escape readonly' : 'Enter readonly' }}</button>
<editor
[apiKey]="apiKey"
[readonly]="isReadonly"
[initialValue]="initialValue"
[init]="{ height: 300 }"
/>
13 changes: 13 additions & 0 deletions stories/readonly/Readonly.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { apiKey, sampleContent } from '../Settings';

@Component({
selector: 'readonly',
templateUrl: './Readonly.component.html',
})
export class ReadonlyComponent {
public isReadonly = false;
public apiKey = apiKey;
public initialValue = sampleContent;
public toggleReadonly = () => (this.isReadonly = !this.isReadonly);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { getTinymce } from '../TinyMCE';
import { listenTinyMCEEvent, bindHandlers, isTextarea, mergePlugins, uuid, noop, isNullOrUndefined } from '../utils/Utils';
import { listenTinyMCEEvent, bindHandlers, isTextarea, mergePlugins, uuid, noop, isNullOrUndefined, setMode } from '../utils/Utils';
import * as DisabledUtils from '../utils/DisabledUtils';
import { EventObj, Events } from './Events';
import { ScriptLoader } from '../utils/ScriptLoader';
import type { Editor as TinyMCEEditor, TinyMCE } from 'tinymce';
Expand Down Expand Up @@ -64,14 +65,26 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
@Input() public modelEvents = 'change input undo redo';
@Input() public allowedEvents?: string | string[];
@Input() public ignoreEvents?: string | string[];
@Input()
public set readonly(val) {
this._readonly = val;
if (this._editor && this._editor.initialized) {
setMode(this._editor, val ? 'readonly' : 'design');
}
}

public get readonly() {
return this._readonly;
}

@Input()
public set disabled(val) {
this._disabled = val;
if (this._editor && this._editor.initialized) {
if (typeof this._editor.mode?.set === 'function') {
this._editor.mode.set(val ? 'readonly' : 'design');
} else if ('setMode' in this._editor && typeof this._editor.setMode === 'function') {
this._editor.setMode(val ? 'readonly' : 'design');
if (DisabledUtils.isDisabledOptionSupported()) {
this._editor.options.set('disabled', val ?? false);
} else {
setMode(this._editor, val ? 'readonly' : 'design');
}
}
}
Expand All @@ -89,6 +102,7 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
private _elementRef: ElementRef;
private _element?: HTMLElement;
private _disabled?: boolean;
private _readonly?: boolean;
private _editor?: TinyMCEEditor;

private onTouchedCallback = noop;
Expand Down Expand Up @@ -176,7 +190,10 @@ export class EditorComponent extends Events implements AfterViewInit, ControlVal
selector: undefined,
target: this._element,
inline: this.inline,
readonly: this.disabled,
...( DisabledUtils.isDisabledOptionSupported()
? { disabled: this.disabled, readonly: this.readonly }
: { readonly: this.disabled || this.readonly }
),
license_key: this.licenseKey,
plugins: mergePlugins((this.init && this.init.plugins) as string, this.plugins),
toolbar: this.toolbar || (this.init && this.init.toolbar),
Expand Down
12 changes: 12 additions & 0 deletions tinymce-angular-component/src/main/ts/utils/DisabledUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getTinymce } from '../TinyMCE';
import { TinyMCE } from 'tinymce';

const isDisabledOptionSupported = () => {
const tiny: TinyMCE = getTinymce();
// Disabled option is supported since Tiny 7.6.0
return Number(tiny.majorVersion) > 7 || (Number(tiny.majorVersion) === 7 && Number(tiny.minorVersion) >= 6);
};

export {
isDisabledOptionSupported
};
16 changes: 13 additions & 3 deletions tinymce-angular-component/src/main/ts/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { HasEventTargetAddRemove } from 'rxjs/internal/observable/fromEvent';

import { EditorComponent } from '../editor/editor.component';
import { validEvents, Events } from '../editor/Events';
import { Editor } from 'tinymce';

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

const parseStringProperty = (property: string | string[] | undefined, defaultValue: (keyof Events)[]): string[] => {
if ( typeof property === 'string') {
if (typeof property === 'string') {
return property.split(',').map((value) => value.trim());
}
if ( Array.isArray(property)) {
if (Array.isArray(property)) {
return property;
}
return defaultValue;
Expand Down Expand Up @@ -91,6 +92,14 @@ const isObserved = (o: Subject<unknown>): boolean =>
// checking if a subject has observers.
o.observed || o.observers?.length > 0;

const setMode = (editor: Editor, mode: 'readonly' | 'design') => {
if (typeof editor.mode?.set === 'function') {
editor.mode.set(mode);
} else if ('setMode' in editor && typeof editor.setMode === 'function') {
editor.setMode(mode);
}
};

export {
listenTinyMCEEvent,
bindHandlers,
Expand All @@ -99,5 +108,6 @@ export {
normalizePluginArray,
mergePlugins,
noop,
isNullOrUndefined
isNullOrUndefined,
setMode
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Assertions } from '@ephox/agar';
import '../alien/InitTestEnvironment';

import { EditorComponent } from '../../../main/ts/public_api';
import { describe, it } from '@ephox/bedrock-client';
import { eachVersionContext, editorHook } from '../alien/TestHooks';
import { Editor } from 'tinymce';

describe('DisabledPropertyTest', () => {
const getMode = (editor: Editor) => {
if (typeof editor.mode?.get === 'function') {
return editor.mode.get();
}
return editor.readonly ? 'readonly' : 'design';
};
const assertDesignMode = (editor: Editor) => Assertions.assertEq('TinyMCE should be in design mode', 'design', getMode(editor));
const assertReadonlyMode = (editor: Editor) => Assertions.assertEq('TinyMCE should be in readonly mode', 'readonly', getMode(editor));
const assertDisabledOption = (editor: Editor, expected: boolean) =>
Assertions.assertEq(`TinyMCE should have disabled option set to ${expected}`, expected, editor.options.get('disabled'));

eachVersionContext([ '7.5.0' ], () => {
const createFixture = editorHook(EditorComponent);

it(`Component 'disabled' property is mapped to editor 'readonly' property`, async () => {
const { editor } = await createFixture({ disabled: true });
assertReadonlyMode(editor);
});

it(`Toggling component's 'disabled' property is mapped to editor 'readonly' property`, async () => {
const fixture = await createFixture();
const { editor } = fixture;

assertDesignMode(editor);

fixture.componentRef.setInput('disabled', true);
fixture.detectChanges();
assertReadonlyMode(editor);

fixture.componentRef.setInput('disabled', false);
fixture.detectChanges();
assertDesignMode(editor);
});

it(`[disabled]=true [readonly]=false triggers readonly mode`, async () => {
const { editor } = await createFixture({ disabled: true, readonly: false });
assertReadonlyMode(editor);
});

it(`[disabled]=false [readonly]=true triggers readonly mode`, async () => {
const { editor } = await createFixture({ disabled: false, readonly: true });
assertReadonlyMode(editor);
});
});

eachVersionContext([ '7' ], () => {
const createFixture = editorHook(EditorComponent);

it(`Component 'disabled' property is mapped to editor 'disabled' property`, async () => {
const { editor } = await createFixture({ disabled: true });

Assertions.assertEq('TinyMCE should have disabled option set to true', true, editor.options.get('disabled'));
assertDesignMode(editor);
});

it(`Toggling component's 'disabled' property is mapped to editor 'disabled' property`, async () => {
const fixture = await createFixture();
const { editor } = fixture;

assertDesignMode(editor);
assertDisabledOption(editor, false);

fixture.componentRef.setInput('disabled', true);
fixture.detectChanges();
assertDesignMode(editor);
assertDisabledOption(editor, true);

fixture.componentRef.setInput('disabled', false);
fixture.detectChanges();
assertDesignMode(editor);
assertDisabledOption(editor, false);
});
});

eachVersionContext([ '4', '5', '6', '7' ], () => {
const createFixture = editorHook(EditorComponent);

it(`Setting the 'readonly' property causing readonly mode`, async () => {
const { editor } = await createFixture({ readonly: true });
assertReadonlyMode(editor);
});

it(`Toggling component's 'readonly' property is mapped to editor 'readonly' mode`, async () => {
const fixture = await createFixture();
const { editor } = fixture;

assertDesignMode(editor);

fixture.componentRef.setInput('readonly', true);
fixture.detectChanges();
assertReadonlyMode(editor);

fixture.componentRef.setInput('readonly', false);
fixture.detectChanges();
assertDesignMode(editor);
});
});
});
11 changes: 8 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13082,10 +13082,15 @@ tiny-invariant@^1.3.1, tiny-invariant@^1.3.3:
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.3.tgz#0025a4aaa4c24dc2a3e32e83dfda705d196fd802"
integrity sha512-3fCHKAeqT+xNwBVESf6iDbDV0VNwZNmfrkx9c/6Gz5iB8piMfaO6s7FvoiTrj1hf1gVbfyLTnz1DooI6DhgINQ==

"tinymce-7.5.0@npm:tinymce@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.5.0.tgz#56388d314399c288a100df4aaf468153f29477f1"
integrity sha512-A7iuQPIfeze5rO6bvnnPwP7TiWnPA9AGr8U/9ssLwrEG+FMYPzvLPt3RT8ktVn/wPSJkVBBSLCAZX2dAHb8YEA==

"tinymce-7@npm:tinymce@^7":
version "7.1.2"
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.1.2.tgz#cb40e527dc03d6a0547a23c91231a946e50dae03"
integrity sha512-I/M5WRyEJjwIhyIv6FhkvZS1mWNbb0sIEvDkP8akBnuV1X78mkNhi6Kz9FBBbHzy61U3pmXgzyCSaDZfdQbCSg==
version "7.8.0"
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-7.8.0.tgz#d57a597aecdc2108f2dd68fe74c6099c0a0ef66f"
integrity sha512-MUER5MWV9mkOB4expgbWknh/C5ZJvOXQlMVSx4tJxTuYtcUCDB6bMZ34fWNOIc8LvrnXmGHGj0eGQuxjQyRgrA==

tinymce@^7:
version "7.2.1"
Expand Down