UNPKG

angular-rich-text-editor

Version:

A lightweight, configurable rich-text editor component for Angular applications.

1 lines โ€ข 74.6 kB
{"version":3,"file":"angular-rich-text-editor.mjs","sources":["../../../projects/rich-text-editor/src/lib/paths.ts","../../../projects/rich-text-editor/src/lib/rich-text-editor.service.ts","../../../projects/rich-text-editor/src/lib/rich-text-editor.constant.ts","../../../projects/rich-text-editor/src/lib/rich-text-editor-license.token.ts","../../../projects/rich-text-editor/src/lib/utils/toolbar-cleaner.ts","../../../projects/rich-text-editor/src/lib/utils/editor-event-manager.ts","../../../projects/rich-text-editor/src/lib/utils/dom-cleanup.ts","../../../projects/rich-text-editor/src/lib/utils/validation-utils.ts","../../../projects/rich-text-editor/src/lib/rich-text-editor.component.ts","../../../projects/rich-text-editor/src/lib/rich-text-editor.module.ts","../../../projects/rich-text-editor/src/public-api.ts","../../../projects/rich-text-editor/src/angular-rich-text-editor.ts"],"sourcesContent":["// src/lib/paths.ts\nimport { InjectionToken } from '@angular/core';\n\n// Default paths\nexport const DEFAULT_RICHTEXTEDITOR_ASSETS_PATH = 'assets/richtexteditor';\n\n// Injection token for configuration\nexport const RICHTEXTEDITOR_ASSETS_PATH = new InjectionToken<string>(\n 'RICHTEXTEDITOR_ASSETS_PATH',\n {\n providedIn: 'root',\n factory: () => DEFAULT_RICHTEXTEDITOR_ASSETS_PATH\n }\n);","import { Inject, Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { RICHTEXTEDITOR_ASSETS_PATH } from './paths';\n\n// Interface for editor instance management\nexport interface RTEInstance {\n id: string;\n component: any; // Reference to the RichTextEditorComponent\n element: HTMLElement;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class RichTextEditorService {\n private currentEditor: any = null; // Reference to the current RTE component\n private contentSubject = new BehaviorSubject<string>('');\n\n // Observable for content changes\n public content$: Observable<string> = this.contentSubject.asObservable();\n\n constructor(@Inject(RICHTEXTEDITOR_ASSETS_PATH) private assetsPath: string) {}\n\n getContentCssUrl(): string {\n return `${this.assetsPath}/runtime/richtexteditor_content.css`;\n }\n\n getPreviewCssUrl(): string {\n return `${this.assetsPath}/runtime/richtexteditor_preview.css`;\n }\n\n getPreviewScriptUrl(): string {\n return `${this.assetsPath}/runtime/richtexteditor_preview.js`;\n }\n\n // Simple editor management\n setCurrentEditor(component: any): void {\n this.currentEditor = component;\n }\n\n clearCurrentEditor(): void {\n this.currentEditor = null;\n this.contentSubject.next('');\n }\n\n // Content manipulation methods\n insertContentAtCursor(content: string): boolean {\n if (!this.currentEditor) {\n console.warn('[RTE Service] No editor is currently active');\n return false;\n }\n\n try {\n this.currentEditor.insertContentAtCursor(content);\n // Update the observable after insertion\n const newContent = this.getContent();\n this.contentSubject.next(newContent);\n return true;\n } catch (error) {\n console.error('[RTE Service] Failed to insert content:', error);\n return false;\n }\n }\n\n /**\n * Get HTML content from current editor\n * @returns HTML string (empty string if no content/editor)\n */\n getContent(): string {\n if (!this.currentEditor?.editorInstance) {\n console.warn('[RTE Service] No active editor found');\n return '';\n }\n\n try {\n const htmlContent = this.currentEditor.editorInstance.getHTMLCode();\n\n // Handle null/undefined cases\n if (htmlContent === null || htmlContent === undefined) {\n return this.getContentFallback();\n }\n\n return htmlContent;\n } catch (error) {\n console.error('[RTE Service] Failed to get content:', error);\n return this.getContentFallback();\n }\n }\n\n /**\n * Fallback method to retrieve content\n */\n private getContentFallback(): string {\n try {\n // Try to get from iframe directly\n const iframe =\n this.currentEditor?.editorContainer?.nativeElement?.querySelector(\n 'iframe'\n );\n if (iframe?.contentDocument?.body) {\n return iframe.contentDocument.body.innerHTML || '';\n }\n\n // Try to get from component's value\n if (this.currentEditor?.value) {\n return this.currentEditor.value;\n }\n\n return '';\n } catch (error) {\n console.error('[RTE Service] Fallback retrieval failed:', error);\n return '';\n }\n }\n\n /**\n * Set HTML content for current editor\n */\n setContent(content: string): boolean {\n if (!this.currentEditor?.editorInstance) {\n console.warn('[RTE Service] No active editor found');\n return false;\n }\n\n try {\n this.currentEditor.editorInstance.setHTMLCode(content);\n\n // Ensure component state is synced\n if (this.currentEditor.value !== content) {\n this.currentEditor.value = content;\n }\n\n // Trigger change event if needed\n if (this.currentEditor.onChange) {\n this.currentEditor.onChange(content);\n }\n\n // Update observable\n this.contentSubject.next(content);\n\n return true;\n } catch (error) {\n console.error('[RTE Service] Failed to set content:', error);\n return false;\n }\n }\n\n /**\n * Clear editor content\n */\n clearContent(): boolean {\n return this.setContent('<p><br></p>');\n }\n\n /**\n * Focus current editor\n */\n focus(): boolean {\n if (!this.currentEditor) {\n console.warn('[RTE Service] No active editor found');\n return false;\n }\n\n try {\n // Try editor's focus method first\n if (this.currentEditor.editorInstance?.focus) {\n this.currentEditor.editorInstance.focus();\n return true;\n }\n\n // Fallback to iframe focus\n const iframe =\n this.currentEditor.editorContainer?.nativeElement?.querySelector(\n 'iframe'\n );\n if (iframe?.contentDocument?.body) {\n iframe.contentDocument.body.focus();\n return true;\n }\n\n return false;\n } catch (error) {\n console.error('[RTE Service] Failed to focus editor:', error);\n return false;\n }\n }\n\n /**\n * Execute command on the editor\n */\n executeCommand(command: string, value?: any): boolean {\n if (!this.currentEditor?.editorInstance) {\n console.warn('[RTE Service] No active editor found');\n return false;\n }\n\n try {\n // Try editor's execCommand if available\n if (typeof this.currentEditor.editorInstance.execCommand === 'function') {\n this.currentEditor.editorInstance.execCommand(command, false, value);\n return true;\n }\n\n // Fallback to iframe execCommand\n const iframe =\n this.currentEditor.editorContainer?.nativeElement?.querySelector(\n 'iframe'\n );\n if (iframe?.contentDocument) {\n iframe.contentDocument.execCommand(command, false, value);\n return true;\n }\n\n return false;\n } catch (error) {\n console.error('[RTE Service] Failed to execute command:', error);\n return false;\n }\n }\n\n /**\n * Get selected text from editor\n */\n getSelectedText(): string {\n if (!this.currentEditor?.editorContainer) {\n return '';\n }\n\n try {\n const iframe =\n this.currentEditor.editorContainer.nativeElement.querySelector(\n 'iframe'\n );\n if (iframe?.contentWindow) {\n const selection = iframe.contentWindow.getSelection();\n return selection ? selection.toString() : '';\n }\n return '';\n } catch (error) {\n console.error('[RTE Service] Failed to get selected text:', error);\n return '';\n }\n }\n\n /**\n * Check if content is empty\n */\n isContentEmpty(): boolean {\n const content = this.getContent();\n\n if (!content) return true;\n\n // Create a temporary div to parse HTML\n const div = document.createElement('div');\n div.innerHTML = content;\n\n // Get text content and clean it\n const text = div.textContent?.replace(/\\u00A0/g, '').trim() || '';\n\n // Check if only contains empty tags\n const cleaned = div.innerHTML\n .replace(/<br\\s*\\/?>/gi, '')\n .replace(/<div>(\\s|&nbsp;)*<\\/div>/gi, '')\n .replace(/<p>(\\s|&nbsp;)*<\\/p>/gi, '')\n .replace(/&nbsp;/gi, '')\n .trim();\n\n return !text && cleaned.length === 0;\n }\n\n /**\n * Get character count\n */\n getCharacterCount(): number {\n const content = this.getContent();\n if (!content) return 0;\n\n const div = document.createElement('div');\n div.innerHTML = content;\n const text = div.textContent?.replace(/\\u00A0/g, '').trim() || '';\n\n return text.length;\n }\n\n /**\n * Get word count\n */\n getWordCount(): number {\n const content = this.getContent();\n if (!content) return 0;\n\n const div = document.createElement('div');\n div.innerHTML = content;\n const text = div.textContent?.replace(/\\u00A0/g, '').trim() || '';\n\n if (!text) return 0;\n\n const words = text.match(/\\b\\w+\\b/g);\n return words ? words.length : 0;\n }\n\n // Check if editor is readonly\n isReadonly(): boolean {\n return this.currentEditor?.readonly || false;\n }\n\n // Check if editor is available\n isAvailable(): boolean {\n return !!this.currentEditor?.editorInstance;\n }\n\n /**\n * Hide all floating panels (useful for cleanup)\n */\n hideFloatingPanels(): void {\n if (this.currentEditor?.hideAllFloatPanels) {\n this.currentEditor.hideAllFloatPanels();\n }\n }\n\n /**\n * Removes the last inserted image with a temporary blob or data URL.\n */\n removeLastPlaceholderImage(): boolean {\n if (!this.currentEditor) return false;\n\n const iframe =\n this.currentEditor?.editorContainer?.nativeElement?.querySelector(\n 'iframe'\n );\n const body = iframe?.contentDocument?.body;\n\n if (!body) return false;\n\n const images = Array.from(body.querySelectorAll('img'));\n\n for (let i = images.length - 1; i >= 0; i--) {\n const img = images[i] as HTMLImageElement;\n if (img.src.startsWith('blob:') || img.src.startsWith('data:')) {\n img.parentElement?.removeChild(img);\n console.debug('[RTE Service] Removed temporary placeholder image.');\n return true;\n }\n }\n\n return false;\n }\n}\n","export type RTEPreset = 'BASIC' | 'STANDARD' | 'FULL' | 'MINIMAL';\n\nexport const RTE_TOOLBAR_PRESETS: Record<RTEPreset, string> = {\n BASIC: 'bold,italic,underline|fontname,fontsize|forecolor,backcolor|removeformat',\n STANDARD: 'bold,italic,underline,strikethrough|fontname,fontsize|forecolor,backcolor|removeformat|undo,redo',\n FULL: \"{bold,italic,underline,forecolor,backcolor}|{justifyleft,justifycenter,justifyright,justifyfull}|{insertorderedlist,insertunorderedlist,indent,outdent}{superscript,subscript}\" +\n \" #{paragraphs:toggle,fontname:toggle,fontsize:toggle,inlinestyle,lineheight}\" +\n \" / {removeformat,cut,copy,paste,delete,find}|{insertlink,unlink,insertblockquote,insertemoji,insertchars,inserttable,insertimage,insertgallery,insertvideo,insertdocument,insertcode}\" +\n \"#{preview,code,selectall}\" +\n \" /{paragraphs:dropdown | fontname:dropdown | fontsize:dropdown} {paragraphstyle,toggle_paragraphop,menu_paragraphop}\" +\n \"#{toggleborder,fullscreenenter,fullscreenexit,undo,redo,togglemore}\",\n MINIMAL: 'bold,italic|fontsize|forecolor|removeformat'\n};\n\nexport type RTEImageTool =\n | 'menu_controlsize'\n | 'imagecaption'\n | 'controlalt'\n | 'controlinsertlink'\n | 'controleditlink'\n | 'controlopenlink'\n | 'controlunlink'\n | 'menu_controljustify'\n | 'imagestyle'\n | 'delete';","// rich-text-editor-license.token.ts\nimport { InjectionToken } from '@angular/core';\n\nexport const RTE_LICENSE_KEY = new InjectionToken<string>('RTE_LICENSE_KEY');\n","export function cleanToolbarString(toolbar: string): string {\n let cleaned = toolbar;\n\n // Remove :toggle and :dropdown\n cleaned = cleaned.replace(/:toggle/g, '').replace(/:dropdown/g, '');\n\n // Fix spacing and redundancy\n cleaned = cleaned\n .replace(/,+/g, ',')\n .replace(/\\{,+/g, '{')\n .replace(/,+\\}/g, '}')\n .replace(/\\|+/g, '|')\n .replace(/\\{\\s*\\|/g, '{')\n .replace(/\\|\\s*\\}/g, '}')\n .replace(/\\{\\s*\\}/g, '')\n .replace(/\\s*,\\s*/g, ',')\n .replace(/\\s*\\|\\s*/g, '|')\n .replace(/\\{\\s+/g, '{')\n .replace(/\\s+\\}/g, '}');\n\n // Fix tool concatenation issues\n cleaned = cleaned.replace(/\\b([a-z]),(?=[a-z],|[a-z]\\b)/g, '$1');\n\n let previousCleaned = '';\n while (previousCleaned !== cleaned) {\n previousCleaned = cleaned;\n cleaned = cleaned.replace(/\\b([a-z]),(?=[a-z],|[a-z]\\b)/g, '$1');\n }\n\n // Process sections\n const sections = cleaned.split(/([/#])/);\n const processedSections: string[] = [];\n\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n\n if (section === '/' || section === '#') {\n processedSections.push(section);\n continue;\n }\n\n if (!section.trim()) continue;\n\n const groups = section.split('|');\n const processedGroups: string[] = [];\n\n for (let group of groups) {\n const hasBraces = group.includes('{') || group.includes('}');\n let content = group.replace(/[{}]/g, '').trim();\n\n if (!content) continue;\n\n // Fix common concatenation issues\n content = content\n .replace(/(?<=fontname)(?=fontsize)/g, ',')\n .replace(/(?<=fontsize)(?=inlinestyle)/g, ',')\n .replace(/(?<=inlinestyle)(?=lineheight)/g, ',')\n .replace(/(?<=paragraphs)(?=fontname)/g, ',')\n .replace(/(?<=paragraphstyle)(?=menu_)/g, ',')\n .replace(/underlinefore/g, 'underline,fore')\n .replace(/forecolorback/g, 'forecolor,back')\n .replace(/backcolor/g, 'backcolor')\n .replace(/outdentsuperscript/g, 'outdent,superscript')\n .replace(/insertlinkun/g, 'insertlink,un')\n .replace(/unlinkinsert/g, 'unlink,insert')\n .replace(/insertblockquote/g, 'insertblockquote')\n .replace(/inserttable/g, 'inserttable')\n .replace(/insertimage/g, 'insertimage')\n .replace(/removeformat/g, 'removeformat');\n\n content = content.replace(/,+/g, ',').trim();\n\n if (content) {\n processedGroups.push(hasBraces ? `{${content}}` : content);\n }\n }\n\n if (processedGroups.length > 0) {\n processedSections.push(processedGroups.join('|'));\n }\n }\n\n cleaned = processedSections.join('');\n\n // Final cleanup\n cleaned = cleaned\n .replace(/\\{\\s*\\}/g, '')\n .replace(/\\|+/g, '|')\n .replace(/\\/+/g, '/')\n .replace(/#+/g, '#')\n .replace(/^[|/#]+|[|/#]+$/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n\n return cleaned;\n}\n","export type EditorEventHandler = () => void;\n\nexport interface EditorEvent {\n event: string;\n handler: EditorEventHandler;\n}\n\nexport class EditorEventManager {\n private listeners: EditorEvent[] = [];\n\n constructor(private editorInstance: any) {}\n\n attach(event: string, handler: EditorEventHandler): void {\n if (this.editorInstance?.attachEvent) {\n this.editorInstance.attachEvent(event, handler);\n this.listeners.push({ event, handler });\n }\n }\n\n attachMany(events: string[], handler: EditorEventHandler): void {\n events.forEach(event => this.attach(event, handler));\n }\n\n detachAll(): void {\n if (this.editorInstance?.detachEvent) {\n this.listeners.forEach(({ event, handler }) => {\n try {\n this.editorInstance.detachEvent(event, handler);\n } catch {}\n });\n }\n this.listeners = [];\n }\n}\n","export function safeCleanupFloatingPanels(): void {\n try {\n const selectors = [\n 'rte-floatpanel',\n '.rte-floatpanel',\n '.rte-floatpanel-paragraphop',\n '[class*=\"rte-float\"]',\n '[class*=\"rte-popup\"]',\n '.rte-toolbar-float',\n '.rte-dropdown-panel',\n ];\n\n selectors.forEach((selector) => {\n const elements = document.querySelectorAll(selector);\n elements.forEach((element) => {\n try {\n if (\n element &&\n element.parentNode &&\n document.body.contains(element)\n ) {\n element.parentNode.removeChild(element);\n }\n } catch (e) {\n if (element instanceof HTMLElement) {\n element.style.display = 'none';\n element.style.visibility = 'hidden';\n }\n }\n });\n });\n\n cleanupOrphanedElements();\n } catch (error) {\n // Silent fail\n }\n}\n\nexport function cleanupOrphanedElements(): void {\n try {\n const rteElements = document.querySelectorAll(\n '[id*=\"rte_\"], [class*=\"rte_\"]'\n );\n\n rteElements.forEach((element) => {\n try {\n if (!document.body.contains(element)) {\n element.remove();\n }\n } catch (e) {\n // Ignore\n }\n });\n } catch (e) {\n // Silent fail\n }\n}\n\n/**\n * Monkey patch Node.prototype.removeChild to avoid NotFoundError\n * when removing already-detached DOM elements.\n */\nexport function patchRemoveChildIfDetached(): void {\n const originalRemoveChild = Node.prototype.removeChild;\n\n Node.prototype.removeChild = function (this: Node, child: Node): Node {\n if (child && child.parentNode === this) {\n return originalRemoveChild.call(this, child);\n }\n return child;\n } as typeof Node.prototype.removeChild;\n}\n","import { AbstractControl } from '@angular/forms';\n\nexport function hasRequiredValidator(control: AbstractControl | null): boolean {\n if (!control || !control.validator) return false;\n const result = control.validator({ value: null } as AbstractControl);\n return !!(result && result['required']);\n}\n\n/**\n * Enhanced empty check that considers images and media as content\n */\nexport function isTrulyEmpty(html: string): boolean {\n if (!html || html.trim() === '') return true;\n\n const div = document.createElement('div');\n div.innerHTML = html;\n\n const hasImages = div.querySelectorAll('img').length > 0;\n if (hasImages) return false;\n\n const hasVideos = div.querySelectorAll('video, iframe').length > 0;\n if (hasVideos) return false;\n\n const hasEmbeds = div.querySelectorAll('embed, object, audio').length > 0;\n if (hasEmbeds) return false;\n\n const text = div.textContent?.replace(/\\u00A0/g, '').trim() || '';\n\n const cleaned = div.innerHTML\n .replace(/<br\\s*\\/?>/gi, '')\n .replace(/<div>(\\s|&nbsp;)*<\\/div>/gi, '')\n .replace(/<p>(\\s|&nbsp;)*<\\/p>/gi, '')\n .replace(/&nbsp;/gi, '')\n .trim();\n\n return !text && cleaned.length === 0;\n}\n","import {\n Component,\n ElementRef,\n forwardRef,\n Input,\n OnDestroy,\n OnInit,\n ViewChild,\n AfterViewInit,\n Injector,\n Inject,\n Optional,\n ChangeDetectorRef,\n NgZone,\n} from '@angular/core';\nimport {\n ControlValueAccessor,\n NG_VALUE_ACCESSOR,\n NG_VALIDATORS,\n NgControl,\n Validator,\n AbstractControl,\n ValidationErrors,\n} from '@angular/forms';\nimport { RichTextEditorService } from './rich-text-editor.service';\nimport {\n RTE_TOOLBAR_PRESETS,\n RTEImageTool,\n RTEPreset,\n} from './rich-text-editor.constant';\nimport { RTE_LICENSE_KEY } from './rich-text-editor-license.token';\nimport { cleanToolbarString } from './utils/toolbar-cleaner';\nimport { EditorEventManager } from './utils/editor-event-manager';\nimport {\n patchRemoveChildIfDetached,\n safeCleanupFloatingPanels,\n} from './utils/dom-cleanup';\nimport { hasRequiredValidator, isTrulyEmpty } from './utils/validation-utils';\n\ndeclare var RichTextEditor: any;\n\n/**\n * Configuration options for the toolbar\n */\nexport interface RichTextEditorConfig {\n height?: number;\n width?: number | string;\n toolbar?: string;\n toolbar_custom?: string;\n [key: string]: any;\n}\n\n@Component({\n selector: 'lib-rich-text-editor',\n template: `\n <div #editorContainer [class.invalid]=\"showError\"></div>\n <div class=\"error-message\" *ngIf=\"showError\">\n {{ currentErrorMessage }}\n </div>\n `,\n styles: [\n `\n :host {\n display: block;\n position: relative;\n }\n .invalid {\n border: 1px solid red;\n }\n .error-message {\n color: red;\n font-size: 12px;\n margin-top: 4px;\n }\n `,\n ],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => RichTextEditorComponent),\n multi: true,\n },\n ],\n})\nexport class RichTextEditorComponent\n implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor, Validator\n{\n @ViewChild('editorContainer', { static: true }) editorContainer!: ElementRef;\n @Input() licenseKey: string = '';\n @Input() config: RichTextEditorConfig = {};\n @Input() rtePreset: RTEPreset | null = null;\n @Input() imageToolbarItems: (RTEImageTool | '/')[] | null = null;\n @Input() excludedToolbarItems: string[] = [];\n @Input() initialContent: string = '';\n\n @Input() errorMessages: { [key: string]: string } = {\n required: 'This field is required.',\n };\n\n @Input()\n fileUploadHandler: (\n file: File,\n callback: (url: string | null, errorCode?: string) => void,\n optionalIndex?: number,\n optionalFiles?: File[]\n ) => void = () => {};\n\n @Input() enableImageUpload: boolean = false;\n @Input() enableVideoEmbed: boolean = false;\n @Input() readonly: boolean = false;\n\n private eventManager: EditorEventManager | null = null;\n private editorInstance: any;\n private value: string = '';\n private ngControl: NgControl | null = null;\n private changeTimer: any;\n private isDestroyed: boolean = false;\n private cleanupAttempts: number = 0;\n private eventListeners: Array<{ event: string; handler: any }> = [];\n private domCleanupTimer: any;\n\n onChange = (value: any) => {};\n onTouched = () => {};\n\n constructor(\n private injector: Injector,\n private rteService: RichTextEditorService,\n private cdr: ChangeDetectorRef,\n private ngZone: NgZone,\n @Optional() @Inject(RTE_LICENSE_KEY) private globalLicenseKey: string\n ) {}\n\n ngOnInit() {\n patchRemoveChildIfDetached();\n this.rteService.setCurrentEditor(this);\n try {\n this.ngControl = this.injector.get(NgControl);\n if (this.ngControl) {\n this.ngControl.valueAccessor = this;\n }\n } catch {\n // Safe fallback\n }\n }\n\n ngAfterViewInit() {\n // Run outside Angular zone to prevent change detection issues\n this.ngZone.runOutsideAngular(() => {\n setTimeout(() => {\n if (!this.isDestroyed) {\n this.initEditor();\n }\n }, 100);\n });\n }\n\n private initEditor() {\n try {\n // Clean up any existing instance first\n this.cleanupExistingEditor();\n\n const fullConfig = this.prepareConfiguration();\n this._applyCustomStyles();\n\n // Create editor instance\n this.editorInstance = new RichTextEditor(\n this.editorContainer.nativeElement,\n fullConfig\n );\n\n // Set initial content\n if (this.value) {\n this.editorInstance.setHTMLCode(this.value);\n } else if (this.initialContent) {\n this.value = this.initialContent;\n this.editorInstance.setHTMLCode(this.initialContent);\n this.ngZone.run(() => {\n this.onChange(this.initialContent);\n this.onTouched();\n });\n }\n\n if (this.readonly && this.editorInstance?.setReadOnly) {\n this.editorInstance.setReadOnly(true);\n }\n\n this.setupEventListeners();\n\n // Update image toolbar if needed\n if (this.imageToolbarItems && this.editorInstance) {\n this.updateImageToolbar();\n }\n } catch (error) {\n console.error('[RTE] Failed to initialize editor:', error);\n }\n }\n\n private cleanupExistingEditor() {\n if (this.editorInstance) {\n try {\n // Remove all event listeners first\n this.removeAllEventListeners();\n\n // Try to destroy the editor instance\n if (typeof this.editorInstance.destroy === 'function') {\n this.editorInstance.destroy();\n }\n } catch (e) {\n console.warn('[RTE] Error during editor cleanup:', e);\n }\n this.editorInstance = null;\n }\n }\n\n private setupEventListeners() {\n if (!this.editorInstance) return;\n\n this.eventManager = new EditorEventManager(this.editorInstance);\n\n const triggerUpdate = () => {\n if (this.changeTimer) clearTimeout(this.changeTimer);\n\n this.changeTimer = setTimeout(() => {\n if (this.isDestroyed || !this.editorInstance) return;\n\n this.ngZone.run(() => {\n try {\n const html = this.editorInstance.getHTMLCode() || '';\n this.value = html;\n\n this.onChange(html);\n this.onTouched();\n\n if (this.ngControl?.control) {\n const finalValue = isTrulyEmpty(html) ? '' : html;\n this.ngControl.control.setValue(finalValue, { emitEvent: false });\n this.ngControl.control.updateValueAndValidity();\n }\n } catch (error) {\n console.error('[RTE] Error in update handler:', error);\n }\n });\n }, 150);\n };\n\n // Change-related events\n this.eventManager.attachMany(\n ['change', 'keyup', 'paste', 'input'],\n triggerUpdate\n );\n\n // Blur\n this.eventManager.attach('blur', () => {\n this.ngZone.run(() => {\n this.onTouched();\n const control = this.ngControl?.control;\n if (control) {\n control.markAsTouched();\n control.updateValueAndValidity();\n }\n });\n });\n\n // Selection change (image toolbar)\n this.eventManager.attach('selectionchange', () => {\n setTimeout(() => this.checkImageSelection(), 100);\n });\n }\n\n private removeAllEventListeners() {\n this.eventManager?.detachAll();\n this.eventManager = null;\n }\n\n private updateImageToolbar() {\n if (this.editorInstance && this.imageToolbarItems) {\n const hasSlash = this.imageToolbarItems.includes('/');\n let imageToolbarString = '';\n\n if (hasSlash) {\n imageToolbarString = this.imageToolbarItems.join('');\n } else {\n imageToolbarString = `{${this.imageToolbarItems.join(',')}}`;\n }\n\n if (this.editorInstance.config) {\n this.editorInstance.config.controltoolbar_IMG = imageToolbarString;\n }\n\n try {\n if (typeof this.editorInstance.setConfig === 'function') {\n this.editorInstance.setConfig(\n 'controltoolbar_IMG',\n imageToolbarString\n );\n }\n } catch (e) {\n // Some versions might not have setConfig\n }\n }\n }\n\n private checkImageSelection() {\n if (!this.editorInstance || this.isDestroyed) return;\n\n try {\n const iframe = this.editorContainer.nativeElement.querySelector('iframe');\n if (iframe?.contentWindow && iframe.contentDocument) {\n const selection = iframe.contentWindow.getSelection();\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const container = range.commonAncestorContainer;\n\n let imgElement = null;\n if (container.nodeType === Node.ELEMENT_NODE) {\n imgElement = (container as Element).querySelector('img');\n } else if (container.parentElement) {\n imgElement = container.parentElement.closest('img');\n }\n\n if (imgElement && this.imageToolbarItems) {\n this.editorInstance.updateToolbar &&\n this.editorInstance.updateToolbar();\n }\n }\n }\n } catch (e) {\n // Ignore errors during selection check\n }\n }\n\n writeValue(value: any): void {\n const incomingValue = value ?? this.initialContent ?? '';\n this.value = incomingValue;\n if (this.editorInstance && !this.isDestroyed) {\n const current = this.editorInstance.getHTMLCode() || '';\n\n if (this.normalizeHtml(current) !== this.normalizeHtml(incomingValue)) {\n try {\n this.editorInstance.setHTMLCode(incomingValue);\n } catch (e) {\n console.warn('[RTE] Error setting HTML code:', e);\n }\n }\n }\n }\n\n private normalizeHtml(html: string): string {\n return (html || '')\n .replace(/\\u00A0/g, '')\n .replace(/\\s+/g, ' ')\n .replace(/<br\\s*\\/?>/gi, '')\n .replace(/<p>\\s*<\\/p>/gi, '')\n .trim();\n }\n\n registerOnChange(fn: any): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: any): void {\n this.onTouched = fn;\n }\n\n setDisabledState?(isDisabled: boolean): void {\n const shouldDisable = isDisabled || this.readonly;\n if (this.editorInstance?.setReadOnly && !this.isDestroyed) {\n try {\n this.editorInstance.setReadOnly(shouldDisable);\n } catch (e) {\n console.warn('[RTE] Error setting disabled state:', e);\n }\n }\n }\n\n ngOnDestroy() {\n this.isDestroyed = true;\n\n // Clear timers\n if (this.changeTimer) {\n clearTimeout(this.changeTimer);\n }\n if (this.domCleanupTimer) {\n clearTimeout(this.domCleanupTimer);\n }\n\n // Clear service reference\n this.rteService.clearCurrentEditor();\n\n // Schedule cleanup outside Angular zone\n this.ngZone.runOutsideAngular(() => {\n // Immediate cleanup attempt\n this.performCleanup();\n\n // Schedule additional cleanup attempts\n this.domCleanupTimer = setTimeout(() => {\n this.performCleanup();\n }, 100);\n });\n }\n\n private performCleanup() {\n // Remove event listeners\n this.removeAllEventListeners();\n\n // Clean up editor instance\n if (this.editorInstance) {\n try {\n if (typeof this.editorInstance.destroy === 'function') {\n this.editorInstance.destroy();\n }\n } catch (error) {\n // Silently ignore destroy errors\n }\n this.editorInstance = null;\n }\n\n // Clean up floating panels with safe DOM manipulation\n safeCleanupFloatingPanels();\n }\n\n validate(control: AbstractControl): ValidationErrors | null {\n const value = control?.value || '';\n const isEmpty = isTrulyEmpty(value);\n\n if (hasRequiredValidator(control) && isEmpty) {\n return { required: true };\n }\n\n return null;\n }\n\n private fixCharacterCount() {\n if (!this.editorInstance || this.isDestroyed) return;\n\n try {\n const html = this.editorInstance.getHTMLCode() || '';\n const div = document.createElement('div');\n div.innerHTML = html;\n\n const text = div.textContent || '';\n const count = text.replace(/\\u00A0/g, '').trim().length;\n\n const counter =\n this.editorContainer.nativeElement.querySelector('.character-count');\n if (counter) {\n counter.textContent = `characters: ${count}`;\n }\n } catch (e) {\n // Ignore character count errors\n }\n }\n\n getCharacterCount(): number {\n try {\n const html = this.editorInstance?.getHTMLCode?.() || '';\n const div = document.createElement('div');\n div.innerHTML = html;\n\n const text = div.textContent || '';\n return text.replace(/\\u00A0/g, '').trim().length;\n } catch (e) {\n return 0;\n }\n }\n\n get showError(): boolean {\n const control = this.ngControl?.control;\n if (!control) return false;\n\n const isRequired = hasRequiredValidator(control);\n return !!(\n control.invalid &&\n control.touched &&\n (isRequired || control.errors?.['required'])\n );\n }\n\n get currentErrorMessage(): string | null {\n const errors = this.ngControl?.control?.errors;\n if (!errors) return null;\n const firstKey = Object.keys(errors)[0];\n return this.errorMessages[firstKey] || 'Invalid field';\n }\n\n private getMobileExpandedToolbar(): string {\n const basicMobileTools = [\n 'paragraphs:dropdown',\n 'paragraphs:toggle',\n 'fontname:toggle',\n 'fontsize:toggle',\n 'bold',\n 'italic',\n 'underline',\n 'fontname',\n 'fontsize',\n 'insertlink',\n 'insertemoji',\n 'insertimage',\n 'insertvideo',\n 'removeformat',\n 'code',\n 'toggleborder',\n 'fullscreenenter',\n 'fullscreenexit',\n 'undo',\n 'redo',\n 'togglemore',\n 'fontname:dropdown',\n 'fontsize:dropdown',\n ];\n\n if (this.rtePreset && RTE_TOOLBAR_PRESETS[this.rtePreset]) {\n let fullToolbar = RTE_TOOLBAR_PRESETS[this.rtePreset];\n\n for (const tool of basicMobileTools) {\n const escapedTool = tool.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const toolPattern = new RegExp(`\\\\b${escapedTool}\\\\b`, 'g');\n fullToolbar = fullToolbar.replace(toolPattern, '');\n }\n\n // ๐Ÿงผ Clean both desktop/mobile exclusions\n fullToolbar = this.excludeToolbarItems(fullToolbar);\n\n return fullToolbar || this.getDefaultMobileExpandedToolbar();\n }\n\n return this.getDefaultMobileExpandedToolbar();\n }\n\n private getDefaultMobileExpandedToolbar(): string {\n return `{strike,subscript,superscript}|{forecolor,backcolor}|\n {justifyleft,justifycenter,justifyright,justifyfull}|\n {insertorderedlist,insertunorderedlist}|{outdent,indent}|\n {inserthorizontalrule,insertblockquote,inserttable}|\n {cut,copy,paste,pastetext,pasteword}|\n {find,replace}|{selectall,print,spellcheck}|{help}`;\n }\n\n private prepareConfiguration(): any {\n const baseConfig = { ...this.config };\n\n if (!baseConfig.height) {\n baseConfig.height = 300;\n }\n\n const enhancedConfig: any = {\n ...baseConfig,\n license: this.globalLicenseKey || this.licenseKey,\n enableObjectResizing: true,\n enableImageUpload: this.enableImageUpload,\n enableVideoEmbed: this.enableVideoEmbed,\n file_upload_handler: (\n file: File,\n callback: (url: string | null, errorCode?: string) => void,\n optionalIndex?: number,\n optionalFiles?: File[]\n ) => {\n const wrappedCallback = (url: string | null, errorCode?: string) => {\n if (!url) {\n // ๐Ÿšจ Upload failed โ€” clean up placeholder\n this.rteService.removeLastPlaceholderImage();\n console.warn('[RTE] Upload failed. Placeholder removed.');\n }\n callback(url, errorCode);\n };\n this.fileUploadHandler(\n file,\n wrappedCallback,\n optionalIndex,\n optionalFiles\n );\n },\n content_changed_callback: () => this.fixCharacterCount(),\n showFloatingToolbar: false,\n forceDesktopMode: true,\n disableMobileMode: true,\n toolbarModeViewport: 'always-desktop',\n showBottomToolbar: false,\n contentCssUrl: '',\n toolbarMobile: 'basic',\n subtoolbar_more_mobile: this.getMobileExpandedToolbar(),\n showControlBoxOnImageSelection: true,\n enableImageFloatStyle: true,\n contentCSSText: `\n /* Custom styles */\n body {\n overflow-y: hidden;\n padding: 0px;\n margin: 0px;\n }\n\n body, table, p, div {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n color: #414141;\n font-size: 14px;\n line-height: 1.6;\n }\n\n img {\n cursor: default;\n }\n `,\n };\n\n if (this.imageToolbarItems && Array.isArray(this.imageToolbarItems)) {\n const hasSlash = this.imageToolbarItems.includes('/');\n let imageToolbarString = '';\n\n if (hasSlash) {\n imageToolbarString = this.imageToolbarItems.join('');\n } else {\n imageToolbarString = `{${this.imageToolbarItems.join(',')}}`;\n }\n\n enhancedConfig.controltoolbar_IMG = imageToolbarString;\n enhancedConfig.imagecontrolbar = imageToolbarString;\n enhancedConfig.image_toolbar = imageToolbarString;\n }\n\n if (this.rtePreset && RTE_TOOLBAR_PRESETS[this.rtePreset]) {\n let fullToolbar = RTE_TOOLBAR_PRESETS[this.rtePreset];\n\n fullToolbar = this.excludeToolbarItems(fullToolbar);\n\n enhancedConfig.toolbar = 'custom';\n enhancedConfig.toolbar_custom = fullToolbar;\n }\n\n return enhancedConfig;\n }\n\n private _applyCustomStyles() {\n if (!document.getElementById('rte-consistent-toolbar-styles')) {\n const styleEl = document.createElement('style');\n styleEl.id = 'rte-consistent-toolbar-styles';\n styleEl.innerHTML = `\n /* Custom mobile styles to fix toolbar */\n @media (max-width: 992px) {\n .rte-toolbar-desktop,\n .rte-toolbar {\n display: flex !important;\n flex-wrap: wrap !important;\n overflow-x: auto !important;\n white-space: nowrap !important;\n -webkit-overflow-scrolling: touch !important;\n max-width: 100% !important;\n padding: 4px 0 !important;\n }\n \n .rte-toolbar button,\n .rte-toolbar .rte-dropdown {\n flex-shrink: 0 !important;\n min-width: 28px !important;\n height: 28px !important;\n margin: 2px !important;\n }\n \n .rte-toolbar-desktop {\n display: flex !important;\n }\n }\n\n /* Force image toolbar visibility */\n .rte-image-controlbox {\n display: block !important;\n opacity: 1 !important;\n visibility: visible !important;\n }\n\n /* Prevent orphaned floating panels */\n rte-floatpanel {\n z-index: 10000;\n }\n `;\n document.head.appendChild(styleEl);\n }\n }\n\n public insertContentAtCursor(content: string) {\n if (this.readonly || this.isDestroyed) return;\n\n try {\n const iframe = this.editorContainer.nativeElement.querySelector('iframe');\n\n if (!iframe?.contentWindow || !iframe.contentDocument) {\n console.warn('[RTE] iframe not found or inaccessible');\n return;\n }\n\n const iframeDoc = iframe.contentDocument;\n const editableBody = iframeDoc.body;\n\n if (!editableBody?.isContentEditable) {\n console.warn('[RTE] iframe body is not editable');\n return;\n }\n\n editableBody.focus();\n iframeDoc.execCommand('insertHTML', false, content);\n\n const html = this.editorInstance.getHTMLCode();\n this.value = html;\n\n this.ngZone.run(() => {\n this.onChange(html);\n this.onTouched();\n\n if (this.ngControl?.control) {\n this.ngControl.control.setValue(html, { emitEvent: false });\n this.ngControl.control.updateValueAndValidity();\n }\n });\n } catch (error) {\n console.error('[RTE] Failed to inject content into iframe:', error);\n }\n }\n\n public hideAllFloatPanels(): void {\n safeCleanupFloatingPanels();\n }\n\n public excludeToolbarItems(toolbar: string): string {\n if (!toolbar || !this.excludedToolbarItems?.length) return toolbar;\n\n for (const tool of this.excludedToolbarItems) {\n const escapedTool = tool.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const toolPattern = new RegExp(`\\\\b${escapedTool}\\\\b`, 'g');\n toolbar = toolbar.replace(toolPattern, '');\n }\n\n return cleanToolbarString(toolbar);\n }\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { RichTextEditorComponent } from './rich-text-editor.component';\nimport { RTE_LICENSE_KEY } from './rich-text-editor-license.token';\n\n@NgModule({\n declarations: [RichTextEditorComponent],\n imports: [CommonModule],\n exports: [RichTextEditorComponent]\n})\nexport class RichTextEditorModule {\n static forRoot(licenseKey: string): ModuleWithProviders<RichTextEditorModule> {\n return {\n ngModule: RichTextEditorModule,\n providers: [\n { provide: RTE_LICENSE_KEY, useValue: licenseKey }\n ]\n };\n }\n}\n","/*\n * Public API Surface of rich-text-editor\n */\n\nexport * from './lib/rich-text-editor.service';\nexport * from './lib/rich-text-editor.component';\nexport * from './lib/rich-text-editor.module';\nexport * from './lib/rich-text-editor.constant'\nexport * from './lib/rich-text-editor-license.token';\n\nexport * from './lib/paths'; // This exports the token for consumers to override\n\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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