@sd-angular/core 19.0.0-beta.12 → 19.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/scss/ckeditor5.scss +59 -2
- package/components/document-builder/src/document-builder.component.d.ts +7 -2
- package/components/document-builder/src/document-builder.model.d.ts +5 -1
- package/components/document-builder/src/plugins/highlight-range/highlight-range.plugin.d.ts +4 -0
- package/components/document-builder/src/plugins/image-custom/image-custom.plugin.d.ts +31 -0
- package/components/document-builder/src/plugins/index.d.ts +1 -0
- package/components/index.d.ts +1 -0
- package/components/mini-editor/index.d.ts +2 -0
- package/components/mini-editor/src/mini-editor.component.d.ts +90 -0
- package/components/mini-editor/src/mini-editor.model.d.ts +42 -0
- package/components/view/index.d.ts +1 -0
- package/components/view/src/view.component.d.ts +14 -0
- package/directives/index.d.ts +1 -0
- package/directives/src/sd-href.directive.d.ts +9 -0
- package/fesm2022/sd-angular-core-components-document-builder.mjs +479 -48
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-mini-editor.mjs +326 -0
- package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -0
- package/fesm2022/sd-angular-core-components-view.mjs +88 -0
- package/fesm2022/sd-angular-core-components-view.mjs.map +1 -0
- package/fesm2022/sd-angular-core-components-workflow.mjs +16 -26
- package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components.mjs +1 -0
- package/fesm2022/sd-angular-core-components.mjs.map +1 -1
- package/fesm2022/sd-angular-core-directives.mjs +51 -2
- package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-radio.mjs +3 -2
- package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +5 -3
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-textarea.mjs +2 -2
- package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +52 -17
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-oidc.mjs +0 -2
- package/fesm2022/sd-angular-core-modules-oidc.mjs.map +1 -1
- package/modules/layout/components/sidebar-v1/components/sidebar/sidebar.component.d.ts +1 -0
- package/modules/layout/components/sidebar-v1/components/user/user.component.d.ts +5 -2
- package/modules/layout/configurations/layout.configuration.d.ts +3 -0
- package/modules/layout/services/storage/storage.service.d.ts +1 -0
- package/package.json +32 -24
- package/sd-angular-core-19.0.0-beta.14.tgz +0 -0
- package/sd-angular-core-19.0.0-beta.12.tgz +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { EventEmitter, forwardRef, Input, Output, Component } from '@angular/core';
|
|
4
|
+
import * as i1 from '@ckeditor/ckeditor5-angular';
|
|
5
|
+
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
6
|
+
import { ClassicEditor, Essentials, FontColor, Paragraph, Bold, Italic, Underline, Link, List, Undo, Widget, Markdown, Mention } from 'ckeditor5';
|
|
7
|
+
import { Subscription, Subject } from 'rxjs';
|
|
8
|
+
import { throttleTime } from 'rxjs/operators';
|
|
9
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Component sd-mini-editor - Editor đơn giản cho comment input
|
|
13
|
+
* Sử dụng CKEditor với chế độ đơn giản (bold, italic, link)
|
|
14
|
+
* Hỗ trợ mention và output format (html/markdown)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <sd-mini-editor
|
|
19
|
+
* [option]="editorOption"
|
|
20
|
+
* [(ngModel)]="content"
|
|
21
|
+
* (contentChange)="onContentChange($event)"
|
|
22
|
+
* >
|
|
23
|
+
* </sd-mini-editor>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
class SdMiniEditor {
|
|
27
|
+
/** Cấu hình option cho editor */
|
|
28
|
+
option;
|
|
29
|
+
/** NgModel binding - nội dung HTML/Markdown */
|
|
30
|
+
value = '';
|
|
31
|
+
valueChange = new EventEmitter();
|
|
32
|
+
/** Event emitter khi content thay đổi */
|
|
33
|
+
contentChange = new EventEmitter();
|
|
34
|
+
/** Event emitter khi blur */
|
|
35
|
+
blur = new EventEmitter();
|
|
36
|
+
/** Event emitter khi focus */
|
|
37
|
+
focus = new EventEmitter();
|
|
38
|
+
/** Disabled state */
|
|
39
|
+
disabled = false;
|
|
40
|
+
Editor = ClassicEditor;
|
|
41
|
+
#editor;
|
|
42
|
+
#subscription = new Subscription();
|
|
43
|
+
#contentChangeSubject = new Subject();
|
|
44
|
+
// Build editor config dynamically
|
|
45
|
+
get editorConfig() {
|
|
46
|
+
const enableMention = this.option?.enableMention ?? false;
|
|
47
|
+
const useMarkdown = this.option?.outputFormat === 'markdown';
|
|
48
|
+
const plugins = [Essentials, FontColor, Paragraph, Bold, Italic, Underline, Link, List, Undo, Widget];
|
|
49
|
+
// Add Markdown plugin if outputFormat is markdown
|
|
50
|
+
if (useMarkdown) {
|
|
51
|
+
plugins.push(Markdown);
|
|
52
|
+
}
|
|
53
|
+
// Add Mention plugin if enabled
|
|
54
|
+
if (enableMention) {
|
|
55
|
+
plugins.push(Mention);
|
|
56
|
+
}
|
|
57
|
+
// Build base config
|
|
58
|
+
const config = {
|
|
59
|
+
licenseKey: 'GPL',
|
|
60
|
+
getOption: () => this.option,
|
|
61
|
+
plugins,
|
|
62
|
+
toolbar: {
|
|
63
|
+
items: ['bold', 'italic', 'underline', '|', 'fontColor', '|', 'bulletedList', 'numberedList', '|', 'link'],
|
|
64
|
+
shouldNotGroupWhenFull: true,
|
|
65
|
+
},
|
|
66
|
+
placeholder: this.option?.placeholder,
|
|
67
|
+
link: {
|
|
68
|
+
addTargetToExternalLinks: true,
|
|
69
|
+
defaultProtocol: 'https://',
|
|
70
|
+
},
|
|
71
|
+
fontColor: {
|
|
72
|
+
columns: 5,
|
|
73
|
+
documentColors: 10,
|
|
74
|
+
colorPicker: { format: 'hex' },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
// Add mention configuration if enabled
|
|
78
|
+
if (enableMention && this.option?.mentionConfig?.feeds) {
|
|
79
|
+
config.mention = {
|
|
80
|
+
feeds: this.option.mentionConfig.feeds,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return config;
|
|
84
|
+
}
|
|
85
|
+
constructor() {
|
|
86
|
+
// Setup debounce cho content change
|
|
87
|
+
this.#subscription.add(this.#contentChangeSubject.pipe(throttleTime(500, undefined, { leading: true, trailing: true })).subscribe(content => {
|
|
88
|
+
const output = this.#convertOutput(content);
|
|
89
|
+
this.value = output;
|
|
90
|
+
this.#onChange(output);
|
|
91
|
+
this.valueChange.emit(output);
|
|
92
|
+
this.contentChange.emit(output);
|
|
93
|
+
this.option?.onChange?.(output);
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
ngOnDestroy() {
|
|
97
|
+
this.#subscription.unsubscribe();
|
|
98
|
+
this.#editor?.destroy?.();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Kiểm tra có nên enable mention plugin không
|
|
102
|
+
*/
|
|
103
|
+
#shouldEnableMention() {
|
|
104
|
+
return this.option?.enableMention ?? false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Xử lý khi editor ready
|
|
108
|
+
*/
|
|
109
|
+
onReady(editor) {
|
|
110
|
+
this.#editor = editor;
|
|
111
|
+
// Set initial content
|
|
112
|
+
if (this.value) {
|
|
113
|
+
this.setContent(this.value);
|
|
114
|
+
}
|
|
115
|
+
// Lắng nghe sự kiện thay đổi nội dung
|
|
116
|
+
editor.model.document.on('change:data', () => {
|
|
117
|
+
const content = editor.getData();
|
|
118
|
+
this.#contentChangeSubject.next(content);
|
|
119
|
+
});
|
|
120
|
+
// Lắng nghe focus/blur events
|
|
121
|
+
editor.editing.view.document.on('focus', evt => {
|
|
122
|
+
const domEvent = evt.domEvent;
|
|
123
|
+
this.focus.emit(domEvent);
|
|
124
|
+
this.option?.onFocus?.(domEvent);
|
|
125
|
+
});
|
|
126
|
+
editor.editing.view.document.on('blur', evt => {
|
|
127
|
+
const domEvent = evt.domEvent;
|
|
128
|
+
this.blur.emit(domEvent);
|
|
129
|
+
this.option?.onBlur?.(domEvent);
|
|
130
|
+
});
|
|
131
|
+
// Lắng nghe sự kiện mention được chọn
|
|
132
|
+
if (this.#shouldEnableMention()) {
|
|
133
|
+
editor.commands.get('mention')?.on('execute', (_evt, data) => {
|
|
134
|
+
const mentionData = data[0];
|
|
135
|
+
this.option?.onMentionSelect?.(mentionData.mention);
|
|
136
|
+
// Trigger content change sau khi insert mention
|
|
137
|
+
const content = editor.getData();
|
|
138
|
+
this.#contentChangeSubject.next(content);
|
|
139
|
+
});
|
|
140
|
+
// Custom downcast converter để thay đổi cấu trúc mention HTML
|
|
141
|
+
editor.conversion.for('downcast').attributeToElement({
|
|
142
|
+
model: 'mention',
|
|
143
|
+
view: (mentionData, { writer }) => {
|
|
144
|
+
const data = mentionData;
|
|
145
|
+
const rawId = data?.id || '';
|
|
146
|
+
const marker = rawId[0];
|
|
147
|
+
const cleanId = rawId.slice(1);
|
|
148
|
+
return writer.createAttributeElement('span', {
|
|
149
|
+
class: 'ck-custom-mention',
|
|
150
|
+
'data-id': cleanId,
|
|
151
|
+
'data-marker': marker,
|
|
152
|
+
contenteditable: 'false',
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
converterPriority: 'highest',
|
|
156
|
+
});
|
|
157
|
+
// Xử lý keyboard để xóa mention 1 lần
|
|
158
|
+
editor.editing.view.document.on('keydown', (evt, data) => {
|
|
159
|
+
const keyEvent = data;
|
|
160
|
+
// Delete (46) hoặc Backspace (8)
|
|
161
|
+
if (keyEvent.keyCode === 46 || keyEvent.keyCode === 8) {
|
|
162
|
+
const model = editor.model;
|
|
163
|
+
const selection = model.document.selection;
|
|
164
|
+
const position = selection.getFirstPosition();
|
|
165
|
+
if (!position)
|
|
166
|
+
return;
|
|
167
|
+
// Tìm text node có mention attribute
|
|
168
|
+
const node = position.textNode || position.nodeBefore || position.nodeAfter;
|
|
169
|
+
if (node && node.is('$text')) {
|
|
170
|
+
const mentionAttr = node.getAttribute('mention');
|
|
171
|
+
if (mentionAttr) {
|
|
172
|
+
// Xóa toàn bộ text node chứa mention
|
|
173
|
+
model.change(writer => {
|
|
174
|
+
writer.remove(node);
|
|
175
|
+
});
|
|
176
|
+
evt.stop();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Convert output theo format (html hoặc markdown)
|
|
185
|
+
* Khi sử dụng CKEditor Markdown plugin, getData() tự động trả về Markdown
|
|
186
|
+
*/
|
|
187
|
+
#convertOutput(content) {
|
|
188
|
+
// CKEditor Markdown plugin tự động xử lý conversion
|
|
189
|
+
// Không cần manual conversion nữa
|
|
190
|
+
return content;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Set nội dung cho editor
|
|
194
|
+
*/
|
|
195
|
+
setContent(content) {
|
|
196
|
+
this.#editor?.setData?.(content);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get nội dung từ editor
|
|
200
|
+
*/
|
|
201
|
+
getContent() {
|
|
202
|
+
if (this.#editor) {
|
|
203
|
+
const html = this.#editor.getData();
|
|
204
|
+
return this.#convertOutput(html);
|
|
205
|
+
}
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get nội dung HTML gốc (không convert)
|
|
210
|
+
*/
|
|
211
|
+
getHtmlContent() {
|
|
212
|
+
return this.#editor?.getData?.() || '';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Focus vào editor
|
|
216
|
+
*/
|
|
217
|
+
focusEditor() {
|
|
218
|
+
this.#editor?.editing?.view?.focus?.();
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Insert mention vào vị trí con trỏ hiện tại
|
|
222
|
+
*/
|
|
223
|
+
insertMention(item) {
|
|
224
|
+
if (!this.#editor)
|
|
225
|
+
return;
|
|
226
|
+
const firstFeed = this.option?.mentionConfig?.feeds?.[0];
|
|
227
|
+
const marker = item.marker || firstFeed?.marker || '@';
|
|
228
|
+
// Sử dụng CKEditor mention command
|
|
229
|
+
this.#editor.execute('mention', {
|
|
230
|
+
marker,
|
|
231
|
+
mention: {
|
|
232
|
+
id: item.id,
|
|
233
|
+
text: `${marker}${item.name}`,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get danh sách mentions trong nội dung
|
|
239
|
+
*/
|
|
240
|
+
getMentions() {
|
|
241
|
+
if (!this.#editor)
|
|
242
|
+
return [];
|
|
243
|
+
const mentions = [];
|
|
244
|
+
const root = this.#editor.model.document.getRoot();
|
|
245
|
+
if (!root)
|
|
246
|
+
return mentions;
|
|
247
|
+
const range = this.#editor.model.createRangeIn(root);
|
|
248
|
+
for (const item of range.getItems()) {
|
|
249
|
+
if (item.is('$text')) {
|
|
250
|
+
const mentionAttr = item.getAttribute('mention');
|
|
251
|
+
if (mentionAttr) {
|
|
252
|
+
const text = item.data;
|
|
253
|
+
const marker = text.charAt(0);
|
|
254
|
+
const name = text.substring(1);
|
|
255
|
+
const id = item.getAttribute('data-user-id');
|
|
256
|
+
mentions.push({ id, name, marker });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return mentions;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* ControlValueAccessor: Write value
|
|
264
|
+
*/
|
|
265
|
+
writeValue(value) {
|
|
266
|
+
this.value = value || '';
|
|
267
|
+
if (this.#editor) {
|
|
268
|
+
this.setContent(this.value);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* ControlValueAccessor: Register change callback
|
|
273
|
+
*/
|
|
274
|
+
registerOnChange(fn) {
|
|
275
|
+
this.#onChange = fn;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* ControlValueAccessor: Register touched callback
|
|
279
|
+
*/
|
|
280
|
+
registerOnTouched(fn) {
|
|
281
|
+
this.#onTouched = fn;
|
|
282
|
+
}
|
|
283
|
+
/** ControlValueAccessor callbacks */
|
|
284
|
+
#onChange = () => { };
|
|
285
|
+
#onTouched = () => { };
|
|
286
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdMiniEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
287
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: SdMiniEditor, isStandalone: true, selector: "sd-mini-editor", inputs: { option: "option", value: "value", disabled: "disabled" }, outputs: { valueChange: "valueChange", contentChange: "contentChange", blur: "blur", focus: "focus" }, providers: [
|
|
288
|
+
{
|
|
289
|
+
provide: NG_VALUE_ACCESSOR,
|
|
290
|
+
useExisting: forwardRef(() => SdMiniEditor),
|
|
291
|
+
multi: true,
|
|
292
|
+
},
|
|
293
|
+
], ngImport: i0, template: "<div class=\"sd-mini-editor\" [style.height]=\"option.height || 'auto'\">\r\n <ckeditor [editor]=\"Editor\" [config]=\"editorConfig\" [disabled]=\"disabled\" (ready)=\"onReady($event)\"> </ckeditor>\r\n</div>\r\n", styles: [".sd-mini-editor{display:block;width:100%}::ng-deep .ck-mentions .ck-button.ck-on{background:var(--sd-primary)!important;color:#fff!important}::ng-deep .ck-mentions .ck-button.ck-on *{color:#fff!important}:host ::ng-deep .ck-editor{--ck-content-font-size: 14px;--ck-content-line-height: 1.5}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}:host ::ng-deep .ck-custom-mention{font-weight:600;padding:0 2px;display:inline-block;pointer-events:none;-webkit-user-select:none;user-select:none;color:#2962ff}:host ::ng-deep .ck-custom-mention:before{content:attr(data-marker)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableWatchdog", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }] });
|
|
294
|
+
}
|
|
295
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdMiniEditor, decorators: [{
|
|
296
|
+
type: Component,
|
|
297
|
+
args: [{ selector: 'sd-mini-editor', standalone: true, imports: [CommonModule, CKEditorModule], providers: [
|
|
298
|
+
{
|
|
299
|
+
provide: NG_VALUE_ACCESSOR,
|
|
300
|
+
useExisting: forwardRef(() => SdMiniEditor),
|
|
301
|
+
multi: true,
|
|
302
|
+
},
|
|
303
|
+
], template: "<div class=\"sd-mini-editor\" [style.height]=\"option.height || 'auto'\">\r\n <ckeditor [editor]=\"Editor\" [config]=\"editorConfig\" [disabled]=\"disabled\" (ready)=\"onReady($event)\"> </ckeditor>\r\n</div>\r\n", styles: [".sd-mini-editor{display:block;width:100%}::ng-deep .ck-mentions .ck-button.ck-on{background:var(--sd-primary)!important;color:#fff!important}::ng-deep .ck-mentions .ck-button.ck-on *{color:#fff!important}:host ::ng-deep .ck-editor{--ck-content-font-size: 14px;--ck-content-line-height: 1.5}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}:host ::ng-deep .ck-custom-mention{font-weight:600;padding:0 2px;display:inline-block;pointer-events:none;-webkit-user-select:none;user-select:none;color:#2962ff}:host ::ng-deep .ck-custom-mention:before{content:attr(data-marker)}\n"] }]
|
|
304
|
+
}], ctorParameters: () => [], propDecorators: { option: [{
|
|
305
|
+
type: Input,
|
|
306
|
+
args: [{ required: true }]
|
|
307
|
+
}], value: [{
|
|
308
|
+
type: Input
|
|
309
|
+
}], valueChange: [{
|
|
310
|
+
type: Output
|
|
311
|
+
}], contentChange: [{
|
|
312
|
+
type: Output
|
|
313
|
+
}], blur: [{
|
|
314
|
+
type: Output
|
|
315
|
+
}], focus: [{
|
|
316
|
+
type: Output
|
|
317
|
+
}], disabled: [{
|
|
318
|
+
type: Input
|
|
319
|
+
}] } });
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Generated bundle index. Do not edit.
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
export { SdMiniEditor };
|
|
326
|
+
//# sourceMappingURL=sd-angular-core-components-mini-editor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sd-angular-core-components-mini-editor.mjs","sources":["../../../projects/sd-angular/components/mini-editor/src/mini-editor.component.ts","../../../projects/sd-angular/components/mini-editor/src/mini-editor.component.html","../../../projects/sd-angular/components/mini-editor/sd-angular-core-components-mini-editor.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\r\nimport { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core';\r\nimport { CKEditorModule } from '@ckeditor/ckeditor5-angular';\r\nimport {\r\n Bold,\r\n ClassicEditor,\r\n Essentials,\r\n Italic,\r\n Link,\r\n List,\r\n FontColor,\r\n Markdown,\r\n Mention,\r\n Paragraph,\r\n Underline,\r\n Undo,\r\n Widget,\r\n} from 'ckeditor5';\r\nimport { Subject, Subscription } from 'rxjs';\r\nimport { throttleTime } from 'rxjs/operators';\r\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\r\n\r\nimport { SdMiniEditorOption, SdMiniEditorConfig, SdMiniEditorMentionItem } from './mini-editor.model';\r\n\r\n/**\r\n * Component sd-mini-editor - Editor đơn giản cho comment input\r\n * Sử dụng CKEditor với chế độ đơn giản (bold, italic, link)\r\n * Hỗ trợ mention và output format (html/markdown)\r\n *\r\n * @example\r\n * ```html\r\n * <sd-mini-editor\r\n * [option]=\"editorOption\"\r\n * [(ngModel)]=\"content\"\r\n * (contentChange)=\"onContentChange($event)\"\r\n * >\r\n * </sd-mini-editor>\r\n * ```\r\n */\r\n@Component({\r\n selector: 'sd-mini-editor',\r\n standalone: true,\r\n imports: [CommonModule, CKEditorModule],\r\n templateUrl: './mini-editor.component.html',\r\n styleUrls: ['./mini-editor.component.scss'],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: forwardRef(() => SdMiniEditor),\r\n multi: true,\r\n },\r\n ],\r\n})\r\nexport class SdMiniEditor implements ControlValueAccessor {\r\n /** Cấu hình option cho editor */\r\n @Input({ required: true }) option!: SdMiniEditorOption;\r\n\r\n /** NgModel binding - nội dung HTML/Markdown */\r\n @Input() value = '';\r\n @Output() valueChange = new EventEmitter<string>();\r\n\r\n /** Event emitter khi content thay đổi */\r\n @Output() contentChange = new EventEmitter<string>();\r\n\r\n /** Event emitter khi blur */\r\n @Output() blur = new EventEmitter<FocusEvent>();\r\n\r\n /** Event emitter khi focus */\r\n @Output() focus = new EventEmitter<FocusEvent>();\r\n\r\n /** Disabled state */\r\n @Input() disabled = false;\r\n\r\n Editor = ClassicEditor;\r\n #editor!: ClassicEditor;\r\n #subscription = new Subscription();\r\n #contentChangeSubject = new Subject<string>();\r\n\r\n // Build editor config dynamically\r\n get editorConfig(): SdMiniEditorConfig {\r\n const enableMention = this.option?.enableMention ?? false;\r\n const useMarkdown = this.option?.outputFormat === 'markdown';\r\n const plugins: any[] = [Essentials, FontColor, Paragraph, Bold, Italic, Underline, Link, List, Undo, Widget];\r\n\r\n // Add Markdown plugin if outputFormat is markdown\r\n if (useMarkdown) {\r\n plugins.push(Markdown);\r\n }\r\n\r\n // Add Mention plugin if enabled\r\n if (enableMention) {\r\n plugins.push(Mention);\r\n }\r\n\r\n // Build base config\r\n const config: SdMiniEditorConfig = {\r\n licenseKey: 'GPL',\r\n getOption: () => this.option,\r\n plugins,\r\n toolbar: {\r\n items: ['bold', 'italic', 'underline', '|', 'fontColor', '|', 'bulletedList', 'numberedList', '|', 'link'],\r\n shouldNotGroupWhenFull: true,\r\n },\r\n placeholder: this.option?.placeholder,\r\n link: {\r\n addTargetToExternalLinks: true,\r\n defaultProtocol: 'https://',\r\n },\r\n fontColor: {\r\n columns: 5,\r\n documentColors: 10,\r\n colorPicker: { format: 'hex' },\r\n },\r\n };\r\n\r\n // Add mention configuration if enabled\r\n if (enableMention && this.option?.mentionConfig?.feeds) {\r\n config.mention = {\r\n feeds: this.option.mentionConfig.feeds,\r\n };\r\n }\r\n\r\n return config;\r\n }\r\n\r\n constructor() {\r\n // Setup debounce cho content change\r\n this.#subscription.add(\r\n this.#contentChangeSubject.pipe(throttleTime(500, undefined, { leading: true, trailing: true })).subscribe(content => {\r\n const output = this.#convertOutput(content);\r\n this.value = output;\r\n this.#onChange(output);\r\n this.valueChange.emit(output);\r\n this.contentChange.emit(output);\r\n this.option?.onChange?.(output);\r\n })\r\n );\r\n }\r\n\r\n ngOnDestroy() {\r\n this.#subscription.unsubscribe();\r\n this.#editor?.destroy?.();\r\n }\r\n\r\n /**\r\n * Kiểm tra có nên enable mention plugin không\r\n */\r\n #shouldEnableMention(): boolean {\r\n return this.option?.enableMention ?? false;\r\n }\r\n\r\n /**\r\n * Xử lý khi editor ready\r\n */\r\n onReady(editor: ClassicEditor) {\r\n this.#editor = editor;\r\n\r\n // Set initial content\r\n if (this.value) {\r\n this.setContent(this.value);\r\n }\r\n\r\n // Lắng nghe sự kiện thay đổi nội dung\r\n editor.model.document.on('change:data', () => {\r\n const content = editor.getData();\r\n this.#contentChangeSubject.next(content);\r\n });\r\n\r\n // Lắng nghe focus/blur events\r\n editor.editing.view.document.on('focus', evt => {\r\n const domEvent = (evt as any).domEvent as FocusEvent;\r\n this.focus.emit(domEvent);\r\n this.option?.onFocus?.(domEvent);\r\n });\r\n\r\n editor.editing.view.document.on('blur', evt => {\r\n const domEvent = (evt as any).domEvent as FocusEvent;\r\n this.blur.emit(domEvent);\r\n this.option?.onBlur?.(domEvent);\r\n });\r\n\r\n // Lắng nghe sự kiện mention được chọn\r\n if (this.#shouldEnableMention()) {\r\n editor.commands.get('mention')?.on('execute', (_evt, data: any) => {\r\n const mentionData = data[0];\r\n this.option?.onMentionSelect?.(mentionData.mention);\r\n // Trigger content change sau khi insert mention\r\n const content = editor.getData();\r\n this.#contentChangeSubject.next(content);\r\n });\r\n\r\n // Custom downcast converter để thay đổi cấu trúc mention HTML\r\n editor.conversion.for('downcast').attributeToElement({\r\n model: 'mention',\r\n view: (mentionData, { writer }) => {\r\n const data = mentionData as SdMiniEditorMentionItem;\r\n const rawId = data?.id || '';\r\n const marker = rawId[0];\r\n const cleanId = rawId.slice(1);\r\n\r\n return writer.createAttributeElement('span', {\r\n class: 'ck-custom-mention',\r\n 'data-id': cleanId,\r\n 'data-marker': marker,\r\n contenteditable: 'false',\r\n });\r\n },\r\n converterPriority: 'highest',\r\n });\r\n\r\n // Xử lý keyboard để xóa mention 1 lần\r\n editor.editing.view.document.on('keydown', (evt, data) => {\r\n const keyEvent = data as { keyCode: number; domEvent: KeyboardEvent };\r\n // Delete (46) hoặc Backspace (8)\r\n if (keyEvent.keyCode === 46 || keyEvent.keyCode === 8) {\r\n const model = editor.model;\r\n const selection = model.document.selection;\r\n const position = selection.getFirstPosition();\r\n if (!position) return;\r\n\r\n // Tìm text node có mention attribute\r\n const node = position.textNode || position.nodeBefore || position.nodeAfter;\r\n if (node && node.is('$text')) {\r\n const mentionAttr = node.getAttribute('mention');\r\n if (mentionAttr) {\r\n // Xóa toàn bộ text node chứa mention\r\n model.change(writer => {\r\n writer.remove(node);\r\n });\r\n evt.stop();\r\n }\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Convert output theo format (html hoặc markdown)\r\n * Khi sử dụng CKEditor Markdown plugin, getData() tự động trả về Markdown\r\n */\r\n #convertOutput(content: string): string {\r\n // CKEditor Markdown plugin tự động xử lý conversion\r\n // Không cần manual conversion nữa\r\n return content;\r\n }\r\n\r\n /**\r\n * Set nội dung cho editor\r\n */\r\n setContent(content: string) {\r\n this.#editor?.setData?.(content);\r\n }\r\n\r\n /**\r\n * Get nội dung từ editor\r\n */\r\n getContent(): string {\r\n if (this.#editor) {\r\n const html = this.#editor.getData();\r\n return this.#convertOutput(html);\r\n }\r\n return '';\r\n }\r\n\r\n /**\r\n * Get nội dung HTML gốc (không convert)\r\n */\r\n getHtmlContent(): string {\r\n return this.#editor?.getData?.() || '';\r\n }\r\n\r\n /**\r\n * Focus vào editor\r\n */\r\n focusEditor() {\r\n this.#editor?.editing?.view?.focus?.();\r\n }\r\n\r\n /**\r\n * Insert mention vào vị trí con trỏ hiện tại\r\n */\r\n insertMention(item: { id: string; name: string; marker?: string }) {\r\n if (!this.#editor) return;\r\n\r\n const firstFeed = this.option?.mentionConfig?.feeds?.[0];\r\n const marker = item.marker || (firstFeed as any)?.marker || '@';\r\n\r\n // Sử dụng CKEditor mention command\r\n this.#editor.execute('mention', {\r\n marker,\r\n mention: {\r\n id: item.id,\r\n text: `${marker}${item.name}`,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Get danh sách mentions trong nội dung\r\n */\r\n getMentions(): Array<{ id: string; name: string; marker: string }> {\r\n if (!this.#editor) return [];\r\n\r\n const mentions: Array<{ id: string; name: string; marker: string }> = [];\r\n const root = this.#editor.model.document.getRoot();\r\n if (!root) return mentions;\r\n\r\n const range = this.#editor.model.createRangeIn(root);\r\n\r\n for (const item of range.getItems()) {\r\n if (item.is('$text')) {\r\n const mentionAttr = item.getAttribute('mention');\r\n if (mentionAttr) {\r\n const text = (item as any).data as string;\r\n const marker = text.charAt(0);\r\n const name = text.substring(1);\r\n const id = item.getAttribute('data-user-id') as string;\r\n\r\n mentions.push({ id, name, marker });\r\n }\r\n }\r\n }\r\n\r\n return mentions;\r\n }\r\n\r\n /**\r\n * ControlValueAccessor: Write value\r\n */\r\n writeValue(value: string): void {\r\n this.value = value || '';\r\n if (this.#editor) {\r\n this.setContent(this.value);\r\n }\r\n }\r\n\r\n /**\r\n * ControlValueAccessor: Register change callback\r\n */\r\n registerOnChange(fn: (value: string) => void): void {\r\n this.#onChange = fn;\r\n }\r\n\r\n /**\r\n * ControlValueAccessor: Register touched callback\r\n */\r\n registerOnTouched(fn: () => void): void {\r\n this.#onTouched = fn;\r\n }\r\n\r\n /** ControlValueAccessor callbacks */\r\n #onChange: (value: string) => void = () => {};\r\n #onTouched: () => void = () => {};\r\n}\r\n","<div class=\"sd-mini-editor\" [style.height]=\"option.height || 'auto'\">\r\n <ckeditor [editor]=\"Editor\" [config]=\"editorConfig\" [disabled]=\"disabled\" (ready)=\"onReady($event)\"> </ckeditor>\r\n</div>\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;AAwBA;;;;;;;;;;;;;;AAcG;MAeU,YAAY,CAAA;;AAEI,IAAA,MAAM;;IAGxB,KAAK,GAAG,EAAE;AACT,IAAA,WAAW,GAAG,IAAI,YAAY,EAAU;;AAGxC,IAAA,aAAa,GAAG,IAAI,YAAY,EAAU;;AAG1C,IAAA,IAAI,GAAG,IAAI,YAAY,EAAc;;AAGrC,IAAA,KAAK,GAAG,IAAI,YAAY,EAAc;;IAGvC,QAAQ,GAAG,KAAK;IAEzB,MAAM,GAAG,aAAa;AACtB,IAAA,OAAO;AACP,IAAA,aAAa,GAAG,IAAI,YAAY,EAAE;AAClC,IAAA,qBAAqB,GAAG,IAAI,OAAO,EAAU;;AAG7C,IAAA,IAAI,YAAY,GAAA;QACd,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa,IAAI,KAAK;QACzD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,KAAK,UAAU;QAC5D,MAAM,OAAO,GAAU,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC;;QAG5G,IAAI,WAAW,EAAE;AACf,YAAA,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC;QACxB;;QAGA,IAAI,aAAa,EAAE;AACjB,YAAA,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACvB;;AAGA,QAAA,MAAM,MAAM,GAAuB;AACjC,YAAA,UAAU,EAAE,KAAK;AACjB,YAAA,SAAS,EAAE,MAAM,IAAI,CAAC,MAAM;YAC5B,OAAO;AACP,YAAA,OAAO,EAAE;gBACP,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,CAAC;AAC1G,gBAAA,sBAAsB,EAAE,IAAI;AAC7B,aAAA;AACD,YAAA,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW;AACrC,YAAA,IAAI,EAAE;AACJ,gBAAA,wBAAwB,EAAE,IAAI;AAC9B,gBAAA,eAAe,EAAE,UAAU;AAC5B,aAAA;AACD,YAAA,SAAS,EAAE;AACT,gBAAA,OAAO,EAAE,CAAC;AACV,gBAAA,cAAc,EAAE,EAAE;AAClB,gBAAA,WAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;AAC/B,aAAA;SACF;;QAGD,IAAI,aAAa,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE;YACtD,MAAM,CAAC,OAAO,GAAG;AACf,gBAAA,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK;aACvC;QACH;AAEA,QAAA,OAAO,MAAM;IACf;AAEA,IAAA,WAAA,GAAA;;AAEE,QAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CACpB,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,IAAG;YACnH,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;AAC3C,YAAA,IAAI,CAAC,KAAK,GAAG,MAAM;AACnB,YAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;AACtB,YAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;AAC7B,YAAA,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;YAC/B,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;QACjC,CAAC,CAAC,CACH;IACH;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE;AAChC,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI;IAC3B;AAEA;;AAEG;IACH,oBAAoB,GAAA;AAClB,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE,aAAa,IAAI,KAAK;IAC5C;AAEA;;AAEG;AACH,IAAA,OAAO,CAAC,MAAqB,EAAA;AAC3B,QAAA,IAAI,CAAC,OAAO,GAAG,MAAM;;AAGrB,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAC7B;;QAGA,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,MAAK;AAC3C,YAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE;AAChC,YAAA,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC;AAC1C,QAAA,CAAC,CAAC;;AAGF,QAAA,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,IAAG;AAC7C,YAAA,MAAM,QAAQ,GAAI,GAAW,CAAC,QAAsB;AACpD,YAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;YACzB,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;AAClC,QAAA,CAAC,CAAC;AAEF,QAAA,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,IAAG;AAC5C,YAAA,MAAM,QAAQ,GAAI,GAAW,CAAC,QAAsB;AACpD,YAAA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;YACxB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;AACjC,QAAA,CAAC,CAAC;;AAGF,QAAA,IAAI,IAAI,CAAC,oBAAoB,EAAE,EAAE;AAC/B,YAAA,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAS,KAAI;AAChE,gBAAA,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;gBAC3B,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAAC,OAAO,CAAC;;AAEnD,gBAAA,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE;AAChC,gBAAA,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC;AAC1C,YAAA,CAAC,CAAC;;YAGF,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,kBAAkB,CAAC;AACnD,gBAAA,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,KAAI;oBAChC,MAAM,IAAI,GAAG,WAAsC;AACnD,oBAAA,MAAM,KAAK,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE;AAC5B,oBAAA,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;oBACvB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAE9B,oBAAA,OAAO,MAAM,CAAC,sBAAsB,CAAC,MAAM,EAAE;AAC3C,wBAAA,KAAK,EAAE,mBAAmB;AAC1B,wBAAA,SAAS,EAAE,OAAO;AAClB,wBAAA,aAAa,EAAE,MAAM;AACrB,wBAAA,eAAe,EAAE,OAAO;AACzB,qBAAA,CAAC;gBACJ,CAAC;AACD,gBAAA,iBAAiB,EAAE,SAAS;AAC7B,aAAA,CAAC;;AAGF,YAAA,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI,KAAI;gBACvD,MAAM,QAAQ,GAAG,IAAoD;;AAErE,gBAAA,IAAI,QAAQ,CAAC,OAAO,KAAK,EAAE,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC,EAAE;AACrD,oBAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK;AAC1B,oBAAA,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS;AAC1C,oBAAA,MAAM,QAAQ,GAAG,SAAS,CAAC,gBAAgB,EAAE;AAC7C,oBAAA,IAAI,CAAC,QAAQ;wBAAE;;AAGf,oBAAA,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,SAAS;oBAC3E,IAAI,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE;wBAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;wBAChD,IAAI,WAAW,EAAE;;AAEf,4BAAA,KAAK,CAAC,MAAM,CAAC,MAAM,IAAG;AACpB,gCAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;AACrB,4BAAA,CAAC,CAAC;4BACF,GAAG,CAAC,IAAI,EAAE;wBACZ;oBACF;gBACF;AACF,YAAA,CAAC,CAAC;QACJ;IACF;AAEA;;;AAGG;AACH,IAAA,cAAc,CAAC,OAAe,EAAA;;;AAG5B,QAAA,OAAO,OAAO;IAChB;AAEA;;AAEG;AACH,IAAA,UAAU,CAAC,OAAe,EAAA;QACxB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IAClC;AAEA;;AAEG;IACH,UAAU,GAAA;AACR,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;YAChB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;AACnC,YAAA,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAClC;AACA,QAAA,OAAO,EAAE;IACX;AAEA;;AAEG;IACH,cAAc,GAAA;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE;IACxC;AAEA;;AAEG;IACH,WAAW,GAAA;QACT,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI;IACxC;AAEA;;AAEG;AACH,IAAA,aAAa,CAAC,IAAmD,EAAA;QAC/D,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE;AAEnB,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,GAAG,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAK,SAAiB,EAAE,MAAM,IAAI,GAAG;;AAG/D,QAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE;YAC9B,MAAM;AACN,YAAA,OAAO,EAAE;gBACP,EAAE,EAAE,IAAI,CAAC,EAAE;AACX,gBAAA,IAAI,EAAE,CAAA,EAAG,MAAM,GAAG,IAAI,CAAC,IAAI,CAAA,CAAE;AAC9B,aAAA;AACF,SAAA,CAAC;IACJ;AAEA;;AAEG;IACH,WAAW,GAAA;QACT,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,EAAE;QAE5B,MAAM,QAAQ,GAAwD,EAAE;AACxE,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE;AAClD,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,QAAQ;AAE1B,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;QAEpD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE;AACnC,YAAA,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE;gBACpB,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;gBAChD,IAAI,WAAW,EAAE;AACf,oBAAA,MAAM,IAAI,GAAI,IAAY,CAAC,IAAc;oBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;oBAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,CAAW;oBAEtD,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gBACrC;YACF;QACF;AAEA,QAAA,OAAO,QAAQ;IACjB;AAEA;;AAEG;AACH,IAAA,UAAU,CAAC,KAAa,EAAA;AACtB,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE;AACxB,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAC7B;IACF;AAEA;;AAEG;AACH,IAAA,gBAAgB,CAAC,EAA2B,EAAA;AAC1C,QAAA,IAAI,CAAC,SAAS,GAAG,EAAE;IACrB;AAEA;;AAEG;AACH,IAAA,iBAAiB,CAAC,EAAc,EAAA;AAC9B,QAAA,IAAI,CAAC,UAAU,GAAG,EAAE;IACtB;;AAGA,IAAA,SAAS,GAA4B,MAAK,EAAE,CAAC;AAC7C,IAAA,UAAU,GAAe,MAAK,EAAE,CAAC;wGA5StB,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,QAAA,EAAA,KAAA,EAAA,OAAA,EAAA,QAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,EAAA,WAAA,EAAA,aAAA,EAAA,aAAA,EAAA,eAAA,EAAA,IAAA,EAAA,MAAA,EAAA,KAAA,EAAA,OAAA,EAAA,EAAA,SAAA,EARZ;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,iBAAiB;AAC1B,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,YAAY,CAAC;AAC3C,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;AACF,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECnDH,uNAGA,EAAA,MAAA,EAAA,CAAA,kzBAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDuCY,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,cAAc,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,SAAA,EAAA,UAAA,EAAA,sBAAA,EAAA,iBAAA,EAAA,0BAAA,EAAA,UAAA,CAAA,EAAA,OAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAAA,OAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAW3B,YAAY,EAAA,UAAA,EAAA,CAAA;kBAdxB,SAAS;+BACE,gBAAgB,EAAA,UAAA,EACd,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,cAAc,CAAC,EAAA,SAAA,EAG5B;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,iBAAiB;AAC1B,4BAAA,WAAW,EAAE,UAAU,CAAC,kBAAkB,CAAC;AAC3C,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;AACF,qBAAA,EAAA,QAAA,EAAA,uNAAA,EAAA,MAAA,EAAA,CAAA,kzBAAA,CAAA,EAAA;wDAI0B,MAAM,EAAA,CAAA;sBAAhC,KAAK;uBAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAGhB,KAAK,EAAA,CAAA;sBAAb;gBACS,WAAW,EAAA,CAAA;sBAApB;gBAGS,aAAa,EAAA,CAAA;sBAAtB;gBAGS,IAAI,EAAA,CAAA;sBAAb;gBAGS,KAAK,EAAA,CAAA;sBAAd;gBAGQ,QAAQ,EAAA,CAAA;sBAAhB;;;AEvEH;;AAEG;;;;"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { Input, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
class SdAvatar {
|
|
6
|
+
src;
|
|
7
|
+
size = 32;
|
|
8
|
+
isUrl = false;
|
|
9
|
+
initials = '';
|
|
10
|
+
bgColor = '#ccc';
|
|
11
|
+
ngOnInit() {
|
|
12
|
+
this.#init();
|
|
13
|
+
}
|
|
14
|
+
#init = () => {
|
|
15
|
+
if (!this.src) {
|
|
16
|
+
this.isUrl = false;
|
|
17
|
+
this.initials = '?';
|
|
18
|
+
this.bgColor = '#bdc3c7';
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Kiểm tra xem src có phải là URL (http, https, data:image, hoặc path /)
|
|
22
|
+
const urlPattern = /^(http|https|data:image|\/)/;
|
|
23
|
+
this.isUrl = urlPattern.test(this.src);
|
|
24
|
+
if (!this.isUrl) {
|
|
25
|
+
this.initials = this.#getInitials(this.src);
|
|
26
|
+
this.bgColor = this.#generateColor(this.src);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
handleError() {
|
|
30
|
+
this.src = undefined; // Nếu ảnh lỗi, chuyển sang hiển thị initials
|
|
31
|
+
this.#init();
|
|
32
|
+
}
|
|
33
|
+
#getInitials = (name) => {
|
|
34
|
+
return name
|
|
35
|
+
.trim()
|
|
36
|
+
.split(' ')
|
|
37
|
+
.map(n => n[0])
|
|
38
|
+
.join('')
|
|
39
|
+
.toUpperCase()
|
|
40
|
+
.substring(0, 2);
|
|
41
|
+
};
|
|
42
|
+
#generateColor = (name) => {
|
|
43
|
+
const colors = [
|
|
44
|
+
'#1abc9c',
|
|
45
|
+
'#2ecc71',
|
|
46
|
+
'#3498db',
|
|
47
|
+
'#9b59b6',
|
|
48
|
+
'#34495e',
|
|
49
|
+
'#16a085',
|
|
50
|
+
'#27ae60',
|
|
51
|
+
'#2980b9',
|
|
52
|
+
'#8e44ad',
|
|
53
|
+
'#2c3e50',
|
|
54
|
+
'#f1c40f',
|
|
55
|
+
'#e67e22',
|
|
56
|
+
'#e74c3c',
|
|
57
|
+
'#95a5a6',
|
|
58
|
+
'#f39c12',
|
|
59
|
+
'#d35400',
|
|
60
|
+
'#c0392b',
|
|
61
|
+
'#bdc3c7',
|
|
62
|
+
'#7f8c8d',
|
|
63
|
+
];
|
|
64
|
+
let hash = 0;
|
|
65
|
+
for (let i = 0; i < name.length; i++) {
|
|
66
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
67
|
+
}
|
|
68
|
+
return colors[Math.abs(hash) % colors.length];
|
|
69
|
+
};
|
|
70
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
71
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: SdAvatar, isStandalone: true, selector: "sd-view", inputs: { src: "src", size: "size" }, ngImport: i0, template: "<div class=\"sd-avatar\" [style.width.px]=\"size\" [style.height.px]=\"size\" [style.line-height.px]=\"size\" [style.backgroundColor]=\"bgColor\">\r\n @if (isUrl) {\r\n <img [src]=\"src\" (error)=\"handleError()\" alt=\"avatar\" />\r\n } @else {\r\n <span class=\"sd-avatar-text\" [style.fontSize.px]=\"size / 2.5\">\r\n {{ initials }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".sd-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;overflow:hidden;color:#fff;font-weight:500;-webkit-user-select:none;user-select:none;background-size:cover}.sd-avatar img{width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
72
|
+
}
|
|
73
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdAvatar, decorators: [{
|
|
74
|
+
type: Component,
|
|
75
|
+
args: [{ selector: 'sd-view', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"sd-avatar\" [style.width.px]=\"size\" [style.height.px]=\"size\" [style.line-height.px]=\"size\" [style.backgroundColor]=\"bgColor\">\r\n @if (isUrl) {\r\n <img [src]=\"src\" (error)=\"handleError()\" alt=\"avatar\" />\r\n } @else {\r\n <span class=\"sd-avatar-text\" [style.fontSize.px]=\"size / 2.5\">\r\n {{ initials }}\r\n </span>\r\n }\r\n</div>\r\n", styles: [".sd-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;overflow:hidden;color:#fff;font-weight:500;-webkit-user-select:none;user-select:none;background-size:cover}.sd-avatar img{width:100%;height:100%;object-fit:cover}\n"] }]
|
|
76
|
+
}], propDecorators: { src: [{
|
|
77
|
+
type: Input,
|
|
78
|
+
args: [{ required: true }]
|
|
79
|
+
}], size: [{
|
|
80
|
+
type: Input
|
|
81
|
+
}] } });
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generated bundle index. Do not edit.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
export { SdAvatar };
|
|
88
|
+
//# sourceMappingURL=sd-angular-core-components-view.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sd-angular-core-components-view.mjs","sources":["../../../projects/sd-angular/components/view/src/view.component.ts","../../../projects/sd-angular/components/view/src/view.component.html","../../../projects/sd-angular/components/view/sd-angular-core-components-view.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\r\nimport { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';\r\n\r\n@Component({\r\n selector: 'sd-view',\r\n standalone: true,\r\n imports: [CommonModule],\r\n templateUrl: './view.component.html',\r\n styleUrls: ['./view.component.scss'],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n})\r\nexport class SdAvatar implements OnInit {\r\n @Input({ required: true }) src: string | undefined | null;\r\n @Input() size: number = 32;\r\n\r\n isUrl: boolean = false;\r\n initials: string = '';\r\n bgColor: string = '#ccc';\r\n\r\n ngOnInit(): void {\r\n this.#init();\r\n }\r\n\r\n #init = () => {\r\n if (!this.src) {\r\n this.isUrl = false;\r\n this.initials = '?';\r\n this.bgColor = '#bdc3c7';\r\n return;\r\n }\r\n // Kiểm tra xem src có phải là URL (http, https, data:image, hoặc path /)\r\n const urlPattern = /^(http|https|data:image|\\/)/;\r\n this.isUrl = urlPattern.test(this.src);\r\n if (!this.isUrl) {\r\n this.initials = this.#getInitials(this.src);\r\n this.bgColor = this.#generateColor(this.src);\r\n }\r\n };\r\n\r\n handleError() {\r\n this.src = undefined; // Nếu ảnh lỗi, chuyển sang hiển thị initials\r\n this.#init();\r\n }\r\n\r\n #getInitials = (name: string): string => {\r\n return name\r\n .trim()\r\n .split(' ')\r\n .map(n => n[0])\r\n .join('')\r\n .toUpperCase()\r\n .substring(0, 2);\r\n };\r\n\r\n #generateColor = (name: string): string => {\r\n const colors = [\r\n '#1abc9c',\r\n '#2ecc71',\r\n '#3498db',\r\n '#9b59b6',\r\n '#34495e',\r\n '#16a085',\r\n '#27ae60',\r\n '#2980b9',\r\n '#8e44ad',\r\n '#2c3e50',\r\n '#f1c40f',\r\n '#e67e22',\r\n '#e74c3c',\r\n '#95a5a6',\r\n '#f39c12',\r\n '#d35400',\r\n '#c0392b',\r\n '#bdc3c7',\r\n '#7f8c8d',\r\n ];\r\n let hash = 0;\r\n for (let i = 0; i < name.length; i++) {\r\n hash = name.charCodeAt(i) + ((hash << 5) - hash);\r\n }\r\n return colors[Math.abs(hash) % colors.length];\r\n };\r\n}\r\n","<div class=\"sd-avatar\" [style.width.px]=\"size\" [style.height.px]=\"size\" [style.line-height.px]=\"size\" [style.backgroundColor]=\"bgColor\">\r\n @if (isUrl) {\r\n <img [src]=\"src\" (error)=\"handleError()\" alt=\"avatar\" />\r\n } @else {\r\n <span class=\"sd-avatar-text\" [style.fontSize.px]=\"size / 2.5\">\r\n {{ initials }}\r\n </span>\r\n }\r\n</div>\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|