Tinymce 6.0.2, Angular 13
Подскажите, если кто знает, мучаюсь уже давно. Подтянула на Angular 13 Tinymce 6.0.2. Но никакого редактора у меня получить так и не получилось. Tinymce новой версии претерпела значительные изменения. Первое из того что я получаю в консоли:
Failed to load model: dom from url models/dom/model.js
В документации я прочитала, что мне необходимо где-то сделать импорт model.js (это одно из изменений, поскольку ранее в папке node_modules/tinymce не было папки models).Я заимпортила его в angular.json. Затем у меня начала появляться ошибка другого плана:
Invalid default value passed for the "toolbar1" option. The value must be a string
Дальше еще 9 подобных ошибок с изменением нумерации "toolbar2","toolbar3" и т.д. При этом с toolbar у меня все в порядке. Помогите плиз. Ошибка возникает при вызове метода setEditorSettings в котором инициализируется tynimce.(В предыдущей версии tinymce код был абсолютно рабочий)
Angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"triggerbee.client": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
{
"glob": "tinymce.min.js",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "plugins",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "skins",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "themes",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "**/*",
"input": "node_modules/tinymce/icons",
"output": "/assets/tinymce/icons/"
},
{
"glob": "Chart.bundle.min.js",
"input": "node_modules/chart.js/dist",
"output": "/assets/js"
},
{
"glob": "web.config",
"input": "config/iis",
"output": "/"
},
{
"glob": "mocks",
"input": "src",
"output": "/"
}
],
"styles": [
],
"scripts": [
"./node_modules/chart.js/dist/Chart.js",
"./node_modules/tinymce/tinymce.js",
"./node_modules/tinymce/models/dom/model.js",
"./node_modules/tinymce/plugins/code/plugin.js",
"./node_modules/tinymce/plugins/lists/plugin.js",
"./node_modules/tinymce/plugins/link/plugin.js",
"src/assets/plugins/tinymce/themes/silver/theme.min.js",
"src/assets/plugins/tinymce/plugins/lineheight/plugin.js",
"src/assets/plugins/tinymce/plugins/labels/plugin.js",
"src/assets/plugins/tinymce/plugins/mergevar/plugin.js",
"src/assets/plugins/tinymce/plugins/labels/langs/sv_SE.js",
"src/assets/plugins/tinymce/plugins/labels/langs/en.js"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"namedChunks": true
},
"configurations": {
"dev": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
]
},
"devcloud": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.devcloud.ts"
}
]
},
"staging": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.staging.ts"
}
]
},
"stagingcloud": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.stagingcloud.ts"
}
]
},
"prod": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.prod.ts"
}
]
},
"test": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.test.ts"
}
]
},
"mock": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"fileReplacements": [
{
"replace": "src/app/environment/environment.dev.ts",
"with": "src/app/environment/environment.mock.ts"
}
]
},
"production": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "150kb"
}
],
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "triggerbee.client:build"
},
"configurations": {
"dev": {
"browserTarget": "triggerbee.client:build:dev"
},
"devcloud": {
"browserTarget": "triggerbee.client:build:devcloud"
},
"staging": {
"browserTarget": "triggerbee.client:build:staging"
},
"stagingcloud": {
"browserTarget": "triggerbee.client:build:stagingcloud"
},
"prod": {
"browserTarget": "triggerbee.client:build:prod"
},
"test": {
"browserTarget": "triggerbee.client:build:test"
},
"mock": {
"browserTarget": "triggerbee.client:build:mock"
},
"production": {
"browserTarget": "triggerbee.client:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "triggerbee.client:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [],
"assets": [
"src/assets",
{
"glob": "tinymce.min.js",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "plugins",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "skins",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "themes",
"input": "node_modules/tinymce",
"output": "/assets/tinymce"
},
{
"glob": "Chart.bundle.min.js",
"input": "node_modules/chart.js/dist",
"output": "/assets/js"
},
{
"glob": "web.config",
"input": "config/iis",
"output": "/"
},
{
"glob": "mocks",
"input": "src",
"output": "/"
}
]
}
}
}
},
"triggerbee.client-e2e": {
"root": "e2e",
"sourceRoot": "e2e",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "triggerbee.client:serve"
}
}
}
}
},
"defaultProject": "triggerbee.client",
"cli": {
"warnings": {},
"analytics": false
},
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"style": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
}
tinymce.service.ts
import {Inject, Injectable} from '@angular/core';
import {LanguageService} from '../../../core/language.service';
import {Subject} from 'rxjs';
import {WidgetComponentType} from 'app/campaign/widgets.model';
import {DOCUMENT} from '@angular/common';
import {AccountVariable, AccountVariableType} from '../../../account-settings/account-settings-models';
import {debounceTime} from 'rxjs/internal/operators';
import {distinctUntilChanged} from 'rxjs/operators';
import {AbstractControl} from '@angular/forms';
declare const tinymce: any;
@Injectable({ providedIn: 'root' })
export class TinyMceService {
private language: string;
public savedTextBS = new Subject<TextComponentEntry>();
public updatedTextBS = new Subject<TextComponentEntry>();
public returnTextOnly = false;
private textUpdatedSubject = new Subject<TextComponentEntry>();
private textSavedSubject = new Subject<TextComponentEntry>();
private currentlyOpenId: string;
private color: string;
private href: string;
constructor(private readonly translateService: LanguageService,
@Inject(DOCUMENT) document) {
this.translateService.getCurrentLanguageNameAsync().then((response) => {
this.language = response;
});
this.textUpdatedSubject.pipe(debounceTime(100), distinctUntilChanged()).subscribe((value => {
if (value.id !== this.currentlyOpenId) {
return;
}
if (!tinymce.get(value.id)) {
console.warn('Can not find tiny editor for id: ' + value.id);
return;
}
value.text = value.onlyTextUpdate ? tinymce.get(value.id).getContent({ format: 'text' })
: tinymce.get(value.id).getContent({ format: 'html' });
this.updatedTextBS.next(value);
}));
this.textSavedSubject.pipe(debounceTime(100), distinctUntilChanged()).subscribe((value => {
if (value.id !== this.currentlyOpenId) {
return;
}
value.text = value.onlyTextUpdate ? tinymce.get(value.id).getContent({ format: 'text' })
: tinymce.get(value.id).getContent({ format: 'html' });
this.savedTextBS.next(value);
}));
}
public rewrittenColorLink(e): void{
if(e.target.style.color){
this.color = e.target.style.color
} else {
this.color = 'rgb(0,0,0)'
}
}
public setEditorSettings(id: string, componentType: WidgetComponentType, inline: boolean,
textVariables: Array<AccountVariable>,
textUpdatedSubject: Subject<TextComponentEntry>,
textSavedSubject: Subject<TextComponentEntry>,
onlyTextShouldBeEdited = false): any {
return {
selector: '#' + id,
body_class: 'widget-editor-tinymce',
componentType: componentType,
theme: 'silver',
menubar: false,
language: this.language,
paste_as_text: true,
inline: inline,
statusbar: false,
default_link_target: '_blank',
link_assume_external_targets: 'https',
setup: (ed) => {
ed.on('keydown', (e) => {
if (!e.shiftKey && e.keyCode === 13) {
if (componentType === WidgetComponentType.Text) {
textUpdatedSubject.next({id: id, onlyTextUpdate: onlyTextShouldBeEdited,
text: '' + ed.getContent({format: 'html'})} as TextComponentEntry);
} else {
e.preventDefault();
// e.target.blur();
ed.editorContainer.style.display = 'none';
textSavedSubject.next({id: id, onlyTextUpdate: onlyTextShouldBeEdited,
text: '' + ed.getContent({format: 'html'})} as TextComponentEntry);
}
} else {
textUpdatedSubject.next({id: id, onlyTextUpdate: onlyTextShouldBeEdited,
text: '' + ed.getContent({format: 'html'})} as TextComponentEntry);
}
});
ed.on('change', (event) => {
if (!(event instanceof KeyboardEvent)) {
textUpdatedSubject.next({id: id, onlyTextUpdate: onlyTextShouldBeEdited,
text: '' + ed.getContent({format: 'html'})} as TextComponentEntry);
}
});
ed.on('click', (e) => {
this.rewrittenColorLink(e)
});
ed.on('mouseUp', (e) => {
this.rewrittenColorLink(e)
});
ed.on('execCommand', (e) => {
if(e.command === 'mceInsertLink') {
const linkElem = (document.querySelector(`a[href='${e.value.href}']`) as HTMLElement);
linkElem.style.color = this.color;
linkElem.style.textDecoration = 'none';
linkElem.style.cursor = 'pointer';
}
});
ed.on('click', function () {
ed.editorContainer.addEventListener('click',(e) =>{
ed.focus();
})
});
},
plugins: 'mergevar link',
toolbar: 'undo redo styleselect bold italic alignleft aligncenter alignright alignjustify | bullist numlist outdent indent',
lineheight_formats: '10px 11px 12px 14px 16px 18px 20px 22px 24px 26px 36px',
font_size_formats: '8px 9px 10px 11px 12px 13px 14px 15px 16px 18px 20px 22px 24px 30px 32px 36px 40px 48px 56px 64px 72px',
theme_url: ('/assets/plugins/tinymce/themes/silver/theme.min.js'),
skin_url: ('/assets/plugins/tinymce/skins/ui/oxide'),
};
}
public closeAllEditors(exceptForId = undefined as string) {
if (tinymce.get() && tinymce.get().length > 0) {
for (let i = 0; i < tinymce.get().length; i++) {
if (tinymce.get()[i].id !== exceptForId) {
tinymce.get(i).destroy();
}
}
}
if (!exceptForId || exceptForId !== this.currentlyOpenId) {
this.currentlyOpenId = undefined;
}
}
public initializeEditor(id: number, componentType: WidgetComponentType, accountVariables: Array<AccountVariable>) {
this.closeAllEditors('' + id);
if (this.currentlyOpenId === ('' + id)) {
return;
}
this.currentlyOpenId = '' + id;
if (!id) {
return;
}
const textVariables = accountVariables ?
accountVariables.filter(a => a.type === AccountVariableType.Text) : undefined;
const onlyTextShouldBeEdited = componentType === WidgetComponentType.InputText ||
componentType === WidgetComponentType.Date || componentType === WidgetComponentType.Dropdown;
this.returnTextOnly = onlyTextShouldBeEdited;
setTimeout(() => {
tinymce.init(
this.setEditorSettings('' + id, componentType, true, textVariables, this.textUpdatedSubject,
this.textSavedSubject, onlyTextShouldBeEdited)
);
// this.focusCurrentEditor();
// Check if tox-container should be removed (to remove arrows)
if (onlyTextShouldBeEdited) {
const wait = setInterval(() => {
if (document.getElementsByClassName('tox-editor-container').length > 0) {
[].forEach.call(document.getElementsByClassName('tox-editor-container'), function (el) {
el.style.visibility = 'hidden';
});
clearInterval(wait);
}
}, 50);
}
}, 200);
}
public closeEditor(id: string) {
this.currentlyOpenId = undefined;
// this.id = undefined;
const tiny = tinymce.get('' + id);
if (tiny) {
tinymce.get('' + id).destroy();
}
}
public editorIsOpen(id: number): boolean {
return !!tinymce.activeEditor && tinymce.activeEditor.id === ('' + id);
}
public focusCurrentEditor() {
setTimeout(() => {
if (!!tinymce.activeEditor) {
tinymce.activeEditor.focus();
}
}, 200);
}
public initializeTiny(id: string, formcontrol: AbstractControl) {
tinymce.init({
selector: '#' + id,
height: 150,
theme: 'silver',
menubar: false,
plugins: 'link',
link_assume_external_targets: 'https',
link_default_target: '_blank',
toolbar: 'bold italic link forecolor',
setup: function (ed) {
ed.on('init', function () {
tinymce.activeEditor.setContent(formcontrol.value);
})
ed.on('keyup', function (e) {
formcontrol.setValue(tinymce.activeEditor.getContent({format: 'html'}));
})
ed.on('click', function () {
ed.editorContainer.addEventListener('click', function (e) {
formcontrol.setValue(tinymce.activeEditor.getContent({format: 'html'}));
})
})
ed.on('focusout', function (e) {
formcontrol.setValue(tinymce.activeEditor.getContent({format: 'html'}));
})
ed.on('focus', function (e) {
formcontrol.markAsTouched();
});
},
theme_url: ('/assets/plugins/tinymce/themes/silver/theme.min.js'),
skin_url: ('/assets/plugins/tinymce/skins/ui/oxide'),
})
}
public closeTiny(id: string): void {
tinymce.remove('#' + id);
}
}
export interface TextComponentEntry {
onlyTextUpdate: boolean;
text: string;
id: string;
}