From 0c65999d13b21039590d60d4ca5ebbc9e748e1ba Mon Sep 17 00:00:00 2001 From: sumeyye Date: Wed, 19 Nov 2025 13:38:15 +0300 Subject: [PATCH] add: common code mirror and toast ui components --- .../code-mirror-editor.component.ts | 70 +++++++++++++++ .../packages/cms-kit/src/components/index.ts | 2 + .../toast-ui/toastui-editor.component.ts | 86 +++++++++++++++++++ .../packages/cms-kit/src/public-api.ts | 2 +- 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 npm/ng-packs/packages/cms-kit/src/components/code-mirror-editor/code-mirror-editor.component.ts create mode 100644 npm/ng-packs/packages/cms-kit/src/components/index.ts create mode 100644 npm/ng-packs/packages/cms-kit/src/components/toast-ui/toastui-editor.component.ts diff --git a/npm/ng-packs/packages/cms-kit/src/components/code-mirror-editor/code-mirror-editor.component.ts b/npm/ng-packs/packages/cms-kit/src/components/code-mirror-editor/code-mirror-editor.component.ts new file mode 100644 index 0000000000..5792a9971d --- /dev/null +++ b/npm/ng-packs/packages/cms-kit/src/components/code-mirror-editor/code-mirror-editor.component.ts @@ -0,0 +1,70 @@ +import { Component, ElementRef, AfterViewInit, ViewChild, forwardRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { EditorState } from '@codemirror/state'; +import { EditorView, lineNumbers } from '@codemirror/view'; +import { basicSetup } from 'codemirror'; + +@Component({ + selector: 'abp-codemirror-editor', + template: `
`, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CodeMirrorEditorComponent), + multi: true, + }, + ], +}) +export class CodeMirrorEditorComponent implements AfterViewInit, ControlValueAccessor { + @ViewChild('cmHost', { static: true }) + host!: ElementRef; + + private view!: EditorView; + private value = ''; + + private onChange = (value: string) => {}; + private onTouched = () => {}; + + writeValue(value: string): void { + this.value = value || ''; + + if (this.view) { + this.view.dispatch({ + changes: { + from: 0, + to: this.view.state.doc.length, + insert: this.value, + }, + }); + } + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + ngAfterViewInit(): void { + const startState = EditorState.create({ + doc: this.value, + extensions: [ + basicSetup, + EditorView.updateListener.of(update => { + if (update.docChanged) { + const text = update.state.doc.toString(); + this.onChange(text); + } + }), + lineNumbers(), + ], + }); + + this.view = new EditorView({ + state: startState, + parent: this.host.nativeElement, + }); + } +} diff --git a/npm/ng-packs/packages/cms-kit/src/components/index.ts b/npm/ng-packs/packages/cms-kit/src/components/index.ts new file mode 100644 index 0000000000..3c9eb8b53f --- /dev/null +++ b/npm/ng-packs/packages/cms-kit/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './code-mirror-editor/code-mirror-editor.component'; +export * from './toast-ui/toastui-editor.component'; diff --git a/npm/ng-packs/packages/cms-kit/src/components/toast-ui/toastui-editor.component.ts b/npm/ng-packs/packages/cms-kit/src/components/toast-ui/toastui-editor.component.ts new file mode 100644 index 0000000000..00095ef5ce --- /dev/null +++ b/npm/ng-packs/packages/cms-kit/src/components/toast-ui/toastui-editor.component.ts @@ -0,0 +1,86 @@ +import { AbpLocalStorageService } from '@abp/ng.core'; +import { isPlatformBrowser } from '@angular/common'; +import { + Component, + AfterViewInit, + ViewChild, + ElementRef, + forwardRef, + inject, + DOCUMENT, + PLATFORM_ID, +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import Editor from '@toast-ui/editor'; + +@Component({ + selector: 'abp-toastui-editor', + template: `
`, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ToastuiEditorComponent), + multi: true, + }, + ], +}) +export class ToastuiEditorComponent implements AfterViewInit, ControlValueAccessor { + @ViewChild('editorContainer', { static: true }) + editorContainer!: ElementRef; + + private editor!: Editor; + private value = ''; + + private document = inject(DOCUMENT); + private platformId = inject(PLATFORM_ID); + private localStorageService = inject(AbpLocalStorageService); + + private onChange: (value: string) => void = () => {}; + private onTouched: () => void = () => {}; + + writeValue(value: string): void { + this.value = value || ''; + if (this.editor) { + this.editor.setMarkdown(this.value); + } + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + ngAfterViewInit(): void { + if (!isPlatformBrowser(this.platformId)) { + return; + } + + this.initializeEditor(); + // TODO: Revise this approach on lepton side :: Service injection through DI + // this.setupThemeListener(); + } + + private getTheme(): string { + return this.localStorageService.getItem('LPX_THEME') || 'light'; + } + + private initializeEditor(): void { + const theme = this.getTheme(); + this.editor = new Editor({ + el: this.editorContainer.nativeElement, + previewStyle: 'tab', + height: '500px', + autofocus: true, + theme: theme, + initialValue: this.value, + }); + + this.editor.addHook('change', () => { + const value = this.editor.getMarkdown(); + this.onChange(value); + }); + } +} diff --git a/npm/ng-packs/packages/cms-kit/src/public-api.ts b/npm/ng-packs/packages/cms-kit/src/public-api.ts index 17cae63d98..e1bb0f97fc 100644 --- a/npm/ng-packs/packages/cms-kit/src/public-api.ts +++ b/npm/ng-packs/packages/cms-kit/src/public-api.ts @@ -1,3 +1,3 @@ // Main package entry point // Use @abp/ng.cms-kit/admin or @abp/ng.cms-kit/public for specific functionality -export {}; +export * from './components';