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);
}
}
}