import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from "@angular/core"; import type { RichTextEditorAccessibilityResult, RichTextEditorAiToolkit, RichTextEditorCollab, RichTextEditorComments, RichTextEditorDictation, RichTextEditorDocumentMetrics, RichTextEditorDocumentOutlineItem, RichTextEditorFootnoteItem, RichTextEditorInstance, RichTextEditorMentions, RichTextEditorPageSetup, RichTextEditorReviewLedger, RichTextEditorRevisionHistory, RichTextEditorSlashCommands, RichTextEditorStructuredDocument, RichTextEditorStructuredInput, RichTextEditorTableOfContentsItem, RichTextEditorTableOfContentsOptions, RichTextEditorTextStatistics, RichTextEditorValueFormat } from "../index"; import * as shared from "../integrations/shared"; export interface RichTextEditorAngularChangeEvent { editor: RichTextEditorInstance; value: string | RichTextEditorStructuredDocument; } @Component({ selector: "rich-text-editor", standalone: true, template: '
', styles: [` :host { display: block; min-height: 360px; } .rte-angular-host { min-height: 360px; } `] }) export class RichTextEditorAngularComponent implements AfterViewInit, OnChanges, OnDestroy { @Input() assetBasePath = "/richtexteditor"; @Input() autoload = true; @Input() config: Record = {}; @Input() value?: RichTextEditorStructuredInput; @Input() valueFormat: RichTextEditorValueFormat = "html"; @Output() change = new EventEmitter(); @Output() error = new EventEmitter(); @Output() ready = new EventEmitter(); @ViewChild("host", { static: true }) hostRef!: ElementRef; private changeHandler: (() => void) | null = null; private editor: RichTextEditorInstance | null = null; async ngAfterViewInit(): Promise { try { if (this.autoload === false) { shared.installStructuredContentBridge(); } else { await shared.loadRichTextEditorAssets({ basePath: this.assetBasePath }); } this.editor = shared.createEditor(this.hostRef.nativeElement, this.config || {}); if (this.value !== undefined) { this.syncEditor(this.value); } this.changeHandler = () => { if (!this.editor) { return; } this.change.emit({ editor: this.editor, value: shared.getEditorValue(this.editor, this.valueFormat) }); }; this.editor.attachEvent("change", this.changeHandler); this.ready.emit(this.editor); } catch (loadError) { this.error.emit(loadError as Error); } } ngOnChanges(changes: SimpleChanges): void { if (!this.editor) { return; } if (changes["value"] || changes["valueFormat"]) { this.syncEditor(this.value); } } ngOnDestroy(): void { if (this.editor) { if (this.changeHandler && typeof this.editor.detachEvent === "function") { this.editor.detachEvent("change", this.changeHandler); } // Tear down instance-level global listeners so unmounting doesn't leak. if (typeof this.editor.destroy === "function") { try { this.editor.destroy(); } catch (e) { /* ignore */ } } } this.hostRef.nativeElement.innerHTML = ""; this.editor = null; this.changeHandler = null; } focus(): void { if (this.editor) { this.editor.focus(); } } getEditor(): RichTextEditorInstance | null { return this.editor; } getHTMLCode(): string { return this.editor ? this.editor.getHTMLCode() : ""; } setHTMLCode(value: string): void { if (this.editor) { this.editor.setHTMLCode(value || ""); } } getJSON(): RichTextEditorStructuredDocument | null { return this.editor ? this.editor.getJSON() : null; } setJSON(value: RichTextEditorStructuredInput): void { if (this.editor) { this.editor.setJSON(value); } } getTextStatistics(): RichTextEditorTextStatistics | null { if (!this.editor || typeof this.editor.getTextStatistics !== "function") { return null; } return this.editor.getTextStatistics(); } getSelectionStatistics(): RichTextEditorTextStatistics | null { if (!this.editor || typeof this.editor.getSelectionStatistics !== "function") { return null; } return this.editor.getSelectionStatistics(); } getDocumentMetrics(): RichTextEditorDocumentMetrics | null { if (!this.editor || typeof this.editor.getDocumentMetrics !== "function") { return null; } return this.editor.getDocumentMetrics(); } getDocumentPageSetup(): RichTextEditorPageSetup | null { if (!this.editor || typeof this.editor.getDocumentPageSetup !== "function") { return null; } return this.editor.getDocumentPageSetup(); } setDocumentPageSetup(pageSetup: RichTextEditorPageSetup | null): RichTextEditorAngularComponent | null { if (!this.editor || typeof this.editor.setDocumentPageSetup !== "function") { return null; } this.editor.setDocumentPageSetup(pageSetup); return this; } getDocumentOutline(): RichTextEditorDocumentOutlineItem[] { if (!this.editor) { return []; } return shared.getDocumentOutline(this.editor.getJSON()); } getFootnotes(): RichTextEditorFootnoteItem[] { if (!this.editor) { return []; } return shared.getFootnotes(this.editor.getJSON()); } getTableOfContents(options?: RichTextEditorTableOfContentsOptions): RichTextEditorTableOfContentsItem[] { if (!this.editor) { return []; } return shared.getTableOfContents(this.editor.getJSON(), options); } syncTableOfContents(options?: RichTextEditorTableOfContentsOptions): RichTextEditorStructuredDocument | null { if (!this.editor) { return null; } var documentModel = shared.syncTableOfContents(this.editor.getJSON(), options); this.editor.setJSON(documentModel); return documentModel; } auditAccessibility(): RichTextEditorAccessibilityResult | null { if (!this.editor) { return null; } return shared.auditAccessibility(this.editor.getJSON()); } getAiToolkit(): RichTextEditorAiToolkit | null | undefined { return this.getPluginNamespace("aiToolkit"); } getDictation(): RichTextEditorDictation | null | undefined { return this.getPluginNamespace("dictation"); } getRevisionHistory(): RichTextEditorRevisionHistory | null | undefined { return this.getPluginNamespace("revisionHistory"); } getCollab(): RichTextEditorCollab | null | undefined { return this.getPluginNamespace("collab"); } getSlashCommands(): RichTextEditorSlashCommands | null | undefined { return this.getPluginNamespace("slashCommands"); } getMentions(): RichTextEditorMentions | null | undefined { return this.getPluginNamespace("mentions"); } getComments(): RichTextEditorComments | null | undefined { return this.getPluginNamespace("comments"); } getReviewLedger(): RichTextEditorReviewLedger | null | undefined { return this.getPluginNamespace("reviewLedger"); } private getPluginNamespace(name: string): T | null | undefined { if (!this.editor) { return null; } return (this.editor as unknown as Record)[name]; } private syncEditor(nextValue: RichTextEditorStructuredInput | undefined): void { if (!this.editor || nextValue === undefined) { return; } var nextHtml = shared.normalizeStructuredContent(nextValue); if (this.editor.getHTMLCode() !== nextHtml) { shared.setEditorValue(this.editor, nextValue, this.valueFormat); } } }