@fui-org/fui-cli 1.3.2 → 2.1.0

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.
Files changed (115) hide show
  1. package/README.md +21 -3
  2. package/dist/fui-cx180e19.js +379 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +3 -6
  5. package/skills/fui/SKILL.md +9 -41
  6. package/skills/fui-skill/SKILL.md +95 -225
  7. package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
  8. package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
  9. package/skills/fui-skill/references/INDEX.md +105 -137
  10. package/skills/fui-skill/references/advanced-techniques.md +76 -68
  11. package/skills/fui-skill/references/coding-standards.md +56 -56
  12. package/skills/fui-skill/references/component-design.md +166 -173
  13. package/skills/fui-skill/references/component-quickref.md +61 -60
  14. package/skills/fui-skill/references/component-table.md +128 -117
  15. package/skills/fui-skill/references/components-dialog.md +55 -56
  16. package/skills/fui-skill/references/components-display.md +24 -30
  17. package/skills/fui-skill/references/components-echart.md +186 -261
  18. package/skills/fui-skill/references/components-input.md +72 -96
  19. package/skills/fui-skill/references/controls-patterns.md +196 -342
  20. package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
  21. package/skills/fui-skill/references/db-table-design.md +24 -28
  22. package/skills/fui-skill/references/db-workflow.md +191 -390
  23. package/skills/fui-skill/references/default-function.md +169 -128
  24. package/skills/fui-skill/references/design-modes.md +35 -63
  25. package/skills/fui-skill/references/echart-templates.md +204 -196
  26. package/skills/fui-skill/references/fastproject.md +62 -60
  27. package/skills/fui-skill/references/fsheet.md +109 -124
  28. package/skills/fui-skill/references/fullstack-workflow.md +90 -128
  29. package/skills/fui-skill/references/module-data-patterns.md +31 -40
  30. package/skills/fui-skill/references/module-json-anatomy.md +47 -52
  31. package/skills/fui-skill/references/module-structure.md +80 -196
  32. package/skills/fui-skill/references/new-session.md +49 -51
  33. package/skills/fui-skill/references/pdfmake.md +17 -17
  34. package/skills/fui-skill/references/permission-system.md +89 -108
  35. package/skills/fui-skill/references/platform-architecture.md +128 -153
  36. package/skills/fui-skill/references/project-config.md +102 -134
  37. package/skills/fui-skill/references/project-provisioning.md +139 -244
  38. package/skills/fui-skill/references/script-map.md +208 -242
  39. package/skills/fui-skill/references/sql-clr-functions.md +98 -97
  40. package/skills/fui-skill/references/system-design.md +63 -88
  41. package/skills/fui-skill/references/tapi-file-api.md +46 -52
  42. package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
  43. package/skills/fui-skill/references/tapi-reference.md +132 -207
  44. package/skills/fui-skill/references/tools-registry.md +84 -460
  45. package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
  46. package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
  47. package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
  48. package/skills/fui-skill/references/ui-patterns.md +71 -83
  49. package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
  50. package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
  51. package/skills/fui-skill/references/ui-templates.md +16 -23
  52. package/skills/fui-skill/references/verification.md +236 -246
  53. package/skills/fui-skill/references/watcher-patterns.md +30 -63
  54. package/skills/fui-skill/references/websocket-realtime.md +83 -66
  55. package/skills/fui-skill/scripts/component-3.0.js +298 -131
  56. package/skills/fui-skill/scripts/component.js +277 -271
  57. package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
  58. package/skills/fui-skill/scripts/componentTable.js +171 -49
  59. package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
  60. package/skills/fui-skill/scripts/defaultfunction.js +88 -3
  61. package/skills/fui-skill/scripts/fsheet.js +38 -0
  62. package/skills/fui-target/.claude-plugin/plugin.json +6 -0
  63. package/skills/fui-target/hooks/hooks.json +1 -0
  64. package/skills/fui-target/hooks/register.tsx +71 -0
  65. package/skills/fui-target/types/index.d.ts +7 -0
  66. package/dist/fui-y8an39cn.js +0 -420
  67. package/skills/fui-skill/README.md +0 -112
  68. package/skills/fui-skill/metadata.json +0 -75
  69. package/skills/fui-skill-cli/SKILL.md +0 -139
  70. package/skills/fui-skill-cli/references/INDEX.md +0 -110
  71. package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
  72. package/skills/fui-skill-cli/references/coding-standards.md +0 -112
  73. package/skills/fui-skill-cli/references/component-design.md +0 -448
  74. package/skills/fui-skill-cli/references/component-quickref.md +0 -78
  75. package/skills/fui-skill-cli/references/component-table.md +0 -248
  76. package/skills/fui-skill-cli/references/components-dialog.md +0 -191
  77. package/skills/fui-skill-cli/references/components-display.md +0 -141
  78. package/skills/fui-skill-cli/references/components-echart.md +0 -316
  79. package/skills/fui-skill-cli/references/components-input.md +0 -335
  80. package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
  81. package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
  82. package/skills/fui-skill-cli/references/db-table-design.md +0 -73
  83. package/skills/fui-skill-cli/references/db-workflow.md +0 -288
  84. package/skills/fui-skill-cli/references/default-function.md +0 -425
  85. package/skills/fui-skill-cli/references/design-modes.md +0 -57
  86. package/skills/fui-skill-cli/references/echart-templates.md +0 -489
  87. package/skills/fui-skill-cli/references/fastproject.md +0 -99
  88. package/skills/fui-skill-cli/references/fsheet.md +0 -203
  89. package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
  90. package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
  91. package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
  92. package/skills/fui-skill-cli/references/module-structure.md +0 -141
  93. package/skills/fui-skill-cli/references/new-session.md +0 -85
  94. package/skills/fui-skill-cli/references/pdfmake.md +0 -60
  95. package/skills/fui-skill-cli/references/permission-system.md +0 -150
  96. package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
  97. package/skills/fui-skill-cli/references/project-config.md +0 -303
  98. package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
  99. package/skills/fui-skill-cli/references/script-map.md +0 -262
  100. package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
  101. package/skills/fui-skill-cli/references/system-design.md +0 -89
  102. package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
  103. package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
  104. package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
  105. package/skills/fui-skill-cli/references/tools-registry.md +0 -84
  106. package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
  107. package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
  108. package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
  109. package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
  110. package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
  111. package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
  112. package/skills/fui-skill-cli/references/ui-templates.md +0 -22
  113. package/skills/fui-skill-cli/references/verification.md +0 -236
  114. package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
  115. package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
@@ -475,6 +475,10 @@ function jsonToExcel(Obj) {
475
475
  filename: "dataFP.xlsx", // filename to export
476
476
  worksheet: "SheetFP", // sheet name for single sheet
477
477
  sheets: null, // New property for multiple sheets
478
+ // optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
479
+ // Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
480
+ // không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
481
+ columns: null,
478
482
  onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
479
483
  showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
480
484
  }
@@ -487,7 +491,7 @@ function jsonToExcel(Obj) {
487
491
  if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
488
492
  } else {
489
493
  if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
490
- sheetList = [{ name: props.worksheet, data: props.data }];
494
+ sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
491
495
  }
492
496
 
493
497
  var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
@@ -520,15 +524,18 @@ function jsonToExcel(Obj) {
520
524
  if (sheetIndex >= sheetList.length) { finalize(); return; }
521
525
  var sheetCfg = sheetList[sheetIndex];
522
526
  var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
527
+ var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
523
528
  // Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
524
- worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
529
+ if (cols) setupColumns(worksheet, cols, sheetCfg.data);
530
+ else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
525
531
 
526
532
  var rows = sheetCfg.data;
527
533
  var i = 0;
528
534
  function nextChunk() {
529
535
  try {
530
536
  var end = Math.min(i + CHUNK_SIZE, rows.length);
531
- for (var r = i; r < end; r++) { worksheet.addRow(rows[r]); }
537
+ for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
538
+ if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
532
539
  processedRows += (end - i);
533
540
  i = end;
534
541
  report('building', sheetCfg.name);
@@ -574,6 +581,84 @@ function jsonToExcel(Obj) {
574
581
  buildSheet(0);
575
582
  }
576
583
 
584
+ //---------------------------------------------------- chế độ `columns`
585
+ function readColumnValue(row, key) {
586
+ if (row === null || row === undefined) return null;
587
+ if (row[key] !== undefined) return row[key];
588
+ return _.get(row, key, null);
589
+ }
590
+ // ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
591
+ // sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
592
+ function toExcelDate(v) {
593
+ if (v === null || v === undefined || v === '') return null;
594
+ var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
595
+ if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
596
+ var d = v instanceof Date ? v : new Date(v);
597
+ if (isNaN(d.getTime())) return v;
598
+ return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
599
+ }
600
+ function htmlToText(v) {
601
+ var s = String(v);
602
+ if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
603
+ // DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
604
+ return new DOMParser().parseFromString(s, 'text/html').body.textContent;
605
+ }
606
+ function convertCellValue(col, v) {
607
+ if (v === undefined) return null;
608
+ if (col.type === 'date') return toExcelDate(v);
609
+ if (v === null) return null;
610
+ if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
611
+ if (col.type === 'html') return htmlToText(v);
612
+ // object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
613
+ if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
614
+ return v;
615
+ }
616
+ function columnRowValues(cols, row) {
617
+ var out = new Array(cols.length);
618
+ for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
619
+ return out;
620
+ }
621
+ function estimateWidth(col, rows) {
622
+ var w = String(col.header != null ? col.header : col.key).length;
623
+ if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
624
+ else {
625
+ for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
626
+ var v = readColumnValue(rows[i], col.key);
627
+ if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
628
+ }
629
+ }
630
+ return Math.min(60, Math.max(8, w + 2));
631
+ }
632
+ function setupColumns(worksheet, cols, rows) {
633
+ // key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
634
+ worksheet.columns = cols.map(function (c, idx) {
635
+ var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
636
+ if (c.numFmt) def.style = { numFmt: c.numFmt };
637
+ return def;
638
+ });
639
+ worksheet.getRow(1).font = { bold: true };
640
+ worksheet.views = [{ state: 'frozen', ySplit: 1 }];
641
+ }
642
+ // Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
643
+ // tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
644
+ function appendSumRow(worksheet, cols, rows) {
645
+ if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
646
+ var row = worksheet.addRow(cols.map(function (c, idx) {
647
+ if (!c.sum) return null;
648
+ var total = 0;
649
+ for (var i = 0; i < rows.length; i++) {
650
+ var n = Number(readColumnValue(rows[i], c.key));
651
+ if (!isNaN(n)) total += n;
652
+ }
653
+ var letter = worksheet.getColumn(idx + 1).letter;
654
+ return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
655
+ }));
656
+ row.font = { bold: true };
657
+ cols.forEach(function (c, idx) {
658
+ if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
659
+ });
660
+ }
661
+
577
662
  // Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
578
663
  // — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
579
664
  function createProgressUI() {
@@ -660,6 +660,10 @@ function jsonToExcel(Obj) {
660
660
  filename: "dataFP.xlsx", // filename to export
661
661
  worksheet: "SheetFP", // sheet name for single sheet
662
662
  sheets: null, // New property for multiple sheets
663
+ // optional: [{header, key, type?: 'number'|'date'|'html', numFmt?, width?, sum?: true|numFmt}]
664
+ // Có columns thì cột/tiêu đề/định dạng theo đúng danh sách này (dùng cho f-table excel="UI");
665
+ // không có thì giữ hành vi cũ: cột = key của dòng đầu, giá trị thô. Mỗi sheet có thể có columns riêng.
666
+ columns: null,
663
667
  onProgress: null, // optional: function({percent, processedRows, totalRows, phase, sheetName}) - phase: 'building'|'finalizing'|'done'|'error'
664
668
  showProgress: true // tự hiện thanh % cố định giữa màn hình khi xuất; truyền false để tắt UI này
665
669
  }
@@ -672,7 +676,7 @@ function jsonToExcel(Obj) {
672
676
  if (!sheetList.length) { showMessage({ title: "Sheets data is empty" }); return; }
673
677
  } else {
674
678
  if (!props.data || !props.data.length) { showMessage({ title: "Data Null" }); return; }
675
- sheetList = [{ name: props.worksheet, data: props.data }];
679
+ sheetList = [{ name: props.worksheet, data: props.data, columns: props.columns }];
676
680
  }
677
681
 
678
682
  var totalRows = sheetList.reduce(function (sum, s) { return sum + s.data.length; }, 0);
@@ -705,15 +709,18 @@ function jsonToExcel(Obj) {
705
709
  if (sheetIndex >= sheetList.length) { finalize(); return; }
706
710
  var sheetCfg = sheetList[sheetIndex];
707
711
  var worksheet = workbook.addWorksheet(sheetCfg.name || 'Sheet');
712
+ var cols = Array.isArray(sheetCfg.columns) && sheetCfg.columns.length ? sheetCfg.columns : null;
708
713
  // Create header (chỉ cần đọc key của dòng đầu, tương đương logic cũ nhưng không lặp check index===0 mỗi vòng)
709
- worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
714
+ if (cols) setupColumns(worksheet, cols, sheetCfg.data);
715
+ else worksheet.columns = Object.keys(sheetCfg.data[0]).map(function (k) { return { header: k, key: k }; });
710
716
 
711
717
  var rows = sheetCfg.data;
712
718
  var i = 0;
713
719
  function nextChunk() {
714
720
  try {
715
721
  var end = Math.min(i + CHUNK_SIZE, rows.length);
716
- for (var r = i; r < end; r++) { worksheet.addRow(rows[r]); }
722
+ for (var r = i; r < end; r++) { worksheet.addRow(cols ? columnRowValues(cols, rows[r]) : rows[r]); }
723
+ if (cols && end === rows.length) appendSumRow(worksheet, cols, rows);
717
724
  processedRows += (end - i);
718
725
  i = end;
719
726
  report('building', sheetCfg.name);
@@ -759,6 +766,84 @@ function jsonToExcel(Obj) {
759
766
  buildSheet(0);
760
767
  }
761
768
 
769
+ //---------------------------------------------------- chế độ `columns`
770
+ function readColumnValue(row, key) {
771
+ if (row === null || row === undefined) return null;
772
+ if (row[key] !== undefined) return row[key];
773
+ return _.get(row, key, null);
774
+ }
775
+ // ExcelJS ghi Date theo UTC → dựng lại mốc UTC từ giờ ĐỊA PHƯƠNG, nếu không ngày 00:00 ở UTC+7
776
+ // sẽ lùi thành 17:00 hôm trước trong Excel. "yyyy-mm-dd" trần thì new Date() hiểu là UTC nên tách riêng.
777
+ function toExcelDate(v) {
778
+ if (v === null || v === undefined || v === '') return null;
779
+ var m = typeof v === 'string' && /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
780
+ if (m) return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
781
+ var d = v instanceof Date ? v : new Date(v);
782
+ if (isNaN(d.getTime())) return v;
783
+ return new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()));
784
+ }
785
+ function htmlToText(v) {
786
+ var s = String(v);
787
+ if (s.indexOf('<') < 0 && s.indexOf('&') < 0) return s;
788
+ // DOMParser không chạy script/handler — innerHTML trên element sống thì <img onerror> VẪN chạy.
789
+ return new DOMParser().parseFromString(s, 'text/html').body.textContent;
790
+ }
791
+ function convertCellValue(col, v) {
792
+ if (v === undefined) return null;
793
+ if (col.type === 'date') return toExcelDate(v);
794
+ if (v === null) return null;
795
+ if (col.type === 'number') return (typeof v === 'string' && v.trim() !== '' && !isNaN(v)) ? Number(v) : v;
796
+ if (col.type === 'html') return htmlToText(v);
797
+ // object/array đưa thẳng vào ExcelJS sẽ bị hiểu nhầm là rich text/hyperlink/formula
798
+ if (typeof v === 'object' && !(v instanceof Date)) return JSON.stringify(v);
799
+ return v;
800
+ }
801
+ function columnRowValues(cols, row) {
802
+ var out = new Array(cols.length);
803
+ for (var c = 0; c < cols.length; c++) out[c] = convertCellValue(cols[c], readColumnValue(row, cols[c].key));
804
+ return out;
805
+ }
806
+ function estimateWidth(col, rows) {
807
+ var w = String(col.header != null ? col.header : col.key).length;
808
+ if (col.type === 'date') w = Math.max(w, String(col.numFmt || 'dd/mm/yyyy').replace(/"/g, '').length);
809
+ else {
810
+ for (var i = 0, n = Math.min(rows.length, 100); i < n; i++) {
811
+ var v = readColumnValue(rows[i], col.key);
812
+ if (v !== null && v !== undefined) w = Math.max(w, String(v).length + (typeof v === 'number' ? 3 : 0));
813
+ }
814
+ }
815
+ return Math.min(60, Math.max(8, w + 2));
816
+ }
817
+ function setupColumns(worksheet, cols, rows) {
818
+ // key nội bộ theo vị trí: hai cột trùng tiêu đề/trùng field vẫn không đè nhau (dòng ghi bằng mảng)
819
+ worksheet.columns = cols.map(function (c, idx) {
820
+ var def = { header: c.header != null ? String(c.header) : String(c.key), key: '__c' + idx, width: c.width || estimateWidth(c, rows) };
821
+ if (c.numFmt) def.style = { numFmt: c.numFmt };
822
+ return def;
823
+ });
824
+ worksheet.getRow(1).font = { bold: true };
825
+ worksheet.views = [{ state: 'frozen', ySplit: 1 }];
826
+ }
827
+ // Hàng tổng = công thức SUM trên chính các dòng đã xuất (kèm kết quả tính sẵn để trình xem không
828
+ // tính công thức vẫn thấy số) — sửa số trong Excel thì tổng tự cập nhật.
829
+ function appendSumRow(worksheet, cols, rows) {
830
+ if (!rows.length || !cols.some(function (c) { return c.sum; })) return;
831
+ var row = worksheet.addRow(cols.map(function (c, idx) {
832
+ if (!c.sum) return null;
833
+ var total = 0;
834
+ for (var i = 0; i < rows.length; i++) {
835
+ var n = Number(readColumnValue(rows[i], c.key));
836
+ if (!isNaN(n)) total += n;
837
+ }
838
+ var letter = worksheet.getColumn(idx + 1).letter;
839
+ return { formula: 'SUM(' + letter + '2:' + letter + (rows.length + 1) + ')', result: total };
840
+ }));
841
+ row.font = { bold: true };
842
+ cols.forEach(function (c, idx) {
843
+ if (typeof c.sum === 'string') row.getCell(idx + 1).numFmt = c.sum;
844
+ });
845
+ }
846
+
762
847
  // Thanh % mặc định, tự tạo/tự dọn bằng DOM thuần (CSS thủ công, không dùng v-dialog/framework nào)
763
848
  // — fix cố định giữa màn hình, không phụ thuộc Vue nên dùng chung được cho cả V2 và V3.
764
849
  function createProgressUI() {
@@ -696,8 +696,35 @@ Vue.component('FSheet', {
696
696
  hit.forEach(function (cell) { cell.classList.remove('ag-cell-copy-flash') })
697
697
  }, 800)
698
698
  },
699
+ // true khi một ô đang ở chế độ sửa (AG Grid đã render <input> thật bên trong và focus
700
+ // vào đó) — dùng ĐÚNG API của AG Grid thay vì đoán qua e.target.tagName, vì cellEditor
701
+ // tuỳ biến có thể không dùng thẻ <input>/<textarea>. Lưới an toàn cho ca cellEditor lạ;
702
+ // ca THƯỜNG GẶP (input chuẩn) đã được isNativeTextTarget() bắt trực tiếp, xem dưới.
703
+ isEditingCell: function () {
704
+ return !!(this.gridApi && this.gridApi.getEditingCells().length > 0)
705
+ },
706
+ // true khi TARGET THẬT của sự kiện copy/paste là một control nhập liệu chuẩn của trình
707
+ // duyệt (input/textarea/contentEditable) — không phải ô lưới. Bắt buộc kiểm theo TARGET,
708
+ // không chỉ theo "đang sửa ô": ô lọc (filter) trong header AG Grid — cả floating filter
709
+ // lẫn popup filter đầy đủ — cũng render một <input> thật NẰM TRONG chính gridEl (fsheet
710
+ // không set popupParent nên popup filter không thoát ra ngoài gridEl), và sự kiện
711
+ // copy/paste của nó nổi bọt lên tới đúng listener này giống hệt input của ô đang sửa.
712
+ // Trước đây chỉ hỏi isEditingCell() nên bắt đúng ca sửa ô nhưng bỏ sót ca filter: đặt con
713
+ // trỏ vào ô filter rồi Ctrl+V lại bị "cướp" xuống dán vào ô đang chọn/focus ở lưới, thay vì
714
+ // dán vào chính ô filter đang gõ.
715
+ isNativeTextTarget: function (e) {
716
+ var t = e && e.target
717
+ if (!t) return false
718
+ var tag = t.tagName
719
+ return tag === 'INPUT' || tag === 'TEXTAREA' || !!t.isContentEditable
720
+ },
699
721
  doCopy: function (e) {
700
722
  var self = this
723
+ // Con trỏ đang ở TRONG một input thật (ô đang sửa, hoặc ô lọc/filter): người dùng có
724
+ // thể chỉ đang copy một PHẦN chữ đã bôi đen trong input đó (không phải cả ô/cả lưới) —
725
+ // để trình duyệt tự xử lý theo cách bình thường, không ghi đè bằng nội dung của cả
726
+ // vùng/ô đang chọn ở tầng lưới.
727
+ if (self.isEditingCell() || self.isNativeTextTarget(e)) return
701
728
  var r = self.currentRangeOrFocus()
702
729
  if (!r) return
703
730
  var text = self.rangeText(r, false)
@@ -772,6 +799,17 @@ Vue.component('FSheet', {
772
799
  doPaste: function (e) {
773
800
  var self = this
774
801
  if (self.readonly) return
802
+ // Con trỏ đang ở TRONG một input thật (ô đang sửa, HOẶC ô lọc/filter trong header):
803
+ // sự kiện 'paste' nổi lên từ chính input đó lên tới gridEl (nơi handler này được gắn),
804
+ // và trước đây bị chặn vô điều kiện bởi e.preventDefault() + pasteText() bên dưới —
805
+ // pasteText() ghi thẳng vào data của Ô ĐANG FOCUS/VÙNG ĐANG CHỌN Ở LƯỚI qua
806
+ // node.setDataValue(), KHÔNG đụng tới input đang hiển thị. Hai triệu chứng khác nhau
807
+ // của cùng một lỗi: (1) đang sửa ô → paste trông như "không có tác dụng gì" (giá trị
808
+ // dán chỉ hiện ra sau khi rời ô sửa, rồi bị input ghi đè lại lúc blur); (2) đang gõ vào
809
+ // ô filter → paste "chạy nhầm chỗ" (dán đè lên ô đang chọn trong lưới thay vì vào ô
810
+ // filter). Phải để trình duyệt tự dán vào đúng vị trí con trỏ trong input đó như một
811
+ // thao tác nhập liệu bình thường.
812
+ if (self.isEditingCell() || self.isNativeTextTarget(e)) return
775
813
  var cb = e.clipboardData || window.clipboardData
776
814
  var text = cb ? cb.getData('text') : ''
777
815
  if (!text) return
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "fui-target",
3
+ "version": "1.0.0",
4
+ "description": "Shows the FUI project and module the fui CLI targets, once a Claude Code session runs fui",
5
+ "types": "./types/index.d.ts"
6
+ }
@@ -0,0 +1 @@
1
+ { "modules": ["./register.tsx"] }
@@ -0,0 +1,71 @@
1
+ import { atom, read, update } from "claude-code";
2
+ import type { Engine, Register } from "claude-code";
3
+
4
+ import type { FuiTarget } from "../types";
5
+
6
+ const target = atom({ plugin: "fui-target", key: "target" } as const, null);
7
+
8
+ const FUI_CALL = /(?:^|[\s;&|(])fui\s+([a-z-]+)(?:\s+([a-z-]+))?/;
9
+
10
+ // Mirrors sessionFile() in src/core/session/state.ts.
11
+ async function sessionFile($: Engine, command: string) {
12
+ const key = command.match(/FUI_SESSION=([A-Za-z0-9_-]+)/)?.[1] ?? (await $.env.get("FUI_SESSION")) ?? "default";
13
+ const dir =
14
+ (await $.env.get("FUI_SESSION_DIR")) ??
15
+ (await $.env.get("FUI_MCP_SESSION_DIR")) ??
16
+ `${await $.env.get("HOME")}/.fui-mcp/session`;
17
+ return `${dir}/cli-${key}.json`;
18
+ }
19
+
20
+ async function sync($: Engine, command: string, lastCommand: string) {
21
+ const text = await $.fs.read(await sessionFile($, command)).catch(() => "");
22
+ let state: { target?: { projectId?: string; moduleId?: string } } = {};
23
+ try {
24
+ state = JSON.parse(text || "{}");
25
+ } catch {}
26
+
27
+ await update($, target, () => ({
28
+ project: state.target?.projectId,
29
+ module: state.target?.moduleId,
30
+ lastCommand,
31
+ }));
32
+ }
33
+
34
+ export const register: Register = (on) => {
35
+ on("tool.call", { tool: "Bash" }, async ($, e, next) => {
36
+ const result = await next(e);
37
+ const call = e.command.match(FUI_CALL);
38
+ if (!call) return result;
39
+
40
+ await sync($, e.command, [call[1], call[2]].filter(Boolean).join(" "));
41
+ return result;
42
+ });
43
+
44
+ on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
45
+ const current: FuiTarget | null = await read($, target);
46
+ if (e.props.hasSurvey || current === null) return next(e);
47
+
48
+ const { Box, Text } = $.ui.resolve(e);
49
+ return (
50
+ <Box flexDirection="row" columnGap={1}>
51
+ <Text>🧩</Text>
52
+ {current.project ? (
53
+ <Text>
54
+ <Text bold color="cyan">
55
+ {current.project}
56
+ </Text>
57
+ {current.module && (
58
+ <Text>
59
+ <Text dimColor> › </Text>
60
+ <Text bold>{current.module}</Text>
61
+ </Text>
62
+ )}
63
+ </Text>
64
+ ) : (
65
+ <Text dimColor>no active module</Text>
66
+ )}
67
+ {current.lastCommand && <Text dimColor> · fui {current.lastCommand}</Text>}
68
+ </Box>
69
+ );
70
+ });
71
+ };
@@ -0,0 +1,7 @@
1
+ export type FuiTarget = { project?: string; module?: string; lastCommand?: string };
2
+
3
+ declare module "claude-code" {
4
+ interface PluginState {
5
+ "fui-target": { target: FuiTarget | null };
6
+ }
7
+ }