@libs-ui/components-preview-text-data 0.2.356-9 → 0.2.357-1
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/README.md +387 -75
- package/esm2022/preview-text-data.component.mjs +295 -21
- package/esm2022/preview-text-data.define.mjs +22 -2
- package/esm2022/preview-text-data.interfaces.mjs +1 -1
- package/fesm2022/libs-ui-components-preview-text-data.mjs +317 -23
- package/fesm2022/libs-ui-components-preview-text-data.mjs.map +1 -1
- package/package.json +27 -5
- package/preview-text-data.component.d.ts +46 -5
- package/preview-text-data.define.d.ts +6 -1
- package/preview-text-data.interfaces.d.ts +29 -0
package/README.md
CHANGED
|
@@ -1,37 +1,36 @@
|
|
|
1
1
|
# @libs-ui/components-preview-text-data
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Code editor/viewer mạnh mẽ dựa trên CodeMirror 6, hỗ trợ highlight cú pháp đa ngôn ngữ, linter thời gian thực, IntelliSense và bảo mật pattern.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiComponentsPreviewTextDataComponent` cung cấp một trình soạn thảo mã nguồn
|
|
7
|
+
`LibsUiComponentsPreviewTextDataComponent` cung cấp một trình soạn thảo/xem mã nguồn tích hợp sẵn vào Angular với kiến trúc Signals và OnPush. Component hỗ trợ highlight cú pháp cho 14 ngôn ngữ lập trình, kiểm tra lỗi cú pháp thời gian thực (linter) cho JavaScript, JSON, SQL và Python, gợi ý code thông minh (IntelliSense với dot-notation), cùng cơ chế bảo mật chặn pattern nguy hiểm cả khi gõ lẫn paste.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
11
|
+
- Highlight cú pháp cho 14 ngôn ngữ: JavaScript/TypeScript, HTML, CSS/SCSS, JSON, SQL, XML, YAML, Python, Java, C/C++, PHP, Go, Markdown, Plain Text
|
|
12
|
+
- Linter tích hợp phát hiện lỗi cú pháp thời gian thực cho JavaScript, JSON, SQL, Python
|
|
13
|
+
- IntelliSense với dot-notation: định nghĩa object/biến sẵn, gợi ý properties/methods khi gõ dấu chấm (giống IDE)
|
|
14
|
+
- Security policy: chặn pattern nguy hiểm (string hoặc RegExp) ngay khi gõ, chặn paste chứa vi phạm, emit danh sách vi phạm qua `(outViolations)`
|
|
15
|
+
- Lazy load ngôn ngữ: chỉ tải extension khi cần, tối ưu bundle size
|
|
16
|
+
- Non-destructive update: đổi ngôn ngữ, wrap, completions không làm mất nội dung hay vị trí con trỏ
|
|
17
|
+
- Chế độ editable/readonly linh hoạt
|
|
18
|
+
- Thanh công cụ tích hợp: copy nội dung, toggle line wrap, dropdown chọn ngôn ngữ
|
|
19
|
+
- Two-way binding cho `langSelected` qua `model()`
|
|
20
|
+
- Standalone component, ChangeDetectionStrategy.OnPush, Angular Signals
|
|
19
21
|
|
|
20
22
|
## Khi nào sử dụng
|
|
21
23
|
|
|
22
|
-
- Hiển thị
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- Kiểm tra
|
|
24
|
+
- Hiển thị cấu hình JSON, YAML hoặc đoạn mã mẫu trong tài liệu hoặc dashboard
|
|
25
|
+
- Cho phép người dùng biên tập câu lệnh SQL hoặc mã JavaScript trong các công cụ quản trị
|
|
26
|
+
- Xây dựng low-code/no-code editor cần kiểm soát bảo mật (chặn eval, fetch, localStorage...)
|
|
27
|
+
- Kiểm tra và xem log hoặc raw data từ API với highlight cú pháp rõ ràng
|
|
28
|
+
- Tích hợp IntelliSense cho đối tượng domain cụ thể (biến, API object, schema) trong form nhập mã
|
|
26
29
|
|
|
27
30
|
## Cài đặt
|
|
28
31
|
|
|
29
32
|
```bash
|
|
30
|
-
# npm
|
|
31
33
|
npm install @libs-ui/components-preview-text-data
|
|
32
|
-
|
|
33
|
-
# yarn
|
|
34
|
-
yarn add @libs-ui/components-preview-text-data
|
|
35
34
|
```
|
|
36
35
|
|
|
37
36
|
## Import
|
|
@@ -44,89 +43,403 @@ import { LibsUiComponentsPreviewTextDataComponent } from '@libs-ui/components-pr
|
|
|
44
43
|
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
45
44
|
// ...
|
|
46
45
|
})
|
|
47
|
-
export class
|
|
46
|
+
export class MyComponent {}
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
Import thêm types, constants khi cần:
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
```typescript
|
|
52
|
+
import {
|
|
53
|
+
LibsUiComponentsPreviewTextDataComponent,
|
|
54
|
+
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
55
|
+
IPreviewTextDataChange,
|
|
56
|
+
IPreviewTextDataCompletionItem,
|
|
57
|
+
IPreviewTextDataViolation,
|
|
58
|
+
ILanguageOptions,
|
|
59
|
+
DEFAULT_SECURITY_PATTERNS,
|
|
60
|
+
} from '@libs-ui/components-preview-text-data';
|
|
61
|
+
```
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
## Ví dụ sử dụng
|
|
64
|
+
|
|
65
|
+
### 1. Chế độ xem (Read-only) — ẩn thanh công cụ
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// my-viewer.component.ts
|
|
69
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
70
|
+
import { LibsUiComponentsPreviewTextDataComponent } from '@libs-ui/components-preview-text-data';
|
|
71
|
+
|
|
72
|
+
@Component({
|
|
73
|
+
selector: 'app-my-viewer',
|
|
74
|
+
standalone: true,
|
|
75
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
76
|
+
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
77
|
+
template: `
|
|
78
|
+
<libs_ui-components-preview_text_data
|
|
79
|
+
[content]="configCode"
|
|
80
|
+
[langSelected]="'json'"
|
|
81
|
+
[hiddenAction]="true"
|
|
82
|
+
background="#fcfcfc" />
|
|
83
|
+
`,
|
|
84
|
+
})
|
|
85
|
+
export class MyViewerComponent {
|
|
86
|
+
readonly configCode = `{
|
|
87
|
+
"projectName": "Libs UI",
|
|
88
|
+
"version": "2.0.0",
|
|
89
|
+
"features": ["Signals", "CodeMirror", "OnPush"]
|
|
90
|
+
}`;
|
|
91
|
+
}
|
|
58
92
|
```
|
|
59
93
|
|
|
60
|
-
### Chế độ chỉnh sửa với Linter
|
|
94
|
+
### 2. Chế độ chỉnh sửa với Linter và đổi ngôn ngữ
|
|
61
95
|
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
```typescript
|
|
97
|
+
// sql-editor.component.ts
|
|
98
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
99
|
+
import {
|
|
100
|
+
LibsUiComponentsPreviewTextDataComponent,
|
|
101
|
+
IPreviewTextDataChange,
|
|
102
|
+
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
103
|
+
} from '@libs-ui/components-preview-text-data';
|
|
104
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
105
|
+
|
|
106
|
+
@Component({
|
|
107
|
+
selector: 'app-sql-editor',
|
|
108
|
+
standalone: true,
|
|
109
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
110
|
+
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
111
|
+
template: `
|
|
112
|
+
<libs_ui-components-preview_text_data
|
|
113
|
+
[content]="sqlQuery"
|
|
114
|
+
[(langSelected)]="currentLang"
|
|
115
|
+
[editable]="true"
|
|
116
|
+
[langsAccept]="['sql', 'javascript', 'json']"
|
|
117
|
+
(outChange)="handlerChange($event)"
|
|
118
|
+
(syntaxErrors)="handlerSyntaxErrors($event)" />
|
|
119
|
+
|
|
120
|
+
@if (lintErrors().length) {
|
|
121
|
+
<div class="p-3 bg-red-50 text-red-600 text-xs font-mono mt-2 rounded">
|
|
122
|
+
Lỗi cú pháp: {{ lintErrors()[0].message }}
|
|
123
|
+
</div>
|
|
124
|
+
}
|
|
125
|
+
`,
|
|
126
|
+
})
|
|
127
|
+
export class SqlEditorComponent {
|
|
128
|
+
readonly currentLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('sql');
|
|
129
|
+
readonly lintErrors = signal<Diagnostic[]>([]);
|
|
130
|
+
readonly sqlQuery = `SELECT id, name, email
|
|
131
|
+
FROM users
|
|
132
|
+
WHERE status = 'active'
|
|
133
|
+
ORDER BY created_at DESC
|
|
134
|
+
LIMIT 50;`;
|
|
135
|
+
|
|
136
|
+
handlerChange(event: IPreviewTextDataChange): void {
|
|
137
|
+
event.stopPropagation?.();
|
|
138
|
+
console.log('Nội dung thay đổi:', event.content, '— ngôn ngữ:', event.language);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
handlerSyntaxErrors(errors: Diagnostic[]): void {
|
|
142
|
+
this.lintErrors.set(errors);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
69
145
|
```
|
|
70
146
|
|
|
71
|
-
|
|
147
|
+
### 3. IntelliSense với dot-notation (giống IDE)
|
|
72
148
|
|
|
73
|
-
|
|
149
|
+
```typescript
|
|
150
|
+
// js-intellisense.component.ts
|
|
151
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
152
|
+
import {
|
|
153
|
+
LibsUiComponentsPreviewTextDataComponent,
|
|
154
|
+
IPreviewTextDataCompletionItem,
|
|
155
|
+
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
156
|
+
} from '@libs-ui/components-preview-text-data';
|
|
74
157
|
|
|
75
|
-
|
|
158
|
+
@Component({
|
|
159
|
+
selector: 'app-js-intellisense',
|
|
160
|
+
standalone: true,
|
|
161
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
162
|
+
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
163
|
+
template: `
|
|
164
|
+
<libs_ui-components-preview_text_data
|
|
165
|
+
[content]="starterCode"
|
|
166
|
+
[(langSelected)]="lang"
|
|
167
|
+
[editable]="true"
|
|
168
|
+
[completions]="completions"
|
|
169
|
+
background="#fafbfc" />
|
|
170
|
+
`,
|
|
171
|
+
})
|
|
172
|
+
export class JsIntelliSenseComponent {
|
|
173
|
+
readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
174
|
+
|
|
175
|
+
readonly starterCode = `// Gõ "user." hoặc "config." để xem gợi ý
|
|
176
|
+
function process() {
|
|
177
|
+
const name = user.
|
|
178
|
+
const url = config.
|
|
179
|
+
}`;
|
|
180
|
+
|
|
181
|
+
readonly completions: IPreviewTextDataCompletionItem[] = [
|
|
182
|
+
{
|
|
183
|
+
label: 'user',
|
|
184
|
+
type: 'variable',
|
|
185
|
+
detail: 'object',
|
|
186
|
+
info: 'User object — thông tin người dùng hiện tại',
|
|
187
|
+
properties: [
|
|
188
|
+
{ label: 'id', type: 'property', detail: 'number', info: 'Unique user ID' },
|
|
189
|
+
{ label: 'name', type: 'property', detail: 'string', info: 'Họ và tên đầy đủ' },
|
|
190
|
+
{ label: 'email', type: 'property', detail: 'string', info: 'Địa chỉ email' },
|
|
191
|
+
{ label: 'role', type: 'property', detail: "'admin' | 'user'", info: 'Vai trò trong hệ thống' },
|
|
192
|
+
{ label: 'logout', type: 'method', detail: '() => Promise<void>', info: 'Đăng xuất tài khoản' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
label: 'config',
|
|
197
|
+
type: 'variable',
|
|
198
|
+
detail: 'object',
|
|
199
|
+
info: 'Application configuration',
|
|
200
|
+
properties: [
|
|
201
|
+
{ label: 'apiUrl', type: 'property', detail: 'string', info: 'Base URL của API server' },
|
|
202
|
+
{ label: 'timeout', type: 'property', detail: 'number', info: 'Request timeout (ms)' },
|
|
203
|
+
{ label: 'debug', type: 'property', detail: 'boolean', info: 'Bật debug logging' },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 4. Security policy — chặn pattern nguy hiểm
|
|
76
211
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
| `[lintIgnorePatterns]` | `string[]` | `[...]` | Các mẫu thông báo lỗi linter JS sẽ bỏ qua. |
|
|
212
|
+
```typescript
|
|
213
|
+
// secure-editor.component.ts
|
|
214
|
+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
215
|
+
import {
|
|
216
|
+
LibsUiComponentsPreviewTextDataComponent,
|
|
217
|
+
DEFAULT_SECURITY_PATTERNS,
|
|
218
|
+
IPreviewTextDataViolation,
|
|
219
|
+
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
220
|
+
} from '@libs-ui/components-preview-text-data';
|
|
87
221
|
|
|
88
|
-
|
|
222
|
+
@Component({
|
|
223
|
+
selector: 'app-secure-editor',
|
|
224
|
+
standalone: true,
|
|
225
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
226
|
+
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
227
|
+
template: `
|
|
228
|
+
<libs_ui-components-preview_text_data
|
|
229
|
+
[content]="starterCode"
|
|
230
|
+
[(langSelected)]="lang"
|
|
231
|
+
[editable]="true"
|
|
232
|
+
[forbiddenPatterns]="securityPatterns"
|
|
233
|
+
(outViolations)="handlerViolations($event)"
|
|
234
|
+
background="#fafbfc" />
|
|
235
|
+
|
|
236
|
+
@if (violations().length) {
|
|
237
|
+
<div class="p-3 bg-red-50 border border-red-200 rounded mt-2">
|
|
238
|
+
<p class="text-sm font-semibold text-red-700 mb-1">
|
|
239
|
+
Phát hiện {{ violations().length }} vi phạm bảo mật:
|
|
240
|
+
</p>
|
|
241
|
+
@for (v of violations(); track v.from) {
|
|
242
|
+
<p class="text-xs text-red-600 font-mono">
|
|
243
|
+
Dòng {{ v.line }}: "{{ v.matched }}" — vi phạm pattern {{ v.pattern }}
|
|
244
|
+
</p>
|
|
245
|
+
}
|
|
246
|
+
</div>
|
|
247
|
+
}
|
|
248
|
+
`,
|
|
249
|
+
})
|
|
250
|
+
export class SecureEditorComponent {
|
|
251
|
+
readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
252
|
+
readonly violations = signal<IPreviewTextDataViolation[]>([]);
|
|
253
|
+
readonly securityPatterns = DEFAULT_SECURITY_PATTERNS;
|
|
254
|
+
|
|
255
|
+
readonly starterCode = `// Code thuần — không gọi API hay truy cập storage
|
|
256
|
+
function calculateTotal(items) {
|
|
257
|
+
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
258
|
+
}
|
|
259
|
+
// Thử gõ: eval("test") hoặc localStorage.getItem('key')
|
|
260
|
+
// → sẽ bị highlight đỏ và chặn paste`;
|
|
261
|
+
|
|
262
|
+
handlerViolations(violations: IPreviewTextDataViolation[]): void {
|
|
263
|
+
this.violations.set(violations);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 5. Chỉ cho phép một ngôn ngữ cố định (không hiển thị dropdown)
|
|
269
|
+
|
|
270
|
+
```html
|
|
271
|
+
<!-- Khi langsAccept có đúng 1 phần tử trùng với langSelected → dropdown bị ẩn -->
|
|
272
|
+
<libs_ui-components-preview_text_data
|
|
273
|
+
[content]="jsonData"
|
|
274
|
+
[(langSelected)]="lang"
|
|
275
|
+
[editable]="true"
|
|
276
|
+
[langsAccept]="['json']" />
|
|
277
|
+
```
|
|
89
278
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
279
|
+
## @Input()
|
|
280
|
+
|
|
281
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
282
|
+
|---|---|---|---|---|
|
|
283
|
+
| `[content]` | `string` | `''` | Nội dung code cần hiển thị hoặc chỉnh sửa | `[content]="myCode"` |
|
|
284
|
+
| `[(langSelected)]` | `PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT` | **Bắt buộc** | Ngôn ngữ highlight hiện tại. Hỗ trợ two-way binding qua `model()` | `[(langSelected)]="currentLang"` |
|
|
285
|
+
| `[editable]` | `boolean` | `false` | Cho phép người dùng chỉnh sửa nội dung trong editor | `[editable]="true"` |
|
|
286
|
+
| `[hiddenAction]` | `boolean` | `false` | Ẩn toàn bộ thanh công cụ (Copy, Wrap, dropdown ngôn ngữ) | `[hiddenAction]="true"` |
|
|
287
|
+
| `[langsAccept]` | `PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT[]` | `undefined` | Danh sách ngôn ngữ được phép chọn trong dropdown. Nếu chỉ có 1 phần tử trùng `langSelected` → dropdown bị ẩn | `[langsAccept]="['json', 'sql']"` |
|
|
288
|
+
| `[langsChangeLabel]` | `ILanguageOptions` | `undefined` | Tùy chỉnh nhãn hiển thị của một ngôn ngữ trong dropdown | `[langsChangeLabel]="{ id: 'json', label: 'Cấu hình JSON' }"` |
|
|
289
|
+
| `[completions]` | `IPreviewTextDataCompletionItem[]` | `[]` | Danh sách object/biến gợi ý khi gõ — hỗ trợ dot-notation cho properties lồng nhau (IntelliSense) | `[completions]="myCompletions"` |
|
|
290
|
+
| `[forbiddenPatterns]` | `T_ForbiddenPattern[]` | `[]` | Danh sách pattern bị cấm (string hoặc RegExp). Highlight đỏ khi gõ, chặn paste chứa vi phạm, phát ra `(outViolations)` | `[forbiddenPatterns]="DEFAULT_SECURITY_PATTERNS"` |
|
|
291
|
+
| `[lintIgnorePatterns]` | `string[]` | `['Cannot use import statement...', 'Unexpected token export', 'import ', '@angular/core']` | Danh sách chuỗi lỗi linter JS sẽ bị bỏ qua (ignore) trong quá trình kiểm tra | `[lintIgnorePatterns]="['import ']"` |
|
|
292
|
+
| `[background]` | `string` | `'#f8f9fa'` | Màu nền của vùng soạn thảo (CSS color value) | `background="#ffffff"` |
|
|
293
|
+
|
|
294
|
+
## @Output()
|
|
295
|
+
|
|
296
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
297
|
+
|---|---|---|---|---|
|
|
298
|
+
| `(outChange)` | `IPreviewTextDataChange` | Phát ra khi nội dung thay đổi, người dùng đổi ngôn ngữ hoặc toggle line wrap | `handlerChange(event: IPreviewTextDataChange): void { event.stopPropagation?.(); this.code.set(event.content); }` | `(outChange)="handlerChange($event)"` |
|
|
299
|
+
| `(syntaxErrors)` | `Diagnostic[]` | Phát ra mảng lỗi cú pháp mỗi khi linter chạy. Mảng rỗng khi không có lỗi | `handlerSyntaxErrors(errors: Diagnostic[]): void { this.errors.set(errors); }` | `(syntaxErrors)="handlerSyntaxErrors($event)"` |
|
|
300
|
+
| `(outViolations)` | `IPreviewTextDataViolation[]` | Phát ra danh sách vi phạm (vị trí, dòng, pattern, chuỗi bị match) mỗi khi nội dung thay đổi khi có `forbiddenPatterns`. Mảng rỗng khi không có vi phạm | `handlerViolations(violations: IPreviewTextDataViolation[]): void { this.violations.set(violations); }` | `(outViolations)="handlerViolations($event)"` |
|
|
94
301
|
|
|
95
302
|
## Types & Interfaces
|
|
96
303
|
|
|
97
304
|
```typescript
|
|
98
|
-
|
|
305
|
+
import {
|
|
306
|
+
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
307
|
+
IPreviewTextDataChange,
|
|
308
|
+
IPreviewTextDataCompletionItem,
|
|
309
|
+
IPreviewTextDataViolation,
|
|
310
|
+
ILanguageOptions,
|
|
311
|
+
T_ForbiddenPattern,
|
|
312
|
+
DEFAULT_SECURITY_PATTERNS,
|
|
313
|
+
} from '@libs-ui/components-preview-text-data';
|
|
314
|
+
```
|
|
99
315
|
|
|
316
|
+
```typescript
|
|
317
|
+
/** Tất cả ngôn ngữ được hỗ trợ */
|
|
318
|
+
export type PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT =
|
|
319
|
+
| 'javascript'
|
|
320
|
+
| 'html'
|
|
321
|
+
| 'css'
|
|
322
|
+
| 'markdown'
|
|
323
|
+
| 'json'
|
|
324
|
+
| 'sql'
|
|
325
|
+
| 'xml'
|
|
326
|
+
| 'yaml'
|
|
327
|
+
| 'python'
|
|
328
|
+
| 'java'
|
|
329
|
+
| 'cpp'
|
|
330
|
+
| 'php'
|
|
331
|
+
| 'go'
|
|
332
|
+
| 'text';
|
|
333
|
+
|
|
334
|
+
/** Payload phát ra qua (outChange) */
|
|
100
335
|
export interface IPreviewTextDataChange {
|
|
101
336
|
content: string;
|
|
102
337
|
isWrap: boolean;
|
|
103
338
|
language: PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT;
|
|
104
|
-
|
|
339
|
+
/** Loại thay đổi: nội dung / ngôn ngữ / line wrap */
|
|
340
|
+
contextChange: 'content' | 'language' | 'isWrap';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Một item trong danh sách completions — hỗ trợ dot-notation qua properties[] */
|
|
344
|
+
export interface IPreviewTextDataCompletionItem {
|
|
345
|
+
label: string;
|
|
346
|
+
/** @default 'variable' */
|
|
347
|
+
type?: 'variable' | 'property' | 'function' | 'class' | 'method' | 'keyword' | 'text' | 'constant' | 'interface' | 'type' | 'enum' | 'field' | 'namespace' | 'module';
|
|
348
|
+
/** Gợi ý type ngắn hiển thị cạnh label, VD: 'string', '() => void' */
|
|
349
|
+
detail?: string;
|
|
350
|
+
/** Mô tả dài hiển thị trong tooltip */
|
|
351
|
+
info?: string;
|
|
352
|
+
/** Properties lồng nhau — được gợi ý sau dấu chấm (dot-notation) */
|
|
353
|
+
properties?: IPreviewTextDataCompletionItem[];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Vi phạm được phát hiện trong editor, phát ra qua (outViolations) */
|
|
357
|
+
export interface IPreviewTextDataViolation {
|
|
358
|
+
/** Vị trí bắt đầu (character offset) */
|
|
359
|
+
from: number;
|
|
360
|
+
/** Vị trí kết thúc (character offset) */
|
|
361
|
+
to: number;
|
|
362
|
+
/** Số dòng (1-indexed) */
|
|
363
|
+
line: number;
|
|
364
|
+
/** Tên/mô tả của pattern đã bị vi phạm */
|
|
365
|
+
pattern: string;
|
|
366
|
+
/** Đoạn text thực tế bị match */
|
|
367
|
+
matched: string;
|
|
105
368
|
}
|
|
106
369
|
|
|
370
|
+
/** Tùy chỉnh label hiển thị của một ngôn ngữ trong dropdown */
|
|
107
371
|
export interface ILanguageOptions {
|
|
108
|
-
|
|
372
|
+
id: PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT;
|
|
373
|
+
label: string;
|
|
109
374
|
}
|
|
375
|
+
|
|
376
|
+
/** Pattern cấm: string literal (case-insensitive) hoặc RegExp tùy chỉnh */
|
|
377
|
+
export type T_ForbiddenPattern = string | RegExp;
|
|
110
378
|
```
|
|
111
379
|
|
|
112
|
-
##
|
|
380
|
+
## Constants
|
|
381
|
+
|
|
382
|
+
### DEFAULT_SECURITY_PATTERNS
|
|
113
383
|
|
|
114
|
-
|
|
384
|
+
Bộ pattern bảo mật mặc định cho low-code platform — chặn các API nguy hiểm:
|
|
115
385
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
| `.cm-editor` | Class gốc của editor CodeMirror. |
|
|
119
|
-
| `.cm-scroller` | Vùng cuộn của editor. |
|
|
120
|
-
| `.cm-content` | Vùng chứa nội dung chữ. |
|
|
386
|
+
```typescript
|
|
387
|
+
import { DEFAULT_SECURITY_PATTERNS } from '@libs-ui/components-preview-text-data';
|
|
121
388
|
|
|
122
|
-
|
|
389
|
+
// Sử dụng trực tiếp
|
|
390
|
+
[forbiddenPatterns]="DEFAULT_SECURITY_PATTERNS"
|
|
391
|
+
```
|
|
123
392
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
393
|
+
Các pattern được chặn:
|
|
394
|
+
|
|
395
|
+
| Pattern | Lý do |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `/eval\s*\(/i` | Thực thi code động |
|
|
398
|
+
| `/new\s+Function\s*\(/i` | Tạo hàm từ string |
|
|
399
|
+
| `/document\.cookie/i` | Đọc cookie nhạy cảm |
|
|
400
|
+
| `/localStorage/i` | Truy cập local storage |
|
|
401
|
+
| `/sessionStorage/i` | Truy cập session storage |
|
|
402
|
+
| `/fetch\s*\(/i` | Gọi HTTP request |
|
|
403
|
+
| `/new\s+XMLHttpRequest/i` | Gọi HTTP request cũ |
|
|
404
|
+
| `/require\s*\(/i` | Load module Node.js |
|
|
405
|
+
| `/import\s*\(/i` | Dynamic import |
|
|
406
|
+
| `/process\.env/i` | Đọc biến môi trường |
|
|
407
|
+
| `/<script/i` | Chèn thẻ script |
|
|
408
|
+
| `/document\.write\s*\(/i` | Ghi trực tiếp vào DOM |
|
|
409
|
+
| `/window\.location\s*=/i` | Chuyển hướng trang |
|
|
410
|
+
|
|
411
|
+
## Ngôn ngữ được hỗ trợ
|
|
412
|
+
|
|
413
|
+
| ID | Nhãn hiển thị | Linter |
|
|
414
|
+
|---|---|---|
|
|
415
|
+
| `text` | Plain Text | Không |
|
|
416
|
+
| `javascript` | JavaScript/TypeScript | Có (JS syntax + trailing dot detection) |
|
|
417
|
+
| `html` | HTML | Không |
|
|
418
|
+
| `css` | CSS/SCSS/SASS | Không |
|
|
419
|
+
| `markdown` | Markdown | Không |
|
|
420
|
+
| `json` | JSON | Có (JSON.parse validation) |
|
|
421
|
+
| `sql` | SQL | Có (syntaxTree parse errors) |
|
|
422
|
+
| `xml` | XML | Không |
|
|
423
|
+
| `yaml` | YAML | Không |
|
|
424
|
+
| `python` | Python | Có (syntaxTree parse errors) |
|
|
425
|
+
| `java` | Java | Không |
|
|
426
|
+
| `cpp` | C/C++ | Không |
|
|
427
|
+
| `php` | PHP | Không |
|
|
428
|
+
| `go` | Go | Không |
|
|
429
|
+
|
|
430
|
+
## Lưu ý quan trọng
|
|
431
|
+
|
|
432
|
+
⚠️ **langSelected là bắt buộc**: Input `[(langSelected)]` là `model.required()` — PHẢI truyền giá trị, không có default. Thiếu giá trị sẽ gây lỗi Angular runtime.
|
|
433
|
+
|
|
434
|
+
⚠️ **Lazy load ngôn ngữ**: Component lazy-load extension của từng ngôn ngữ khi cần. Lần đầu tiên chọn một ngôn ngữ mới có thể có độ trễ nhỏ (vài trăm ms) để tải thư viện từ bundle.
|
|
435
|
+
|
|
436
|
+
⚠️ **Linter chỉ hoạt động khi editable**: Linter phát hiện lỗi cú pháp ngay cả khi `[editable]="false"`, nhưng người dùng chỉ nhập được khi `[editable]="true"`. Output `(syntaxErrors)` và `(outViolations)` luôn phát ra dù ở chế độ nào.
|
|
437
|
+
|
|
438
|
+
⚠️ **forbiddenPatterns chặn paste hoàn toàn**: Khi `[forbiddenPatterns]` được cấu hình, mọi thao tác paste chứa pattern bị cấm sẽ bị hủy toàn bộ (không paste được phần nào). Người dùng cần paste từng phần hoặc nhập thủ công.
|
|
439
|
+
|
|
440
|
+
⚠️ **Dropdown ngôn ngữ bị ẩn tự động**: Khi `[langsAccept]` chứa đúng 1 ngôn ngữ trùng với `langSelected` hiện tại, dropdown chọn ngôn ngữ sẽ bị ẩn tự động (computed `acceptChangeLang = false`).
|
|
441
|
+
|
|
442
|
+
⚠️ **Cleanup tự động**: Component tự hủy EditorView instance và xóa cache ngôn ngữ khi bị destroy (qua `DestroyRef`). Không cần thao tác cleanup thủ công từ phía consumer.
|
|
130
443
|
|
|
131
444
|
## Demo
|
|
132
445
|
|
|
@@ -134,18 +447,17 @@ export interface ILanguageOptions {
|
|
|
134
447
|
npx nx serve core-ui
|
|
135
448
|
```
|
|
136
449
|
|
|
137
|
-
Truy cập:
|
|
450
|
+
Truy cập: http://localhost:4500/preview-text-data
|
|
138
451
|
|
|
139
452
|
## Unit Tests
|
|
140
453
|
|
|
141
454
|
```bash
|
|
142
|
-
# Chạy
|
|
455
|
+
# Chạy test cho lib
|
|
143
456
|
npx nx test components-preview-text-data
|
|
144
457
|
|
|
458
|
+
# Chạy test một file cụ thể
|
|
459
|
+
npx nx test components-preview-text-data --testFile=libs-ui/components/preview-text-data/src/preview-text-data.component.spec.ts
|
|
460
|
+
|
|
145
461
|
# Coverage
|
|
146
462
|
npx nx test components-preview-text-data --coverage
|
|
147
463
|
```
|
|
148
|
-
|
|
149
|
-
## License
|
|
150
|
-
|
|
151
|
-
MIT
|