Headless CMS and Content Managment Hub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

234 lines
7.1 KiB

/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable:prefer-for-of
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, OnDestroy, Output, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AppsState,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogModel,
ResourceLoaderService,
Types
} from '@app/shared/internal';
declare var tinymce: any;
export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true
};
const ImageTypes = [
'image/jpeg',
'image/png',
'image/jpg',
'image/gif'
];
@Component({
selector: 'sqx-rich-editor',
styleUrls: ['./rich-editor.component.scss'],
templateUrl: './rich-editor.component.html',
providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
private tinyEditor: any;
private tinyInitTimer: any;
private value: string;
private isDisabled = false;
public assetsDialog = new DialogModel();
@ViewChild('editor')
public editor: ElementRef;
@Output()
public assetPluginClicked = new EventEmitter<any>();
constructor(
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly changeDetector: ChangeDetectorRef,
private readonly resourceLoader: ResourceLoaderService
) {
}
public ngOnDestroy() {
clearTimeout(this.tinyInitTimer);
tinymce.remove(this.editor);
}
public ngAfterViewInit() {
const self = this;
this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.3/tinymce.min.js').then(() => {
tinymce.init(self.getEditorOptions());
});
}
private showSelector = () => {
this.assetsDialog.show();
this.changeDetector.detectChanges();
}
private getEditorOptions() {
const self = this;
return {
convert_fonts_to_spans: true,
convert_urls: false,
plugins: 'code image media link lists advlist paste',
removed_menuitems: 'newdocument',
resize: true,
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter | bullist numlist outdent indent | link image media | assets',
images_upload_handler: (blob: any, success: (url: string) => void, failed: () => void) => {
const file = new File([blob.blob()], blob.filename(), { lastModified: new Date().getTime() });
this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now())
.subscribe(asset => {
if (Types.is(asset, AssetDto)) {
success(asset.url);
}
}, () => {
failed();
});
},
setup: (editor: any) => {
self.tinyEditor = editor;
self.tinyEditor.setMode(this.isDisabled ? 'readonly' : 'design');
self.tinyEditor.addButton('assets', {
onclick: this.showSelector,
icon: 'assets',
text: '',
tooltip: 'Insert Assets'
});
self.tinyEditor.on('change', () => {
const value = editor.getContent();
if (this.value !== value) {
this.value = value;
self.callChange(value);
}
});
self.tinyEditor.on('paste', (event: ClipboardEvent) => {
for (let i = 0; i < event.clipboardData.items.length; i++) {
const file = event.clipboardData.items[i].getAsFile();
if (file && ImageTypes.indexOf(file.type) >= 0) {
self.uploadFile(file);
}
}
});
self.tinyEditor.on('drop', (event: DragEvent) => {
if (event.dataTransfer) {
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files.item(i);
if (file && ImageTypes.indexOf(file.type) >= 0) {
self.uploadFile(file);
}
}
}
});
self.tinyEditor.on('blur', () => {
self.callTouched();
});
self.tinyInitTimer =
setTimeout(() => {
self.tinyEditor.setContent(this.value || '');
}, 1000);
},
target: this.editor.nativeElement
};
}
public writeValue(obj: any) {
this.value = Types.isString(obj) ? obj : '';
if (this.tinyEditor) {
this.tinyEditor.setContent(this.value);
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.tinyEditor) {
this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design');
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
public insertAssets(assets: AssetDto[]) {
let content = '';
for (let asset of assets) {
content += `<img src="${asset.url}" alt="${asset.fileName}" />`;
}
if (content.length > 0) {
this.tinyEditor.execCommand('mceInsertContent', false, content);
}
this.assetsDialog.hide();
}
public insertFiles(files: File[]) {
for (let file of files) {
this.uploadFile(file);
}
}
private uploadFile(file: File) {
const uploadText = `[Uploading file...${new Date()}]`;
this.tinyEditor.execCommand('mceInsertContent', false, uploadText);
const replaceText = (replacement: string) => {
const content = this.tinyEditor.getContent().replace(uploadText, replacement);
this.tinyEditor.setContent(content);
};
this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now())
.subscribe(asset => {
if (Types.is(asset, AssetDto)) {
replaceText(`<img src="${asset.url}" alt="${asset.fileName}" />`);
}
}, () => {
replaceText('FAILED');
});
}
}