@libs-ui/components-preview-text-data 0.2.357-3 → 0.2.357-4
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 +48 -0
- package/editor/editor-completions.d.ts +8 -0
- package/editor/editor-forbidden.d.ts +8 -0
- package/editor/editor-height.d.ts +21 -0
- package/esm2022/editor/editor-completions.mjs +31 -0
- package/esm2022/editor/editor-forbidden.mjs +65 -0
- package/esm2022/editor/editor-height.mjs +87 -0
- package/esm2022/linters/js-linter.mjs +348 -0
- package/esm2022/linters/json-linter.mjs +14 -0
- package/esm2022/linters/linter-shared.mjs +8 -0
- package/esm2022/linters/python-linter.mjs +52 -0
- package/esm2022/linters/sql-linter.mjs +34 -0
- package/esm2022/preview-text-data.component.mjs +39 -319
- package/esm2022/preview-text-data.define.mjs +13 -1
- package/esm2022/preview-text-data.type.mjs +2 -0
- package/fesm2022/libs-ui-components-preview-text-data.mjs +675 -320
- package/fesm2022/libs-ui-components-preview-text-data.mjs.map +1 -1
- package/linters/js-linter.d.ts +45 -0
- package/linters/json-linter.d.ts +4 -0
- package/linters/linter-shared.d.ts +3 -0
- package/linters/python-linter.d.ts +4 -0
- package/linters/sql-linter.d.ts +4 -0
- package/package.json +9 -7
- package/preview-text-data.component.d.ts +18 -35
- package/preview-text-data.define.d.ts +12 -0
- package/preview-text-data.type.d.ts +33 -0
package/README.md
CHANGED
|
@@ -276,6 +276,50 @@ function calculateTotal(items) {
|
|
|
276
276
|
[langsAccept]="['json']" />
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
+
### 6. Màn nhập code (min/max số dòng + giới hạn ngôn ngữ)
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// component.ts
|
|
283
|
+
import { Component, signal } from '@angular/core';
|
|
284
|
+
import { LibsUiComponentsPreviewTextDataComponent, PREVIEW_TEXT_DATA_LANGUAGE_SUPPORT } from '@libs-ui/components-preview-text-data';
|
|
285
|
+
|
|
286
|
+
@Component({
|
|
287
|
+
selector: 'app-code-input',
|
|
288
|
+
standalone: true,
|
|
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
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 7. Editor tự fit theo container (min/max-height)
|
|
308
|
+
|
|
309
|
+
Thay vì truyền `minLines`/`maxLines`, bọc editor trong 1 div có `min-height`/`max-height` rồi truyền chính 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
|
+
|
|
311
|
+
```html
|
|
312
|
+
<!-- #boxRef là template reference tới div container -->
|
|
313
|
+
<div #boxRef class="min-h-[120px] max-h-[300px]">
|
|
314
|
+
<libs_ui-components-preview_text_data
|
|
315
|
+
[content]="''"
|
|
316
|
+
[(langSelected)]="lang"
|
|
317
|
+
[editable]="true"
|
|
318
|
+
[langsAccept]="['javascript', 'python']"
|
|
319
|
+
[containerElement]="boxRef" />
|
|
320
|
+
</div>
|
|
321
|
+
```
|
|
322
|
+
|
|
279
323
|
## @Input()
|
|
280
324
|
|
|
281
325
|
| Input | Type | Default | Mô tả | Ví dụ |
|
|
@@ -290,6 +334,10 @@ function calculateTotal(items) {
|
|
|
290
334
|
| `[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
335
|
| `[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
336
|
| `[background]` | `string` | `'#f8f9fa'` | Màu nền của vùng soạn thảo (CSS color value) | `background="#ffffff"` |
|
|
337
|
+
| `[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"` |
|
|
338
|
+
| `[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"` |
|
|
339
|
+
| `[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"` |
|
|
340
|
+
| `[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"` |
|
|
293
341
|
|
|
294
342
|
## @Output()
|
|
295
343
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { IPreviewTextDataCompletionItem } from '../preview-text-data.interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* Tạo extension autocomplete từ danh sách `items`:
|
|
5
|
+
* 1. Gõ `obj.` → gợi ý properties của obj (dot-notation).
|
|
6
|
+
* 2. Gõ ký tự → gợi ý tên biến/object top-level.
|
|
7
|
+
*/
|
|
8
|
+
export declare const buildCompletionExtension: (items: IPreviewTextDataCompletionItem[]) => Extension;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Extension } from '@codemirror/state';
|
|
2
|
+
import { IPreviewTextDataViolation, T_ForbiddenPattern } from '../preview-text-data.interfaces';
|
|
3
|
+
/**
|
|
4
|
+
* Extension bảo mật gồm 2 layer:
|
|
5
|
+
* 1. Linter: highlight đỏ tại đúng vị trí match (bỏ qua match trong comment) + gọi `emitViolations`.
|
|
6
|
+
* 2. Transaction filter: chặn paste nếu nội dung paste chứa pattern cấm.
|
|
7
|
+
*/
|
|
8
|
+
export declare const buildForbiddenExtension: (patterns: T_ForbiddenPattern[], emitViolations: (violations: IPreviewTextDataViolation[]) => void) => Extension;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EditorView } from '@codemirror/view';
|
|
2
|
+
/** Parse giá trị CSS px → number. Trả về undefined nếu không phải px hợp lệ (vd `none`, `0`). */
|
|
3
|
+
export declare const parsePx: (raw: string) => number | undefined;
|
|
4
|
+
/** Bù dòng trống cho đủ `minLines` (xử lý 1 lần khi khởi tạo). Đủ rồi giữ nguyên, không cắt bớt. */
|
|
5
|
+
export declare const applyMinLines: (content: string, minLines?: number) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Tính min/max-height vùng cuộn theo CSS của container ngoài, snap về bội số dòng.
|
|
8
|
+
* Trừ offset phần header/toolbar (giữa đỉnh container và đỉnh vùng cuộn) để vừa khít container.
|
|
9
|
+
*/
|
|
10
|
+
export declare const applyContainerHeight: (view: EditorView, container: HTMLElement) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Giới hạn chiều cao editor (xử lý 1 lần). 2 chế độ loại trừ nhau:
|
|
13
|
+
* - `maxLines`: cap theo số dòng truyền vào.
|
|
14
|
+
* - `container`: tự tính số dòng theo `min-height`/`max-height` (CSS) của container, snap bội số dòng.
|
|
15
|
+
* Cấu hình đồng thời `container` + `minLines`/`maxLines` → throw.
|
|
16
|
+
*/
|
|
17
|
+
export declare const applyMaxHeight: (view: EditorView | undefined, options: {
|
|
18
|
+
minLines?: number;
|
|
19
|
+
maxLines?: number;
|
|
20
|
+
container?: HTMLElement;
|
|
21
|
+
}) => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { autocompletion } from '@codemirror/autocomplete';
|
|
2
|
+
/**
|
|
3
|
+
* Tạo extension autocomplete từ danh sách `items`:
|
|
4
|
+
* 1. Gõ `obj.` → gợi ý properties của obj (dot-notation).
|
|
5
|
+
* 2. Gõ ký tự → gợi ý tên biến/object top-level.
|
|
6
|
+
*/
|
|
7
|
+
export const buildCompletionExtension = (items) => {
|
|
8
|
+
const completionSource = (context) => {
|
|
9
|
+
const dotMatch = context.matchBefore(/\w+\.\w*/);
|
|
10
|
+
if (dotMatch) {
|
|
11
|
+
const dotPos = dotMatch.text.indexOf('.');
|
|
12
|
+
const objectName = dotMatch.text.slice(0, dotPos);
|
|
13
|
+
const parent = items.find((i) => i.label === objectName);
|
|
14
|
+
if (parent?.properties?.length) {
|
|
15
|
+
return {
|
|
16
|
+
from: dotMatch.from + dotPos + 1,
|
|
17
|
+
options: parent.properties.map((p) => ({ label: p.label, type: p.type ?? 'property', detail: p.detail ?? '', info: p.info ?? '' })),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const word = context.matchBefore(/\w*/);
|
|
22
|
+
if (!word || (word.from === word.to && !context.explicit))
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
from: word.from,
|
|
26
|
+
options: items.map((item) => ({ label: item.label, type: item.type ?? 'variable', detail: item.detail ?? '', info: item.info ?? '' })),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
return autocompletion({ override: [completionSource] });
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRpdG9yLWNvbXBsZXRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3ByZXZpZXctdGV4dC1kYXRhL3NyYy9lZGl0b3IvZWRpdG9yLWNvbXBsZXRpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxjQUFjLEVBQXVDLE1BQU0sMEJBQTBCLENBQUM7QUFJL0Y7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLENBQUMsS0FBdUMsRUFBYSxFQUFFO0lBQzdGLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxPQUEwQixFQUEyQixFQUFFO1FBQy9FLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDakQsSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUNiLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzFDLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNsRCxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxLQUFLLFVBQVUsQ0FBQyxDQUFDO1lBQ3pELElBQUksTUFBTSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsQ0FBQztnQkFDL0IsT0FBTztvQkFDTCxJQUFJLEVBQUUsUUFBUSxDQUFDLElBQUksR0FBRyxNQUFNLEdBQUcsQ0FBQztvQkFDaEMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLElBQUksVUFBVSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLElBQUksSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO2lCQUNwSSxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7UUFFRCxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3hDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO1lBQUUsT0FBTyxJQUFJLENBQUM7UUFFdkUsT0FBTztZQUNMLElBQUksRUFBRSxJQUFJLENBQUMsSUFBSTtZQUNmLE9BQU8sRUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLElBQUksVUFBVSxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTSxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1NBQ3ZJLENBQUM7SUFDSixDQUFDLENBQUM7SUFFRixPQUFPLGNBQWMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQzFELENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGF1dG9jb21wbGV0aW9uLCBDb21wbGV0aW9uQ29udGV4dCwgQ29tcGxldGlvblJlc3VsdCB9IGZyb20gJ0Bjb2RlbWlycm9yL2F1dG9jb21wbGV0ZSc7XG5pbXBvcnQgeyBFeHRlbnNpb24gfSBmcm9tICdAY29kZW1pcnJvci9zdGF0ZSc7XG5pbXBvcnQgeyBJUHJldmlld1RleHREYXRhQ29tcGxldGlvbkl0ZW0gfSBmcm9tICcuLi9wcmV2aWV3LXRleHQtZGF0YS5pbnRlcmZhY2VzJztcblxuLyoqXG4gKiBU4bqhbyBleHRlbnNpb24gYXV0b2NvbXBsZXRlIHThu6sgZGFuaCBzw6FjaCBgaXRlbXNgOlxuICogMS4gR8O1IGBvYmouYCDihpIgZ+G7o2kgw70gcHJvcGVydGllcyBj4bunYSBvYmogKGRvdC1ub3RhdGlvbikuXG4gKiAyLiBHw7Uga8O9IHThu7Eg4oaSIGfhu6NpIMO9IHTDqm4gYmnhur9uL29iamVjdCB0b3AtbGV2ZWwuXG4gKi9cbmV4cG9ydCBjb25zdCBidWlsZENvbXBsZXRpb25FeHRlbnNpb24gPSAoaXRlbXM6IElQcmV2aWV3VGV4dERhdGFDb21wbGV0aW9uSXRlbVtdKTogRXh0ZW5zaW9uID0+IHtcbiAgY29uc3QgY29tcGxldGlvblNvdXJjZSA9IChjb250ZXh0OiBDb21wbGV0aW9uQ29udGV4dCk6IENvbXBsZXRpb25SZXN1bHQgfCBudWxsID0+IHtcbiAgICBjb25zdCBkb3RNYXRjaCA9IGNvbnRleHQubWF0Y2hCZWZvcmUoL1xcdytcXC5cXHcqLyk7XG4gICAgaWYgKGRvdE1hdGNoKSB7XG4gICAgICBjb25zdCBkb3RQb3MgPSBkb3RNYXRjaC50ZXh0LmluZGV4T2YoJy4nKTtcbiAgICAgIGNvbnN0IG9iamVjdE5hbWUgPSBkb3RNYXRjaC50ZXh0LnNsaWNlKDAsIGRvdFBvcyk7XG4gICAgICBjb25zdCBwYXJlbnQgPSBpdGVtcy5maW5kKChpKSA9PiBpLmxhYmVsID09PSBvYmplY3ROYW1lKTtcbiAgICAgIGlmIChwYXJlbnQ/LnByb3BlcnRpZXM/Lmxlbmd0aCkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGZyb206IGRvdE1hdGNoLmZyb20gKyBkb3RQb3MgKyAxLFxuICAgICAgICAgIG9wdGlvbnM6IHBhcmVudC5wcm9wZXJ0aWVzLm1hcCgocCkgPT4gKHsgbGFiZWw6IHAubGFiZWwsIHR5cGU6IHAudHlwZSA/PyAncHJvcGVydHknLCBkZXRhaWw6IHAuZGV0YWlsID8/ICcnLCBpbmZvOiBwLmluZm8gPz8gJycgfSkpLFxuICAgICAgICB9O1xuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IHdvcmQgPSBjb250ZXh0Lm1hdGNoQmVmb3JlKC9cXHcqLyk7XG4gICAgaWYgKCF3b3JkIHx8ICh3b3JkLmZyb20gPT09IHdvcmQudG8gJiYgIWNvbnRleHQuZXhwbGljaXQpKSByZXR1cm4gbnVsbDtcblxuICAgIHJldHVybiB7XG4gICAgICBmcm9tOiB3b3JkLmZyb20sXG4gICAgICBvcHRpb25zOiBpdGVtcy5tYXAoKGl0ZW0pID0+ICh7IGxhYmVsOiBpdGVtLmxhYmVsLCB0eXBlOiBpdGVtLnR5cGUgPz8gJ3ZhcmlhYmxlJywgZGV0YWlsOiBpdGVtLmRldGFpbCA/PyAnJywgaW5mbzogaXRlbS5pbmZvID8/ICcnIH0pKSxcbiAgICB9O1xuICB9O1xuXG4gIHJldHVybiBhdXRvY29tcGxldGlvbih7IG92ZXJyaWRlOiBbY29tcGxldGlvblNvdXJjZV0gfSk7XG59O1xuIl19
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { syntaxTree } from '@codemirror/language';
|
|
2
|
+
import { linter } from '@codemirror/lint';
|
|
3
|
+
import { EditorState, Transaction } from '@codemirror/state';
|
|
4
|
+
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
5
|
+
const toGlobalRegex = (pattern) => {
|
|
6
|
+
if (pattern instanceof RegExp) {
|
|
7
|
+
const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
|
|
8
|
+
return new RegExp(pattern.source, flags);
|
|
9
|
+
}
|
|
10
|
+
return new RegExp(escapeRegex(pattern), 'gi');
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Extension bảo mật gồm 2 layer:
|
|
14
|
+
* 1. Linter: highlight đỏ tại đúng vị trí match (bỏ qua match trong comment) + gọi `emitViolations`.
|
|
15
|
+
* 2. Transaction filter: chặn paste nếu nội dung paste chứa pattern cấm.
|
|
16
|
+
*/
|
|
17
|
+
export const buildForbiddenExtension = (patterns, emitViolations) => {
|
|
18
|
+
const forbiddenLinter = linter((view) => {
|
|
19
|
+
const diagnostics = [];
|
|
20
|
+
const violations = [];
|
|
21
|
+
const text = view.state.doc.toString();
|
|
22
|
+
const commentRanges = [];
|
|
23
|
+
syntaxTree(view.state).iterate({
|
|
24
|
+
enter: (node) => {
|
|
25
|
+
if (node.type.name.toLowerCase().includes('comment')) {
|
|
26
|
+
commentRanges.push({ from: node.from, to: node.to });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const isInComment = (from, to) => commentRanges.some((r) => from >= r.from && to <= r.to);
|
|
31
|
+
for (const pattern of patterns) {
|
|
32
|
+
const re = toGlobalRegex(pattern);
|
|
33
|
+
re.lastIndex = 0;
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = re.exec(text)) !== null) {
|
|
36
|
+
const from = match.index;
|
|
37
|
+
const to = from + match[0].length;
|
|
38
|
+
if (isInComment(from, to))
|
|
39
|
+
continue;
|
|
40
|
+
const line = view.state.doc.lineAt(from).number;
|
|
41
|
+
const patternLabel = pattern instanceof RegExp ? pattern.source : pattern;
|
|
42
|
+
diagnostics.push({ from, to, severity: 'error', message: `Mã bị cấm: "${match[0]}" — vi phạm chính sách bảo mật` });
|
|
43
|
+
violations.push({ from, to, line, pattern: patternLabel, matched: match[0] });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
emitViolations(violations);
|
|
47
|
+
return diagnostics;
|
|
48
|
+
});
|
|
49
|
+
const pasteFilter = EditorState.transactionFilter.of((tr) => {
|
|
50
|
+
if (!tr.docChanged)
|
|
51
|
+
return tr;
|
|
52
|
+
if (!tr.annotation(Transaction.userEvent)?.startsWith('input.paste'))
|
|
53
|
+
return tr;
|
|
54
|
+
const newText = tr.newDoc.toString();
|
|
55
|
+
for (const pattern of patterns) {
|
|
56
|
+
const re = toGlobalRegex(pattern);
|
|
57
|
+
re.lastIndex = 0;
|
|
58
|
+
if (re.test(newText))
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return tr;
|
|
62
|
+
});
|
|
63
|
+
return [forbiddenLinter, pasteFilter];
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRpdG9yLWZvcmJpZGRlbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL2xpYnMtdWkvY29tcG9uZW50cy9wcmV2aWV3LXRleHQtZGF0YS9zcmMvZWRpdG9yL2VkaXRvci1mb3JiaWRkZW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBYyxNQUFNLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsV0FBVyxFQUFhLFdBQVcsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBSXhFLE1BQU0sV0FBVyxHQUFHLENBQUMsR0FBVyxFQUFVLEVBQUUsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBRXhGLE1BQU0sYUFBYSxHQUFHLENBQUMsT0FBMkIsRUFBVSxFQUFFO0lBQzVELElBQUksT0FBTyxZQUFZLE1BQU0sRUFBRSxDQUFDO1FBQzlCLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxLQUFLLEdBQUcsQ0FBQztRQUNoRixPQUFPLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUNELE9BQU8sSUFBSSxNQUFNLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQ2hELENBQUMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxDQUFDLFFBQThCLEVBQUUsY0FBaUUsRUFBYSxFQUFFO0lBQ3RKLE1BQU0sZUFBZSxHQUFHLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFB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QUFPLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNyQyxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQy9CLE1BQU0sRUFBRSxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNsQyxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsQ0FBQztZQUNqQixJQUFJLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDO2dCQUFFLE9BQU8sRUFBRSxDQUFDO1FBQ2xDLENBQUM7UUFDRCxPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUMsQ0FBQyxDQUFDO0lBRUgsT0FBTyxDQUFDLGVBQWUsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUN4QyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBzeW50YXhUcmVlIH0gZnJvbSAnQGNvZGVtaXJyb3IvbGFuZ3VhZ2UnO1xuaW1wb3J0IHsgRGlhZ25vc3RpYywgbGludGVyIH0gZnJvbSAnQGNvZGVtaXJyb3IvbGludCc7XG5pbXBvcnQgeyBFZGl0b3JTdGF0ZSwgRXh0ZW5zaW9uLCBUcmFuc2FjdGlvbiB9IGZyb20gJ0Bjb2RlbWlycm9yL3N0YXRlJztcbmltcG9ydCB0eXBlIHsgU3ludGF4Tm9kZVJlZiB9IGZyb20gJ0BsZXplci9jb21tb24nO1xuaW1wb3J0IHsgSVByZXZpZXdUZXh0RGF0YVZpb2xhdGlvbiwgVF9Gb3JiaWRkZW5QYXR0ZXJuIH0gZnJvbSAnLi4vcHJldmlldy10ZXh0LWRhdGEuaW50ZXJmYWNlcyc7XG5cbmNvbnN0IGVzY2FwZVJlZ2V4ID0gKHN0cjogc3RyaW5nKTogc3RyaW5nID0+IHN0ci5yZXBsYWNlKC9bLiorP14ke30oKXxbXFxdXFxcXF0vZywgJ1xcXFwkJicpO1xuXG5jb25zdCB0b0dsb2JhbFJlZ2V4ID0gKHBhdHRlcm46IFRfRm9yYmlkZGVuUGF0dGVybik6IFJlZ0V4cCA9PiB7XG4gIGlmIChwYXR0ZXJuIGluc3RhbmNlb2YgUmVnRXhwKSB7XG4gICAgY29uc3QgZmxhZ3MgPSBwYXR0ZXJuLmZsYWdzLmluY2x1ZGVzKCdnJykgPyBwYXR0ZXJuLmZsYWdzIDogYCR7cGF0dGVybi5mbGFnc31nYDtcbiAgICByZXR1cm4gbmV3IFJlZ0V4cChwYXR0ZXJuLnNvdXJjZSwgZmxhZ3MpO1xuICB9XG4gIHJldHVybiBuZXcgUmVnRXhwKGVzY2FwZVJlZ2V4KHBhdHRlcm4pLCAnZ2knKTtcbn07XG5cbi8qKlxuICogRXh0ZW5zaW9uIGLhuqNvIG3huq10IGfhu5NtIDIgbGF5ZXI6XG4gKiAxLiBMaW50ZXI6IGhpZ2hsaWdodCDEkeG7jyB04bqhaSDEkcO6bmcgduG7iyB0csOtIG1hdGNoIChi4buPIHF1YSBtYXRjaCB0cm9uZyBjb21tZW50KSArIGfhu41pIGBlbWl0VmlvbGF0aW9uc2AuXG4gKiAyLiBUcmFuc2FjdGlvbiBmaWx0ZXI6IGNo4bq3biBwYXN0ZSBu4bq/dSBu4buZaSBkdW5nIHBhc3RlIGNo4bupYSBwYXR0ZXJuIGPhuqVtLlxuICovXG5leHBvcnQgY29uc3QgYnVpbGRGb3JiaWRkZW5FeHRlbnNpb24gPSAocGF0dGVybnM6IFRfRm9yYmlkZGVuUGF0dGVybltdLCBlbWl0VmlvbGF0aW9uczogKHZpb2xhdGlvbnM6IElQcmV2aWV3VGV4dERhdGFWaW9sYXRpb25bXSkgPT4gdm9pZCk6IEV4dGVuc2lvbiA9PiB7XG4gIGNvbnN0IGZvcmJpZGRlbkxpbnRlciA9IGxpbnRlcigodmlldykgPT4ge1xuICAgIGNvbnN0IGRpYWdub3N0aWNzOiBEaWFnbm9zdGljW10gPSBbXTtcbiAgICBjb25zdCB2aW9sYXRpb25zOiBJUHJldmlld1RleHREYXRhVmlvbGF0aW9uW10gPSBbXTtcbiAgICBjb25zdCB0ZXh0ID0gdmlldy5zdGF0ZS5kb2MudG9TdHJpbmcoKTtcblxuICAgIGNvbnN0IGNvbW1lbnRSYW5nZXM6IHsgZnJvbTogbnVtYmVyOyB0bzogbnVtYmVyIH1bXSA9IFtdO1xuICAgIHN5bnRheFRyZWUodmlldy5zdGF0ZSkuaXRlcmF0ZSh7XG4gICAgICBlbnRlcjogKG5vZGU6IFN5bnRheE5vZGVSZWYpID0+IHtcbiAgICAgICAgaWYgKG5vZGUudHlwZS5uYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoJ2NvbW1lbnQnKSkge1xuICAgICAgICAgIGNvbW1lbnRSYW5nZXMucHVzaCh7IGZyb206IG5vZGUuZnJvbSwgdG86IG5vZGUudG8gfSk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSk7XG4gICAgY29uc3QgaXNJbkNvbW1lbnQgPSAoZnJvbTogbnVtYmVyLCB0bzogbnVtYmVyKTogYm9vbGVhbiA9PiBjb21tZW50UmFuZ2VzLnNvbWUoKHIpID0+IGZyb20gPj0gci5mcm9tICYmIHRvIDw9IHIudG8pO1xuXG4gICAgZm9yIChjb25zdCBwYXR0ZXJuIG9mIHBhdHRlcm5zKSB7XG4gICAgICBjb25zdCByZSA9IHRvR2xvYmFsUmVnZXgocGF0dGVybik7XG4gICAgICByZS5sYXN0SW5kZXggPSAwO1xuICAgICAgbGV0IG1hdGNoOiBSZWdFeHBFeGVjQXJyYXkgfCBudWxsO1xuICAgICAgd2hpbGUgKChtYXRjaCA9IHJlLmV4ZWModGV4dCkpICE9PSBudWxsKSB7XG4gICAgICAgIGNvbnN0IGZyb20gPSBtYXRjaC5pbmRleDtcbiAgICAgICAgY29uc3QgdG8gPSBmcm9tICsgbWF0Y2hbMF0ubGVuZ3RoO1xuICAgICAgICBpZiAoaXNJbkNvbW1lbnQoZnJvbSwgdG8pKSBjb250aW51ZTtcbiAgICAgICAgY29uc3QgbGluZSA9IHZpZXcuc3RhdGUuZG9jLmxpbmVBdChmcm9tKS5udW1iZXI7XG4gICAgICAgIGNvbnN0IHBhdHRlcm5MYWJlbCA9IHBhdHRlcm4gaW5zdGFuY2VvZiBSZWdFeHAgPyBwYXR0ZXJuLnNvdXJjZSA6IHBhdHRlcm47XG4gICAgICAgIGRpYWdub3N0aWNzLnB1c2goeyBmcm9tLCB0bywgc2V2ZXJpdHk6ICdlcnJvcicsIG1lc3NhZ2U6IGBNw6MgYuG7iyBj4bqlbTogXCIke21hdGNoWzBdfVwiIOKAlCB2aSBwaOG6oW0gY2jDrW5oIHPDoWNoIGLhuqNvIG3huq10YCB9KTtcbiAgICAgICAgdmlvbGF0aW9ucy5wdXNoKHsgZnJvbSwgdG8sIGxpbmUsIHBhdHRlcm46IHBhdHRlcm5MYWJlbCwgbWF0Y2hlZDogbWF0Y2hbMF0gfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgZW1pdFZpb2xhdGlvbnModmlvbGF0aW9ucyk7XG4gICAgcmV0dXJuIGRpYWdub3N0aWNzO1xuICB9KTtcblxuICBjb25zdCBwYXN0ZUZpbHRlciA9IEVkaXRvclN0YXRlLnRyYW5zYWN0aW9uRmlsdGVyLm9mKCh0cjogVHJhbnNhY3Rpb24pID0+IHtcbiAgICBpZiAoIXRyLmRvY0NoYW5nZWQpIHJldHVybiB0cjtcbiAgICBpZiAoIXRyLmFubm90YXRpb24oVHJhbnNhY3Rpb24udXNlckV2ZW50KT8uc3RhcnRzV2l0aCgnaW5wdXQucGFzdGUnKSkgcmV0dXJuIHRyO1xuICAgIGNvbnN0IG5ld1RleHQgPSB0ci5uZXdEb2MudG9TdHJpbmcoKTtcbiAgICBmb3IgKGNvbnN0IHBhdHRlcm4gb2YgcGF0dGVybnMpIHtcbiAgICAgIGNvbnN0IHJlID0gdG9HbG9iYWxSZWdleChwYXR0ZXJuKTtcbiAgICAgIHJlLmxhc3RJbmRleCA9IDA7XG4gICAgICBpZiAocmUudGVzdChuZXdUZXh0KSkgcmV0dXJuIFtdO1xuICAgIH1cbiAgICByZXR1cm4gdHI7XG4gIH0pO1xuXG4gIHJldHVybiBbZm9yYmlkZGVuTGludGVyLCBwYXN0ZUZpbHRlcl07XG59O1xuIl19
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Parse giá trị CSS px → number. Trả về undefined nếu không phải px hợp lệ (vd `none`, `0`). */
|
|
2
|
+
export const parsePx = (raw) => {
|
|
3
|
+
if (!raw || !raw.endsWith('px')) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const value = parseFloat(raw);
|
|
7
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
8
|
+
};
|
|
9
|
+
/** Bù dòng trống cho đủ `minLines` (xử lý 1 lần khi khởi tạo). Đủ rồi giữ nguyên, không cắt bớt. */
|
|
10
|
+
export const applyMinLines = (content, minLines) => {
|
|
11
|
+
const value = content ?? '';
|
|
12
|
+
if (!minLines || minLines <= 0) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
const currentLines = value === '' ? 1 : value.split('\n').length;
|
|
16
|
+
if (currentLines >= minLines) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return value + '\n'.repeat(minLines - currentLines);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Tính min/max-height vùng cuộn theo CSS của container ngoài, snap về bội số dòng.
|
|
23
|
+
* Trừ offset phần header/toolbar (giữa đỉnh container và đỉnh vùng cuộn) để vừa khít container.
|
|
24
|
+
*/
|
|
25
|
+
export const applyContainerHeight = (view, container) => {
|
|
26
|
+
const style = getComputedStyle(container);
|
|
27
|
+
const minPx = parsePx(style.minHeight);
|
|
28
|
+
const maxPx = parsePx(style.maxHeight);
|
|
29
|
+
if (minPx === undefined && maxPx === undefined) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
view.requestMeasure({
|
|
33
|
+
read: (measureView) => {
|
|
34
|
+
const line = measureView.contentDOM.querySelector('.cm-line');
|
|
35
|
+
const lineHeight = (line ? line.getBoundingClientRect().height : measureView.defaultLineHeight) || 20;
|
|
36
|
+
const offsetTop = Math.max(measureView.scrollDOM.getBoundingClientRect().top - container.getBoundingClientRect().top, 0);
|
|
37
|
+
const toLines = (px) => Math.max(1, Math.floor((px - offsetTop - 8) / lineHeight));
|
|
38
|
+
return { lineHeight, minLines: minPx === undefined ? undefined : toLines(minPx), maxLines: maxPx === undefined ? undefined : toLines(maxPx) };
|
|
39
|
+
},
|
|
40
|
+
write: ({ lineHeight, minLines, maxLines }, writeView) => {
|
|
41
|
+
if (maxLines !== undefined) {
|
|
42
|
+
writeView.scrollDOM.style.maxHeight = `${maxLines * lineHeight + 8}px`;
|
|
43
|
+
writeView.scrollDOM.style.overflowY = 'auto';
|
|
44
|
+
}
|
|
45
|
+
// min-height: bù dòng trống vào content (hiện đủ line-number 1..N), giống cơ chế minLines.
|
|
46
|
+
if (minLines !== undefined) {
|
|
47
|
+
const currentLines = writeView.state.doc.lines;
|
|
48
|
+
if (currentLines < minLines) {
|
|
49
|
+
queueMicrotask(() => writeView.dispatch({ changes: { from: writeView.state.doc.length, insert: '\n'.repeat(minLines - currentLines) } }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Giới hạn chiều cao editor (xử lý 1 lần). 2 chế độ loại trừ nhau:
|
|
57
|
+
* - `maxLines`: cap theo số dòng truyền vào.
|
|
58
|
+
* - `container`: tự tính số dòng theo `min-height`/`max-height` (CSS) của container, snap bội số dòng.
|
|
59
|
+
* Cấu hình đồng thời `container` + `minLines`/`maxLines` → throw.
|
|
60
|
+
*/
|
|
61
|
+
export const applyMaxHeight = (view, options) => {
|
|
62
|
+
const { minLines, maxLines, container } = options;
|
|
63
|
+
if (container && (minLines || maxLines)) {
|
|
64
|
+
throw new Error('[preview-text-data] CẤM cấu hình [containerElement] cùng [minLines]/[maxLines]. Container tự quản lý min/max height.');
|
|
65
|
+
}
|
|
66
|
+
if (!view) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (container) {
|
|
70
|
+
applyContainerHeight(view, container);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!maxLines || maxLines <= 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
view.requestMeasure({
|
|
77
|
+
read: (measureView) => {
|
|
78
|
+
const line = measureView.contentDOM.querySelector('.cm-line');
|
|
79
|
+
return (line ? line.getBoundingClientRect().height : measureView.defaultLineHeight) || 20;
|
|
80
|
+
},
|
|
81
|
+
write: (lineHeight, writeView) => {
|
|
82
|
+
writeView.scrollDOM.style.maxHeight = `${maxLines * lineHeight + 8}px`;
|
|
83
|
+
writeView.scrollDOM.style.overflowY = 'auto';
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRpdG9yLWhlaWdodC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL2xpYnMtdWkvY29tcG9uZW50cy9wcmV2aWV3LXRleHQtZGF0YS9zcmMvZWRpdG9yL2VkaXRvci1oZWlnaHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsaUdBQWlHO0FBQ2pHLE1BQU0sQ0FBQyxNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQVcsRUFBc0IsRUFBRTtJQUN6RCxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQ2hDLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFDRCxNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDOUIsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQ2pFLENBQUMsQ0FBQztBQUVGLG9HQUFvRztBQUNwRyxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsQ0FBQyxPQUFlLEVBQUUsUUFBaUIsRUFBVSxFQUFFO0lBQzFFLE1BQU0sS0FBSyxHQUFHLE9BQU8sSUFBSSxFQUFFLENBQUM7SUFDNUIsSUFBSSxDQUFDLFFBQVEsSUFBSSxRQUFRLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDL0IsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBQ0QsTUFBTSxZQUFZLEdBQUcsS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUNqRSxJQUFJLFlBQVksSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUM3QixPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxPQUFPLEtB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U0FBUyxHQUFHLE1BQU0sQ0FBQztRQUMvQyxDQUFDO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRWRpdG9yVmlldyB9IGZyb20gJ0Bjb2RlbWlycm9yL3ZpZXcnO1xuXG4vKiogUGFyc2UgZ2nDoSB0cuG7iyBDU1MgcHgg4oaSIG51bWJlci4gVHLhuqMgduG7gSB1bmRlZmluZWQgbuG6v3Uga2jDtG5nIHBo4bqjaSBweCBo4bujcCBs4buHICh2ZCBgbm9uZWAsIGAwYCkuICovXG5leHBvcnQgY29uc3QgcGFyc2VQeCA9IChyYXc6IHN0cmluZyk6IG51bWJlciB8IHVuZGVmaW5lZCA9PiB7XG4gIGlmICghcmF3IHx8ICFyYXcuZW5kc1dpdGgoJ3B4JykpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG4gIGNvbnN0IHZhbHVlID0gcGFyc2VGbG9hdChyYXcpO1xuICByZXR1cm4gTnVtYmVyLmlzRmluaXRlKHZhbHVlKSAmJiB2YWx1ZSA+IDAgPyB2YWx1ZSA6IHVuZGVmaW5lZDtcbn07XG5cbi8qKiBCw7kgZMOybmcgdHLhu5FuZyBjaG8gxJHhu6cgYG1pbkxpbmVzYCAoeOG7rSBsw70gMSBs4bqnbiBraGkga2jhu59pIHThuqFvKS4gxJDhu6cgcuG7k2kgZ2nhu68gbmd1ecOqbiwga2jDtG5nIGPhuq90IGLhu5t0LiAqL1xuZXhwb3J0IGNvbnN0IGFwcGx5TWluTGluZXMgPSAoY29udGVudDogc3RyaW5nLCBtaW5MaW5lcz86IG51bWJlcik6IHN0cmluZyA9PiB7XG4gIGNvbnN0IHZhbHVlID0gY29udGVudCA/PyAnJztcbiAgaWYgKCFtaW5MaW5lcyB8fCBtaW5MaW5lcyA8PSAwKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGNvbnN0IGN1cnJlbnRMaW5lcyA9IHZhbHVlID09PSAnJyA/IDEgOiB2YWx1ZS5zcGxpdCgnXFxuJykubGVuZ3RoO1xuICBpZiAoY3VycmVudExpbmVzID49IG1pbkxpbmVzKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIHJldHVybiB2YWx1ZSArICdcXG4nLnJlcGVhdChtaW5MaW5lcyAtIGN1cnJlbnRMaW5lcyk7XG59O1xuXG4vKipcbiAqIFTDrW5oIG1pbi9tYXgtaGVpZ2h0IHbDuW5nIGN14buZbiB0aGVvIENTUyBj4bunYSBjb250YWluZXIgbmdvw6BpLCBzbmFwIHbhu4EgYuG7mWkgc+G7kSBkw7JuZy5cbiAqIFRy4burIG9mZnNldCBwaOG6p24gaGVhZGVyL3Rvb2xiYXIgKGdp4buvYSDEkeG7iW5oIGNvbnRhaW5lciB2w6AgxJHhu4luaCB2w7luZyBjdeG7mW4pIMSR4buDIHbhu6thIGtow610IGNvbnRhaW5lci5cbiAqL1xuZXhwb3J0IGNvbnN0IGFwcGx5Q29udGFpbmVySGVpZ2h0ID0gKHZpZXc6IEVkaXRvclZpZXcsIGNvbnRhaW5lcjogSFRNTEVsZW1lbnQpOiB2b2lkID0+IHtcbiAgY29uc3Qgc3R5bGUgPSBnZXRDb21wdXRlZFN0eWxlKGNvbnRhaW5lcik7XG4gIGNvbnN0IG1pblB4ID0gcGFyc2VQeChzdHlsZS5taW5IZWlnaHQpO1xuICBjb25zdCBtYXhQeCA9IHBhcnNlUHgoc3R5bGUubWF4SGVpZ2h0KTtcbiAgaWYgKG1pblB4ID09PSB1bmRlZmluZWQgJiYgbWF4UHggPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybjtcbiAgfVxuICB2aWV3LnJlcXVlc3RNZWFzdXJlKHtcbiAgICByZWFkOiAobWVhc3VyZVZpZXcpID0+IHtcbiAgICAgIGNvbnN0IGxpbmUgPSBtZWFzdXJlVmlldy5jb250ZW50RE9NLnF1ZXJ5U2VsZWN0b3IoJy5jbS1saW5lJyk7XG4gICAgICBjb25zdCBsaW5lSGVpZ2h0ID0gKGxpbmUgPyBsaW5lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLmhlaWdodCA6IG1lYXN1cmVWaWV3LmRlZmF1bHRMaW5lSGVpZ2h0KSB8fCAyMDtcbiAgICAgIGNvbnN0IG9mZnNldFRvcCA9IE1hdGgubWF4KG1lYXN1cmVWaWV3LnNjcm9sbERPTS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBjb250YWluZXIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wLCAwKTtcbiAgICAgIGNvbnN0IHRvTGluZXMgPSAocHg6IG51bWJlcikgPT4gTWF0aC5tYXgoMSwgTWF0aC5mbG9vcigocHggLSBvZmZzZXRUb3AgLSA4KSAvIGxpbmVIZWlnaHQpKTtcbiAgICAgIHJldHVybiB7IGxpbmVIZWlnaHQsIG1pbkxpbmVzOiBtaW5QeCA9PT0gdW5kZWZpbmVkID8gdW5kZWZpbmVkIDogdG9MaW5lcyhtaW5QeCksIG1heExpbmVzOiBtYXhQeCA9PT0gdW5kZWZpbmVkID8gdW5kZWZpbmVkIDogdG9MaW5lcyhtYXhQeCkgfTtcbiAgICB9LFxuICAgIHdyaXRlOiAoeyBsaW5lSGVpZ2h0LCBtaW5MaW5lcywgbWF4TGluZXMgfSwgd3JpdGVWaWV3KSA9PiB7XG4gICAgICBpZiAobWF4TGluZXMgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICB3cml0ZVZpZXcuc2Nyb2xsRE9NLnN0eWxlLm1heEhlaWdodCA9IGAke21heExpbmVzICogbGluZUhlaWdodCArIDh9cHhgO1xuICAgICAgICB3cml0ZVZpZXcuc2Nyb2xsRE9NLnN0eWxlLm92ZXJmbG93WSA9ICdhdXRvJztcbiAgICAgIH1cbiAgICAgIC8vIG1pbi1oZWlnaHQ6IGLDuSBkw7JuZyB0cuG7kW5nIHbDoG8gY29udGVudCAoaGnhu4duIMSR4bunIGxpbmUtbnVtYmVyIDEuLk4pLCBnaeG7kW5nIGPGoSBjaOG6vyBtaW5MaW5lcy5cbiAgICAgIGlmIChtaW5MaW5lcyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGNvbnN0IGN1cnJlbnRMaW5lcyA9IHdyaXRlVmlldy5zdGF0ZS5kb2MubGluZXM7XG4gICAgICAgIGlmIChjdXJyZW50TGluZXMgPCBtaW5MaW5lcykge1xuICAgICAgICAgIHF1ZXVlTWljcm90YXNrKCgpID0+IHdyaXRlVmlldy5kaXNwYXRjaCh7IGNoYW5nZXM6IHsgZnJvbTogd3JpdGVWaWV3LnN0YXRlLmRvYy5sZW5ndGgsIGluc2VydDogJ1xcbicucmVwZWF0KG1pbkxpbmVzIC0gY3VycmVudExpbmVzKSB9IH0pKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gIH0pO1xufTtcblxuLyoqXG4gKiBHaeG7m2kgaOG6oW4gY2hp4buBdSBjYW8gZWRpdG9yICh44butIGzDvSAxIGzhuqduKS4gMiBjaOG6vyDEkeG7mSBsb+G6oWkgdHLhu6sgbmhhdTpcbiAqIC0gYG1heExpbmVzYDogY2FwIHRoZW8gc+G7kSBkw7JuZyB0cnV54buBbiB2w6BvLlxuICogLSBgY29udGFpbmVyYDogdOG7sSB0w61uaCBz4buRIGTDsm5nIHRoZW8gYG1pbi1oZWlnaHRgL2BtYXgtaGVpZ2h0YCAoQ1NTKSBj4bunYSBjb250YWluZXIsIHNuYXAgYuG7mWkgc+G7kSBkw7JuZy5cbiAqIEPhuqV1IGjDrG5oIMSR4buTbmcgdGjhu51pIGBjb250YWluZXJgICsgYG1pbkxpbmVzYC9gbWF4TGluZXNgIOKGkiB0aHJvdy5cbiAqL1xuZXhwb3J0IGNvbnN0IGFwcGx5TWF4SGVpZ2h0ID0gKHZpZXc6IEVkaXRvclZpZXcgfCB1bmRlZmluZWQsIG9wdGlvbnM6IHsgbWluTGluZXM/OiBudW1iZXI7IG1heExpbmVzPzogbnVtYmVyOyBjb250YWluZXI/OiBIVE1MRWxlbWVudCB9KTogdm9pZCA9PiB7XG4gIGNvbnN0IHsgbWluTGluZXMsIG1heExpbmVzLCBjb250YWluZXIgfSA9IG9wdGlvbnM7XG4gIGlmIChjb250YWluZXIgJiYgKG1pbkxpbmVzIHx8IG1heExpbmVzKSkge1xuICAgIHRocm93IG5ldyBFcnJvcignW3ByZXZpZXctdGV4dC1kYXRhXSBD4bqkTSBj4bqldSBow6xuaCBbY29udGFpbmVyRWxlbWVudF0gY8O5bmcgW21pbkxpbmVzXS9bbWF4TGluZXNdLiBDb250YWluZXIgdOG7sSBxdeG6o24gbMO9IG1pbi9tYXggaGVpZ2h0LicpO1xuICB9XG4gIGlmICghdmlldykge1xuICAgIHJldHVybjtcbiAgfVxuICBpZiAoY29udGFpbmVyKSB7XG4gICAgYXBwbHlDb250YWluZXJIZWlnaHQodmlldywgY29udGFpbmVyKTtcbiAgICByZXR1cm47XG4gIH1cbiAgaWYgKCFtYXhMaW5lcyB8fCBtYXhMaW5lcyA8PSAwKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIHZpZXcucmVxdWVzdE1lYXN1cmUoe1xuICAgIHJlYWQ6IChtZWFzdXJlVmlldykgPT4ge1xuICAgICAgY29uc3QgbGluZSA9IG1lYXN1cmVWaWV3LmNvbnRlbnRET00ucXVlcnlTZWxlY3RvcignLmNtLWxpbmUnKTtcbiAgICAgIHJldHVybiAobGluZSA/IGxpbmUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkuaGVpZ2h0IDogbWVhc3VyZVZpZXcuZGVmYXVsdExpbmVIZWlnaHQpIHx8IDIwO1xuICAgIH0sXG4gICAgd3JpdGU6IChsaW5lSGVpZ2h0LCB3cml0ZVZpZXcpID0+IHtcbiAgICAgIHdyaXRlVmlldy5zY3JvbGxET00uc3R5bGUubWF4SGVpZ2h0ID0gYCR7bWF4TGluZXMgKiBsaW5lSGVpZ2h0ICsgOH1weGA7XG4gICAgICB3cml0ZVZpZXcuc2Nyb2xsRE9NLnN0eWxlLm92ZXJmbG93WSA9ICdhdXRvJztcbiAgICB9LFxuICB9KTtcbn07XG4iXX0=
|