@sd-angular/core 19.0.0-beta.93 → 19.0.0-beta.94
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/components/document-builder/src/components/header-footer-builder/header-footer-builder.component.d.ts +4 -1
- package/components/document-builder/src/document-builder.model.d.ts +2 -0
- package/components/editor/src/models/editor.model.d.ts +2 -0
- package/components/editor/src/plugins/image-upload/utils/validate.utils.d.ts +2 -1
- package/components/splitter/src/splitter.component.d.ts +15 -2
- package/components/table/src/components/selector-action/selector-action.component.d.ts +1 -0
- package/components/table/src/table.component.d.ts +1 -0
- package/configurations/src/sd-core.configuration.d.ts +1 -0
- package/fesm2022/sd-angular-core-components-code-editor.mjs +3 -2
- package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-document-builder.mjs +29 -8
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-editor.mjs +31 -16
- package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-form-generic.mjs +68 -61
- package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-history.mjs +3 -2
- package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-import-excel.mjs +25 -23
- package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-preview.mjs +6 -4
- package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-splitter.mjs +188 -18
- package/fesm2022/sd-angular-core-components-splitter.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-tab-router.mjs +3 -1
- package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +30 -23
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs +24 -21
- package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
- package/fesm2022/sd-angular-core-configurations.mjs.map +1 -1
- package/fesm2022/sd-angular-core-directives.mjs +6 -2
- package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs +3 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +5 -3
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip.mjs +5 -3
- package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date-range.mjs +8 -5
- package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs +7 -5
- package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs +10 -8
- package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs +5 -3
- package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs +13 -6
- package/fesm2022/sd-angular-core-forms-input.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 +8 -5
- package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
- package/fesm2022/sd-angular-core-handlers.mjs +7 -6
- package/fesm2022/sd-angular-core-handlers.mjs.map +1 -1
- package/fesm2022/sd-angular-core-i18n.mjs +790 -0
- package/fesm2022/sd-angular-core-i18n.mjs.map +1 -0
- package/fesm2022/sd-angular-core-interceptors.mjs +10 -6
- package/fesm2022/sd-angular-core-interceptors.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-authom.mjs +1 -0
- package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-keycloak.mjs +1 -0
- package/fesm2022/sd-angular-core-modules-keycloak.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +47 -46
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs +15 -13
- package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-docx.mjs +7 -7
- package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-excel.mjs +5 -3
- package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-extensions.mjs +18 -11
- package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-models.mjs +30 -28
- package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
- package/fesm2022/sd-angular-core.mjs +1 -0
- package/fesm2022/sd-angular-core.mjs.map +1 -1
- package/i18n/index.d.ts +5 -0
- package/i18n/src/en.d.ts +2 -0
- package/i18n/src/sd-i18n.messages.d.ts +2 -0
- package/i18n/src/sd-i18n.pipe.d.ts +9 -0
- package/i18n/src/sd-i18n.service.d.ts +12 -0
- package/i18n/src/sd-i18n.token.d.ts +1 -0
- package/i18n/src/sd-i18n.types.d.ts +5 -0
- package/i18n/src/vi.d.ts +312 -0
- package/package.json +53 -49
- package/public-api.d.ts +1 -0
- package/sd-angular-core-19.0.0-beta.94.tgz +0 -0
- package/services/confirm/src/lib/confirm.service.d.ts +1 -0
- package/sd-angular-core-19.0.0-beta.93.tgz +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from '@angular/core';
|
|
2
|
+
import { SdI18nService } from '@sd-angular/core/i18n';
|
|
2
3
|
import { ClassicEditor, EditorConfig } from 'ckeditor5';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
5
|
export declare class SdHeaderFooterBuilder {
|
|
@@ -6,7 +7,9 @@ export declare class SdHeaderFooterBuilder {
|
|
|
6
7
|
Editor: typeof ClassicEditor;
|
|
7
8
|
set model(value: string | undefined | null);
|
|
8
9
|
modelChange: EventEmitter<string>;
|
|
9
|
-
config: EditorConfig
|
|
10
|
+
config: EditorConfig & {
|
|
11
|
+
_i18n?: SdI18nService;
|
|
12
|
+
};
|
|
10
13
|
onReady(editor: ClassicEditor): void;
|
|
11
14
|
static ɵfac: i0.ɵɵFactoryDeclaration<SdHeaderFooterBuilder, never>;
|
|
12
15
|
static ɵcmp: i0.ɵɵComponentDeclaration<SdHeaderFooterBuilder, "sd-header-footer-builder", never, { "model": { "alias": "model"; "required": false; }; }, { "modelChange": "modelChange"; }, never, never, true, never>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { EditorConfig, EventInfo, ModelDocumentSelection, ViewDataTransfer } from 'ckeditor5';
|
|
2
|
+
import { SdI18nService } from '@sd-angular/core/i18n';
|
|
2
3
|
import { CkCommentConfig } from './plugins/ck-comment/ck-comment.plugin.model';
|
|
3
4
|
export type DocumentBuilderOption = EditorConfig & {
|
|
4
5
|
getOption?: () => SdDocumentBuilderOption;
|
|
6
|
+
_i18n?: SdI18nService;
|
|
5
7
|
};
|
|
6
8
|
export interface SdDocumentBuilderOption {
|
|
7
9
|
onDropVariable?: (variable: SdDocumentBuilderVariable) => boolean | Promise<boolean | SdDocumentBuilderVariable>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { EditorConfig } from 'ckeditor5';
|
|
2
|
+
import { SdI18nService } from '@sd-angular/core/i18n';
|
|
2
3
|
import { SdEditorImageConfig } from './image-upload.plugin.model';
|
|
3
4
|
export interface SdEditorOption {
|
|
4
5
|
imageConfig?: SdEditorImageConfig;
|
|
6
|
+
_i18n?: SdI18nService;
|
|
5
7
|
}
|
|
6
8
|
export type EditorOption = EditorConfig & {
|
|
7
9
|
getOption?: () => SdEditorOption;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { FileLoader } from 'ckeditor5';
|
|
2
|
+
import { SdI18nService } from '@sd-angular/core/i18n';
|
|
2
3
|
import { SdEditorImageUploadValidation } from '../../../models';
|
|
3
|
-
export declare const validateAndGetFile: (loader: FileLoader, validation?: SdEditorImageUploadValidation, onWarning?: (message: string) => void) => Promise<File>;
|
|
4
|
+
export declare const validateAndGetFile: (loader: FileLoader, validation?: SdEditorImageUploadValidation, onWarning?: (message: string) => void, i18n?: SdI18nService) => Promise<File>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SdSplitterPanelComponent } from './splitter-panel/splitter-panel.component';
|
|
2
|
-
import { SplitterOrientation } from './splitter.models';
|
|
2
|
+
import { SplitterLayoutState, SplitterOrientation } from './splitter.models';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
export declare class SdSplitterComponent {
|
|
5
5
|
#private;
|
|
@@ -8,8 +8,21 @@ export declare class SdSplitterComponent {
|
|
|
8
8
|
storageKey: import("@angular/core").InputSignal<string | undefined>;
|
|
9
9
|
snapThreshold: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
10
10
|
keyboardStep: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
11
|
+
readonly resizeEnd: import("@angular/core").OutputEmitterRef<SplitterLayoutState>;
|
|
12
|
+
readonly collapsedChange: import("@angular/core").OutputEmitterRef<{
|
|
13
|
+
panelId: string | number;
|
|
14
|
+
collapsed: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
readonly layoutChange: import("@angular/core").OutputEmitterRef<SplitterLayoutState>;
|
|
11
17
|
readonly panels: import("@angular/core").Signal<readonly SdSplitterPanelComponent[]>;
|
|
12
18
|
constructor();
|
|
19
|
+
getLayout(): SplitterLayoutState;
|
|
20
|
+
setLayout(state: SplitterLayoutState): void;
|
|
21
|
+
resetLayout(): void;
|
|
22
|
+
collapse(target: number | string): void;
|
|
23
|
+
expand(target: number | string): void;
|
|
24
|
+
toggle(target: number | string): void;
|
|
25
|
+
resizePanel(target: number | string, size: number): void;
|
|
13
26
|
static ɵfac: i0.ɵɵFactoryDeclaration<SdSplitterComponent, never>;
|
|
14
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SdSplitterComponent, "sd-splitter", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "snapThreshold": { "alias": "snapThreshold"; "required": false; "isSignal": true; }; "keyboardStep": { "alias": "keyboardStep"; "required": false; "isSignal": true; }; }, {}, ["panels"], ["sd-splitter-panel"], true, never>;
|
|
27
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SdSplitterComponent, "sd-splitter", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "snapThreshold": { "alias": "snapThreshold"; "required": false; "isSignal": true; }; "keyboardStep": { "alias": "keyboardStep"; "required": false; "isSignal": true; }; }, { "resizeEnd": "resizeEnd"; "collapsedChange": "collapsedChange"; "layoutChange": "layoutChange"; }, ["panels"], ["sd-splitter-panel"], true, never>;
|
|
15
28
|
}
|
|
@@ -4,6 +4,7 @@ import { SdTableOption } from '../../models/table-option.model';
|
|
|
4
4
|
import { Action } from './action-filter.pipe';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
export declare class SelectorActionComponent {
|
|
7
|
+
#private;
|
|
7
8
|
tableOption: import("@angular/core").InputSignal<SdTableOption | undefined>;
|
|
8
9
|
selectedTableItems: import("@angular/core").InputSignal<SdTableItem<any>[] | undefined>;
|
|
9
10
|
clear: EventEmitter<any>;
|
|
@@ -24,6 +24,7 @@ import { SdTableItem } from './models/table-item.model';
|
|
|
24
24
|
import { ConfiguredTableResult } from './models/table-option-config.model';
|
|
25
25
|
import * as i0 from "@angular/core";
|
|
26
26
|
export declare class MatPaginatorIntlCro extends MatPaginatorIntl {
|
|
27
|
+
#private;
|
|
27
28
|
firstPageLabel: string;
|
|
28
29
|
lastPageLabel: string;
|
|
29
30
|
itemsPerPageLabel: string;
|
|
@@ -12,6 +12,7 @@ import 'prismjs/components/prism-typescript';
|
|
|
12
12
|
import 'prismjs/components/prism-json';
|
|
13
13
|
import 'prismjs/components/prism-css';
|
|
14
14
|
import 'prismjs/components/prism-scss';
|
|
15
|
+
import { SdTPipe } from '@sd-angular/core/i18n';
|
|
15
16
|
import 'prismjs/components/prism-markup';
|
|
16
17
|
|
|
17
18
|
class SdCodeEditor {
|
|
@@ -112,11 +113,11 @@ class SdCodeEditor {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdCodeEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
115
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdCodeEditor, isStandalone: true, selector: "sd-code-editor", inputs: { valueModel: { classPropertyName: "valueModel", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, viewed: { classPropertyName: "viewed", publicName: "viewed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueModel: "modelChange" }, ngImport: i0, template: "<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? '
|
|
116
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdCodeEditor, isStandalone: true, selector: "sd-code-editor", inputs: { valueModel: { classPropertyName: "valueModel", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, viewed: { classPropertyName: "viewed", publicName: "viewed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueModel: "modelChange" }, ngImport: i0, template: "<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? ('core.component.code-editor.copied' | sdT) : 'Copy code'\" (click)=\"copyToClipboard()\">\r\n <mat-icon [class.text-success]=\"copied()\">{{ copied() ? 'check' : 'content_copy' }}</mat-icon>\r\n <span>{{ copied() ? 'Copied' : 'Copy' }}</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"sd-code-content\" [style.maxHeight]=\"maxHeight()\">\r\n <div class=\"editor-overlay-container\">\r\n \r\n @if (!viewed()) {\r\n <textarea\r\n class=\"code-textarea\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextChange($any($event.target).value)\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\">\r\n </textarea>\r\n }\r\n \r\n <pre aria-hidden=\"true\" class=\"code-display\"><code [class]=\"'language-' + prismLang()\" [innerHTML]=\"highlightedCode()\"></code></pre>\r\n \r\n </div>\r\n </div>\r\n</div>", styles: ["@charset \"UTF-8\";.sd-code-wrapper{background-color:#1e1e1e;border-radius:8px;overflow:hidden;box-shadow:0 4px 12px #00000026;margin:16px 0;font-family:Fira Code,Consolas,Monaco,monospace}.sd-code-wrapper .sd-code-header{display:flex;align-items:center;justify-content:space-between;background-color:#2d2d2d;padding:8px 16px;border-bottom:1px solid #404040}.sd-code-wrapper .sd-code-header .mac-dots{display:flex;gap:6px}.sd-code-wrapper .sd-code-header .mac-dots .dot{width:12px;height:12px;border-radius:50%}.sd-code-wrapper .sd-code-header .mac-dots .dot.red{background-color:#ff5f56}.sd-code-wrapper .sd-code-header .mac-dots .dot.yellow{background-color:#ffbd2e}.sd-code-wrapper .sd-code-header .mac-dots .dot.green{background-color:#27c93f}.sd-code-wrapper .sd-code-header .lang-badge{color:#858585;font-size:12px;font-weight:600;letter-spacing:.5px}.sd-code-wrapper .sd-code-header .copy-btn{display:flex;align-items:center;gap:4px;background:none;border:none;color:#ccc;cursor:pointer;font-size:13px;padding:4px 8px;border-radius:4px;transition:all .2s ease}.sd-code-wrapper .sd-code-header .copy-btn mat-icon{font-size:16px;width:16px;height:16px}.sd-code-wrapper .sd-code-header .copy-btn:hover{background-color:#404040;color:#fff}.sd-code-wrapper .sd-code-header .copy-btn .text-success{color:#27c93f!important}.sd-code-wrapper .sd-code-content{overflow-y:auto;position:relative}.sd-code-wrapper .sd-code-content::-webkit-scrollbar{width:8px;height:8px}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-thumb{background:#555;border-radius:4px}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-thumb:hover{background:#777}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-track{background:#1e1e1e}.sd-code-wrapper .sd-code-content .editor-overlay-container{position:relative;min-height:100px}.sd-code-wrapper .sd-code-content .code-textarea,.sd-code-wrapper .sd-code-content .code-display{margin:0;padding:16px;border:0;width:100%;min-height:100%;font-family:inherit;font-size:14px;line-height:1.5;white-space:pre-wrap;word-break:break-all;tab-size:2}.sd-code-wrapper .sd-code-content .code-textarea{position:absolute;top:0;left:0;z-index:2;background:transparent!important;color:transparent!important;-webkit-text-fill-color:transparent!important;caret-color:#fff;resize:none;outline:none;overflow:hidden}.sd-code-wrapper .sd-code-content .code-display{position:relative;z-index:1;pointer-events:none}.sd-code-wrapper .sd-code-content .code-display code{color:#d4d4d4;font-family:inherit}.sd-code-wrapper .token.comment,.sd-code-wrapper .token.block-comment,.sd-code-wrapper .token.prolog,.sd-code-wrapper .token.doctype,.sd-code-wrapper .token.cdata{color:#999}.sd-code-wrapper .token.punctuation{color:#ccc}.sd-code-wrapper .token.tag,.sd-code-wrapper .token.attr-name,.sd-code-wrapper .token.namespace,.sd-code-wrapper .token.deleted{color:#e2777a}.sd-code-wrapper .token.function-name{color:#6196cc}.sd-code-wrapper .token.boolean,.sd-code-wrapper .token.number,.sd-code-wrapper .token.function{color:#f08d49}.sd-code-wrapper .token.property,.sd-code-wrapper .token.class-name,.sd-code-wrapper .token.constant,.sd-code-wrapper .token.symbol{color:#f8c555}.sd-code-wrapper .token.selector,.sd-code-wrapper .token.important,.sd-code-wrapper .token.atrule,.sd-code-wrapper .token.keyword,.sd-code-wrapper .token.builtin{color:#cc99cd}.sd-code-wrapper .token.string,.sd-code-wrapper .token.char,.sd-code-wrapper .token.attr-value,.sd-code-wrapper .token.regex,.sd-code-wrapper .token.variable{color:#7ec699}.sd-code-wrapper .token.operator,.sd-code-wrapper .token.entity,.sd-code-wrapper .token.url{color:#67cdcc}.sd-code-wrapper .token.important,.sd-code-wrapper .token.bold{font-weight:700}.sd-code-wrapper .token.italic{font-style:italic}.sd-code-wrapper .token.entity{cursor:help}.sd-code-wrapper .token.inserted{color:green}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: SdTPipe, name: "sdT" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
116
117
|
}
|
|
117
118
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdCodeEditor, decorators: [{
|
|
118
119
|
type: Component,
|
|
119
|
-
args: [{ selector: 'sd-code-editor', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? '
|
|
120
|
+
args: [{ selector: 'sd-code-editor', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule, SdTPipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? ('core.component.code-editor.copied' | sdT) : 'Copy code'\" (click)=\"copyToClipboard()\">\r\n <mat-icon [class.text-success]=\"copied()\">{{ copied() ? 'check' : 'content_copy' }}</mat-icon>\r\n <span>{{ copied() ? 'Copied' : 'Copy' }}</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"sd-code-content\" [style.maxHeight]=\"maxHeight()\">\r\n <div class=\"editor-overlay-container\">\r\n \r\n @if (!viewed()) {\r\n <textarea\r\n class=\"code-textarea\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextChange($any($event.target).value)\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\">\r\n </textarea>\r\n }\r\n \r\n <pre aria-hidden=\"true\" class=\"code-display\"><code [class]=\"'language-' + prismLang()\" [innerHTML]=\"highlightedCode()\"></code></pre>\r\n \r\n </div>\r\n </div>\r\n</div>", styles: ["@charset \"UTF-8\";.sd-code-wrapper{background-color:#1e1e1e;border-radius:8px;overflow:hidden;box-shadow:0 4px 12px #00000026;margin:16px 0;font-family:Fira Code,Consolas,Monaco,monospace}.sd-code-wrapper .sd-code-header{display:flex;align-items:center;justify-content:space-between;background-color:#2d2d2d;padding:8px 16px;border-bottom:1px solid #404040}.sd-code-wrapper .sd-code-header .mac-dots{display:flex;gap:6px}.sd-code-wrapper .sd-code-header .mac-dots .dot{width:12px;height:12px;border-radius:50%}.sd-code-wrapper .sd-code-header .mac-dots .dot.red{background-color:#ff5f56}.sd-code-wrapper .sd-code-header .mac-dots .dot.yellow{background-color:#ffbd2e}.sd-code-wrapper .sd-code-header .mac-dots .dot.green{background-color:#27c93f}.sd-code-wrapper .sd-code-header .lang-badge{color:#858585;font-size:12px;font-weight:600;letter-spacing:.5px}.sd-code-wrapper .sd-code-header .copy-btn{display:flex;align-items:center;gap:4px;background:none;border:none;color:#ccc;cursor:pointer;font-size:13px;padding:4px 8px;border-radius:4px;transition:all .2s ease}.sd-code-wrapper .sd-code-header .copy-btn mat-icon{font-size:16px;width:16px;height:16px}.sd-code-wrapper .sd-code-header .copy-btn:hover{background-color:#404040;color:#fff}.sd-code-wrapper .sd-code-header .copy-btn .text-success{color:#27c93f!important}.sd-code-wrapper .sd-code-content{overflow-y:auto;position:relative}.sd-code-wrapper .sd-code-content::-webkit-scrollbar{width:8px;height:8px}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-thumb{background:#555;border-radius:4px}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-thumb:hover{background:#777}.sd-code-wrapper .sd-code-content::-webkit-scrollbar-track{background:#1e1e1e}.sd-code-wrapper .sd-code-content .editor-overlay-container{position:relative;min-height:100px}.sd-code-wrapper .sd-code-content .code-textarea,.sd-code-wrapper .sd-code-content .code-display{margin:0;padding:16px;border:0;width:100%;min-height:100%;font-family:inherit;font-size:14px;line-height:1.5;white-space:pre-wrap;word-break:break-all;tab-size:2}.sd-code-wrapper .sd-code-content .code-textarea{position:absolute;top:0;left:0;z-index:2;background:transparent!important;color:transparent!important;-webkit-text-fill-color:transparent!important;caret-color:#fff;resize:none;outline:none;overflow:hidden}.sd-code-wrapper .sd-code-content .code-display{position:relative;z-index:1;pointer-events:none}.sd-code-wrapper .sd-code-content .code-display code{color:#d4d4d4;font-family:inherit}.sd-code-wrapper .token.comment,.sd-code-wrapper .token.block-comment,.sd-code-wrapper .token.prolog,.sd-code-wrapper .token.doctype,.sd-code-wrapper .token.cdata{color:#999}.sd-code-wrapper .token.punctuation{color:#ccc}.sd-code-wrapper .token.tag,.sd-code-wrapper .token.attr-name,.sd-code-wrapper .token.namespace,.sd-code-wrapper .token.deleted{color:#e2777a}.sd-code-wrapper .token.function-name{color:#6196cc}.sd-code-wrapper .token.boolean,.sd-code-wrapper .token.number,.sd-code-wrapper .token.function{color:#f08d49}.sd-code-wrapper .token.property,.sd-code-wrapper .token.class-name,.sd-code-wrapper .token.constant,.sd-code-wrapper .token.symbol{color:#f8c555}.sd-code-wrapper .token.selector,.sd-code-wrapper .token.important,.sd-code-wrapper .token.atrule,.sd-code-wrapper .token.keyword,.sd-code-wrapper .token.builtin{color:#cc99cd}.sd-code-wrapper .token.string,.sd-code-wrapper .token.char,.sd-code-wrapper .token.attr-value,.sd-code-wrapper .token.regex,.sd-code-wrapper .token.variable{color:#7ec699}.sd-code-wrapper .token.operator,.sd-code-wrapper .token.entity,.sd-code-wrapper .token.url{color:#67cdcc}.sd-code-wrapper .token.important,.sd-code-wrapper .token.bold{font-weight:700}.sd-code-wrapper .token.italic{font-style:italic}.sd-code-wrapper .token.entity{cursor:help}.sd-code-wrapper .token.inserted{color:green}\n"] }]
|
|
120
121
|
}], ctorParameters: () => [] });
|
|
121
122
|
|
|
122
123
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-angular-core-components-code-editor.mjs","sources":["../../../projects/sd-angular/components/code-editor/src/code-editor.component.ts","../../../projects/sd-angular/components/code-editor/src/code-editor.component.html","../../../projects/sd-angular/components/code-editor/sd-angular-core-components-code-editor.ts"],"sourcesContent":["import { Clipboard } from '@angular/cdk/clipboard';\r\nimport { CommonModule } from '@angular/common';\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n computed,\r\n effect,\r\n inject,\r\n input,\r\n model,\r\n signal,\r\n untracked,\r\n ViewEncapsulation,\r\n booleanAttribute\r\n} from '@angular/core';\r\nimport { MatIconModule } from '@angular/material/icon';\r\nimport { MatTooltipModule } from '@angular/material/tooltip';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\n\r\nimport * as Prism from 'prismjs';\r\nimport 'prismjs/components/prism-typescript';\r\nimport 'prismjs/components/prism-json';\r\nimport 'prismjs/components/prism-css';\r\nimport 'prismjs/components/prism-scss';\r\nimport 'prismjs/components/prism-markup'; // HTML\r\n\r\nexport type CodeLanguage = 'html' | 'typescript' | 'json' | 'css' | 'scss';\r\n\r\n@Component({\r\n selector: 'sd-code-editor',\r\n standalone: true,\r\n imports: [CommonModule, MatIconModule, MatTooltipModule],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n templateUrl: './code-editor.component.html',\r\n styleUrls: ['./code-editor.component.scss'],\r\n encapsulation: ViewEncapsulation.None, // Bắt buộc để nhận màu Prism\r\n})\r\nexport class SdCodeEditor {\r\n #clipboard = inject(Clipboard);\r\n #sanitizer = inject(DomSanitizer);\r\n\r\n // ==========================================\r\n // 1. SIGNAL INPUTS & MODEL\r\n // ==========================================\r\n // Nhận bất kỳ kiểu dữ liệu nào (string, array, object)\r\n valueModel = model<any>(undefined, { alias: 'model' });\r\n \r\n language = input<CodeLanguage>('typescript');\r\n maxHeight = input<string>('500px');\r\n \r\n // Trạng thái Viewed (true = Read Only, false = Editable)\r\n viewed = input(false, { transform: booleanAttribute });\r\n\r\n // ==========================================\r\n // 2. INTERNAL STATE\r\n // ==========================================\r\n copied = signal<boolean>(false);\r\n \r\n // Chuỗi text nội bộ dùng để map với thẻ <textarea>\r\n textValue = signal<string>('');\r\n \r\n prismLang = computed(() => this.language() === 'html' ? 'markup' : this.language());\r\n\r\n // Cờ lưu vết để chống Loop (Vòng lặp vô tận khi bắn 2 chiều)\r\n private _lastEmittedValue: any = undefined;\r\n\r\n constructor() {\r\n // ==========================================\r\n // EFFECT 1: Dữ liệu từ CHA truyền vào (Model -> TextValue)\r\n // ==========================================\r\n effect(() => {\r\n const extVal = this.valueModel();\r\n const lang = this.language();\r\n \r\n untracked(() => {\r\n // Nếu giá trị này do chính component bắn ra, bỏ qua để tránh loop\r\n if (extVal === this._lastEmittedValue) return;\r\n\r\n if (typeof extVal === 'string') {\r\n this.textValue.set(extVal);\r\n } else if (extVal !== undefined && extVal !== null) {\r\n // Tự động format Object -> String nếu là JSON\r\n if (lang === 'json') {\r\n try {\r\n this.textValue.set(JSON.stringify(extVal, null, 2));\r\n } catch {\r\n this.textValue.set('// Lỗi: Object có tham chiếu vòng (Circular Reference)');\r\n }\r\n } else {\r\n this.textValue.set(String(extVal));\r\n }\r\n } else {\r\n this.textValue.set('');\r\n }\r\n });\r\n });\r\n }\r\n\r\n // ==========================================\r\n // EFFECT 2: PrismJS render (TextValue -> HTML MÀU)\r\n // ==========================================\r\n highlightedCode = computed<SafeHtml>(() => {\r\n // Dùng khoảng trắng để giữ độ cao cho thẻ pre nếu rỗng\r\n const rawCode = this.textValue() || ' '; \r\n const langKey = this.prismLang();\r\n const grammar = Prism.languages[langKey] || Prism.languages['markup'];\r\n \r\n // Cộng thêm \\n ở cuối để chống lỗi con trỏ textarea ăn lẹm dòng cuối\r\n const highlightedString = Prism.highlight(rawCode, grammar, langKey) + '\\n';\r\n return this.#sanitizer.bypassSecurityTrustHtml(highlightedString);\r\n });\r\n\r\n // ==========================================\r\n // EVENTS\r\n // ==========================================\r\n \r\n // Khi người dùng gõ vào Textarea (TextValue -> Model)\r\n onTextChange(newText: string) {\r\n this.textValue.set(newText);\r\n \r\n let valToEmit: any = newText;\r\n \r\n // Nếu ngôn ngữ là JSON, cố gắng trả về Object thật\r\n if (this.language() === 'json') {\r\n try {\r\n valToEmit = JSON.parse(newText);\r\n } catch {\r\n // Nếu gõ dở ngoặc/sai cú pháp -> Trả về chuỗi String tạm\r\n valToEmit = newText; \r\n }\r\n }\r\n \r\n // Ghi sổ và bắn ra ngoài\r\n this._lastEmittedValue = valToEmit;\r\n this.valueModel.set(valToEmit);\r\n }\r\n\r\n copyToClipboard() {\r\n const rawCode = this.textValue();\r\n if (!rawCode) return;\r\n \r\n if (this.#clipboard.copy(rawCode)) {\r\n this.copied.set(true);\r\n setTimeout(() => this.copied.set(false), 2000);\r\n }\r\n }\r\n}","<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? 'Đã copy!' : 'Copy code'\" (click)=\"copyToClipboard()\">\r\n <mat-icon [class.text-success]=\"copied()\">{{ copied() ? 'check' : 'content_copy' }}</mat-icon>\r\n <span>{{ copied() ? 'Copied' : 'Copy' }}</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"sd-code-content\" [style.maxHeight]=\"maxHeight()\">\r\n <div class=\"editor-overlay-container\">\r\n \r\n @if (!viewed()) {\r\n <textarea\r\n class=\"code-textarea\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextChange($any($event.target).value)\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\">\r\n </textarea>\r\n }\r\n \r\n <pre aria-hidden=\"true\" class=\"code-display\"><code [class]=\"'language-' + prismLang()\" [innerHTML]=\"highlightedCode()\"></code></pre>\r\n \r\n </div>\r\n </div>\r\n</div>","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;MAqCa,YAAY,CAAA;AACvB,IAAA,UAAU,GAAG,MAAM,CAAC,SAAS,CAAC;AAC9B,IAAA,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC;;;;;IAMjC,UAAU,GAAG,KAAK,CAAM,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAEtD,IAAA,QAAQ,GAAG,KAAK,CAAe,YAAY,CAAC;AAC5C,IAAA,SAAS,GAAG,KAAK,CAAS,OAAO,CAAC;;IAGlC,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;;;;AAKtD,IAAA,MAAM,GAAG,MAAM,CAAU,KAAK,CAAC;;AAG/B,IAAA,SAAS,GAAG,MAAM,CAAS,EAAE,CAAC;IAE9B,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,KAAK,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;;IAG3E,iBAAiB,GAAQ,SAAS;AAE1C,IAAA,WAAA,GAAA;;;;QAIE,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE;AAChC,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;YAE5B,SAAS,CAAC,MAAK;;AAEb,gBAAA,IAAI,MAAM,KAAK,IAAI,CAAC,iBAAiB;oBAAE;AAEvC,gBAAA,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC9B,oBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;gBAC5B;qBAAO,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,EAAE;;AAElD,oBAAA,IAAI,IAAI,KAAK,MAAM,EAAE;AACnB,wBAAA,IAAI;AACF,4BAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;wBACrD;AAAE,wBAAA,MAAM;AACN,4BAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,wDAAwD,CAAC;wBAC9E;oBACF;yBAAO;wBACL,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACpC;gBACF;qBAAO;AACL,oBAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB;AACF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;;;;AAKA,IAAA,eAAe,GAAG,QAAQ,CAAW,MAAK;;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG;AACvC,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE;AAChC,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;;AAGrE,QAAA,MAAM,iBAAiB,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI;QAC3E,OAAO,IAAI,CAAC,UAAU,CAAC,uBAAuB,CAAC,iBAAiB,CAAC;AACnE,IAAA,CAAC,CAAC;;;;;AAOF,IAAA,YAAY,CAAC,OAAe,EAAA;AAC1B,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;QAE3B,IAAI,SAAS,GAAQ,OAAO;;AAG5B,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE,KAAK,MAAM,EAAE;AAC9B,YAAA,IAAI;AACF,gBAAA,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YACjC;AAAE,YAAA,MAAM;;gBAEN,SAAS,GAAG,OAAO;YACrB;QACF;;AAGA,QAAA,IAAI,CAAC,iBAAiB,GAAG,SAAS;AAClC,QAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC;IAChC;IAEA,eAAe,GAAA;AACb,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE;AAChC,QAAA,IAAI,CAAC,OAAO;YAAE;QAEd,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;AACjC,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;AACrB,YAAA,UAAU,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;QAChD;IACF;wGA5GW,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,goBCrCzB,shDAyCM,EAAA,MAAA,EAAA,CAAA,8wHAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDVM,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAE,aAAa,mLAAE,gBAAgB,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,oBAAA,EAAA,4BAAA,EAAA,oBAAA,EAAA,qBAAA,EAAA,qBAAA,EAAA,yBAAA,EAAA,YAAA,EAAA,iBAAA,CAAA,EAAA,QAAA,EAAA,CAAA,YAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;4FAM5C,YAAY,EAAA,UAAA,EAAA,CAAA;kBATxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,cACd,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,aAAa,EAAE,gBAAgB,CAAC,mBACvC,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAGhC,iBAAiB,CAAC,IAAI,EAAA,QAAA,EAAA,shDAAA,EAAA,MAAA,EAAA,CAAA,8wHAAA,CAAA,EAAA;;;AEnCvC;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"sd-angular-core-components-code-editor.mjs","sources":["../../../projects/sd-angular/components/code-editor/src/code-editor.component.ts","../../../projects/sd-angular/components/code-editor/src/code-editor.component.html","../../../projects/sd-angular/components/code-editor/sd-angular-core-components-code-editor.ts"],"sourcesContent":["import { Clipboard } from '@angular/cdk/clipboard';\r\nimport { CommonModule } from '@angular/common';\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n computed,\r\n effect,\r\n inject,\r\n input,\r\n model,\r\n signal,\r\n untracked,\r\n ViewEncapsulation,\r\n booleanAttribute\r\n} from '@angular/core';\r\nimport { MatIconModule } from '@angular/material/icon';\r\nimport { MatTooltipModule } from '@angular/material/tooltip';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\n\r\nimport * as Prism from 'prismjs';\r\nimport 'prismjs/components/prism-typescript';\r\nimport 'prismjs/components/prism-json';\r\nimport 'prismjs/components/prism-css';\r\nimport 'prismjs/components/prism-scss';\nimport { SdTPipe } from '@sd-angular/core/i18n';\r\nimport 'prismjs/components/prism-markup'; // HTML\r\n\r\nexport type CodeLanguage = 'html' | 'typescript' | 'json' | 'css' | 'scss';\r\n\r\n@Component({\r\n selector: 'sd-code-editor',\r\n standalone: true,\r\n imports: [CommonModule, MatIconModule, MatTooltipModule, SdTPipe],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n templateUrl: './code-editor.component.html',\r\n styleUrls: ['./code-editor.component.scss'],\r\n encapsulation: ViewEncapsulation.None, // Bắt buộc để nhận màu Prism\r\n})\r\nexport class SdCodeEditor {\r\n #clipboard = inject(Clipboard);\r\n #sanitizer = inject(DomSanitizer);\r\n\r\n // ==========================================\r\n // 1. SIGNAL INPUTS & MODEL\r\n // ==========================================\r\n // Nhận bất kỳ kiểu dữ liệu nào (string, array, object)\r\n valueModel = model<any>(undefined, { alias: 'model' });\r\n \r\n language = input<CodeLanguage>('typescript');\r\n maxHeight = input<string>('500px');\r\n \r\n // Trạng thái Viewed (true = Read Only, false = Editable)\r\n viewed = input(false, { transform: booleanAttribute });\r\n\r\n // ==========================================\r\n // 2. INTERNAL STATE\r\n // ==========================================\r\n copied = signal<boolean>(false);\r\n \r\n // Chuỗi text nội bộ dùng để map với thẻ <textarea>\r\n textValue = signal<string>('');\r\n \r\n prismLang = computed(() => this.language() === 'html' ? 'markup' : this.language());\r\n\r\n // Cờ lưu vết để chống Loop (Vòng lặp vô tận khi bắn 2 chiều)\r\n private _lastEmittedValue: any = undefined;\r\n\r\n constructor() {\r\n // ==========================================\r\n // EFFECT 1: Dữ liệu từ CHA truyền vào (Model -> TextValue)\r\n // ==========================================\r\n effect(() => {\r\n const extVal = this.valueModel();\r\n const lang = this.language();\r\n \r\n untracked(() => {\r\n // Nếu giá trị này do chính component bắn ra, bỏ qua để tránh loop\r\n if (extVal === this._lastEmittedValue) return;\r\n\r\n if (typeof extVal === 'string') {\r\n this.textValue.set(extVal);\r\n } else if (extVal !== undefined && extVal !== null) {\r\n // Tự động format Object -> String nếu là JSON\r\n if (lang === 'json') {\r\n try {\r\n this.textValue.set(JSON.stringify(extVal, null, 2));\r\n } catch {\r\n this.textValue.set('// Lỗi: Object có tham chiếu vòng (Circular Reference)');\r\n }\r\n } else {\r\n this.textValue.set(String(extVal));\r\n }\r\n } else {\r\n this.textValue.set('');\r\n }\r\n });\r\n });\r\n }\r\n\r\n // ==========================================\r\n // EFFECT 2: PrismJS render (TextValue -> HTML MÀU)\r\n // ==========================================\r\n highlightedCode = computed<SafeHtml>(() => {\r\n // Dùng khoảng trắng để giữ độ cao cho thẻ pre nếu rỗng\r\n const rawCode = this.textValue() || ' '; \r\n const langKey = this.prismLang();\r\n const grammar = Prism.languages[langKey] || Prism.languages['markup'];\r\n \r\n // Cộng thêm \\n ở cuối để chống lỗi con trỏ textarea ăn lẹm dòng cuối\r\n const highlightedString = Prism.highlight(rawCode, grammar, langKey) + '\\n';\r\n return this.#sanitizer.bypassSecurityTrustHtml(highlightedString);\r\n });\r\n\r\n // ==========================================\r\n // EVENTS\r\n // ==========================================\r\n \r\n // Khi người dùng gõ vào Textarea (TextValue -> Model)\r\n onTextChange(newText: string) {\r\n this.textValue.set(newText);\r\n \r\n let valToEmit: any = newText;\r\n \r\n // Nếu ngôn ngữ là JSON, cố gắng trả về Object thật\r\n if (this.language() === 'json') {\r\n try {\r\n valToEmit = JSON.parse(newText);\r\n } catch {\r\n // Nếu gõ dở ngoặc/sai cú pháp -> Trả về chuỗi String tạm\r\n valToEmit = newText; \r\n }\r\n }\r\n \r\n // Ghi sổ và bắn ra ngoài\r\n this._lastEmittedValue = valToEmit;\r\n this.valueModel.set(valToEmit);\r\n }\r\n\r\n copyToClipboard() {\r\n const rawCode = this.textValue();\r\n if (!rawCode) return;\r\n \r\n if (this.#clipboard.copy(rawCode)) {\r\n this.copied.set(true);\r\n setTimeout(() => this.copied.set(false), 2000);\r\n }\r\n }\r\n}","<div class=\"sd-code-wrapper\" [class.is-editable]=\"!viewed()\">\r\n \r\n <div class=\"sd-code-header\">\r\n <div class=\"mac-dots\">\r\n <span class=\"dot red\"></span>\r\n <span class=\"dot yellow\"></span>\r\n <span class=\"dot green\"></span>\r\n </div>\r\n \r\n <div class=\"lang-badge\">\r\n {{ language().toUpperCase() }} \r\n <span style=\"opacity: 0.6; margin-left: 4px; font-weight: normal;\">\r\n {{ viewed() ? '(READ ONLY)' : '(EDITING)' }}\r\n </span>\r\n </div>\r\n \r\n <button class=\"copy-btn\" [matTooltip]=\"copied() ? ('core.component.code-editor.copied' | sdT) : 'Copy code'\" (click)=\"copyToClipboard()\">\r\n <mat-icon [class.text-success]=\"copied()\">{{ copied() ? 'check' : 'content_copy' }}</mat-icon>\r\n <span>{{ copied() ? 'Copied' : 'Copy' }}</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"sd-code-content\" [style.maxHeight]=\"maxHeight()\">\r\n <div class=\"editor-overlay-container\">\r\n \r\n @if (!viewed()) {\r\n <textarea\r\n class=\"code-textarea\"\r\n [value]=\"textValue()\"\r\n (input)=\"onTextChange($any($event.target).value)\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\">\r\n </textarea>\r\n }\r\n \r\n <pre aria-hidden=\"true\" class=\"code-display\"><code [class]=\"'language-' + prismLang()\" [innerHTML]=\"highlightedCode()\"></code></pre>\r\n \r\n </div>\r\n </div>\r\n</div>","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { EventEmitter, Output, Input, Component } from '@angular/core';
|
|
2
|
+
import { inject, EventEmitter, Output, Input, Component } from '@angular/core';
|
|
3
3
|
import { CommonModule } from '@angular/common';
|
|
4
4
|
import * as i1 from '@ckeditor/ckeditor5-angular';
|
|
5
5
|
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
6
|
+
import { SdI18nService } from '@sd-angular/core/i18n';
|
|
6
7
|
import { Plugin, ButtonView, ClassicEditor, Essentials, Paragraph, Bold, Italic, Underline, FontSize, FontColor, FontBackgroundColor, Alignment, Widget, toWidget, ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter, ClipboardPipeline, ContextualBalloon, FontFamily, Heading, List, Table, TableToolbar, TableProperties, TableCellProperties, TableColumnResize, PageBreak, Undo, Subscript, Superscript, Image, ImageUpload, ImageToolbar, ImageCaption, ImageResize, ImageStyle, ImageBlock, Indent, IndentBlock } from 'ckeditor5';
|
|
7
8
|
import { Subscription, Subject, throttleTime } from 'rxjs';
|
|
8
9
|
import { SdResolveMaybeAsync, hslToHex, rgbToHex, SdUtilities } from '@sd-angular/core/utilities';
|
|
@@ -11,11 +12,13 @@ import { v4 } from 'uuid';
|
|
|
11
12
|
class PageNumberPlugin extends Plugin {
|
|
12
13
|
init() {
|
|
13
14
|
const editor = this.editor;
|
|
15
|
+
// i18n nằm trong editor.config — plugin không có DI nên đọc qua config; Angular wrapper luôn truyền _i18n
|
|
16
|
+
const i18n = editor.config.get('_i18n');
|
|
14
17
|
// 1. Đăng ký nút "Số trang hiện tại"
|
|
15
18
|
editor.ui.componentFactory.add('pageNumber', locale => {
|
|
16
19
|
const view = new ButtonView(locale);
|
|
17
20
|
view.set({
|
|
18
|
-
label: '
|
|
21
|
+
label: i18n?.t('core.component.document-builder.page-number.current') ?? '',
|
|
19
22
|
icon: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M16 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H4V4h12v12zM6 6h2v2H6zm0 4h2v2H6zm0 4h2v2H6zm4-8h4v2h-4zm0 4h4v2h-4zm0 4h4v2h-4z"/></svg>', // Icon đơn giản
|
|
20
23
|
tooltip: true
|
|
21
24
|
});
|
|
@@ -36,7 +39,7 @@ class PageNumberPlugin extends Plugin {
|
|
|
36
39
|
editor.ui.componentFactory.add('totalPages', locale => {
|
|
37
40
|
const view = new ButtonView(locale);
|
|
38
41
|
view.set({
|
|
39
|
-
label: '
|
|
42
|
+
label: i18n?.t('core.component.document-builder.page-number.total') ?? '',
|
|
40
43
|
icon: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>',
|
|
41
44
|
tooltip: true
|
|
42
45
|
});
|
|
@@ -53,6 +56,7 @@ class PageNumberPlugin extends Plugin {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
class SdHeaderFooterBuilder {
|
|
59
|
+
#i18n = inject(SdI18nService);
|
|
56
60
|
#editor;
|
|
57
61
|
Editor = ClassicEditor;
|
|
58
62
|
#model;
|
|
@@ -64,6 +68,8 @@ class SdHeaderFooterBuilder {
|
|
|
64
68
|
modelChange = new EventEmitter();
|
|
65
69
|
config = {
|
|
66
70
|
licenseKey: 'GPL', // Hoặc key thương mại nếu có
|
|
71
|
+
// Truyền i18n service xuống CKEditor plugin (ngoài DI tree) để dịch label
|
|
72
|
+
_i18n: this.#i18n,
|
|
67
73
|
// 1. PLUGIN RÚT GỌN (Bỏ Table, List, PageBreak...)
|
|
68
74
|
plugins: [
|
|
69
75
|
Essentials,
|
|
@@ -532,9 +538,11 @@ class VariablePlugin extends Plugin {
|
|
|
532
538
|
// Bug 4 Fix (Q2-A): Xóa tham số dropIndex — không còn tryền giá trị hardcode 0
|
|
533
539
|
const result = await SdResolveMaybeAsync(option.onDropVariable(variable));
|
|
534
540
|
// * Hỗ trợ dữ liệu có sẵn sẽ chỉ cần nhận vào boolean có cho phép thả hay không?
|
|
541
|
+
// i18n nằm trong editor.config — plugin không có DI nên đọc qua config; Angular wrapper luôn truyền _i18n
|
|
542
|
+
const i18n = editor.config.get('_i18n');
|
|
535
543
|
if (typeof result === 'boolean') {
|
|
536
544
|
if (!result) {
|
|
537
|
-
throw new Error('
|
|
545
|
+
throw new Error(i18n?.t('core.component.document-builder.variable.not-allowed') ?? '');
|
|
538
546
|
}
|
|
539
547
|
}
|
|
540
548
|
else {
|
|
@@ -543,7 +551,7 @@ class VariablePlugin extends Plugin {
|
|
|
543
551
|
variable = result;
|
|
544
552
|
}
|
|
545
553
|
else {
|
|
546
|
-
throw new Error('
|
|
554
|
+
throw new Error(i18n?.t('core.component.document-builder.variable.invalid-data') ?? '');
|
|
547
555
|
}
|
|
548
556
|
}
|
|
549
557
|
}
|
|
@@ -768,6 +776,7 @@ class VariablePlugin extends Plugin {
|
|
|
768
776
|
resolved = await SdResolveMaybeAsync(option.onPasteVariable(fragment.display));
|
|
769
777
|
}
|
|
770
778
|
catch (e) {
|
|
779
|
+
// @i18n-ignore — dev console warning
|
|
771
780
|
console.warn(`[VariablePlugin] onPasteVariable("${fragment.display}") thất bại:`, e);
|
|
772
781
|
}
|
|
773
782
|
}
|
|
@@ -922,6 +931,7 @@ class VariablePlugin extends Plugin {
|
|
|
922
931
|
}
|
|
923
932
|
}
|
|
924
933
|
else {
|
|
934
|
+
// @i18n-ignore — dev console warning
|
|
925
935
|
console.warn(`Variable với uuid "${uuid}" không tìm thấy trong tài liệu.`);
|
|
926
936
|
}
|
|
927
937
|
}
|
|
@@ -3489,6 +3499,10 @@ class CkCommentPlugin extends Plugin {
|
|
|
3489
3499
|
return [ContextualBalloon];
|
|
3490
3500
|
}
|
|
3491
3501
|
#comments = new Map();
|
|
3502
|
+
// i18n đọc từ editor.config — plugin không có DI nên dùng pattern này; fallback giữ VI để consumer chưa setup vẫn chạy
|
|
3503
|
+
#getI18n() {
|
|
3504
|
+
return this.editor.config.get('_i18n');
|
|
3505
|
+
}
|
|
3492
3506
|
#selectedId = null;
|
|
3493
3507
|
#pendingId = null; // ID cho pending highlight
|
|
3494
3508
|
#isCreatingPending = false; // Flag để prevent clearing pending khi đang tạo
|
|
@@ -3552,8 +3566,9 @@ class CkCommentPlugin extends Plugin {
|
|
|
3552
3566
|
const editor = this.editor;
|
|
3553
3567
|
editor.ui.componentFactory.add('ckCommentBtn', locale => {
|
|
3554
3568
|
const view = new ButtonView(locale);
|
|
3569
|
+
const i18n = this.#getI18n();
|
|
3555
3570
|
view.set({
|
|
3556
|
-
label: '
|
|
3571
|
+
label: i18n?.t('core.component.document-builder.ck-comment.label') ?? '',
|
|
3557
3572
|
icon: '<svg width="16px" height="16px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M18 13v6l-4-4H4a2 2 0 01-2-2V4a2 2 0 012-2h14a2 2 0 012 2v9zM5 7h10v2H5V7zm0 4h10v2H5v-2z"/></svg>',
|
|
3558
3573
|
tooltip: true,
|
|
3559
3574
|
isEnabled: false,
|
|
@@ -3577,6 +3592,7 @@ class CkCommentPlugin extends Plugin {
|
|
|
3577
3592
|
// Kiểm tra: có content, không phải chỉ khoảng trắng, và không vượt quá max length
|
|
3578
3593
|
hasValidContent = trimmedText.length > 0 && trimmedText.length <= maxTextLength;
|
|
3579
3594
|
if (trimmedText.length > maxTextLength) {
|
|
3595
|
+
// @i18n-ignore — dev-only debug log, không hiển thị cho người dùng
|
|
3580
3596
|
this.#log(`Độ dài text vượt quá giới hạn: ${trimmedText.length} > ${maxTextLength}`);
|
|
3581
3597
|
}
|
|
3582
3598
|
}
|
|
@@ -3818,8 +3834,9 @@ class CkCommentPlugin extends Plugin {
|
|
|
3818
3834
|
this.#hideBalloon();
|
|
3819
3835
|
// Tạo balloon button
|
|
3820
3836
|
const buttonView = new ButtonView(editor.locale);
|
|
3837
|
+
const balloonI18n = this.#getI18n();
|
|
3821
3838
|
buttonView.set({
|
|
3822
|
-
label: '
|
|
3839
|
+
label: balloonI18n?.t('core.component.document-builder.ck-comment.label') ?? '',
|
|
3823
3840
|
icon: '<svg width="16px" height="16px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M18 13v6l-4-4H4a2 2 0 01-2-2V4a2 2 0 012-2h14a2 2 0 012 2v9zM5 7h10v2H5V7zm0 4h10v2H5v-2z"/></svg>',
|
|
3824
3841
|
tooltip: true,
|
|
3825
3842
|
withText: true,
|
|
@@ -4126,9 +4143,10 @@ class CkCommentPlugin extends Plugin {
|
|
|
4126
4143
|
if (trimmedText.length > maxTextLength) {
|
|
4127
4144
|
this.#warn(`Text too long: ${trimmedText.length} > ${maxTextLength}`);
|
|
4128
4145
|
// Fire error callback
|
|
4146
|
+
const errorI18n = this.#getI18n();
|
|
4129
4147
|
this.#config.onError?.({
|
|
4130
4148
|
code: 'TEXT_TOO_LONG',
|
|
4131
|
-
message:
|
|
4149
|
+
message: errorI18n?.t('core.component.document-builder.ck-comment.text-too-long', { length: trimmedText.length, max: maxTextLength }) ?? '',
|
|
4132
4150
|
data: { textLength: trimmedText.length, maxLength: maxTextLength },
|
|
4133
4151
|
});
|
|
4134
4152
|
return null;
|
|
@@ -4395,6 +4413,7 @@ function normalize(content) {
|
|
|
4395
4413
|
}
|
|
4396
4414
|
|
|
4397
4415
|
class SdDocumentBuilder {
|
|
4416
|
+
#i18n = inject(SdI18nService);
|
|
4398
4417
|
option;
|
|
4399
4418
|
disabled = false;
|
|
4400
4419
|
set _disabled(val) {
|
|
@@ -4416,6 +4435,8 @@ class SdDocumentBuilder {
|
|
|
4416
4435
|
// Config
|
|
4417
4436
|
config = {
|
|
4418
4437
|
getOption: () => this.option,
|
|
4438
|
+
// Truyền i18n service xuống CKEditor plugin (ngoài DI tree) để dịch label/error
|
|
4439
|
+
_i18n: this.#i18n,
|
|
4419
4440
|
licenseKey: 'GPL', // Hoặc key thương mại nếu có
|
|
4420
4441
|
plugins: [
|
|
4421
4442
|
FontSize,
|