import { isPlatformBrowser } from '@angular/common'; import { AfterViewInit, Component, ElementRef, forwardRef, Inject, Input, NgZone, OnDestroy, PLATFORM_ID, InjectionToken, Optional, Injector, LOCALE_ID, OnInit, HostBinding, ViewEncapsulation, Renderer2 } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable } from 'rxjs'; import { getTinymce } from './TinyMCE'; import { bindHandlers, isTextarea, mergePlugins, uuid, noop, isNullOrUndefined } from './utils/Utils'; import { Events } from './Events'; import { ScriptLoader } from './utils/ScriptLoader'; import { EditorService } from './editor.service'; import { NavigationEnd, NavigationStart, Router } from '@angular/router'; import { Convert2HtmlEditorToolbars, RichTextEditorToolbars } from '@farris/ui-common'; export const TINYMCE_SCRIPT_SRC = new InjectionToken('TINYMCE_SCRIPT_SRC'); const EDITOR_COMPONENT_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditorComponent), multi: true }; @Component({ selector: 'farris-editor', template: '', styleUrls: ['./editor.component.scss'], providers: [EDITOR_COMPONENT_VALUE_ACCESSOR], encapsulation: ViewEncapsulation.None }) export class EditorComponent extends Events implements OnInit, AfterViewInit, ControlValueAccessor, OnDestroy { @HostBinding('class') rootClass = 'farris-editor'; @HostBinding('class.farris-editor-disabled') get disabledClass(): boolean { return this._disabled; } @HostBinding('class.farris-editor-readonly') get readonlyClass(): boolean { return this._readOnly; } @Input() set disabled(val) { this._disabled = val; if (this._editor && this._editor.initialized) { this._editor.setMode(val ? 'readonly' : 'design'); this.setDivElement(val); this.editorBlured(); } } get disabled() { return this._disabled; } @Input() customToolbar = false; @Input() set readOnly(val) { this._readOnly = val; if (this._editor && this._editor.initialized) { this._editor.setMode(val ? 'readonly' : 'design'); this.setDivElement(val); this.editorBlured(); } } get readOnly() { return this._readOnly; } get editor() { return this._editor; } public ngZone: NgZone; localeIds = { 'en': 'es', 'zh-CHS': 'zh_CN' }; currentLocale = 'zh-CHS'; @Input() public cloudChannel = '5'; @Input() public apiKey = 'no-api-key'; // @Input() public init: Record | undefined; @Input() public editorId = ''; @Input() public initialValue: string | undefined; @Input() public outputFormat: 'html' | 'text' | undefined; @Input() public inline: boolean | undefined; @Input() public tagName: string | undefined; @Input() public plugins: string | undefined; @Input() public toolbar: RichTextEditorToolbars; @Input() public modelEvents = 'change keyup undo redo'; @Input() public height = 100; @Input() public autoresizeMaxHeight = 800; @Input() @HostBinding('class.farris-editor-border') showBorder = true; _fontFamilyList: Array = ['Microsoft YaHei', 'Helvetica Neue', 'PingFang SC', 'sans-serif', 'simsun', 'serif', 'SimHei', 'arial', 'helvetica', 'arial black', 'avant garde', 'book antiqua', 'palatino', 'FangSong']; @Input() set fontFamilyList(val) { if (val) { this._fontFamilyList = val; } } get fontFamilyList() { return this._fontFamilyList; } _defaultFontFamily = 'Microsoft YaHei'; @Input() set defaultFontFamily(val) { if (val) { this._defaultFontFamily = val; } } get defaultFontFamily() { return this._defaultFontFamily; } @Input() beforeUpload: (fileObj?: {}) => Observable; private init: any; private _elementRef: ElementRef; private _element: Element | undefined; private _disabled: boolean | undefined; private _readOnly: boolean | undefined; private _editor: any; private _divElement: Element | undefined; private onTouchedCallback = noop; private onChangeCallback = noop; editorService: EditorService; public _render: Renderer2; private router: Router; private __WEBPAGEURL__ = ''; private editorBodyCls = 'mce-content-body'; constructor( elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object, @Optional() private injector: Injector, @Optional() @Inject(TINYMCE_SCRIPT_SRC) private tinymceScriptSrc?: string ) { super(); this._elementRef = elementRef; this.ngZone = ngZone; this._render = this.injector.get(Renderer2); this.initialise = this.initialise.bind(this); if (this.injector) { this.currentLocale = this.injector.get(LOCALE_ID); this.editorService = this.injector.get(EditorService); this.router = this.injector.get(Router, null); if (this.router) { this.router.events.subscribe((e: any) => { if (e instanceof NavigationStart) { if (!this.__WEBPAGEURL__) { this.__WEBPAGEURL__ = e.url; } } else if (e instanceof NavigationEnd) { if (this.__WEBPAGEURL__ === e.url && this.editor) { // console.log(this.editor); this.editor.destroy(); this.initialise(!1); } } }); } } } ngOnInit() { const _this = this; this.init = { selector: 'textarea', menubar: false, inline: false, // statusbar: false, plugins: [ 'advlist autolink lists link image charmap print preview anchor textcolor powerpaste', 'searchreplace visualblocks code fullscreen', 'insertdatetime media table image imagetools code help nonbreaking' ], language: this.localeIds[this.currentLocale], // toolbar定义快捷栏的操作, | 用来分隔显示 toolbar: 'undo redo | formatselect fontsizeselect fontselect | \ bold italic underline strikethrough forecolor backcolor link | \ alignleft aligncenter alignright alignjustify | \ bullist numlist outdent indent table image | removeformat', min_height: 100, // max_height: 400, block_formats: 'Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6', fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px', font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;\ 宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;\ Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;', images_upload_url: 'postAcceptor.php', images_upload_handler: function (blobInfo, success, failure) { if (_this.beforeUpload) { _this.beforeUpload(blobInfo).subscribe(res => { success(res); }, error => { failure(error); return; } ); } else { const reader = new FileReader(); reader.readAsDataURL(blobInfo.blob()); reader.onload = function () { success(this.result); }; } }, branding: false, readonly: false, setup: null, end_container_on_empty_block: true, powerpaste_word_import: 'propmt', powerpaste_html_import: 'propmt', powerpaste_allow_local_images: true, paste_data_images: true, autoresize_min_height: 100, autoresize_max_height: 800, autoresize_on_init: true, autoresize_overflow_padding: 0, nonbreaking_force_tab: true, default_link_target: '_blank' }; } private editorBlured() { setTimeout(() => { const tinyBody = this._editor.iframeElement.contentWindow.document.getElementsByClassName(this.editorBodyCls); if (tinyBody && tinyBody.length) { tinyBody[0].blur(); } }, 0); } public writeValue(value: string | null): void { if (this._editor && this._editor.initialized) { this._editor.setContent(isNullOrUndefined(value) ? '' : value); this._divElement.innerHTML = (isNullOrUndefined(value) ? '' : value); } else { this.initialValue = value === null ? undefined : value; } if (!this.__WEBPAGEURL__ && this.router) { this.__WEBPAGEURL__ = this.router.url; } } public registerOnChange(fn: (_: any) => void): void { this.onChangeCallback = fn; } public registerOnTouched(fn: any): void { this.onTouchedCallback = fn; } public setDisabledState(isDisabled: boolean) { if (this._editor) { this._editor.setMode(isDisabled ? 'readonly' : 'design'); } else if (isDisabled) { this.init = { ...this.init, readonly: true }; } } public ngAfterViewInit() { if (isPlatformBrowser(this.platformId)) { this.editorId = this.editorId || uuid('tiny-angular'); this.inline = typeof this.inline !== 'undefined' ? (typeof this.inline === 'boolean' ? this.inline : true) : this.init && this.init.inline; this.createElement(); this.init.selector = '#' + this.editorId; if (getTinymce()) { this.initialise(); } else if (this._element && this._element.ownerDocument) { ScriptLoader.load( this._element.ownerDocument, this.getScriptSrc(), this.initialise ); } } } public ngOnDestroy() { this.__WEBPAGEURL__ = ''; if (this._editor) { this._editor.remove(); this._editor = null; this._divElement = null; } } public createElement() { const tagName = typeof this.tagName === 'string' ? this.tagName : 'div'; this._element = document.createElement(this.inline ? tagName : 'textarea'); this._element.setAttribute('class', 'd-none'); this._divElement = document.createElement('div'); if (this.showBorder) { this._divElement.className = 'farris-editor-placeholder'; } else { this._divElement.className = 'farris-editor-placeholder farris-editor-placeholder-border'; } this._render.setStyle(this._divElement, 'height', this.height + 'px'); if (this._element) { this._element.id = this.editorId; if (isTextarea(this._element)) { this._element.style.visibility = 'hidden'; } this._elementRef.nativeElement.appendChild(this._element); this._elementRef.nativeElement.appendChild(this._divElement); } } public initialise(isfirstInit = true) { const finalInit = { ...this.init, target: this._element, inline: this.inline, // readonly: this.disabled || this.readOnly, plugins: mergePlugins(this.init && this.init.plugins, this.plugins), toolbar: this.init && this.init.toolbar, height: this.height, autoresize_max_height: this.autoresizeMaxHeight, setup: (editor: any) => { this._editor = editor; editor.on('init', (e) => { this.initEditor(editor, isfirstInit); if (this._readOnly || this._disabled) { this._editor.setMode('readonly'); } else { this._editor.setMode('design'); } setTimeout(() => { this.setDivElement(this._readOnly || this._disabled); }, 0); }); editor.on('PastePostProcess', (data) => { this.replaceFontFamily(data.node); }); bindHandlers(this, editor); if (this.init && typeof this.init.setup === 'function') { this.init.setup(editor); } } }; if (this.customToolbar) { finalInit.toolbar = Convert2HtmlEditorToolbars('advanced', this.toolbar); } if (isTextarea(this._element)) { this._element.style.visibility = ''; } this.ngZone.runOutsideAngular(() => { setTimeout(() => { getTinymce().init(finalInit); }, 100); }); } refresh() { if (this.editor) { this.editor.destroy(); } this.initialise(false); } setToolbar(toolbar: RichTextEditorToolbars) { this.toolbar = toolbar; this.refresh(); } private setDivElement(readonly) { if (readonly) { this._divElement.classList.remove('d-none'); } else { this._divElement.classList.add('d-none'); } // if(this._editor && this._editor.initialized){ // this._divElement.innerHTML = this._editor.getContent({ format: this.outputFormat }); // } } private getScriptSrc() { return isNullOrUndefined(this.tinymceScriptSrc) ? `https://cdn.tiny.cloud/1/${this.apiKey}/tinymce/${this.cloudChannel}/tinymce.min.js` : this.tinymceScriptSrc; } private initEditor(editor: any, isfirstInit) { editor.on('blur', () => this.ngZone.run(() => this.onTouchedCallback())); editor.on(this.modelEvents, () => { this.ngZone.run(() => { this.onChangeCallback(editor.getContent({ format: this.outputFormat })); this._divElement.innerHTML = editor.getContent({ format: this.outputFormat }); }); // console.log(editor.getContent({ format: this.outputFormat })); }); if (typeof this.initialValue === 'string' && isfirstInit) { this.ngZone.run(() => { editor.setContent(this.initialValue); this._divElement.innerHTML = this.initialValue; this.onChangeCallback(editor.getContent({ format: this.outputFormat })); }); } } private replaceFontFamily(el) { if (el.children && el.children.length) { for (let k = 0; k < el.children.length; k++) { this.replaceFontFamily(el.children[k]); } } let ff = el.style.fontFamily; let face = el.getAttribute('face'); if (ff) { let fonts = ff.split(','); let r = this.getFontFamilyArray(fonts); el.style.fontFamily = r; } if (face) { let faces = face.split(','); let r = this.getFontFamilyArray(faces); el.setAttribute('face', r); } } private getFontFamilyArray(fonts, zcFontArr = this._fontFamilyList) { let ss = []; fonts.forEach(n => { if (zcFontArr.find(f => f === this.trim(n))) { ss.push(this.trim(n)); } }); let r = ss.join(','); if (!r) { r = this._defaultFontFamily; } return r; } private trim(str) { return str.replace(/^\s*|\s*$/g, ''); } }