underpost 3.2.8 → 3.2.10

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 (92) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +223 -2
  6. package/CLI-HELP.md +36 -7
  7. package/README.md +38 -9
  8. package/bin/build.js +27 -11
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +32 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +40 -25
  23. package/scripts/k3s-node-setup.sh +30 -11
  24. package/scripts/nat-iptables.sh +103 -18
  25. package/src/api/core/core.router.js +19 -14
  26. package/src/api/core/core.service.js +5 -5
  27. package/src/api/default/default.router.js +22 -18
  28. package/src/api/default/default.service.js +5 -5
  29. package/src/api/document/document.router.js +28 -23
  30. package/src/api/document/document.service.js +100 -23
  31. package/src/api/file/file.router.js +19 -13
  32. package/src/api/file/file.service.js +9 -7
  33. package/src/api/test/test.router.js +17 -12
  34. package/src/api/types.js +24 -0
  35. package/src/api/user/guest.service.js +5 -4
  36. package/src/api/user/user.router.js +297 -288
  37. package/src/api/user/user.service.js +100 -35
  38. package/src/cli/baremetal.js +20 -11
  39. package/src/cli/cluster.js +243 -55
  40. package/src/cli/db.js +106 -62
  41. package/src/cli/deploy.js +297 -154
  42. package/src/cli/fs.js +19 -3
  43. package/src/cli/index.js +37 -9
  44. package/src/cli/ipfs.js +4 -6
  45. package/src/cli/kubectl.js +4 -1
  46. package/src/cli/lxd.js +217 -135
  47. package/src/cli/release.js +289 -131
  48. package/src/cli/repository.js +91 -34
  49. package/src/cli/run.js +297 -56
  50. package/src/cli/test.js +9 -3
  51. package/src/client/Default.index.js +9 -3
  52. package/src/client/components/core/Auth.js +19 -5
  53. package/src/client/components/core/Docs.js +6 -34
  54. package/src/client/components/core/FileExplorer.js +6 -6
  55. package/src/client/components/core/Modal.js +65 -2
  56. package/src/client/components/core/PanelForm.js +56 -52
  57. package/src/client/components/core/Recover.js +4 -4
  58. package/src/client/components/core/Worker.js +170 -350
  59. package/src/client/services/default/default.management.js +20 -25
  60. package/src/client/services/user/guest.service.js +10 -3
  61. package/src/client/sw/core.sw.js +174 -112
  62. package/src/db/DataBaseProvider.js +120 -20
  63. package/src/db/mongo/MongoBootstrap.js +587 -0
  64. package/src/db/mongo/MongooseDB.js +126 -22
  65. package/src/index.js +1 -1
  66. package/src/runtime/express/Express.js +2 -2
  67. package/src/runtime/wp/Wp.js +8 -5
  68. package/src/server/auth.js +2 -2
  69. package/src/server/client-build-docs.js +1 -1
  70. package/src/server/client-build.js +94 -129
  71. package/src/server/conf.js +20 -65
  72. package/src/server/data-query.js +32 -20
  73. package/src/server/dns.js +22 -0
  74. package/src/server/process.js +180 -19
  75. package/src/server/runtime.js +1 -1
  76. package/src/server/start.js +26 -7
  77. package/src/server/valkey.js +9 -2
  78. package/src/ws/IoInterface.js +16 -16
  79. package/src/ws/core/channels/core.ws.chat.js +11 -11
  80. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  81. package/src/ws/core/channels/core.ws.stream.js +19 -19
  82. package/src/ws/core/core.ws.connection.js +8 -8
  83. package/src/ws/core/core.ws.server.js +6 -5
  84. package/src/ws/default/channels/default.ws.main.js +10 -10
  85. package/src/ws/default/default.ws.connection.js +4 -4
  86. package/src/ws/default/default.ws.server.js +4 -3
  87. package/typedoc.json +10 -1
  88. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  89. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  90. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  91. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  92. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -255,9 +255,11 @@ class Auth {
255
255
  * @returns {Promise<object>} A promise resolving to the newly created guest session data.
256
256
  */
257
257
  async sessionOut() {
258
- // 1. End User Session
258
+ // 1. End User Session — skip the network call when there is no active user session
259
+ // (avoids a wasted DELETE round-trip for guests / new visitors)
260
+ const hasUserSession = !!GuestService.getUserToken();
259
261
  try {
260
- const result = await UserService.delete({ id: 'logout' });
262
+ const result = hasUserSession ? await UserService.delete({ id: 'logout' }) : null;
261
263
  SearchBox.RecentResults.clear();
262
264
  this.deleteToken();
263
265
  if (this.#refreshTimeout) {
@@ -267,7 +269,7 @@ class Auth {
267
269
  this.renderGuestUi();
268
270
  // Reset user data in the LogIn state/model
269
271
  LogIn.Scope.user.main.model.user = {};
270
- await LogOut.Trigger(result);
272
+ if (result) await LogOut.Trigger(result);
271
273
  } catch (error) {
272
274
  logger.error('Error during user logout:', error);
273
275
  }
@@ -279,8 +281,16 @@ class Auth {
279
281
 
280
282
  if (result.status === 'success' && result.data.token) {
281
283
  this.setGuestToken(result.data.token);
282
- // Recursively call sessionIn to complete the guest login process (UI update, etc.)
283
- return await this.sessionIn();
284
+ // Use the POST response data directly avoids a redundant GET /user/auth
285
+ // round-trip that would otherwise re-verify the token we just received.
286
+ await GuestService.setMeta('guest-session', {
287
+ role: result.data.user?.role,
288
+ userId: result.data.user?._id,
289
+ });
290
+ LogIn.Scope.user.main.model.user = {};
291
+ await LogIn.Trigger(result.data);
292
+ await Account.updateForm(result.data.user);
293
+ return result.data;
284
294
  } else {
285
295
  logger.error('Failed to get a new guest token.');
286
296
  return { user: UserMock.default };
@@ -309,6 +319,10 @@ class Auth {
309
319
  // Close any open login/signup modals
310
320
  if (s(`.modal-log-in`)) s(`.btn-close-modal-log-in`).click();
311
321
  if (s(`.modal-sign-up`)) s(`.btn-close-modal-sign-up`).click();
322
+ if (!s(`.main-body-btn-ui-open`).classList.contains('hide'))
323
+ s(`.main-body-btn-ui-open`).click();
324
+ if (!s(`.main-body-btn-ui-bar-custom-open`).classList.contains('hide'))
325
+ s(`.main-body-btn-ui-bar-custom-open`).click();
312
326
  });
313
327
  }
314
328
 
@@ -1,9 +1,7 @@
1
- import { Badge } from './Badge.js';
2
- import { BtnIcon } from './BtnIcon.js';
3
- import { Css, darkTheme, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
4
- import { buildBadgeToolTipMenuOption, Modal, renderViewTitle } from './Modal.js';
1
+ import { Css, darkTheme, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
2
+ import { Modal, renderViewTitle } from './Modal.js';
5
3
  import { listenQueryPathInstance, setQueryPath, closeModalRouteChangeEvent, getProxyPath } from './Router.js';
6
- import { htmls, s, sIframe } from './VanillaJs.js';
4
+ import { s, sIframe } from './VanillaJs.js';
7
5
  // https://mintlify.com/docs/quickstart
8
6
  class Docs {
9
7
  static async RenderModal(type) {
@@ -289,41 +287,15 @@ class Docs {
289
287
  },
290
288
  });
291
289
  });
292
- let docMenuRender = '';
290
+ // Register theme events for items that have them (Docs-specific concern)
293
291
  for (const docData of Docs.Data) {
294
292
  if (docData.themeEvent) {
295
293
  ThemeEvents[`doc-icon-${docData.type}`] = docData.themeEvent;
296
294
  setTimeout(ThemeEvents[`doc-icon-${docData.type}`]);
297
295
  }
298
- let tabHref, style, labelStyle;
299
- switch (docData.type) {
300
- case 'repo':
301
- case 'coverage-link':
302
- style = renderCssAttr({ style: { height: '45px' } });
303
- labelStyle = renderCssAttr({ style: { top: '8px', left: '9px' } });
304
- break;
305
- default:
306
- break;
307
- }
308
- tabHref = docData.url();
309
- const subMenuIcon =
310
- options.subMenuIcon && typeof options.subMenuIcon === 'function'
311
- ? options.subMenuIcon(docData.type)
312
- : docData.icon;
313
- docMenuRender += html`
314
- ${await BtnIcon.instance({
315
- class: `in wfa main-btn-menu submenu-btn btn-docs btn-docs-${docData.type}`,
316
- label: html`<span class="inl menu-btn-icon">${subMenuIcon}</span
317
- ><span class="menu-label-text menu-label-text-docs"> ${docData.text} </span>`,
318
- tabHref,
319
- tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption(docData.text, 'right')),
320
- useMenuBtn: true,
321
- })}
322
- `;
323
296
  }
324
- // s(`.menu-btn-container-children`).classList.remove('hide');
325
- // htmls(`.nav-path-display-${'modal-menu'}`, location.pathname);
326
- htmls('.menu-btn-container-children-docs', docMenuRender);
297
+ // Build submenu items and populate — submenu system is owned by Modal
298
+ Modal.subMenuPopulate('docs', await Modal.buildSubMenuItemsHtml('docs', Docs.Data, options));
327
299
  {
328
300
  const docsData = [
329
301
  {
@@ -469,12 +469,12 @@ class FileExplorer {
469
469
  async init(params) {
470
470
  console.log('LoadFileActionsRenderer created', params);
471
471
  // params.data._id
472
- FileExplorer.eGui = document.createElement('div');
472
+ this.eGui = document.createElement('div');
473
473
  const isPublic = params.data.isPublic;
474
474
  const toggleId = `toggle-public-${params.data._id}`;
475
475
  const hasGenericFile = !!params.data.hasGenericFile;
476
476
  const hasMdFile = !!params.data.hasMdFile;
477
- FileExplorer.eGui.innerHTML = html`
477
+ this.eGui.innerHTML = html`
478
478
  <div class="fl">
479
479
  ${await BtnIcon.instance({
480
480
  class: `in fll management-table-btn-mini btn-file-download-${params.data._id}${!hasGenericFile ? ' btn-disabled' : ''}`,
@@ -981,7 +981,7 @@ class FileExplorer {
981
981
  });
982
982
  }
983
983
  getGui() {
984
- return FileExplorer.eGui;
984
+ return this.eGui;
985
985
  }
986
986
  refresh(params) {
987
987
  console.log('LoadFileActionsRenderer refreshed', params);
@@ -994,8 +994,8 @@ class FileExplorer {
994
994
  console.log('LoadFolderActionsRenderer created', params);
995
995
  // params.data._id
996
996
  const id = params.data.locationId;
997
- FileExplorer.eGui = document.createElement('div');
998
- FileExplorer.eGui.innerHTML = html`
997
+ this.eGui = document.createElement('div');
998
+ this.eGui.innerHTML = html`
999
999
  <div class="fl">
1000
1000
  ${await BtnIcon.instance({
1001
1001
  class: `in fll management-table-btn-mini btn-folder-delete-${id}`,
@@ -1058,7 +1058,7 @@ class FileExplorer {
1058
1058
  });
1059
1059
  }
1060
1060
  getGui() {
1061
- return FileExplorer.eGui;
1061
+ return this.eGui;
1062
1062
  }
1063
1063
  refresh(params) {
1064
1064
  console.log('LoadFolderActionsRenderer refreshed', params);
@@ -1789,6 +1789,12 @@ class Modal {
1789
1789
  if (options.onCollapseMenu) options.onCollapseMenu();
1790
1790
  s(`.sub-menu-title-container-${'modal-menu'}`).classList.add('hide');
1791
1791
  s(`.nav-path-container-${'modal-menu'}`).classList.add('hide');
1792
+ // Shrink any already-open submenu containers to icon-only width
1793
+ Object.keys(Modal.subMenuBtnClass).forEach((subMenuId) => {
1794
+ const container = s(`.menu-btn-container-children-${subMenuId}`);
1795
+ if (container && container.style.height && container.style.height !== '0px')
1796
+ container.style.width = `${collapseSlideMenuWidth}px`;
1797
+ });
1792
1798
  Object.keys(this.Data[idModal].onCollapseMenuListener).map((keyListener) =>
1793
1799
  this.Data[idModal].onCollapseMenuListener[keyListener](),
1794
1800
  );
@@ -1806,6 +1812,12 @@ class Modal {
1806
1812
  if (options.onExtendMenu) options.onExtendMenu();
1807
1813
  s(`.sub-menu-title-container-${'modal-menu'}`).classList.remove('hide');
1808
1814
  s(`.nav-path-container-${'modal-menu'}`).classList.remove('hide');
1815
+ // Expand any already-open submenu containers back to full width
1816
+ Object.keys(Modal.subMenuBtnClass).forEach((subMenuId) => {
1817
+ const container = s(`.menu-btn-container-children-${subMenuId}`);
1818
+ if (container && container.style.height && container.style.height !== '0px')
1819
+ container.style.width = `${originSlideMenuWidth}px`;
1820
+ });
1809
1821
  Object.keys(this.Data[idModal].onExtendMenuListener).map((keyListener) =>
1810
1822
  this.Data[idModal].onExtendMenuListener[keyListener](),
1811
1823
  );
@@ -2464,6 +2476,52 @@ class Modal {
2464
2476
  }
2465
2477
  };
2466
2478
 
2479
+ /**
2480
+ * Builds submenu item HTML using the canonical BtnIcon pattern.
2481
+ * Centralises the item-rendering contract so individual submenu owners (e.g. Docs)
2482
+ * only need to supply data — not layout concerns.
2483
+ * @param {string} subMenuId - Submenu identifier (e.g. 'docs')
2484
+ * @param {Array<{type:string, icon:string, text:string, url:function|string}>} items
2485
+ * @param {Object} [options]
2486
+ * @param {function} [options.subMenuIcon] - Optional icon override per item type
2487
+ * @returns {Promise<string>} Rendered HTML string
2488
+ */
2489
+ static buildSubMenuItemsHtml = async (subMenuId, items = [], options = {}) => {
2490
+ let result = '';
2491
+ const _menuMode = Modal.Data['modal-menu']?.options?.mode;
2492
+ const _tooltipSide = options.tooltipSide || (_menuMode === 'slide-menu-right' ? 'right' : 'left');
2493
+ for (const item of items) {
2494
+ const tabHref = typeof item.url === 'function' ? item.url() : item.url || '';
2495
+ const icon =
2496
+ options.subMenuIcon && typeof options.subMenuIcon === 'function' ? options.subMenuIcon(item.type) : item.icon;
2497
+ result += html`${await BtnIcon.instance({
2498
+ class: `in wfa main-btn-menu submenu-btn btn-${subMenuId} btn-${subMenuId}-${item.type}`,
2499
+ label: html`<span class="inl menu-btn-icon">${icon}</span
2500
+ ><span class="menu-label-text menu-label-text-${subMenuId}"> ${item.text} </span>`,
2501
+ tabHref,
2502
+ tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption(item.text, _tooltipSide)),
2503
+ useMenuBtn: true,
2504
+ })} `;
2505
+ }
2506
+ return result;
2507
+ };
2508
+
2509
+ /**
2510
+ * Injects pre-built submenu item HTML into the submenu container and syncs
2511
+ * the collapse state so labels are hidden when the menu is icon-only.
2512
+ * @param {string} subMenuId
2513
+ * @param {string} itemsHtml
2514
+ */
2515
+ static subMenuPopulate = (subMenuId, itemsHtml) => {
2516
+ htmls(`.menu-btn-container-children-${subMenuId}`, itemsHtml);
2517
+ const _menuMode = Modal.Data['modal-menu']?.options?.mode;
2518
+ const _collapseIndicatorClass =
2519
+ _menuMode === 'slide-menu-right' ? '.btn-icon-menu-mode-left' : '.btn-icon-menu-mode-right';
2520
+ if (s(_collapseIndicatorClass) && !s(_collapseIndicatorClass).classList.contains('hide')) {
2521
+ sa(`.menu-label-text-${subMenuId}`).forEach((el) => el.classList.add('hide'));
2522
+ }
2523
+ };
2524
+
2467
2525
  // Move modal title element into the bar's render container so it aligns with control buttons
2468
2526
  /**
2469
2527
  * Position a modal relative to an anchor element.
@@ -2769,9 +2827,14 @@ const subMenuRender = async (subMenuId) => {
2769
2827
  setTimeout(() => {
2770
2828
  Modal.menuTextLabelAnimation('modal-menu', subMenuId);
2771
2829
  });
2772
- // Open animation
2830
+ // Open animation — match the current menu width (collapsed = 50px, extended = 320px)
2831
+ const _menuMode = Modal.Data['modal-menu']?.options?.mode;
2832
+ const _collapseIndicatorClass =
2833
+ _menuMode === 'slide-menu-right' ? '.btn-icon-menu-mode-left' : '.btn-icon-menu-mode-right';
2834
+ const _isMenuCollapsed = s(_collapseIndicatorClass) && !s(_collapseIndicatorClass).classList.contains('hide');
2835
+ const _menuContainerWidth = _isMenuCollapsed ? 50 : 320;
2773
2836
  setTimeout(top, 360);
2774
- menuContainer.style.width = '320px';
2837
+ menuContainer.style.width = `${_menuContainerWidth}px`;
2775
2838
  menuContainer.style.overflow = null;
2776
2839
  menuContainer.style.height = '0px';
2777
2840
  menuContainer.style.height = `${_hBtn * 6}px`;
@@ -73,7 +73,7 @@ class PanelForm {
73
73
  parentIdModal: undefined,
74
74
  route: 'home',
75
75
  htmlFormHeader: async () => '',
76
- firsUpdateEvent: async () => {},
76
+ firsUpdateEvent: async () => { },
77
77
  share: {
78
78
  copyLink: false,
79
79
  copySourceMd: false,
@@ -196,12 +196,12 @@ class PanelForm {
196
196
  <img
197
197
  class="abs center"
198
198
  style="${renderCssAttr({
199
- style: {
200
- width: '100px',
201
- height: '100px',
202
- opacity: 0.2,
203
- },
204
- })}"
199
+ style: {
200
+ width: '100px',
201
+ height: '100px',
202
+ opacity: 0.2,
203
+ },
204
+ })}"
205
205
  src="${defaultUrlImage}"
206
206
  />
207
207
  `,
@@ -332,16 +332,10 @@ class PanelForm {
332
332
  }, 50);
333
333
  },
334
334
  initEdit: async function ({ data }) {
335
- // Clear file input when entering edit mode
336
- const fileFormData = formData.find((f) => f.inputType === 'file');
337
- if (fileFormData && s(`.${fileFormData.id}`)) {
338
- s(`.${fileFormData.id}`).value = '';
339
- s(`.${fileFormData.id}`).inputFiles = null;
340
- htmls(
341
- `.file-name-render-${fileFormData.id}`,
342
- `<div class="abs center"><i style="font-size: 25px" class="fa-solid fa-cloud"></i></div>`,
343
- );
344
- }
335
+ // Do NOT clear the file input here - the file should remain as-is when entering edit mode.
336
+ // If user wants to remove the file, they use the "clean file" button.
337
+ // If user wants to replace the file, they select a new file.
338
+ // Unconditionally clearing the file here would cause the server to receive fileId: null on save.
345
339
  setTimeout(() => {
346
340
  s(`.modal-${options.route}`).scrollTo({ top: 0, behavior: 'smooth' });
347
341
  }, 50);
@@ -388,15 +382,15 @@ class PanelForm {
388
382
  // It will be filtered from the tags array to keep visibility control separate from content tags
389
383
  const tags = data.tags
390
384
  ? uniqueArray(
391
- data.tags
392
- .replaceAll('/', ',')
393
- .replaceAll('-', ',')
394
- .replaceAll(' ', ',')
395
- .split(',')
396
- .map((t) => t.trim())
397
- .filter((t) => t)
398
- .concat(prefixTags),
399
- )
385
+ data.tags
386
+ .replaceAll('/', ',')
387
+ .replaceAll('-', ',')
388
+ .replaceAll(' ', ',')
389
+ .split(',')
390
+ .map((t) => t.trim())
391
+ .filter((t) => t)
392
+ .concat(prefixTags),
393
+ )
400
394
  : prefixTags;
401
395
  let originObj, originFileObj, indexOriginObj;
402
396
  if (editId) {
@@ -434,7 +428,17 @@ class PanelForm {
434
428
  for (const file of inputFiles) {
435
429
  indexFormDoc++;
436
430
  let fileId = undefined; // Reset for each iteration - only set if user uploaded a file
431
+ // Track whether the file input was explicitly cleared (null) vs never had a file (undefined)
432
+ // In edit mode, null means user cleared the file - we need to tell server to remove it
433
+ const isFileCleared = data.fileId === null && editId;
437
434
  await (async () => {
435
+ // When file is null and not the first iteration or not in edit mode, skip upload
436
+ if (!file && !isFileCleared) return;
437
+ // When user cleared file in edit mode, set fileId=null so server removes the reference
438
+ if (isFileCleared) {
439
+ fileId = null;
440
+ return;
441
+ }
438
442
  const body = new FormData();
439
443
  // Only append md file if it was created (has content)
440
444
  if (md) body.append('md', md);
@@ -485,8 +489,8 @@ class PanelForm {
485
489
  message: documentMessage,
486
490
  data: documentData,
487
491
  } = originObj && indexFormDoc === 0
488
- ? await DocumentService.put({ id: originObj._id, body })
489
- : await DocumentService.post({
492
+ ? await DocumentService.put({ id: originObj._id, body })
493
+ : await DocumentService.post({
490
494
  body,
491
495
  });
492
496
  const newDoc = {
@@ -514,12 +518,12 @@ class PanelForm {
514
518
  fileId: {
515
519
  fileBlob: file
516
520
  ? {
517
- data: {
518
- data: await getDataFromInputFile(file),
519
- },
520
- mimetype: file.type,
521
- name: file.name,
522
- }
521
+ data: {
522
+ data: await getDataFromInputFile(file),
523
+ },
524
+ mimetype: file.type,
525
+ name: file.name,
526
+ }
523
527
  : undefined,
524
528
  filePlain: undefined,
525
529
  },
@@ -738,36 +742,36 @@ class PanelForm {
738
742
  <div
739
743
  class="in fll ssr-shimmer-search-box"
740
744
  style="${renderCssAttr({
741
- style: {
742
- width: '80%',
743
- height: '30px',
744
- top: '-13px',
745
- left: '10px',
746
- },
747
- })}"
745
+ style: {
746
+ width: '80%',
747
+ height: '30px',
748
+ top: '-13px',
749
+ left: '10px',
750
+ },
751
+ })}"
748
752
  ></div>
749
753
  </div>`,
750
754
  createdAt: html`<div class="fl">
751
755
  <div
752
756
  class="in fll ssr-shimmer-search-box"
753
757
  style="${renderCssAttr({
754
- style: {
755
- width: '50%',
756
- height: '30px',
757
- left: '-5px',
758
- },
759
- })}"
758
+ style: {
759
+ width: '50%',
760
+ height: '30px',
761
+ left: '-5px',
762
+ },
763
+ })}"
760
764
  ></div>
761
765
  </div>`,
762
766
  mdFileId: html`<div class="fl section-mp">
763
767
  <div
764
768
  class="in fll ssr-shimmer-search-box"
765
769
  style="${renderCssAttr({
766
- style: {
767
- width: '80%',
768
- height: '30px',
769
- },
770
- })}"
770
+ style: {
771
+ width: '80%',
772
+ height: '30px',
773
+ },
774
+ })}"
771
775
  ></div>
772
776
  </div>`.repeat(random(2, 4)),
773
777
  ssr: true,
@@ -37,7 +37,7 @@ class Recover {
37
37
  rules: [{ type: 'isEmpty' }, { type: 'isLength', options: { min: 2, max: 20 } }],
38
38
  show: () => false,
39
39
  disable: function () {
40
- return !Recover.show();
40
+ return !this.show();
41
41
  },
42
42
  },
43
43
  'recover-email': {
@@ -46,7 +46,7 @@ class Recover {
46
46
  rules: [{ type: 'isEmpty' }, { type: 'isEmail' }],
47
47
  show: () => mode === 'recover-verify-email',
48
48
  disable: function () {
49
- return !Recover.show();
49
+ return !this.show();
50
50
  },
51
51
  },
52
52
  'recover-password': {
@@ -55,7 +55,7 @@ class Recover {
55
55
  rules: [{ type: 'isStrongPassword' }],
56
56
  show: () => mode === 'change-password',
57
57
  disable: function () {
58
- return !Recover.show();
58
+ return !this.show();
59
59
  },
60
60
  },
61
61
  'recover-repeat-password': {
@@ -63,7 +63,7 @@ class Recover {
63
63
  rules: [{ type: 'isEmpty' }, { type: 'passwordMismatch', options: `recover-password` }],
64
64
  show: () => mode === 'change-password',
65
65
  disable: function () {
66
- return !Recover.show();
66
+ return !this.show();
67
67
  },
68
68
  },
69
69
  };