@ecomcx/nexus-admin-sdk 0.0.0-stage → 1.24.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.
@@ -0,0 +1,752 @@
1
+ import type { ComponentType, ReactNode } from "react";
2
+ export interface ApiClient {
3
+ get<T = any>(path: string): Promise<T>;
4
+ post<T = any>(path: string, body?: unknown): Promise<T>;
5
+ put<T = any>(path: string, body?: unknown): Promise<T>;
6
+ del<T = any>(path: string): Promise<T>;
7
+ upload<T = any>(path: string, file: File, field?: string): Promise<T>;
8
+ }
9
+ export interface MetaBoxProps {
10
+ postType: string;
11
+ post: {
12
+ id?: number;
13
+ title: string;
14
+ slug: string;
15
+ status: string;
16
+ meta: Record<string, any>;
17
+ [k: string]: any;
18
+ };
19
+ /** Giá trị meta hiện tại (đã gồm thay đổi chưa lưu). */
20
+ meta: Record<string, any>;
21
+ /** Đặt meta — được lưu cùng bài viết khi bấm Lưu/Đăng. silent: không đánh dấu bài "Chưa lưu" (giá trị tự tính). */
22
+ setMeta(key: string, value: any, opts?: {
23
+ silent?: boolean;
24
+ }): void;
25
+ /** Nội dung đang soạn ({ version, blocks }) — cập nhật khi gõ (trễ ~0,4 giây), dùng để phân tích nội dung. */
26
+ content?: {
27
+ version?: number;
28
+ blocks: Array<{
29
+ type: string;
30
+ data?: any;
31
+ }>;
32
+ };
33
+ excerpt?: string;
34
+ /** Đường dẫn công khai (xem trước) và địa chỉ website. */
35
+ link?: string;
36
+ siteUrl?: string;
37
+ locale?: string;
38
+ featuredMedia?: {
39
+ id: number;
40
+ url: string;
41
+ alt?: string;
42
+ width?: number;
43
+ height?: number;
44
+ } | null;
45
+ /** id chuyên mục/thẻ đang chọn theo taxonomy. */
46
+ terms?: Record<string, number[]>;
47
+ }
48
+ /** Một dòng trong danh sách bài (kèm meta đã khai báo trong metaKeys của cột). */
49
+ export interface PostListRow {
50
+ id: number;
51
+ type: string;
52
+ title: string;
53
+ slug: string;
54
+ status: string;
55
+ locale: string;
56
+ link: string;
57
+ meta?: Record<string, any> | null;
58
+ [k: string]: any;
59
+ }
60
+ /** Cột thêm vào danh sách bài viết/trang. */
61
+ export interface PostListColumn {
62
+ id: string;
63
+ title: string;
64
+ /** Bỏ trống = mọi post type. */
65
+ postTypes?: string[];
66
+ /** Meta cần tải kèm mỗi dòng (vd ["_ecx_seo"]). */
67
+ metaKeys?: string[];
68
+ width?: number;
69
+ align?: "left" | "right" | "center";
70
+ component: ComponentType<{
71
+ row: PostListRow;
72
+ }>;
73
+ }
74
+ export interface TermMetaBoxProps {
75
+ taxonomy: string;
76
+ term: {
77
+ id?: number;
78
+ name: string;
79
+ slug: string;
80
+ description: string;
81
+ meta: Record<string, any>;
82
+ };
83
+ meta: Record<string, any>;
84
+ setMeta(key: string, value: any): void;
85
+ }
86
+ /** Hộp thêm vào form sửa chuyên mục/thẻ. */
87
+ export interface TermMetaBox {
88
+ id: string;
89
+ title: string;
90
+ /** Bỏ trống = mọi taxonomy. */
91
+ taxonomies?: string[];
92
+ component: ComponentType<TermMetaBoxProps>;
93
+ }
94
+ export interface MetaBox {
95
+ id: string;
96
+ title: string;
97
+ /** Bỏ trống = mọi post type. */
98
+ postTypes?: string[];
99
+ context?: "main" | "side";
100
+ priority?: number;
101
+ component: ComponentType<MetaBoxProps>;
102
+ }
103
+ /**
104
+ * Sidebar riêng của plugin trong trình soạn bài (ghim được lên thanh đầu trang).
105
+ * Sidebar chỉ hiện một panel mỗi lúc: "Cài đặt" của core hoặc một sidebar plugin; nút trên thanh hành động để chuyển qua lại.
106
+ * Panel luôn được giữ mount (chỉ ẩn khi không mở) nên plugin vẫn phân tích/lưu meta khi người dùng không xem.
107
+ */
108
+ export interface EditorSidebar {
109
+ id: string;
110
+ title: string;
111
+ /** Bỏ trống = mọi post type. */
112
+ postTypes?: string[];
113
+ /** Icon cho nút mặc định trên thanh hành động (vd icon lucide). */
114
+ icon?: ComponentType<{
115
+ className?: string;
116
+ }>;
117
+ /** Nút tự vẽ trên thanh hành động (vd điểm SEO) — thay cho nút icon mặc định. */
118
+ headerItem?: ComponentType<MetaBoxProps & {
119
+ active: boolean;
120
+ toggle(): void;
121
+ }>;
122
+ priority?: number;
123
+ component: ComponentType<MetaBoxProps>;
124
+ }
125
+ export interface AdminPage {
126
+ /** Khớp với Path khai báo trong IAdminRegistry.AddMenu ở backend, vd "/redirects". */
127
+ path: string;
128
+ title?: string;
129
+ /** Toàn màn hình: không thanh bên, không khung trang của admin (vd trình dựng trang kéo thả). Plugin tự vẽ nút quay lại. */
130
+ fullscreen?: boolean;
131
+ component: ComponentType<{
132
+ path: string;
133
+ }>;
134
+ }
135
+ export interface DashboardWidget {
136
+ id: string;
137
+ title: string;
138
+ component: ComponentType;
139
+ }
140
+ export interface Notify {
141
+ success(message: string): void;
142
+ error(message: string): void;
143
+ info(message: string): void;
144
+ }
145
+ export interface Column<T> {
146
+ key: string;
147
+ title: ReactNode;
148
+ render?: (row: T) => ReactNode;
149
+ width?: number | string;
150
+ align?: "left" | "right" | "center";
151
+ className?: string;
152
+ /** Bấm tiêu đề để sắp xếp: bảng gọi `onSort`, plugin tự sắp xếp / gửi lên máy chủ (CMS có UI kit v2). */
153
+ sortable?: boolean;
154
+ }
155
+ export type SortState = {
156
+ key: string;
157
+ dir: "asc" | "desc";
158
+ } | null;
159
+ export interface UiOption {
160
+ value: string;
161
+ label: string;
162
+ description?: string;
163
+ disabled?: boolean;
164
+ }
165
+ export interface TreeNode {
166
+ id: string | number;
167
+ label: ReactNode;
168
+ text?: string;
169
+ children?: TreeNode[];
170
+ disabled?: boolean;
171
+ }
172
+ export type Density = "comfortable" | "compact";
173
+ export interface TableSettings {
174
+ hidden: string[];
175
+ density: Density;
176
+ toggleColumn(key: string): void;
177
+ setDensity(d: Density): void;
178
+ }
179
+ export interface AskOptions {
180
+ title?: ReactNode;
181
+ confirmLabel?: string;
182
+ cancelLabel?: string;
183
+ danger?: boolean;
184
+ }
185
+ export interface AdminSlotItem {
186
+ id?: string;
187
+ slot: string;
188
+ component: ComponentType<any>;
189
+ priority?: number;
190
+ postTypes?: string[];
191
+ }
192
+ export interface PostRowAction {
193
+ id: string;
194
+ title: string;
195
+ postTypes?: string[];
196
+ icon?: ComponentType<any>;
197
+ component?: ComponentType<{
198
+ row: PostListRow;
199
+ navigate?: (path: string) => void;
200
+ }>;
201
+ onClick?: (row: PostListRow, navigate: (path: string) => void) => void;
202
+ priority?: number;
203
+ }
204
+ /** Danh sách khác (ngoài bài viết) cho plugin mở rộng: người dùng, bình luận, media. */
205
+ export type AdminListName = "users" | "comments" | "media";
206
+ /** Mẫu (pattern): đoạn nội dung dựng sẵn, chèn = chép khối vào bài. */
207
+ export interface EditorPattern {
208
+ /** "slug-plugin/tên". */
209
+ name: string;
210
+ /** Chuỗi thường hoặc "%khoá%" (locales/ của plugin). */
211
+ title: string;
212
+ description?: string;
213
+ /** hero, features, cta, pricing, testimonials, faq, team, contact, text, gallery, header, footer… */
214
+ category: string;
215
+ keywords?: string;
216
+ /** Cây khối { type: "doc", content }, mảng khối, hoặc { version, blocks } (@ecomcx/nexus-content). */
217
+ content: unknown;
218
+ }
219
+ /** Cột thêm vào danh sách người dùng / bình luận / media. row = một dòng của API admin. */
220
+ export interface ListColumn {
221
+ list: AdminListName;
222
+ id: string;
223
+ title: string;
224
+ width?: number;
225
+ align?: "left" | "right" | "center";
226
+ component: ComponentType<{
227
+ row: any;
228
+ }>;
229
+ }
230
+ /** Nút trên từng dòng của danh sách người dùng / bình luận / media (user_row_actions, comment_row_actions…). */
231
+ export interface ListRowAction {
232
+ list: AdminListName;
233
+ id: string;
234
+ title: string;
235
+ icon?: ComponentType<any>;
236
+ component?: ComponentType<{
237
+ row: any;
238
+ navigate?: (path: string) => void;
239
+ }>;
240
+ onClick?: (row: any, navigate: (path: string) => void) => void;
241
+ priority?: number;
242
+ }
243
+ /** Thao tác hàng loạt trên danh sách người dùng / bình luận / media. */
244
+ export interface ListBulkAction {
245
+ list: AdminListName;
246
+ id: string;
247
+ title: string;
248
+ icon?: ComponentType<any>;
249
+ /** Hỏi lại trước khi chạy ({0} = số mục). */
250
+ confirm?: string;
251
+ run(ids: number[]): Promise<void | string>;
252
+ priority?: number;
253
+ }
254
+ /** Thao tác hàng loạt trên danh sách bài (hiện khi chọn nhiều dòng). */
255
+ export interface PostBulkAction {
256
+ id: string;
257
+ title: string;
258
+ postTypes?: string[];
259
+ icon?: ComponentType<any>;
260
+ /** Hỏi lại trước khi chạy (vd "Đặt hết hàng cho {0} sản phẩm?" — {0} = số mục). */
261
+ confirm?: string;
262
+ /** Gọi API của plugin; ném lỗi để báo thất bại. Xong thì danh sách tự tải lại. */
263
+ run(ids: number[]): Promise<void | string>;
264
+ priority?: number;
265
+ }
266
+ /** Bộ lọc thêm vào danh sách bài: chọn một giá trị → chỉ hiện bài có meta[metaKey] == giá trị đó. */
267
+ export interface PostListFilter {
268
+ id: string;
269
+ /** Nhãn của lựa chọn "tất cả", vd "Mọi tình trạng kho". */
270
+ label: string;
271
+ postTypes?: string[];
272
+ metaKey: string;
273
+ /** value là JSON ("true", "12", "\"instock\"") hoặc chuỗi thường. */
274
+ options: Array<{
275
+ value: string;
276
+ label: string;
277
+ }>;
278
+ }
279
+ export interface UserProfileBoxProps {
280
+ user: {
281
+ id: number;
282
+ userName: string;
283
+ email: string;
284
+ displayName: string;
285
+ roles: string[];
286
+ };
287
+ /** Meta của người dùng (trừ khoá bắt đầu bằng "_" — dữ liệu riêng của plugin). */
288
+ meta: Record<string, any>;
289
+ /** Lưu ngay các khoá (null = xoá). Trả về meta mới. */
290
+ saveMeta(patch: Record<string, any>): Promise<Record<string, any>>;
291
+ /** Đang xem hồ sơ của chính mình. */
292
+ isSelf: boolean;
293
+ }
294
+ /** Hộp thêm vào trang sửa thành viên và "Hồ sơ của bạn" (dữ liệu lưu trong user meta). */
295
+ export interface UserProfileBox {
296
+ id: string;
297
+ title: string;
298
+ description?: string;
299
+ /** Chỉ hiện với thành viên có một trong các vai trò này (bỏ trống = mọi người). */
300
+ roles?: string[];
301
+ component: ComponentType<UserProfileBoxProps>;
302
+ priority?: number;
303
+ }
304
+ export interface MetaBoxesProps {
305
+ postType: string;
306
+ context?: "main" | "side" | "normal" | "all";
307
+ post: {
308
+ id?: number;
309
+ title: string;
310
+ slug: string;
311
+ status: string;
312
+ meta: Record<string, any>;
313
+ [k: string]: any;
314
+ };
315
+ meta: Record<string, any>;
316
+ setMeta(key: string, value: any, opts?: {
317
+ silent?: boolean;
318
+ }): void;
319
+ content?: any;
320
+ excerpt?: string;
321
+ link?: string;
322
+ siteUrl?: string;
323
+ locale?: string;
324
+ featuredMedia?: any;
325
+ terms?: Record<string, number[]>;
326
+ excludeIds?: string[];
327
+ className?: string;
328
+ }
329
+ export interface AdminHookRegistry {
330
+ addAction(hook: string, namespace: string, callback: (...args: any[]) => void, priority?: number): void;
331
+ doAction(hook: string, ...args: any[]): void;
332
+ addFilter<T = any>(filter: string, namespace: string, callback: (value: T, ...args: any[]) => T, priority?: number): void;
333
+ applyFilters<T = any>(filter: string, value: T, ...args: any[]): T;
334
+ removeAction(hook: string, namespace: string): void;
335
+ removeFilter(filter: string, namespace: string): void;
336
+ hasAction(hook: string, namespace?: string): boolean;
337
+ hasFilter(filter: string, namespace?: string): boolean;
338
+ }
339
+ type Placement = "top" | "bottom" | "left" | "right" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | "right-start" | "right-end";
340
+ /**
341
+ * Bộ component UI của admin — plugin dùng để giao diện đồng bộ với admin. Xem trực tiếp mọi component tại trang /ui-kit trong admin.
342
+ * Mục đánh dấu "UI kit v2" chỉ có trên CMS mới (từ 22/10/2026); SDK tự thay bằng bản đơn giản khi CMS cũ chưa có.
343
+ */
344
+ export interface UiKit {
345
+ Button: ComponentType<{
346
+ variant?: "primary" | "secondary" | "danger" | "ghost";
347
+ size?: "sm" | "md";
348
+ loading?: boolean;
349
+ icon?: ReactNode;
350
+ } & React.ButtonHTMLAttributes<HTMLButtonElement>>;
351
+ Input: ComponentType<React.InputHTMLAttributes<HTMLInputElement> & {
352
+ ref?: React.Ref<HTMLInputElement>;
353
+ }>;
354
+ Textarea: ComponentType<React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
355
+ ref?: React.Ref<HTMLTextAreaElement>;
356
+ }>;
357
+ Select: ComponentType<React.SelectHTMLAttributes<HTMLSelectElement> & {
358
+ options: Array<{
359
+ value: string;
360
+ label: string;
361
+ }>;
362
+ }>;
363
+ /** `ariaLabel` khi không có nhãn hiện; `indeterminate` = chọn một phần. */
364
+ Checkbox: ComponentType<{
365
+ checked: boolean;
366
+ onChange(checked: boolean): void;
367
+ label?: ReactNode;
368
+ disabled?: boolean;
369
+ description?: ReactNode;
370
+ ariaLabel?: string;
371
+ indeterminate?: boolean;
372
+ }>;
373
+ /** Nhãn + chú thích/lỗi tự gắn vào ô nhập đầu tiên bên trong (trình đọc màn hình). */
374
+ Field: ComponentType<{
375
+ label: ReactNode;
376
+ help?: ReactNode;
377
+ error?: ReactNode;
378
+ children: ReactNode;
379
+ className?: string;
380
+ }>;
381
+ Card: ComponentType<{
382
+ title?: ReactNode;
383
+ description?: ReactNode;
384
+ actions?: ReactNode;
385
+ children: ReactNode;
386
+ className?: string;
387
+ padded?: boolean;
388
+ }>;
389
+ PageHeader: ComponentType<{
390
+ title: ReactNode;
391
+ description?: ReactNode;
392
+ actions?: ReactNode;
393
+ }>;
394
+ /** Bảng. Sắp xếp: cột `sortable` + `sort`/`onSort`; ẩn cột + mật độ: lấy từ `useTableSettings`. */
395
+ Table: ComponentType<{
396
+ columns: Column<any>[];
397
+ rows: any[];
398
+ rowKey?: (row: any) => string | number;
399
+ empty?: ReactNode;
400
+ loading?: boolean;
401
+ selectable?: boolean;
402
+ selected?: Set<string | number>;
403
+ onSelect?(s: Set<string | number>): void;
404
+ onRowClick?(row: any): void;
405
+ sort?: SortState;
406
+ onSort?(s: SortState): void;
407
+ hiddenColumns?: string[];
408
+ density?: Density;
409
+ ariaLabel?: string;
410
+ }>;
411
+ Badge: ComponentType<{
412
+ tone?: "gray" | "green" | "yellow" | "red" | "blue" | "purple";
413
+ children: ReactNode;
414
+ dot?: boolean;
415
+ }>;
416
+ /** Hộp thoại (= Dialog): giữ focus bên trong, Esc / bấm ra ngoài để đóng, trả focus khi đóng. */
417
+ Modal: ComponentType<{
418
+ open: boolean;
419
+ title: ReactNode;
420
+ onClose(): void;
421
+ children: ReactNode;
422
+ footer?: ReactNode;
423
+ width?: number;
424
+ description?: ReactNode;
425
+ dismissOnBackdrop?: boolean;
426
+ }>;
427
+ Spinner: ComponentType<{
428
+ className?: string;
429
+ }>;
430
+ EmptyState: ComponentType<{
431
+ title: ReactNode;
432
+ description?: ReactNode;
433
+ action?: ReactNode;
434
+ icon?: ReactNode;
435
+ }>;
436
+ MediaPicker: ComponentType<{
437
+ value?: {
438
+ id: number;
439
+ url: string;
440
+ } | null;
441
+ onChange(value: {
442
+ id: number;
443
+ url: string;
444
+ } | null): void;
445
+ accept?: string;
446
+ }>;
447
+ /** Có `children` thì hiện nhãn bên cạnh; không thì cần `label` (đọc cho trình đọc màn hình). */
448
+ Switch: ComponentType<{
449
+ checked: boolean;
450
+ onChange(v: boolean): void;
451
+ disabled?: boolean;
452
+ label?: string;
453
+ children?: ReactNode;
454
+ description?: ReactNode;
455
+ }>;
456
+ IconButton: ComponentType<{
457
+ label: string;
458
+ active?: boolean;
459
+ children: ReactNode;
460
+ } & React.ButtonHTMLAttributes<HTMLButtonElement>>;
461
+ Section: ComponentType<{
462
+ title: ReactNode;
463
+ children: ReactNode;
464
+ defaultOpen?: boolean;
465
+ actions?: ReactNode;
466
+ }>;
467
+ Tabs: ComponentType<{
468
+ value: string;
469
+ onChange(v: string): void;
470
+ items: Array<{
471
+ value: string;
472
+ label: ReactNode;
473
+ count?: number;
474
+ }>;
475
+ }>;
476
+ MetaBoxes: ComponentType<MetaBoxesProps>;
477
+ Slot: ComponentType<{
478
+ name: string;
479
+ props?: any;
480
+ fallback?: ReactNode;
481
+ postType?: string;
482
+ className?: string;
483
+ }>;
484
+ Fill: ComponentType<{
485
+ name: string;
486
+ priority?: number;
487
+ children: ReactNode;
488
+ }>;
489
+ Dialog?: UiKit["Modal"];
490
+ Drawer?: ComponentType<{
491
+ open: boolean;
492
+ title: ReactNode;
493
+ onClose(): void;
494
+ children: ReactNode;
495
+ footer?: ReactNode;
496
+ width?: number;
497
+ side?: "left" | "right";
498
+ description?: ReactNode;
499
+ }>;
500
+ Popover?: ComponentType<{
501
+ trigger: (p: {
502
+ open: boolean;
503
+ toggle(): void;
504
+ }) => ReactNode;
505
+ children: (close: () => void) => ReactNode;
506
+ align?: "start" | "end";
507
+ className?: string;
508
+ placement?: Placement;
509
+ }>;
510
+ /** Menu thả xuống chuẩn bàn phím: `trigger` là một phần tử (Button/IconButton). */
511
+ DropdownMenu?: ComponentType<{
512
+ trigger: React.ReactElement;
513
+ children: ReactNode;
514
+ align?: "start" | "end";
515
+ className?: string;
516
+ placement?: Placement;
517
+ }>;
518
+ MenuItem?: ComponentType<{
519
+ icon?: ReactNode;
520
+ children: ReactNode;
521
+ onClick?(): void;
522
+ danger?: boolean;
523
+ shortcut?: ReactNode;
524
+ disabled?: boolean;
525
+ keepOpen?: boolean;
526
+ }>;
527
+ MenuSeparator?: ComponentType<{}>;
528
+ MenuLabel?: ComponentType<{
529
+ children: ReactNode;
530
+ }>;
531
+ /** Chú thích khi rê chuột / focus bàn phím; con phải nhận focus được. */
532
+ Tooltip?: ComponentType<{
533
+ content: ReactNode;
534
+ children: React.ReactElement;
535
+ placement?: Placement;
536
+ delay?: number;
537
+ }>;
538
+ Skeleton?: ComponentType<{
539
+ className?: string;
540
+ }>;
541
+ /** Chọn một trong danh sách dài, gõ để lọc (không dấu). `onSearch` để tải gợi ý từ máy chủ. */
542
+ Combobox?: ComponentType<{
543
+ value: string | null;
544
+ onChange(v: string | null): void;
545
+ options: UiOption[];
546
+ placeholder?: string;
547
+ disabled?: boolean;
548
+ ariaLabel?: string;
549
+ onSearch?(q: string): void;
550
+ clearable?: boolean;
551
+ className?: string;
552
+ emptyText?: ReactNode;
553
+ }>;
554
+ /** Chọn nhiều / nhập thẻ: có `options` là MultiSelect, `creatable` cho gõ giá trị mới (Enter hoặc dấu phẩy). */
555
+ TagInput?: ComponentType<{
556
+ value: string[];
557
+ onChange(v: string[]): void;
558
+ options?: UiOption[];
559
+ creatable?: boolean;
560
+ placeholder?: string;
561
+ ariaLabel?: string;
562
+ disabled?: boolean;
563
+ max?: number;
564
+ onSearch?(q: string): void;
565
+ }>;
566
+ /** Ngày "YYYY-MM-DD"; `withTime`: "YYYY-MM-DDTHH:mm" giờ địa phương (đổi với ISO bằng `isoToLocal` / `localToIso`). */
567
+ DatePicker?: ComponentType<{
568
+ value: string | null;
569
+ onChange(v: string | null): void;
570
+ withTime?: boolean;
571
+ placeholder?: string;
572
+ ariaLabel?: string;
573
+ disabled?: boolean;
574
+ min?: string;
575
+ max?: string;
576
+ clearable?: boolean;
577
+ }>;
578
+ RadioGroup?: ComponentType<{
579
+ value: string;
580
+ onChange(v: string): void;
581
+ options: Array<{
582
+ value: string;
583
+ label: ReactNode;
584
+ description?: ReactNode;
585
+ disabled?: boolean;
586
+ }>;
587
+ ariaLabel?: string;
588
+ variant?: "list" | "cards" | "segmented";
589
+ disabled?: boolean;
590
+ }>;
591
+ TreeView?: ComponentType<{
592
+ nodes: TreeNode[];
593
+ ariaLabel: string;
594
+ checkable?: boolean;
595
+ checked?: Array<string | number>;
596
+ onCheck?(id: string | number, value: boolean): void;
597
+ selected?: string | number | null;
598
+ onSelect?(id: string | number): void;
599
+ defaultExpanded?: "all" | "none" | Array<string | number>;
600
+ className?: string;
601
+ }>;
602
+ Pagination?: ComponentType<{
603
+ page: number;
604
+ totalPages: number;
605
+ onPage(p: number): void;
606
+ total?: number;
607
+ pageSize?: number;
608
+ onPageSize?(n: number): void;
609
+ pageSizes?: number[];
610
+ }>;
611
+ TableSettingsMenu?: ComponentType<{
612
+ columns: Array<{
613
+ key: string;
614
+ title: ReactNode;
615
+ }>;
616
+ settings: TableSettings;
617
+ }>;
618
+ useTableSettings?: (key: string, defaults?: Partial<Pick<TableSettings, "hidden" | "density">>) => TableSettings;
619
+ isoToLocal?: (iso?: string | null) => string | null;
620
+ localToIso?: (value: string | null) => string | null;
621
+ confirmDialog?: (message: ReactNode, opts?: AskOptions) => Promise<boolean>;
622
+ promptDialog?: (message: ReactNode, opts?: AskOptions & {
623
+ defaultValue?: string;
624
+ placeholder?: string;
625
+ }) => Promise<string | null>;
626
+ alertDialog?: (message: ReactNode, opts?: AskOptions) => Promise<void>;
627
+ }
628
+ export interface CmsAdmin {
629
+ version: string;
630
+ React: typeof import("react");
631
+ jsxRuntime: typeof import("react/jsx-runtime");
632
+ api: ApiClient;
633
+ notify: Notify;
634
+ ui: UiKit;
635
+ hooks: AdminHookRegistry;
636
+ registerPage(page: AdminPage): void;
637
+ registerMetaBox(box: MetaBox): void;
638
+ /** Sidebar riêng trong trình soạn bài. */
639
+ registerEditorSidebar?(sidebar: EditorSidebar): void;
640
+ registerDashboardWidget(widget: DashboardWidget): void;
641
+ registerPostListColumn(column: PostListColumn): void;
642
+ registerTermMetaBox(box: TermMetaBox): void;
643
+ registerSlot(slot: AdminSlotItem): void;
644
+ getSlots(slot: string, postType?: string): Array<AdminSlotItem>;
645
+ registerPostRowAction(action: PostRowAction): void;
646
+ registerPostBulkAction?(action: PostBulkAction): void;
647
+ registerPostListFilter?(filter: PostListFilter): void;
648
+ registerUserProfileBox?(box: UserProfileBox): void;
649
+ registerListColumn?(column: ListColumn): void;
650
+ registerListRowAction?(action: ListRowAction): void;
651
+ registerListBulkAction?(action: ListBulkAction): void;
652
+ getPostRowActions(postType?: string): Array<PostRowAction>;
653
+ getMetaBoxes?(postType?: string): MetaBox[];
654
+ usePluginUi?(): any;
655
+ /** Hook React lấy dữ liệu đơn giản: const { data, loading, error, reload } = useApi("/admin/...") */
656
+ useApi<T = any>(path: string | null): {
657
+ data: T | undefined;
658
+ loading: boolean;
659
+ error: Error | null;
660
+ reload(): void;
661
+ };
662
+ currentUser(): {
663
+ id: number;
664
+ displayName: string;
665
+ capabilities: string[];
666
+ } | null;
667
+ can(capability: string): boolean;
668
+ /** Hàm công bố cho plugin khác dùng trong admin (xem exposeApi/getPluginApi). */
669
+ plugins?: {
670
+ expose(slug: string, api: Record<string, unknown>): void;
671
+ get<T = Record<string, unknown>>(slug: string): T | undefined;
672
+ wait<T = Record<string, unknown>>(slug: string, timeoutMs?: number): Promise<T | undefined>;
673
+ };
674
+ /** Dịch giao diện: ngôn ngữ admin hiện tại ("vi" | "en"), hàm dịch theo khoá (ICU) và bổ sung bản dịch của plugin. */
675
+ i18n: {
676
+ locale: string;
677
+ /** "slug:khoá" của plugin, hoặc khoá của lõi. Thiếu bản dịch: ngôn ngữ đang chọn → en → vi → hiện khoá. */
678
+ t?(key: string, values?: Record<string, unknown>): string;
679
+ registerMessages?(namespace: string, locale: string, dict: Record<string, string>): void;
680
+ /** @deprecated Chuỗi gốc làm khoá — dùng t(). */
681
+ __(text: string, ...args: unknown[]): string;
682
+ /** @deprecated Dùng registerMessages / locales/*.json. */
683
+ registerTranslations(locale: string, dict: Record<string, string>): void;
684
+ };
685
+ /** Trình soạn nội dung (@ecomcx/nexus-editor): thêm khối/lệnh chèn; dùng chung TipTap của admin. */
686
+ editor: {
687
+ register(pack: EditorExtensionPack): void;
688
+ /** Khối khai báo bằng defineBlock (CMS ≥ 1.8.0 / admin có @ecomcx/nexus-editor ≥ 1.1). */
689
+ registerBlock?(def: import("@ecomcx/nexus-content").BlockDefinition, options?: RegisterBlockOptions): void;
690
+ /** Panel trong tab "Khối" của trình soạn (CMS ≥ 1.10.0). */
691
+ registerBlockPanel?(panel: BlockPanel): void;
692
+ /** Mẫu — đoạn nội dung dựng sẵn trong tab "Mẫu" của trình chèn (CMS ≥ 1.12.0). */
693
+ registerPattern?(pattern: EditorPattern): void;
694
+ tiptap: {
695
+ Node: any;
696
+ Mark: any;
697
+ Extension: any;
698
+ mergeAttributes: (...attrs: any[]) => Record<string, any>;
699
+ };
700
+ };
701
+ }
702
+ /** Tuỳ chọn khi đăng ký khối: biểu tượng (component lucide), phần xem trước tuỳ chỉnh trong trình soạn. */
703
+ export interface RegisterBlockOptions {
704
+ icon?: any;
705
+ edit?: import("react").ComponentType<{
706
+ attrs: Record<string, any>;
707
+ setAttrs(attrs: Record<string, unknown>): void;
708
+ selected: boolean;
709
+ children?: import("react").ReactNode;
710
+ }>;
711
+ }
712
+ /** Props của panel plugin trong tab "Khối". */
713
+ export interface BlockPanelProps {
714
+ /** Loại khối đang chọn (vd "image", "button", "shop/product"). */
715
+ type: string;
716
+ attrs: Record<string, any>;
717
+ /** Đổi thuộc tính của khối (thuộc tính sẵn có hoặc đã khai báo ở `attributes`). */
718
+ setAttrs(attrs: Record<string, unknown>): void;
719
+ }
720
+ /**
721
+ * Panel thêm vào tab "Khối" của trình soạn khi chọn một khối — cho khối của chính plugin
722
+ * hoặc khối lõi / khối plugin khác.
723
+ */
724
+ export interface BlockPanel {
725
+ /** Mã duy nhất dạng "slug-plugin/tên" — tiền tố slug dùng để dịch nhãn "%khoá%". */
726
+ id: string;
727
+ /** Tiêu đề mục ("%khoá%" dịch theo locales của plugin). */
728
+ title: string;
729
+ /** Loại khối áp dụng; bỏ trống = mọi khối. */
730
+ blocks?: string[];
731
+ /** Thứ tự (nhỏ đứng trước, mặc định 10). */
732
+ order?: number;
733
+ /** Thuộc tính thêm vào khối, vd { lazy: { default: true } } — cần khai báo để trình soạn lưu được (bắt buộc có `blocks`). */
734
+ attributes?: Record<string, {
735
+ default?: unknown;
736
+ }>;
737
+ component: import("react").ComponentType<BlockPanelProps>;
738
+ }
739
+ /** Gói mở rộng trình soạn: node/mark TipTap + lệnh chèn trong slash menu "/". */
740
+ export interface EditorExtensionPack {
741
+ name: string;
742
+ extensions?: any[];
743
+ insertItems?: Array<{
744
+ key: string;
745
+ title: string;
746
+ hint: string;
747
+ icon: any;
748
+ keywords: string;
749
+ run(editor: any, range?: any): void;
750
+ }>;
751
+ }
752
+ export {};