This commit is contained in:
Chris Chen
2025-11-04 12:42:10 -08:00
parent b8b35645ac
commit 46ec236ed5
11 changed files with 335 additions and 92 deletions
@@ -1,5 +1,5 @@
<kendo-editor [value]="value" (valueChange)="onChange($event)" (blur)="onTouched()" [disabled]="disabled"
[schema]="messageTemplateSchema" [iframe]="false">
[schema]="messageTemplateSchema" [iframe]="false" class="h-100">
<kendo-toolbar>
<!-- Standard editing tools -->
<kendo-toolbar-buttongroup>
@@ -1,5 +1,5 @@
import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild, AfterViewInit } from '@angular/core';
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild, AfterViewInit, forwardRef, ChangeDetectorRef } from '@angular/core';
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NG_VALUE_ACCESSOR } from '@angular/forms';
import { EditorComponent, NodeSpec, schema, Schema, FontSizeItem } from '@progress/kendo-angular-editor';
import { MsgBoxService } from '../../../services/msg-box.service';
@@ -10,10 +10,18 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service';
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
import { NbDialogService } from '@nebular/theme';
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
import { DialogService } from '@progress/kendo-angular-dialog';
@Component({
selector: 'md2-html-editor',
templateUrl: './md2-html-editor.component.html',
styleUrl: './md2-html-editor.component.scss'
styleUrl: './md2-html-editor.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MD2HtmlEditorComponent),
multi: true
}
]
})
export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewInit {
@ViewChild(EditorComponent) editor: EditorComponent;
@@ -52,7 +60,8 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
constructor(
private msgBoxService: MsgBoxService,
private md2StateService: MD2StateService,
private dialogService: NbDialogService,
private dialogService: DialogService,
private cdr: ChangeDetectorRef,
elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {
}
@@ -71,12 +80,38 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
this.fontSizeDropdown.defaultItem = this.defaultFontSize;
}
}
// Ensure the editor value is set if writeValue was called before view init
if (this.editor && this.value && this.editor.value !== this.value) {
this.editor.value = this.value;
}
}, 0);
}
// ControlValueAccessor implementation
writeValue(value: string): void {
this.value = value || '';
writeValue(value: string | null | undefined): void {
const newValue = value || '';
// Only update if the value actually changed to avoid unnecessary updates
if (this.value !== newValue) {
this.value = newValue;
// Angular's [value] binding will handle updating the editor
// But if editor is already initialized and value is out of sync, ensure sync
if (this.editor && this.editor.value !== this.value) {
// Use setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError
setTimeout(() => {
if (this.editor && this.editor.value !== this.value) {
this.editor.value = this.value;
}
}, 0);
}
// Trigger change detection to ensure the binding updates
if (!this.cdr['destroyed']) {
this.cdr.markForCheck();
}
}
}
registerOnChange(fn: (value: string) => void): void {
@@ -92,8 +127,11 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
}
onChange(value: string): void {
this.value = value;
this.onChangeFn(value);
// Only update if value actually changed to prevent infinite loops
if (this.value !== value) {
this.value = value || '';
this.onChangeFn(this.value);
}
}
onTouched(): void {
@@ -101,10 +139,12 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewIn
}
showInsertMD2Icon() {
this.dialogService.open(MD2IconPickerDlgComponent, {
closeOnBackdropClick: true,
closeOnEsc: true
}).onClose.pipe(first()).subscribe((html: string) => {
this.dialogService.open({
title: 'Insert MD2 Icon',
content: MD2IconPickerDlgComponent,
width: '800px',
height: 600
}).result.subscribe((html: string) => {
if (html && this.editor) {
this.insertAfterSelection(html, true);
return;
@@ -2,16 +2,11 @@ import { Component, OnInit } from '@angular/core';
import { NbDialogRef } from '@nebular/theme';
import { MD2Icon } from '../massive-darkness2.model';
import { MD2StateService } from '../../../services/MD2/md2-state.service';
import { DialogRef } from '@progress/kendo-angular-dialog';
@Component({
selector: 'md2-icon-picker-dlg',
template: `
<nb-card style="max-width: 800px;">
<nb-card-header>
<h5>Insert MD2 Icon</h5>
</nb-card-header>
<nb-card-body>
<div class="md2-icon-grid">
template: ` <div class="md2-icon-grid">
<div
*ngFor="let iconData of iconList"
(click)="selectIcon(iconData)"
@@ -19,13 +14,23 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service';
[innerHTML]="iconData.html">
</div>
</div>
<!-- <nb-card>
<nb-card-header>
<h5>Insert MD2 Icon</h5>
</nb-card-header>
<nb-card-body>
</nb-card-body>
<nb-card-footer>
<button nbButton status="primary" (click)="cancel()">Cancel</button>
</nb-card-footer>
</nb-card>
</nb-card> -->
`,
styles: [`
nb-card{
max-width: 800px;
z-index: 1050;
}
.md2-icon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
@@ -57,7 +62,7 @@ export class MD2IconPickerDlgComponent implements OnInit {
iconList: Array<{ icon: MD2Icon, name: string, html: string }> = [];
constructor(
private dlgRef: NbDialogRef<MD2IconPickerDlgComponent>,
private dlgRef: DialogRef,
private md2StateService: MD2StateService
) { }