underpost 2.8.847 → 2.8.851

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 (35) hide show
  1. package/README.md +49 -35
  2. package/cli.md +84 -84
  3. package/conf.js +1 -1
  4. package/docker-compose.yml +1 -1
  5. package/manifests/deployment/dd-template-development/deployment.yaml +2 -2
  6. package/package.json +1 -1
  7. package/src/api/user/user.service.js +9 -38
  8. package/src/cli/run.js +7 -0
  9. package/src/client/Default.index.js +6 -2
  10. package/src/client/components/core/Account.js +1 -1
  11. package/src/client/components/core/Content.js +11 -7
  12. package/src/client/components/core/Css.js +195 -15
  13. package/src/client/components/core/CssCore.js +1 -1
  14. package/src/client/components/core/Docs.js +4 -4
  15. package/src/client/components/core/Input.js +6 -1
  16. package/src/client/components/core/LoadingAnimation.js +8 -15
  17. package/src/client/components/core/LogIn.js +3 -0
  18. package/src/client/components/core/LogOut.js +1 -1
  19. package/src/client/components/core/Modal.js +21 -10
  20. package/src/client/components/core/PanelForm.js +2 -3
  21. package/src/client/components/core/Recover.js +2 -1
  22. package/src/client/components/core/Router.js +22 -23
  23. package/src/client/components/core/SignUp.js +1 -0
  24. package/src/client/components/core/SocketIo.js +3 -3
  25. package/src/client/components/core/VanillaJs.js +48 -17
  26. package/src/client/components/core/Worker.js +3 -1
  27. package/src/client/components/default/CssDefault.js +17 -3
  28. package/src/client/components/default/MenuDefault.js +243 -50
  29. package/src/client/components/default/RoutesDefault.js +8 -14
  30. package/src/client/public/default/assets/background/dark.jpg +0 -0
  31. package/src/client/public/default/assets/background/dark.svg +557 -0
  32. package/src/client/public/default/assets/logo/underpost.gif +0 -0
  33. package/src/index.js +1 -1
  34. package/src/mailer/MailerProvider.js +37 -0
  35. package/src/server/client-build.js +1 -8
@@ -28,7 +28,7 @@ import { setDocTitle, closeModalRouteChangeEvent, handleModalViewRoute } from '.
28
28
  import { NotificationManager } from './NotificationManager.js';
29
29
  import { EventsUI } from './EventsUI.js';
30
30
  import { Translate } from './Translate.js';
31
- import { Input } from './Input.js';
31
+ import { Input, isTextInputFocused } from './Input.js';
32
32
  import { Validator } from './Validator.js';
33
33
  import { DropDown } from './DropDown.js';
34
34
  import { Keyboard } from './Keyboard.js';
@@ -166,9 +166,9 @@ const Modal = {
166
166
  height: 100px;
167
167
  }
168
168
  .default-slide-menu-top-bar-fix-logo {
169
- width: 80px;
170
- height: 80px;
171
- padding: 10px;
169
+ width: 50px;
170
+ height: 50px;
171
+ padding: 24px;
172
172
  }
173
173
  .default-slide-menu-top-bar-fix-title-container-text {
174
174
  font-size: 30px;
@@ -186,7 +186,7 @@ const Modal = {
186
186
  `.default-slide-menu-top-bar-fix-title-container`,
187
187
  html`
188
188
  <div class="inl default-slide-menu-top-bar-fix-title-container-text">
189
- ${options.RouterInstance.NameApp}
189
+ ${options.RouterInstance.BannerAppTemplate}
190
190
  </div>
191
191
  `,
192
192
  );
@@ -502,7 +502,9 @@ const Modal = {
502
502
  class="abs modal slide-menu-top-bar-fix"
503
503
  style="height: ${options.heightTopBar}px; top: 0px"
504
504
  >
505
- <a class="a-link-top-banner"> ${await options.slideMenuTopBarBannerFix()}</a>
505
+ <a class="a-link-top-banner">
506
+ <div class="inl">${await options.slideMenuTopBarBannerFix()}</div></a
507
+ >
506
508
  </div>`
507
509
  : ''}
508
510
  </div>`,
@@ -606,6 +608,7 @@ const Modal = {
606
608
  }),
607
609
  );
608
610
  s(`.search-result-btn-${result.routerId}`).onclick = () => {
611
+ if (!s(`.html-${searchBoxHistoryId}`) || !s(`.html-${searchBoxHistoryId}`).hasChildNodes()) return;
609
612
  s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
610
613
  `main-btn-menu-active`,
611
614
  );
@@ -619,6 +622,7 @@ const Modal = {
619
622
  };
620
623
 
621
624
  const getResultSearchBox = (validatorData) => {
625
+ if (!s(`.html-${searchBoxHistoryId}`) || !s(`.html-${searchBoxHistoryId}`).hasChildNodes()) return;
622
626
  const { model, id } = validatorData;
623
627
  switch (model) {
624
628
  case 'search-box':
@@ -711,6 +715,9 @@ const Modal = {
711
715
 
712
716
  const updateSearchBoxValue = (selector) => {
713
717
  if (!selector) selector = getDefaultSearchBoxSelector();
718
+ // check exist childNodes
719
+ if (!s(selector) || !s(selector).hasChildNodes()) return;
720
+
714
721
  if (s(selector).childNodes) {
715
722
  if (
716
723
  s(selector).childNodes[s(selector).childNodes.length - 1] &&
@@ -734,6 +741,10 @@ const Modal = {
734
741
 
735
742
  const setSearchValue = (selector) => {
736
743
  if (!selector) selector = getDefaultSearchBoxSelector();
744
+
745
+ // check exist childNodes
746
+ if (!s(selector) || !s(selector).hasChildNodes()) return;
747
+
737
748
  historySearchBox = historySearchBox.filter(
738
749
  (h) => h.routerId !== results[currentKeyBoardSearchBoxIndex].routerId,
739
750
  );
@@ -989,6 +1000,7 @@ const Modal = {
989
1000
  ['Alt', 'k'],
990
1001
  ],
991
1002
  eventCallBack: () => {
1003
+ if (isTextInputFocused()) return;
992
1004
  if (s(`.top-bar-search-box`)) {
993
1005
  if (s(`.main-body-btn-ui-close`).classList.contains('hide')) {
994
1006
  s(`.main-body-btn-ui-open`).click();
@@ -1538,12 +1550,12 @@ const Modal = {
1538
1550
  if (
1539
1551
  ![idModal, 'main-body-top', 'main-body'].concat(this.Data[idModal]?.homeModals || []).includes(keyModal)
1540
1552
  )
1541
- s(`.btn-close-${keyModal}`).click();
1553
+ if (s(`.btn-close-${keyModal}`)) s(`.btn-close-${keyModal}`).click();
1542
1554
  backMenuButtonEvent();
1543
1555
  }
1544
- s(`.btn-close-modal-menu`).click();
1556
+ if (s(`.btn-close-modal-menu`)) s(`.btn-close-modal-menu`).click();
1545
1557
  setPath(getProxyPath());
1546
- setDocTitle({ ...options.RouterInstance, route: '' });
1558
+ setDocTitle();
1547
1559
  };
1548
1560
  s(`.main-btn-home`).onclick = async () => {
1549
1561
  // await this.onHomeRouterEvent();
@@ -2217,7 +2229,6 @@ const Modal = {
2217
2229
  if (s(`.a-link-top-banner`)) {
2218
2230
  s(`.a-link-top-banner`).setAttribute('href', `${location.origin}${getProxyPath()}`);
2219
2231
  EventsUI.onClick(`.a-link-top-banner`, (e) => {
2220
- if (location.pathname === '/') return location.reload();
2221
2232
  e.preventDefault();
2222
2233
  s(`.action-btn-home`).click();
2223
2234
  });
@@ -15,7 +15,7 @@ import { getSrcFromFileData } from './Input.js';
15
15
  import { imageShimmer, renderCssAttr } from './Css.js';
16
16
  import { Translate } from './Translate.js';
17
17
  import { Modal } from './Modal.js';
18
- import { closeModalRouteChangeEvents, listenQueryPathInstance, renderTitle, setQueryPath } from './Router.js';
18
+ import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath } from './Router.js';
19
19
 
20
20
  const PanelForm = {
21
21
  Data: {},
@@ -92,7 +92,7 @@ const PanelForm = {
92
92
  },
93
93
  ];
94
94
 
95
- const titleIcon = html`<i class="fa-solid fa-quote-left"></i>`;
95
+ const titleIcon = html`<i class="fa-solid fa-quote-left title-icon-${idPanel}"></i>`;
96
96
  const panelRender = async ({ data }) =>
97
97
  await Panel.Render({
98
98
  idPanel,
@@ -440,7 +440,6 @@ const PanelForm = {
440
440
  if (options.route === 'home') Modal.homeCid = newInstance(cid);
441
441
  htmls(`.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`, await renderSrrPanelData());
442
442
  await getPanelData();
443
- if (this.Data[idPanel].data.length === 1) renderTitle(this.Data[idPanel].data[0].title);
444
443
  htmls(
445
444
  `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`,
446
445
  await panelRender({ data: this.Data[idPanel].data }),
@@ -7,7 +7,7 @@ import { LogIn } from './LogIn.js';
7
7
  import { NotificationManager } from './NotificationManager.js';
8
8
  import { Translate } from './Translate.js';
9
9
  import { Validator } from './Validator.js';
10
- import { getQueryParams, s } from './VanillaJs.js';
10
+ import { getProxyPath, getQueryParams, s } from './VanillaJs.js';
11
11
 
12
12
  const Recover = {
13
13
  Event: {},
@@ -80,6 +80,7 @@ const Recover = {
80
80
  }
81
81
  switch (mode) {
82
82
  case 'recover-verify-email': {
83
+ body.proxyPath = getProxyPath();
83
84
  const result = await UserService.post({ id: 'recover-verify-email', body });
84
85
  NotificationManager.Push({
85
86
  html:
@@ -6,28 +6,29 @@ import { Worker } from './Worker.js';
6
6
 
7
7
  // Router
8
8
 
9
+ const RouterEvents = {};
10
+ const closeModalRouteChangeEvents = {};
11
+
9
12
  const logger = loggerFactory(import.meta);
10
13
 
11
- const renderTitle = (title, nameApp) => htmls('title', html`${title} | ${nameApp ?? Worker.RouterInstance.NameApp}`);
12
-
13
- const setDocTitle = (options = { Routes: () => {}, route: '', NameApp: '' }) => {
14
- const { Routes, route, NameApp } = options;
15
- let title = titleFormatted(Routes()[`/${route}`].title);
16
- if (Routes()[`/${route}`].upperCase) title = title.toUpperCase();
17
- renderTitle(title, NameApp);
18
- {
19
- const routeId = route === '' ? 'home' : route;
20
- if (s(`.main-btn-${routeId}`)) {
21
- if (s(`.main-btn-menu-active`)) s(`.main-btn-menu-active`).classList.remove(`main-btn-menu-active`);
22
- if (s(`.main-btn-${routeId}`)) s(`.main-btn-${routeId}`).classList.add(`main-btn-menu-active`);
23
- }
14
+ const sanitizeRoute = (route) =>
15
+ !route || route === '/' || route === `\\`
16
+ ? 'home'
17
+ : route.toLowerCase().replaceAll('/', '').replaceAll(`\\`, '').replaceAll(' ', '-');
18
+
19
+ const setDocTitle = (route) => {
20
+ const _route = sanitizeRoute(route);
21
+ // logger.warn('setDocTitle', _route);
22
+ const title = titleFormatted(_route);
23
+ htmls('title', html`${title}${title.match(Worker.title.toLowerCase()) ? '' : ` | ${Worker.title}`}`);
24
+ if (s(`.main-btn-${_route}`)) {
25
+ if (s(`.main-btn-menu-active`)) s(`.main-btn-menu-active`).classList.remove(`main-btn-menu-active`);
26
+ if (s(`.main-btn-${_route}`)) s(`.main-btn-${_route}`).classList.add(`main-btn-menu-active`);
24
27
  }
25
28
  };
26
29
 
27
- const RouterEvents = {};
28
-
29
- const Router = function (options = { Routes: () => {}, e: new PopStateEvent(), NameApp: '' }) {
30
- const { e, Routes, NameApp } = options;
30
+ const Router = function (options = { Routes: () => {}, e: new PopStateEvent() }) {
31
+ const { e, Routes } = options;
31
32
  const proxyPath = getProxyPath();
32
33
  let path = window.location.pathname;
33
34
  logger.info(options);
@@ -43,7 +44,7 @@ const Router = function (options = { Routes: () => {}, e: new PopStateEvent(), N
43
44
 
44
45
  if (path === pushPath) {
45
46
  for (const event of Object.keys(RouterEvents)) RouterEvents[event](routerEvent);
46
- setDocTitle({ Routes, route, NameApp });
47
+ setDocTitle(route);
47
48
  return Routes()[`/${route}`].render();
48
49
  }
49
50
  }
@@ -79,7 +80,6 @@ const listenQueryPathInstance = ({ id, routeId, event }, queryKey = 'cid') => {
79
80
  });
80
81
  };
81
82
 
82
- const closeModalRouteChangeEvents = {};
83
83
  const triggerCloseModalRouteChangeEvents = (newPath) => {
84
84
  console.warn('[closeModalRouteChangeEvent]', newPath);
85
85
  for (const event of Object.keys(closeModalRouteChangeEvents)) closeModalRouteChangeEvents[event](newPath);
@@ -99,14 +99,14 @@ const closeModalRouteChangeEvent = (options = {}) => {
99
99
  newPath = `${newPath}${Modal.Data[subIdModal].options.route}`;
100
100
  triggerCloseModalRouteChangeEvents(newPath);
101
101
  setPath(newPath);
102
+ setDocTitle(newPath);
102
103
  Modal.setTopModalCallback(subIdModal);
103
- return setDocTitle({ ...RouterInstance, route: Modal.Data[subIdModal].options.route });
104
104
  }
105
105
  }
106
106
  newPath = `${newPath}${homeCid ? `?cid=${homeCid}` : ''}`;
107
107
  triggerCloseModalRouteChangeEvents(newPath);
108
108
  setPath(newPath);
109
- return setDocTitle({ ...RouterInstance, route: '' });
109
+ setDocTitle(newPath);
110
110
  }
111
111
  };
112
112
 
@@ -121,7 +121,7 @@ const handleModalViewRoute = (options = {}) => {
121
121
 
122
122
  if (path !== newPath) {
123
123
  setPath(newPath);
124
- setDocTitle({ ...RouterInstance, route });
124
+ setDocTitle(newPath);
125
125
  }
126
126
  };
127
127
 
@@ -135,5 +135,4 @@ export {
135
135
  closeModalRouteChangeEvent,
136
136
  handleModalViewRoute,
137
137
  closeModalRouteChangeEvents,
138
- renderTitle,
139
138
  };
@@ -53,6 +53,7 @@ const SignUp = {
53
53
  status: result.status,
54
54
  });
55
55
  if (result.status === 'success') {
56
+ await Auth.sessionOut();
56
57
  await Auth.signUpToken(result);
57
58
  s(`.btn-close-${options.idModal}`).click();
58
59
  }
@@ -21,11 +21,11 @@ const SocketIo = {
21
21
  },
22
22
  Init: async function (options) {
23
23
  if (this.socket) this.socket.disconnect();
24
- this.host = getWsBaseUrl({ wsBasePath: '' });
24
+ this.host = options.host ?? getWsBaseUrl({ wsBasePath: '' });
25
25
  logger.info(`ws host:`, this.host);
26
- const path = getWsBasePath();
26
+ const path = typeof options.path === 'string' ? options.path : getWsBasePath();
27
27
  const connectOptions = {
28
- path,
28
+ path: path === '/' ? undefined : path,
29
29
  // auth: {
30
30
  // token: '',
31
31
  // },
@@ -31,8 +31,6 @@ VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=bierner
31
31
 
32
32
  */
33
33
 
34
- // Docs by https://mintlify.com
35
-
36
34
  /**
37
35
  * Query selector.
38
36
  *
@@ -135,22 +133,56 @@ const copyData = (data) =>
135
133
  const pasteData = () => new Promise((resolve) => navigator.clipboard.readText().then((clipText) => resolve(clipText)));
136
134
 
137
135
  /**
138
- * The setPath function in JavaScript updates the browser's history with a new path, state, and title.
139
- * @param path - The `path` parameter is a string that represents the URL path where you want to
140
- * navigate or update in the browser history. It is the first parameter in the `setPath` function and
141
- * has a default value of `'/'`.
142
- * @param stateStorage - The `stateStorage` parameter in the `setPath` function is an object that
143
- * represents the state object associated with the new history entry. It is used to store data related
144
- * to the state of the application when navigating to a new path using `history.pushState()`. This data
145
- * can be accessed later
146
- * @param title - The `title` parameter in the `setPath` function is a string that represents the
147
- * title of the new history entry. It is used as the title of the new history entry in the browser's
148
- * history.
149
- * @memberof VanillaJS
136
+ * Normalize a path/URL (ensure leading slash, collapse duplicate slashes,
137
+ * keep query + hash, remove trailing slash except for root).
138
+ * Examples:
139
+ * - 'a/b' -> '/a/b'
140
+ * - '/a//b/' -> '/a/b'
141
+ * - 'https://example.com/a/b?q=1#top' -> '/a/b?q=1#top' (origin ignored)
142
+ * - '#section' -> '/current/path#section' (resolved relative by new URL)
143
+ */
144
+ function normalizePath(input) {
145
+ // ensure string and trim
146
+ if (typeof input !== 'string') input = String(input ?? '');
147
+ input = input.trim();
148
+
149
+ // empty -> root
150
+ if (input === '') return '/';
151
+
152
+ // Use URL to parse relative or absolute paths relative to current origin
153
+ let url;
154
+ try {
155
+ url = new URL(input, window.location.origin);
156
+ } catch (e) {
157
+ // fallback (very rare)
158
+ input = input.replace(/\s+/g, ''); // remove problematic spaces
159
+ input = input.replace(/\/+/g, '/'); // collapse multiple '/'
160
+ if (!input.startsWith('/')) input = '/' + input;
161
+ if (input.length > 1 && input.endsWith('/')) input = input.slice(0, -1);
162
+ return input;
163
+ }
164
+
165
+ // Normalize pathname: collapse slashes, ensure leading slash, remove trailing except root
166
+ let pathname = url.pathname.replace(/\/+/g, '/');
167
+ if (!pathname.startsWith('/')) pathname = '/' + pathname;
168
+ if (pathname.length > 1 && pathname.endsWith('/')) pathname = pathname.slice(0, -1);
169
+
170
+ // Rebuild: pathname + search + hash
171
+ return pathname + url.search + url.hash;
172
+ }
173
+
174
+ /**
175
+ * Improved setPath: normalizes path and avoids pushState if nothing changes.
150
176
  */
151
177
  const setPath = (path = '/', stateStorage = {}, title = '') => {
152
- if (window.location.pathname === path || window.location.pathname === `${path}/`) return;
153
- return history.pushState(stateStorage, title, path);
178
+ const normalized = normalizePath(path);
179
+
180
+ // Compare the full form (pathname + search + hash) with current
181
+ const currentFull = window.location.pathname + window.location.search + window.location.hash;
182
+ if (currentFull === normalized) return; // nothing to do
183
+
184
+ // pushState: third arg should be a path relative to same origin (we pass normalized)
185
+ return history.pushState(stateStorage, title, normalized);
154
186
  };
155
187
 
156
188
  /**
@@ -355,7 +387,6 @@ const getBlobFromUint8ArrayFile = (data = [[]], mimetype = 'application/octet-st
355
387
  * @memberof VanillaJS
356
388
  */
357
389
  const getProxyPath = () => {
358
- // warning: evaluates headers html source
359
390
  let path = location.pathname.split('/')[1] ? `/${location.pathname.split('/')[1]}/` : '/';
360
391
  if (window.Routes && path !== '/' && path.slice(0, -1) in window.Routes()) path = '/';
361
392
  return path;
@@ -12,7 +12,8 @@ const logger = loggerFactory(import.meta);
12
12
  const Worker = {
13
13
  devMode: () => location.origin.match('localhost') || location.origin.match('127.0.0.1'),
14
14
  instance: async function ({ router, render }) {
15
- logger.warn('Init');
15
+ Worker.title = `${s('title').textContent}`;
16
+ // logger.warn('Init worker', Worker.title);
16
17
  window.ononline = async () => {
17
18
  logger.warn('ononline');
18
19
  };
@@ -81,6 +82,7 @@ const Worker = {
81
82
  }
82
83
  window.serviceWorkerReady = true;
83
84
  },
85
+
84
86
  // Get the current service worker registration.
85
87
  getRegistration: async function () {
86
88
  return await navigator.serviceWorker.getRegistration();
@@ -1,13 +1,27 @@
1
+ import { subThemeManager } from '../core/Css.js';
2
+
3
+ const CssCommonDefault = async () => {
4
+ // use #4f46e5, #7c3aed);
5
+ subThemeManager.setDarkTheme('#7c3aed');
6
+ subThemeManager.setLightTheme('#7c3aed');
7
+
8
+ return html``;
9
+ };
10
+
1
11
  const CssDefaultDark = {
2
12
  theme: 'default-dark',
3
13
  dark: true,
4
- render: async () => html` <style></style> `,
14
+ render: async () => {
15
+ return (await CssCommonDefault()) + html``;
16
+ },
5
17
  };
6
18
 
7
19
  const CssDefaultLight = {
8
20
  theme: 'default-light',
9
21
  dark: false,
10
- render: async () => html` <style></style> `,
22
+ render: async () => {
23
+ return (await CssCommonDefault()) + html``;
24
+ },
11
25
  };
12
26
 
13
- export { CssDefaultDark, CssDefaultLight };
27
+ export { CssDefaultDark, CssCommonDefault, CssDefaultLight };