underpost 2.8.877 → 2.8.881

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 (54) hide show
  1. package/.env.development +35 -3
  2. package/.env.production +40 -3
  3. package/.env.test +35 -3
  4. package/.github/workflows/release.cd.yml +3 -3
  5. package/README.md +48 -36
  6. package/bin/deploy.js +40 -0
  7. package/cli.md +89 -86
  8. package/conf.js +28 -3
  9. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  10. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  11. package/package.json +1 -2
  12. package/src/api/document/document.controller.js +66 -0
  13. package/src/api/document/document.model.js +51 -0
  14. package/src/api/document/document.router.js +24 -0
  15. package/src/api/document/document.service.js +125 -0
  16. package/src/api/file/file.controller.js +15 -1
  17. package/src/api/user/user.router.js +4 -3
  18. package/src/cli/deploy.js +1 -1
  19. package/src/cli/index.js +3 -0
  20. package/src/cli/repository.js +2 -2
  21. package/src/cli/run.js +29 -1
  22. package/src/client/Default.index.js +42 -1
  23. package/src/client/components/core/Account.js +8 -1
  24. package/src/client/components/core/AgGrid.js +18 -9
  25. package/src/client/components/core/BtnIcon.js +3 -2
  26. package/src/client/components/core/Content.js +13 -11
  27. package/src/client/components/core/CssCore.js +4 -0
  28. package/src/client/components/core/Docs.js +0 -3
  29. package/src/client/components/core/Input.js +34 -19
  30. package/src/client/components/core/Modal.js +29 -7
  31. package/src/client/components/core/ObjectLayerEngine.js +370 -0
  32. package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
  33. package/src/client/components/core/Panel.js +7 -2
  34. package/src/client/components/core/PanelForm.js +187 -63
  35. package/src/client/components/core/VanillaJs.js +3 -0
  36. package/src/client/components/default/MenuDefault.js +94 -41
  37. package/src/client/components/default/RoutesDefault.js +2 -0
  38. package/src/client/services/default/default.management.js +1 -0
  39. package/src/client/services/document/document.service.js +97 -0
  40. package/src/client/services/file/file.service.js +2 -0
  41. package/src/client/services/user/user.service.js +1 -0
  42. package/src/client/ssr/Render.js +1 -1
  43. package/src/client/ssr/head/DefaultScripts.js +2 -0
  44. package/src/client/ssr/head/Seo.js +1 -0
  45. package/src/index.js +1 -1
  46. package/src/mailer/EmailRender.js +1 -1
  47. package/src/server/auth.js +4 -3
  48. package/src/server/client-build.js +2 -3
  49. package/src/server/client-formatted.js +40 -12
  50. package/src/server/conf.js +42 -3
  51. package/src/server/object-layer.js +196 -0
  52. package/src/server/runtime.js +18 -21
  53. package/src/server/ssr.js +52 -10
  54. package/src/server/valkey.js +89 -1
@@ -1,4 +1,4 @@
1
- import { getId, newInstance } from './CommonJs.js';
1
+ import { getId, newInstance, s4 } from './CommonJs.js';
2
2
  import { Draggable } from '@neodrag/vanilla';
3
3
  import { append, s, prepend, htmls, sa, getAllChildNodes, isActiveElement } from './VanillaJs.js';
4
4
  import { BtnIcon } from './BtnIcon.js';
@@ -30,8 +30,9 @@ import { DropDown } from './DropDown.js';
30
30
  import { Keyboard } from './Keyboard.js';
31
31
  import { Badge } from './Badge.js';
32
32
  import { Worker } from './Worker.js';
33
+ import { Scroll } from './Scroll.js';
33
34
 
34
- const logger = loggerFactory(import.meta);
35
+ const logger = loggerFactory(import.meta, { trace: true });
35
36
 
36
37
  const Modal = {
37
38
  Data: {},
@@ -87,10 +88,15 @@ const Modal = {
87
88
  homeModals: options.homeModals ? options.homeModals : [],
88
89
  query: options.query ? `${window.location.search}` : undefined,
89
90
  getTop: () => window.innerHeight - (options.heightBottomBar ? options.heightBottomBar : heightDefaultBottomBar),
90
- getHeight: () =>
91
- window.innerHeight -
92
- (options.heightTopBar ? options.heightTopBar : heightDefaultTopBar) -
93
- (options.heightBottomBar ? options.heightBottomBar : heightDefaultBottomBar),
91
+ getHeight: () => {
92
+ return (
93
+ window.innerHeight -
94
+ (s(`.main-body-btn-ui-close`) && !s(`.main-body-btn-ui-close`).classList.contains('hide')
95
+ ? (options.heightTopBar ? options.heightTopBar : heightDefaultTopBar) +
96
+ (options.heightBottomBar ? options.heightBottomBar : heightDefaultBottomBar)
97
+ : 0)
98
+ );
99
+ },
94
100
  };
95
101
 
96
102
  if (idModal !== 'main-body' && options.mode !== 'view') {
@@ -367,6 +373,7 @@ const Modal = {
367
373
  s(`.modal-menu`).style.top = '0px';
368
374
  s(`.main-body-btn-container`).style.top = '50px';
369
375
  s(`.main-body`).style.top = '0px';
376
+ s(`.main-body`).style.height = `${window.innerHeight}px`;
370
377
  for (const event of Object.keys(Modal.Data[idModal].onBarUiClose))
371
378
  Modal.Data[idModal].onBarUiClose[event]();
372
379
  } else {
@@ -379,6 +386,7 @@ const Modal = {
379
386
  s(`.slide-menu-top-bar`).classList.remove('hide');
380
387
  s(`.bottom-bar`).classList.remove('hide');
381
388
  s(`.main-body`).style.top = `${options.heightTopBar}px`;
389
+ s(`.main-body`).style.height = `${window.innerHeight - options.heightTopBar}px`;
382
390
  for (const event of Object.keys(Modal.Data[idModal].onBarUiOpen))
383
391
  Modal.Data[idModal].onBarUiOpen[event]();
384
392
  }
@@ -1291,6 +1299,15 @@ const Modal = {
1291
1299
  }
1292
1300
 
1293
1301
  await NotificationManager.RenderBoard(options);
1302
+
1303
+ const { removeEvent } = Scroll.setEvent('.main-body', async (payload) => {
1304
+ console.warn('scroll', payload);
1305
+ if (payload.scrollTop > 100) {
1306
+ if (!s(`.main-body-btn-ui-close`).classList.contains('hide')) s(`.main-body-btn-ui-close`).click();
1307
+
1308
+ removeEvent();
1309
+ }
1310
+ });
1294
1311
  });
1295
1312
  })();
1296
1313
  break;
@@ -1385,7 +1402,7 @@ const Modal = {
1385
1402
  <div
1386
1403
  class="fix ${options && options.class ? options.class : ''} modal ${options.disableBoxShadow
1387
1404
  ? ''
1388
- : 'box-shadow'} ${idModal}"
1405
+ : 'box-shadow'} ${idModal === 'main-body' ? `${idModal} modal-home` : idModal}"
1389
1406
  >
1390
1407
  <div class="abs modal-handle-${idModal}"></div>
1391
1408
  <div class="in modal-html-${idModal}">
@@ -2246,6 +2263,11 @@ const Modal = {
2246
2263
  s(`.bottom-bar`).classList.remove('hide');
2247
2264
  s(`.modal-menu`).classList.remove('hide');
2248
2265
  },
2266
+ RenderSeoSanitizer: async () => {
2267
+ sa('img').forEach((img) => {
2268
+ if (!img.getAttribute('alt')) img.setAttribute('alt', 'image ' + Worker.title + ' ' + s4());
2269
+ });
2270
+ },
2249
2271
  };
2250
2272
 
2251
2273
  const renderMenuLabel = ({ img, text, icon }) => {
@@ -861,3 +861,373 @@ customElements.define('object-layer-engine', ObjectLayerEngineElement);
861
861
  Example usage:
862
862
  <object-layer-engine id="ole" width="20" height="12" pixel-size="20"></object-layer-engine>
863
863
  */
864
+
865
+ const template = document.createElement('template');
866
+ template.innerHTML = html`
867
+ <style>
868
+ :host {
869
+ display: block;
870
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
871
+ }
872
+ .wrap {
873
+ display: flex;
874
+ flex-direction: column;
875
+ gap: 8px;
876
+ }
877
+ .controls {
878
+ display: flex;
879
+ gap: 8px;
880
+ align-items: center;
881
+ flex-wrap: wrap;
882
+ }
883
+ .drop-area {
884
+ border: 2px dashed #999;
885
+ padding: 12px;
886
+ border-radius: 8px;
887
+ text-align: center;
888
+ color: #555;
889
+ user-select: none;
890
+ }
891
+ .drop-area.dragover {
892
+ border-color: #4a90e2;
893
+ color: #1a73e8;
894
+ background: rgba(74, 144, 226, 0.04);
895
+ }
896
+ input[type='file'] {
897
+ display: inline-block;
898
+ }
899
+ .hint {
900
+ font-size: 0.9rem;
901
+ color: #666;
902
+ }
903
+ </style>
904
+
905
+ <div class="wrap">
906
+ <div class="controls">
907
+ <label title="Load PNG file">
908
+ <input type="file" accept="image/png" part="file-input" />
909
+ <span class="btn">Choose PNG</span>
910
+ </label>
911
+ <div class="hint">Only PNG images accepted. Drop PNG onto the box below.</div>
912
+ </div>
913
+
914
+ <div class="drop-area" part="drop-area">Drop PNG here or click "Choose PNG"</div>
915
+ </div>
916
+ `;
917
+
918
+ class ObjectLayerPngLoader extends HTMLElement {
919
+ constructor() {
920
+ super();
921
+ this.attachShadow({ mode: 'open' });
922
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
923
+
924
+ this._fileInput = this.shadowRoot.querySelector('input[type="file"]');
925
+ this._dropArea = this.shadowRoot.querySelector('.drop-area');
926
+
927
+ this._editor = null; // will hold external editor instance
928
+ this._options = { fitMode: 'contain' };
929
+
930
+ // Bind handlers
931
+ this._onFileChange = this._onFileChange.bind(this);
932
+ this._onDrop = this._onDrop.bind(this);
933
+ this._onDragOver = this._onDragOver.bind(this);
934
+ this._onDragLeave = this._onDragLeave.bind(this);
935
+ }
936
+
937
+ static get observedAttributes() {
938
+ return ['editor-selector', 'fit-mode', 'target-cells-x', 'target-cells-y'];
939
+ }
940
+
941
+ attributeChangedCallback(name, oldVal, newVal) {
942
+ if (name === 'editor-selector' && newVal) {
943
+ const el = document.querySelector(newVal);
944
+ if (el) this.setEditor(el);
945
+ }
946
+ if (name === 'fit-mode') {
947
+ this._options.fitMode = newVal || 'contain';
948
+ }
949
+ }
950
+
951
+ connectedCallback() {
952
+ this._fileInput.addEventListener('change', this._onFileChange);
953
+ this._dropArea.addEventListener('dragover', this._onDragOver);
954
+ this._dropArea.addEventListener('dragleave', this._onDragLeave);
955
+ this._dropArea.addEventListener('drop', this._onDrop);
956
+ this.addEventListener('dragover', this._onDragOver);
957
+ this.addEventListener('dragleave', this._onDragLeave);
958
+ this.addEventListener('drop', this._onDrop);
959
+
960
+ // If editor-selector attribute was present at creation, try to resolve
961
+ const sel = this.getAttribute('editor-selector');
962
+ if (sel) {
963
+ const target = document.querySelector(sel);
964
+ if (target) this.setEditor(target);
965
+ }
966
+
967
+ // read fit-mode
968
+ const fit = this.getAttribute('fit-mode');
969
+ if (fit) this._options.fitMode = fit;
970
+ }
971
+
972
+ disconnectedCallback() {
973
+ this._fileInput.removeEventListener('change', this._onFileChange);
974
+ this._dropArea.removeEventListener('dragover', this._onDragOver);
975
+ this._dropArea.removeEventListener('dragleave', this._onDragLeave);
976
+ this._dropArea.removeEventListener('drop', this._onDrop);
977
+ this.removeEventListener('dragover', this._onDragOver);
978
+ this.removeEventListener('dragleave', this._onDragLeave);
979
+ this.removeEventListener('drop', this._onDrop);
980
+ }
981
+
982
+ // ----------------- Public API -----------------
983
+ setEditor(editor) {
984
+ if (!editor) throw new Error('Editor cannot be null/undefined');
985
+ if (typeof editor.loadMatrix !== 'function') {
986
+ throw new Error('Provided editor does not expose loadMatrix(matrix)');
987
+ }
988
+ this._editor = editor;
989
+ this.dispatchEvent(new CustomEvent('editorconnected', { detail: { editor } }));
990
+ }
991
+
992
+ setOptions(options = {}) {
993
+ if (options.fitMode) this._options.fitMode = options.fitMode;
994
+ if (options.targetCellsX) this.setAttribute('target-cells-x', String(options.targetCellsX));
995
+ if (options.targetCellsY) this.setAttribute('target-cells-y', String(options.targetCellsY));
996
+ }
997
+
998
+ get editor() {
999
+ return this._editor;
1000
+ }
1001
+
1002
+ // ----------------- Events -----------------
1003
+ _onFileChange(e) {
1004
+ const file = e.target.files && e.target.files[0] ? e.target.files[0] : null;
1005
+ if (!file) return;
1006
+ this._handleFile(file);
1007
+ this._fileInput.value = '';
1008
+ }
1009
+
1010
+ _onDragOver(e) {
1011
+ e.preventDefault();
1012
+ e.dataTransfer.dropEffect = 'copy';
1013
+ this._dropArea.classList.add('dragover');
1014
+ }
1015
+ _onDragLeave(e) {
1016
+ e.preventDefault();
1017
+ this._dropArea.classList.remove('dragover');
1018
+ }
1019
+
1020
+ _onDrop(e) {
1021
+ e.preventDefault();
1022
+ this._dropArea.classList.remove('dragover');
1023
+ const file = e.dataTransfer.files && e.dataTransfer.files[0] ? e.dataTransfer.files[0] : null;
1024
+ if (!file) return;
1025
+ this._handleFile(file);
1026
+ }
1027
+
1028
+ // ----------------- File handling -----------------
1029
+ async _handleFile(file) {
1030
+ const isPngByType = file.type === 'image/png';
1031
+ const isPngByName = file.name && file.name.toLowerCase().endsWith('.png');
1032
+ if (!isPngByType && !isPngByName) {
1033
+ this._showError('Only PNG files are supported.');
1034
+ return;
1035
+ }
1036
+
1037
+ if (!this._editor) {
1038
+ this._showError('No editor connected. Use setEditor(editor) or provide editor-selector attribute.');
1039
+ return;
1040
+ }
1041
+
1042
+ try {
1043
+ await this._loadPngToEditorAdaptive(file);
1044
+ this._dispatchLoadedEvent(file.name);
1045
+ } catch (err) {
1046
+ console.error('Failed to load PNG', err);
1047
+ this._showError('Failed to load PNG (see console).');
1048
+ }
1049
+ }
1050
+
1051
+ _showError(msg) {
1052
+ alert(msg);
1053
+ }
1054
+ _dispatchLoadedEvent(filename) {
1055
+ this.dispatchEvent(new CustomEvent('pngloaded', { detail: { filename } }));
1056
+ }
1057
+
1058
+ // ----------------- Adaptive load -----------------
1059
+ _readEditorConfig() {
1060
+ const ed = this._editor;
1061
+ const cfg = { pixelSize: null, cellsX: null, cellsY: null };
1062
+
1063
+ if (!ed) return cfg;
1064
+
1065
+ // pixel size detection (try multiple forms)
1066
+ cfg.pixelSize = ed.pixelSize || ed.pixel_size || null;
1067
+ if (!cfg.pixelSize) {
1068
+ const attr = ed.getAttribute && (ed.getAttribute('pixel-size') || ed.getAttribute('pixelSize'));
1069
+ if (attr) cfg.pixelSize = parseInt(attr, 10);
1070
+ }
1071
+ if (typeof cfg.pixelSize === 'string') cfg.pixelSize = parseInt(cfg.pixelSize, 10);
1072
+
1073
+ // cells detection (common attribute names: width/height on engine represent cells)
1074
+ const widthAttr = ed.getAttribute && ed.getAttribute('width');
1075
+ const heightAttr = ed.getAttribute && ed.getAttribute('height');
1076
+ if (widthAttr && heightAttr) {
1077
+ cfg.cellsX = parseInt(widthAttr, 10);
1078
+ cfg.cellsY = parseInt(heightAttr, 10);
1079
+ }
1080
+
1081
+ // alternative property names
1082
+ cfg.cellsX = cfg.cellsX || ed.cellsX || ed.cellCountX || (ed.cells && ed.cells.x) || null;
1083
+ cfg.cellsY = cfg.cellsY || ed.cellsY || ed.cellCountY || (ed.cells && ed.cells.y) || null;
1084
+
1085
+ // if editor exposes getCells() prefer that
1086
+ try {
1087
+ if ((!cfg.cellsX || !cfg.cellsY) && typeof ed.getCells === 'function') {
1088
+ const c = ed.getCells();
1089
+ if (c && c.x && c.y) {
1090
+ cfg.cellsX = cfg.cellsX || c.x;
1091
+ cfg.cellsY = cfg.cellsY || c.y;
1092
+ }
1093
+ }
1094
+ } catch (e) {
1095
+ /* ignore */
1096
+ }
1097
+
1098
+ return cfg;
1099
+ }
1100
+
1101
+ // core adaptive loader: scales image to editor cells (or computes fallback)
1102
+ async _loadPngToEditorAdaptive(blobOrFile) {
1103
+ const imgBitmap = await createImageBitmap(blobOrFile);
1104
+ const srcW = imgBitmap.width;
1105
+ const srcH = imgBitmap.height;
1106
+
1107
+ // read editor config and loader explicit overrides
1108
+ const editorCfg = this._readEditorConfig();
1109
+ const overrideX = this.getAttribute('target-cells-x');
1110
+ const overrideY = this.getAttribute('target-cells-y');
1111
+
1112
+ let targetCellsX = overrideX ? parseInt(overrideX, 10) : editorCfg.cellsX || null;
1113
+ let targetCellsY = overrideY ? parseInt(overrideY, 10) : editorCfg.cellsY || null;
1114
+
1115
+ // if cells unknown but pixelSize known, compute approximate cells from image dimensions
1116
+ if ((!targetCellsX || !targetCellsY) && editorCfg.pixelSize) {
1117
+ const px = parseInt(editorCfg.pixelSize, 10);
1118
+ if (px > 0) {
1119
+ if (!targetCellsX) targetCellsX = Math.max(1, Math.round(srcW / px));
1120
+ if (!targetCellsY) targetCellsY = Math.max(1, Math.round(srcH / px));
1121
+ }
1122
+ }
1123
+
1124
+ // if still missing, fallback to native image pixels
1125
+ if (!targetCellsX) targetCellsX = srcW;
1126
+ if (!targetCellsY) targetCellsY = srcH;
1127
+
1128
+ // Decide fit mode
1129
+ const fitMode = this._options.fitMode || this.getAttribute('fit-mode') || 'contain';
1130
+
1131
+ // Create a small canvas sized to the target cells (we will render the image into this canvas
1132
+ // with smoothing disabled to preserve blocky/pixel look). Then read each pixel as a cell.
1133
+ const small = document.createElement('canvas');
1134
+ small.width = targetCellsX;
1135
+ small.height = targetCellsY;
1136
+ const sctx = small.getContext('2d');
1137
+
1138
+ // nearest-neighbour / crisp scaling
1139
+ sctx.imageSmoothingEnabled = false;
1140
+ sctx.clearRect(0, 0, small.width, small.height);
1141
+
1142
+ if (fitMode === 'stretch') {
1143
+ // non-uniform scale to fill exactly
1144
+ sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, 0, 0, small.width, small.height);
1145
+ } else {
1146
+ // compute uniform scale to contain or cover
1147
+ let scaleX = small.width / srcW;
1148
+ let scaleY = small.height / srcH;
1149
+ let scale = fitMode === 'cover' ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
1150
+ // compute destination size in small-canvas pixels
1151
+ const destW = Math.max(1, Math.round(srcW * scale));
1152
+ const destH = Math.max(1, Math.round(srcH * scale));
1153
+ const dx = Math.floor((small.width - destW) / 2);
1154
+ const dy = Math.floor((small.height - destH) / 2);
1155
+ sctx.drawImage(imgBitmap, 0, 0, srcW, srcH, dx, dy, destW, destH);
1156
+ }
1157
+
1158
+ // read pixel data from the small canvas
1159
+ const imageData = sctx.getImageData(0, 0, small.width, small.height).data;
1160
+
1161
+ // build matrix[y][x] = [r,g,b,a]
1162
+ const matrix = new Array(small.height);
1163
+ let p = 0;
1164
+ for (let y = 0; y < small.height; y++) {
1165
+ const row = new Array(small.width);
1166
+ for (let x = 0; x < small.width; x++) {
1167
+ const r = imageData[p++];
1168
+ const g = imageData[p++];
1169
+ const b = imageData[p++];
1170
+ const a = imageData[p++];
1171
+ row[x] = [r, g, b, a];
1172
+ }
1173
+ matrix[y] = row;
1174
+ }
1175
+
1176
+ // attempt to optionally align editor settings (best-effort)
1177
+ try {
1178
+ // if editor has setCells(x,y) or setCellCount, call it
1179
+ if (typeof this._editor.setCells === 'function') {
1180
+ this._editor.setCells(small.width, small.height);
1181
+ } else if (typeof this._editor.setCellCount === 'function') {
1182
+ this._editor.setCellCount(small.width, small.height);
1183
+ } else {
1184
+ // try common attribute setter
1185
+ if (this._editor.setAttribute) {
1186
+ this._editor.setAttribute('width', String(small.width));
1187
+ this._editor.setAttribute('height', String(small.height));
1188
+ }
1189
+ }
1190
+
1191
+ // if editor has setPixelSize and editorCfg.pixelSize exists, keep it
1192
+ if (editorCfg.pixelSize && typeof this._editor.setPixelSize === 'function') {
1193
+ this._editor.setPixelSize(parseInt(editorCfg.pixelSize, 10));
1194
+ }
1195
+ } catch (e) {
1196
+ // non-critical; continue
1197
+ console.warn('Failed to align editor config:', e);
1198
+ }
1199
+
1200
+ // finally, hand matrix to editor
1201
+ if (!this._editor || typeof this._editor.loadMatrix !== 'function') {
1202
+ throw new Error('Editor disconnected or does not expose loadMatrix');
1203
+ }
1204
+
1205
+ this._editor.loadMatrix(matrix);
1206
+ }
1207
+
1208
+ // Public helpers
1209
+ async loadPngBlob(blob) {
1210
+ return this._handleFile(blob);
1211
+ }
1212
+ async loadPngUrl(url) {
1213
+ const resp = await fetch(url);
1214
+ const blob = await resp.blob();
1215
+ return this._handleFile(blob);
1216
+ }
1217
+ }
1218
+
1219
+ customElements.define('object-layer-png-loader', ObjectLayerPngLoader);
1220
+
1221
+ /* Example wiring (NOT code repeated in canvas):
1222
+
1223
+ // HTML
1224
+ <object-layer-engine id="editor"></object-layer-engine>
1225
+ <object-layer-png-loader id="loader" editor-selector="#editor"></object-layer-png-loader>
1226
+
1227
+ // JS (programmatic)
1228
+ const editor = document.getElementById('editor');
1229
+ const loader = document.getElementById('loader');
1230
+ // Alternatively: loader.setEditor(editor);
1231
+ loader.addEventListener('pngloaded', (e) => console.log('Loaded', e.detail.filename));
1232
+
1233
+ */
@@ -272,6 +272,7 @@ const ObjectLayerEngineModal = {
272
272
 
273
273
  <object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
274
274
  </object-layer-engine>
275
+ <object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
275
276
  </div>
276
277
 
277
278
  <div class="in section-mp section-mp-border">
@@ -114,6 +114,7 @@ const Panel = {
114
114
  options.originData().find((d) => d._id === obj._id || d.id === obj.id),
115
115
  options.filesData().find((d) => d._id === obj._id || d.id === obj.id),
116
116
  );
117
+ if (options.on.initEdit) await options.on.initEdit({ data: obj });
117
118
  });
118
119
  s(`.a-${payload._id}`).onclick = async (e) => {
119
120
  e.preventDefault();
@@ -469,7 +470,7 @@ const Panel = {
469
470
  }
470
471
  s(`.${idPanel}-form-body`).classList.add('hide');
471
472
  };
472
- s(`.btn-${idPanel}-add`).onclick = (e) => {
473
+ s(`.btn-${idPanel}-add`).onclick = async (e) => {
473
474
  e.preventDefault();
474
475
  // s(`.btn-${idPanel}-clean`).click();
475
476
  Panel.Tokens[idPanel].editId = undefined;
@@ -478,16 +479,20 @@ const Panel = {
478
479
  s(`.${scrollClassContainer}`).scrollTop = 0;
479
480
 
480
481
  openPanelForm();
482
+ if (options.on.initAdd) await options.on.initAdd();
481
483
  };
482
484
  if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
483
485
  });
484
486
 
485
487
  if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
486
- else
488
+ else {
487
489
  render += html`<div class="in" style="min-height: 200px">
488
490
  <div class="abs center"><i class="fas fa-exclamation-circle"></i> ${Translate.Render(`no-result-found`)}</div>
489
491
  </div>`;
490
492
 
493
+ if (options.on.noResultFound) setTimeout(options.on.noResultFound);
494
+ }
495
+
491
496
  this.Tokens[idPanel] = { idPanel, scrollClassContainer, formData, data, titleKey, subTitleKey, renderPanel };
492
497
 
493
498
  let customButtonsRender = '';