underpost 2.8.844 → 2.8.845

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.
package/README.md CHANGED
@@ -35,10 +35,13 @@ template
35
35
 
36
36
 
37
37
 
38
+
39
+
40
+
38
41
  <!-- badges -->
39
42
 
40
43
 
41
- [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.8.844)](https://socket.dev/npm/package/underpost/overview/2.8.844) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
44
+ [![Node.js CI](https://github.com/underpostnet/engine/actions/workflows/docker-image.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [![Test](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml/badge.svg?branch=master)](https://github.com/underpostnet/engine/actions/workflows/coverall.yml) [![Downloads](https://img.shields.io/npm/dm/underpost.svg)](https://www.npmjs.com/package/underpost) [![Socket Badge](https://socket.dev/api/badge/npm/package/underpost/2.8.845)](https://socket.dev/npm/package/underpost/overview/2.8.845) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine/badge.svg?branch=master)](https://coveralls.io/github/underpostnet/engine?branch=master) [![Version](https://img.shields.io/npm/v/underpost.svg)](https://www.npmjs.org/package/underpost) [![License](https://img.shields.io/npm/l/underpost.svg)](https://www.npmjs.com/package/underpost)
42
45
 
43
46
 
44
47
  <!-- end-badges -->
@@ -56,6 +59,9 @@ template
56
59
 
57
60
 
58
61
 
62
+
63
+
64
+
59
65
  </div>
60
66
 
61
67
  <div align="center">
@@ -100,7 +106,7 @@ Run dev client server
100
106
  npm run dev
101
107
  ```
102
108
  <!-- -->
103
- ## underpost ci/cd cli v2.8.844
109
+ ## underpost ci/cd cli v2.8.845
104
110
 
105
111
  ### Usage: `underpost [options] [command]`
106
112
  ```
package/cli.md CHANGED
@@ -1,4 +1,4 @@
1
- ## underpost ci/cd cli v2.8.844
1
+ ## underpost ci/cd cli v2.8.845
2
2
 
3
3
  ### Usage: `underpost [options] [command]`
4
4
  ```
@@ -58,7 +58,7 @@ services:
58
58
  cpus: '0.25'
59
59
  memory: 20M
60
60
  labels: # labels in Compose file instead of Dockerfile
61
- engine.version: '2.8.844'
61
+ engine.version: '2.8.845'
62
62
  networks:
63
63
  - load-balancer
64
64
 
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-template-development-blue
20
- image: localhost/rockylinux9-underpost:v2.8.844
20
+ image: localhost/rockylinux9-underpost:v2.8.845
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -100,7 +100,7 @@ spec:
100
100
  spec:
101
101
  containers:
102
102
  - name: dd-template-development-green
103
- image: localhost/rockylinux9-underpost:v2.8.844
103
+ image: localhost/rockylinux9-underpost:v2.8.845
104
104
  # resources:
105
105
  # requests:
106
106
  # memory: "124Ki"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "2.8.844",
5
+ "version": "2.8.845",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -105,7 +105,6 @@
105
105
  "rrule": "^2.8.1",
106
106
  "sharp": "^0.32.5",
107
107
  "shelljs": "^0.8.5",
108
- "simple-icons": "^13.9.0",
109
108
  "sitemap": "^7.1.1",
110
109
  "socket.io": "^4.8.0",
111
110
  "sortablejs": "^1.15.0",
@@ -1,9 +1,9 @@
1
- import { getId, newInstance, range, s4, splitEveryXChar } from './CommonJs.js';
1
+ import { getId, newInstance, range, rgbToHex, s4, splitEveryXChar } from './CommonJs.js';
2
2
  import { CssCoreDark, CssCoreLight } from './CssCore.js';
3
3
  import { DropDown } from './DropDown.js';
4
4
  import { Modal } from './Modal.js';
5
5
  import { Translate } from './Translate.js';
6
- import { append, getProxyPath, htmls, s } from './VanillaJs.js';
6
+ import { append, getProxyPath, htmls, s, sa } from './VanillaJs.js';
7
7
 
8
8
  let ThemesScope = [];
9
9
 
@@ -12,6 +12,39 @@ let ThemesScope = [];
12
12
  // https://www.1001fonts.com/
13
13
 
14
14
  const Css = {
15
+ // Menu button container transition styles
16
+ menuButtonContainer: () => css`
17
+ .main-btn-menu {
18
+ transition: all 0.2s ease-in-out;
19
+ position: relative;
20
+ }
21
+
22
+ .main-btn-menu::after {
23
+ content: '';
24
+ position: absolute;
25
+ bottom: 0;
26
+ left: 50%;
27
+ width: 0;
28
+ height: 2px;
29
+ background: currentColor;
30
+ transition: all 0.2s ease-in-out;
31
+ transform: translateX(-50%);
32
+ }
33
+
34
+ .main-btn-menu:hover::after {
35
+ width: 60%;
36
+ }
37
+
38
+ .main-btn-menu.active {
39
+ background: rgba(255, 255, 255, 0.1);
40
+ }
41
+
42
+ .main-btn-menu.active::after {
43
+ width: 80%;
44
+ background: currentColor;
45
+ }
46
+ `,
47
+
15
48
  loadThemes: async function (themes = []) {
16
49
  ThemesScope = [];
17
50
  for (const themeOptions of themes) addTheme(themeOptions);
@@ -31,6 +64,16 @@ const Css = {
31
64
  Init: async function (options) {
32
65
  if (!options) options = ThemesScope[0];
33
66
  const { theme } = options;
67
+
68
+ // Inject menu button container styles
69
+ const styleId = 'menu-btn-container-styles';
70
+ if (!document.getElementById(styleId)) {
71
+ const style = document.createElement('style');
72
+ style.id = styleId;
73
+ style.textContent = this.menuButtonContainer();
74
+ document.head.appendChild(style);
75
+ }
76
+
34
77
  return await Themes[theme](options);
35
78
  },
36
79
  RenderSetting: async function () {
@@ -811,6 +854,12 @@ const imageShimmer = () => html`<div
811
854
  </div>
812
855
  </div>`;
813
856
 
857
+ const simpleIconsRender = (selector) => {
858
+ sa(selector).forEach((el) => {
859
+ el.src = `https://cdn.simpleicons.org/coveralls/${rgbToHex(window.getComputedStyle(s('html')).color)}`;
860
+ });
861
+ };
862
+
814
863
  export {
815
864
  Css,
816
865
  Themes,
@@ -843,4 +892,5 @@ export {
843
892
  renderWave,
844
893
  cssEffect,
845
894
  imageShimmer,
895
+ simpleIconsRender,
846
896
  };
@@ -1,12 +1,12 @@
1
1
  import { Badge } from './Badge.js';
2
2
  import { BtnIcon } from './BtnIcon.js';
3
3
  import { rgbToHex } from './CommonJs.js';
4
- import { Css, darkTheme, dynamicCol, renderCssAttr, ThemeEvents, Themes } from './Css.js';
4
+ import { Css, darkTheme, dynamicCol, renderCssAttr, simpleIconsRender, ThemeEvents, Themes } from './Css.js';
5
5
  import { DropDown } from './DropDown.js';
6
6
  import { buildBadgeToolTipMenuOption, Modal, renderMenuLabel, renderViewTitle } from './Modal.js';
7
7
  import { listenQueryPathInstance, setQueryPath } from './Router.js';
8
8
  import { Translate } from './Translate.js';
9
- import { getProxyPath, getQueryParams, htmls, s } from './VanillaJs.js';
9
+ import { getProxyPath, getQueryParams, htmls, s, sa } from './VanillaJs.js';
10
10
  import Sortable from 'sortablejs';
11
11
 
12
12
  // https://mintlify.com/docs/quickstart
@@ -46,6 +46,11 @@ const Docs = {
46
46
  Modal.Data[ModalId].onObserverListener[ModalId] = () => {
47
47
  if (s(`.iframe-${ModalId}`))
48
48
  s(`.iframe-${ModalId}`).style.height = `${s(`.${ModalId}`).offsetHeight - Modal.headerTitleHeight}px`;
49
+
50
+ if (type.match('coverage')) {
51
+ simpleIconsRender(`.doc-icon-coverage`);
52
+ simpleIconsRender(`.doc-icon-coverage-link`);
53
+ }
49
54
  };
50
55
  Modal.Data[ModalId].onObserverListener[ModalId]();
51
56
  },
@@ -96,10 +101,7 @@ const Docs = {
96
101
  return `${getProxyPath()}docs/coverage`;
97
102
  },
98
103
  themeEvent: () => {
99
- if (s(`.doc-icon-coverage`))
100
- s(`.doc-icon-coverage`).src = `https://cdn.simpleicons.org/coveralls/${rgbToHex(
101
- window.getComputedStyle(s('html')).color,
102
- )}`;
104
+ if (s(`.doc-icon-coverage`)) setTimeout(() => simpleIconsRender(`.doc-icon-coverage`));
103
105
  },
104
106
  },
105
107
  {
@@ -110,10 +112,7 @@ const Docs = {
110
112
  return `https://coveralls.io/github/underpostnet/engine`;
111
113
  },
112
114
  themeEvent: () => {
113
- if (s(`.doc-icon-coverage-link`))
114
- s(`.doc-icon-coverage-link`).src = `https://cdn.simpleicons.org/coveralls/${rgbToHex(
115
- window.getComputedStyle(s('html')).color,
116
- )}`;
115
+ if (s(`.doc-icon-coverage-link`)) setTimeout(() => simpleIconsRender(`.doc-icon-coverage-link`));
117
116
  },
118
117
  },
119
118
  ],
@@ -8,7 +8,7 @@ const DropDown = {
8
8
  Tokens: {},
9
9
  Render: async function (options) {
10
10
  const id = options.id ? options.id : getId(this.Tokens, 'dropdown-');
11
- this.Tokens[id] = { onClickEvents: {}, lastSelectValue: undefined };
11
+ this.Tokens[id] = { onClickEvents: {}, lastSelectValue: undefined, oncheckvalues: {} };
12
12
 
13
13
  options.data.push({
14
14
  value: 'reset',
@@ -39,6 +39,96 @@ const DropDown = {
39
39
  else s(`.dropdown-option-${id}`).classList.add('hide');
40
40
  };
41
41
 
42
+ const _render = async (data) => {
43
+ let render = '';
44
+ let index = -1;
45
+ for (const optionData of data) {
46
+ index++;
47
+ const i = index;
48
+ const valueDisplay = optionData.value.trim().replaceAll(' ', '-');
49
+ setTimeout(() => {
50
+ const onclick = (e) => {
51
+ if (options && options.lastSelectClass && s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`)) {
52
+ s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`).classList.remove(options.lastSelectClass);
53
+ }
54
+ this.Tokens[id].lastSelectValue = valueDisplay;
55
+ if (options && options.lastSelectClass && s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`)) {
56
+ s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`).classList.add(options.lastSelectClass);
57
+ }
58
+
59
+ if (
60
+ !(options && options.disableClose) &&
61
+ (options.type !== 'checkbox' || optionData.value === 'close' || optionData.value === 'reset')
62
+ )
63
+ s(`.dropdown-option-${id}`).classList.add('hide');
64
+
65
+ if (options.type === 'checkbox' && ToggleSwitch.Tokens[`checkbox-role-${valueDisplay}`])
66
+ ToggleSwitch.Tokens[`checkbox-role-${valueDisplay}`].click();
67
+ if (optionData.value !== 'close') {
68
+ if (optionData.value !== 'reset')
69
+ htmls(
70
+ `.dropdown-current-${id}`,
71
+ options.type === 'checkbox'
72
+ ? data
73
+ .filter((d) => d.checked)
74
+ .map((v, i, a) => `${v.display}${i < a.length - 1 ? ',' : ''}`)
75
+ .join('')
76
+ : optionData.display,
77
+ );
78
+ else htmls(`.dropdown-current-${id}`, '');
79
+
80
+ this.Tokens[id].value =
81
+ options.type === 'checkbox' ? data.filter((d) => d.checked).map((d) => d.data) : optionData.data;
82
+
83
+ console.warn('current value dropdown id:' + id, this.Tokens[id].value);
84
+
85
+ s(`.${id}`).value = this.Tokens[id].value;
86
+
87
+ optionData.onClick(e);
88
+ }
89
+ };
90
+
91
+ this.Tokens[id].onClickEvents[`dropdown-option-${id}-${i}`] = onclick;
92
+ this.Tokens[id].onClickEvents[`dropdown-option-${id}-${valueDisplay}`] = onclick;
93
+ this.Tokens[id].onClickEvents[`dropdown-option-${valueDisplay}`] = onclick;
94
+
95
+ s(`.dropdown-option-${id}-${i}`).onclick = onclick;
96
+ });
97
+ render += html`
98
+ <div
99
+ class="in dropdown-option dropdown-option-${id}-${i} dropdown-option-${id}-${valueDisplay} dropdown-option-${valueDisplay} ${valueDisplay ===
100
+ 'reset' &&
101
+ options &&
102
+ !(options.resetOption === true)
103
+ ? 'hide'
104
+ : ''}"
105
+ >
106
+ ${options.type === 'checkbox' && optionData.value !== 'close' && optionData.value !== 'reset'
107
+ ? html`
108
+ ${await ToggleSwitch.Render({
109
+ id: `checkbox-role-${valueDisplay}`,
110
+ type: 'checkbox',
111
+ disabledOnClick: true,
112
+ checked: optionData.checked,
113
+ on: {
114
+ unchecked: () => {
115
+ optionData.checked = false;
116
+ delete DropDown.Tokens[id].oncheckvalues[valueDisplay];
117
+ },
118
+ checked: () => {
119
+ optionData.checked = true;
120
+ DropDown.Tokens[id].oncheckvalues[valueDisplay] = {};
121
+ },
122
+ },
123
+ })}
124
+ `
125
+ : ''}${optionData.display}
126
+ </div>
127
+ `;
128
+ }
129
+ return { render, index };
130
+ };
131
+
42
132
  setTimeout(() => {
43
133
  if (options.type === 'checkbox')
44
134
  options.data.map((optionData) => {
@@ -53,92 +143,48 @@ const DropDown = {
53
143
  s(`.dropdown-label-${id}`).onclick = switchOptionsPanel;
54
144
  s(`.dropdown-current-${id}`).onclick = switchOptionsPanel;
55
145
  if (options && options.open) switchOptionsPanel();
56
- });
57
146
 
58
- let render = '';
59
- let index = -1;
60
- for (const optionData of options.data) {
61
- index++;
62
- const i = index;
63
- const valueDisplay = optionData.value.trim().replaceAll(' ', '-');
64
- setTimeout(() => {
65
- const onclick = (e) => {
66
- if (options && options.lastSelectClass && s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`)) {
67
- s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`).classList.remove(options.lastSelectClass);
68
- }
69
- this.Tokens[id].lastSelectValue = valueDisplay;
70
- if (options && options.lastSelectClass && s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`)) {
71
- s(`.dropdown-option-${this.Tokens[id].lastSelectValue}`).classList.add(options.lastSelectClass);
72
- }
147
+ const dropDownSearchHandle = async () => {
148
+ const _data = [];
149
+ if (!s(`.search-box-${id}`)) return;
73
150
 
151
+ let _value = s(`.search-box-${id}`).value.toLowerCase();
152
+
153
+ for (const objData of options.data) {
154
+ const objValue = objData.value.toLowerCase();
74
155
  if (
75
- !(options && options.disableClose) &&
76
- (options.type !== 'checkbox' || optionData.value === 'close' || optionData.value === 'reset')
77
- )
78
- s(`.dropdown-option-${id}`).classList.add('hide');
79
-
80
- if (options.type === 'checkbox' && ToggleSwitch.Tokens[`checkbox-role-${valueDisplay}`])
81
- ToggleSwitch.Tokens[`checkbox-role-${valueDisplay}`].click();
82
- if (optionData.value !== 'close') {
83
- if (optionData.value !== 'reset')
84
- htmls(
85
- `.dropdown-current-${id}`,
86
- options.type === 'checkbox'
87
- ? options.data
88
- .filter((d) => d.checked)
89
- .map((v, i, a) => `${v.display}${i < a.length - 1 ? ',' : ''}`)
90
- .join('')
91
- : optionData.display,
92
- );
93
- else htmls(`.dropdown-current-${id}`, '');
94
-
95
- this.Tokens[id].value =
96
- options.type === 'checkbox' ? options.data.filter((d) => d.checked).map((d) => d.data) : optionData.data;
97
-
98
- console.warn('current value dropdown id:' + id, this.Tokens[id].value);
99
-
100
- s(`.${id}`).value = this.Tokens[id].value;
101
-
102
- optionData.onClick(e);
156
+ objValue.match(_value) ||
157
+ (Translate.Data[objData.value] &&
158
+ Object.keys(Translate.Data[objData.value]).find((t) =>
159
+ Translate.Data[objData.value][t].toLowerCase().match(_value),
160
+ ))
161
+ ) {
162
+ _data.push(objData);
103
163
  }
104
- };
164
+ }
165
+
166
+ if (_data.length > 0) {
167
+ const { render, index } = await _render(_data);
168
+ htmls(`.${id}-render-container`, render);
169
+ } else {
170
+ // const { render, index } = await _render(options.data);
171
+ htmls(
172
+ `.${id}-render-container`,
173
+ html` <div class="inl" style="padding: 10px; color: red">
174
+ <i class="fas fa-exclamation-circle"></i> ${Translate.Render('no-result-found')}
175
+ </div>`,
176
+ );
177
+ }
178
+ };
179
+
180
+ s(`.search-box-${id}`).oninput = dropDownSearchHandle;
181
+
182
+ // Not use onblur generate bug on input toggle
183
+ // s(`.search-box-${id}`).onblur = dropDownSearchHandle;
184
+ });
105
185
 
106
- this.Tokens[id].onClickEvents[`dropdown-option-${id}-${i}`] = onclick;
107
- this.Tokens[id].onClickEvents[`dropdown-option-${id}-${valueDisplay}`] = onclick;
108
- this.Tokens[id].onClickEvents[`dropdown-option-${valueDisplay}`] = onclick;
186
+ const { render, index } = await _render(options.data);
109
187
 
110
- s(`.dropdown-option-${id}-${i}`).onclick = onclick;
111
- });
112
- render += html`
113
- <div
114
- class="in dropdown-option dropdown-option-${id}-${i} dropdown-option-${id}-${valueDisplay} dropdown-option-${valueDisplay} ${valueDisplay ===
115
- 'reset' &&
116
- options &&
117
- !(options.resetOption === true)
118
- ? 'hide'
119
- : ''}"
120
- >
121
- ${options.type === 'checkbox' && optionData.value !== 'close' && optionData.value !== 'reset'
122
- ? html`
123
- ${await ToggleSwitch.Render({
124
- id: `checkbox-role-${valueDisplay}`,
125
- type: 'checkbox',
126
- disabledOnClick: true,
127
- checked: optionData.checked,
128
- on: {
129
- unchecked: () => {
130
- optionData.checked = false;
131
- },
132
- checked: () => {
133
- optionData.checked = true;
134
- },
135
- },
136
- })}
137
- `
138
- : ''}${optionData.display}
139
- </div>
140
- `;
141
- }
142
188
  return html`
143
189
  <div class="inl dropdown-container ${id} ${options?.containerClass ? options.containerClass : ''}">
144
190
  <div class="in dropdown-option dropdown-label-${id} ${options && options.disableSelectLabel ? 'hide' : ''}">
@@ -158,7 +204,7 @@ const DropDown = {
158
204
  placeholder: true,
159
205
  })}
160
206
  </div>
161
- ${render}
207
+ <div class="${id}-render-container">${render}</div>
162
208
  </div>
163
209
  </div>
164
210
  `;
@@ -24,7 +24,7 @@ import {
24
24
  renderStatus,
25
25
  renderCssAttr,
26
26
  } from './Css.js';
27
- import { setDocTitle } from './Router.js';
27
+ import { setDocTitle, closeModalRouteChangeEvent, handleModalViewRoute } from './Router.js';
28
28
  import { NotificationManager } from './NotificationManager.js';
29
29
  import { EventsUI } from './EventsUI.js';
30
30
  import { Translate } from './Translate.js';
@@ -126,19 +126,13 @@ const Modal = {
126
126
  };
127
127
  Responsive.Event[`view-${idModal}`]();
128
128
 
129
- // Router
130
- if (options.route)
131
- (() => {
132
- let path = window.location.pathname;
133
- if (path !== '/' && path[path.length - 1] === '/') path = path.slice(0, -1);
134
- const proxyPath = getProxyPath();
135
- const newPath = `${proxyPath}${options.route}`;
136
- if (path !== newPath) {
137
- // console.warn('SET MODAL URI', newPath);
138
- setPath(`${newPath}`); // ${location.search}
139
- setDocTitle({ ...options.RouterInstance, route: options.route });
140
- }
141
- })();
129
+ // Handle view mode modal route
130
+ if (options.route) {
131
+ handleModalViewRoute({
132
+ route: options.route,
133
+ RouterInstance: options.RouterInstance,
134
+ });
135
+ }
142
136
 
143
137
  break;
144
138
  case 'slide-menu':
@@ -697,6 +691,7 @@ const Modal = {
697
691
  Modal.MoveTitleToBar(id);
698
692
 
699
693
  prepend(`.btn-bar-modal-container-${id}`, html`<div class="hide">${inputInfoNode.outerHTML}</div>`);
694
+ if (s(`.slide-menu-top-bar-fix`)) s(`.slide-menu-top-bar-fix`).classList.add('hide');
700
695
  }
701
696
  };
702
697
 
@@ -716,6 +711,7 @@ const Modal = {
716
711
  } catch (e) {}
717
712
  }
718
713
  Modal.removeModal(searchBoxHistoryId);
714
+ if (s(`.slide-menu-top-bar-fix`)) s(`.slide-menu-top-bar-fix`).classList.remove('hide');
719
715
  };
720
716
  s('.top-bar-search-box').onblur = () => {
721
717
  hoverFocusCtl.checkDismiss();
@@ -890,6 +886,9 @@ const Modal = {
890
886
  ],
891
887
  eventCallBack: () => {
892
888
  if (s(`.top-bar-search-box`)) {
889
+ if (s(`.main-body-btn-ui-close`).classList.contains('hide')) {
890
+ s(`.main-body-btn-ui-open`).click();
891
+ }
893
892
  s(`.top-bar-search-box`).blur();
894
893
  s(`.top-bar-search-box`).focus();
895
894
  s(`.top-bar-search-box`).select();
@@ -1089,12 +1088,13 @@ const Modal = {
1089
1088
  icon: html`<i class="fas fa-language mini-title"></i>`,
1090
1089
  text: Translate.Render('select lang'),
1091
1090
  })}`,
1092
- html: () => html``,
1091
+ html: async () => html`${await Translate.RenderSetting('action-drop-modal' + id)}`,
1093
1092
  titleClass: 'mini-title',
1094
1093
  style: {
1095
1094
  resize: 'none',
1096
- 'max-width': '300px',
1097
- height: '150px !important',
1095
+ width: '100% !important',
1096
+ height: '350px !important',
1097
+ 'max-width': '350px !important',
1098
1098
  'z-index': 7,
1099
1099
  },
1100
1100
  dragDisabled: true,
@@ -1652,27 +1652,14 @@ const Modal = {
1652
1652
  setTimeout(() => {
1653
1653
  if (!s(`.${idModal}`)) return;
1654
1654
  this.removeModal(idModal);
1655
- // Router
1656
- if (options.route)
1657
- (() => {
1658
- let path = window.location.pathname;
1659
- if (path[path.length - 1] !== '/') path = `${path}/`;
1660
- let newPath = `${getProxyPath()}`;
1661
- if (path !== newPath) {
1662
- for (const subIdModal of Object.keys(this.Data).reverse()) {
1663
- if (this.Data[subIdModal].options.route) {
1664
- newPath = `${newPath}${this.Data[subIdModal].options.route}`;
1665
- console.warn('------------> SET MODAL URI', newPath);
1666
- setPath(newPath);
1667
- this.setTopModalCallback(subIdModal);
1668
- return setDocTitle({ ...options.RouterInstance, route: this.Data[subIdModal].options.route });
1669
- }
1670
- }
1671
- console.warn('-------------> SET MODAL URI', newPath);
1672
- setPath(`${newPath}${Modal.homeCid ? `?cid=${Modal.homeCid}` : ''}`);
1673
- return setDocTitle({ ...options.RouterInstance, route: '' });
1674
- }
1675
- })();
1655
+ // Handle modal route change
1656
+ if (options.route) {
1657
+ closeModalRouteChangeEvent({
1658
+ route: options.route,
1659
+ RouterInstance: options.RouterInstance,
1660
+ homeCid: Modal.homeCid,
1661
+ });
1662
+ }
1676
1663
  }, 300);
1677
1664
  };
1678
1665
  s(`.btn-close-${idModal}`).onclick = btnCloseEvent;
@@ -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 { listenQueryPathInstance, setQueryPath } from './Router.js';
18
+ import { closeModalRouteChangeEvents, listenQueryPathInstance, renderTitle, setQueryPath } from './Router.js';
19
19
 
20
20
  const PanelForm = {
21
21
  Data: {},
@@ -425,15 +425,22 @@ const PanelForm = {
425
425
  });
426
426
  let firsUpdateEvent = false;
427
427
  let lastCid;
428
- console.warn('-------------> updatePanel 1');
428
+ let lastUserId;
429
+ closeModalRouteChangeEvents[idPanel] = (newPath) => {
430
+ if (newPath.split('?')[0] === '/' && PanelForm.Data[idPanel].data && PanelForm.Data[idPanel].data.length === 0) {
431
+ this.Data[idPanel].updatePanel();
432
+ }
433
+ };
429
434
  this.Data[idPanel].updatePanel = async () => {
430
- console.warn('-------------> updatePanel 2');
431
435
  const cid = getQueryParams().cid ? getQueryParams().cid : '';
432
- if (lastCid === cid) return;
436
+ const forceUpdate = lastUserId !== Elements.Data.user.main.model.user._id;
437
+ if (lastCid === cid && !forceUpdate) return;
438
+ lastUserId = newInstance(Elements.Data.user.main.model.user._id);
433
439
  lastCid = cid;
434
440
  if (options.route === 'home') Modal.homeCid = newInstance(cid);
435
441
  htmls(`.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`, await renderSrrPanelData());
436
442
  await getPanelData();
443
+ if (this.Data[idPanel].data.length === 1) renderTitle(this.Data[idPanel].data[0].title);
437
444
  htmls(
438
445
  `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`,
439
446
  await panelRender({ data: this.Data[idPanel].data }),
@@ -454,6 +461,7 @@ const PanelForm = {
454
461
  if (!options.parentIdModal)
455
462
  Modal.Data['modal-menu'].onHome[idPanel] = async () => {
456
463
  lastCid = undefined;
464
+ lastUserId = undefined;
457
465
  setQueryPath({ path: options.route, queryPath: '' });
458
466
  await this.Data[idPanel].updatePanel();
459
467
  };
@@ -1,16 +1,20 @@
1
1
  import { titleFormatted } from './CommonJs.js';
2
2
  import { loggerFactory } from './Logger.js';
3
3
  import { getProxyPath, getQueryParams, htmls, s, setPath } from './VanillaJs.js';
4
+ import { Modal } from './Modal.js';
5
+ import { Worker } from './Worker.js';
4
6
 
5
7
  // Router
6
8
 
7
9
  const logger = loggerFactory(import.meta);
8
10
 
11
+ const renderTitle = (title, nameApp) => htmls('title', html`${title} | ${nameApp ?? Worker.RouterInstance.NameApp}`);
12
+
9
13
  const setDocTitle = (options = { Routes: () => {}, route: '', NameApp: '' }) => {
10
14
  const { Routes, route, NameApp } = options;
11
15
  let title = titleFormatted(Routes()[`/${route}`].title);
12
16
  if (Routes()[`/${route}`].upperCase) title = title.toUpperCase();
13
- htmls('title', html`${title} | ${NameApp}`);
17
+ renderTitle(title, NameApp);
14
18
  {
15
19
  const routeId = route === '' ? 'home' : route;
16
20
  if (s(`.main-btn-${routeId}`)) {
@@ -75,4 +79,61 @@ const listenQueryPathInstance = ({ id, routeId, event }, queryKey = 'cid') => {
75
79
  });
76
80
  };
77
81
 
78
- export { Router, setDocTitle, LoadRouter, RouterEvents, setQueryPath, listenQueryPathInstance };
82
+ const closeModalRouteChangeEvents = {};
83
+ const triggerCloseModalRouteChangeEvents = (newPath) => {
84
+ console.warn('[closeModalRouteChangeEvent]', newPath);
85
+ for (const event of Object.keys(closeModalRouteChangeEvents)) closeModalRouteChangeEvents[event](newPath);
86
+ };
87
+
88
+ const closeModalRouteChangeEvent = (options = {}) => {
89
+ const { route, RouterInstance, homeCid } = options;
90
+ if (!route) return;
91
+
92
+ let path = window.location.pathname;
93
+ if (path[path.length - 1] !== '/') path = `${path}/`;
94
+ let newPath = `${getProxyPath()}`;
95
+
96
+ if (path !== newPath) {
97
+ for (const subIdModal of Object.keys(Modal.Data).reverse()) {
98
+ if (Modal.Data[subIdModal]?.options?.route) {
99
+ newPath = `${newPath}${Modal.Data[subIdModal].options.route}`;
100
+ triggerCloseModalRouteChangeEvents(newPath);
101
+ setPath(newPath);
102
+ Modal.setTopModalCallback(subIdModal);
103
+ return setDocTitle({ ...RouterInstance, route: Modal.Data[subIdModal].options.route });
104
+ }
105
+ }
106
+ newPath = `${newPath}${homeCid ? `?cid=${homeCid}` : ''}`;
107
+ triggerCloseModalRouteChangeEvents(newPath);
108
+ setPath(newPath);
109
+ return setDocTitle({ ...RouterInstance, route: '' });
110
+ }
111
+ };
112
+
113
+ const handleModalViewRoute = (options = {}) => {
114
+ const { route, RouterInstance } = options;
115
+ if (!route) return;
116
+
117
+ let path = window.location.pathname;
118
+ if (path !== '/' && path[path.length - 1] === '/') path = path.slice(0, -1);
119
+ const proxyPath = getProxyPath();
120
+ const newPath = `${proxyPath}${route}`;
121
+
122
+ if (path !== newPath) {
123
+ setPath(newPath);
124
+ setDocTitle({ ...RouterInstance, route });
125
+ }
126
+ };
127
+
128
+ export {
129
+ Router,
130
+ setDocTitle,
131
+ LoadRouter,
132
+ RouterEvents,
133
+ setQueryPath,
134
+ listenQueryPathInstance,
135
+ closeModalRouteChangeEvent,
136
+ handleModalViewRoute,
137
+ closeModalRouteChangeEvents,
138
+ renderTitle,
139
+ };
@@ -60,10 +60,10 @@ const Translate = {
60
60
  this.Parse(language);
61
61
  if (s(`.action-btn-lang-render`)) htmls(`.action-btn-lang-render`, s('html').lang);
62
62
  },
63
- RenderSetting: async function () {
63
+ RenderSetting: async function (id) {
64
64
  return html` <div class="in section-mp">
65
65
  ${await DropDown.Render({
66
- id: 'settings-lang',
66
+ id: id ?? 'settings-lang',
67
67
  value: s('html').lang ? s('html').lang : 'en',
68
68
  label: html`${Translate.Render('lang')}`,
69
69
  data: ['en', 'es'].map((language) => {
package/src/index.js CHANGED
@@ -34,7 +34,7 @@ class Underpost {
34
34
  * @type {String}
35
35
  * @memberof Underpost
36
36
  */
37
- static version = 'v2.8.844';
37
+ static version = 'v2.8.845';
38
38
  /**
39
39
  * Repository cli API
40
40
  * @static
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+
3
+ import fs from 'fs-extra';
4
+ import swaggerAutoGen from 'swagger-autogen';
5
+ import { shellExec } from './process.js';
6
+ import { loggerFactory } from './logger.js';
7
+ import { JSONweb } from './client-formatted.js';
8
+
9
+ /**
10
+ * Builds API documentation using Swagger
11
+ * @param {Object} options - Documentation build options
12
+ * @param {string} options.host - The hostname for the API
13
+ * @param {string} options.path - The base path for the API
14
+ * @param {number} options.port - The port number for the API
15
+ * @param {Object} options.metadata - Metadata for the API documentation
16
+ * @param {Array<string>} options.apis - List of API modules to document
17
+ * @param {string} options.publicClientId - Client ID for the public documentation
18
+ * @param {string} options.rootClientPath - Root path for client files
19
+ * @param {Object} options.packageData - Package.json data
20
+ */
21
+ const buildApiDocs = async ({
22
+ host,
23
+ path,
24
+ port,
25
+ metadata = {},
26
+ apis = [],
27
+ publicClientId,
28
+ rootClientPath,
29
+ packageData,
30
+ }) => {
31
+ const logger = loggerFactory(import.meta);
32
+ const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
33
+
34
+ const doc = {
35
+ info: {
36
+ version: packageData.version,
37
+ title: metadata?.title ? `${metadata.title}` : 'REST API',
38
+ description: metadata?.description ? metadata.description : '',
39
+ },
40
+ servers: [
41
+ {
42
+ url:
43
+ process.env.NODE_ENV === 'development'
44
+ ? `http://localhost:${port}${path}${basePath}`
45
+ : `https://${host}${path}${basePath}`,
46
+ description: `${process.env.NODE_ENV} server`,
47
+ },
48
+ ],
49
+ tags: [
50
+ {
51
+ name: 'user',
52
+ description: 'User API operations',
53
+ },
54
+ ],
55
+ components: {
56
+ schemas: {
57
+ userRequest: {
58
+ username: 'user123',
59
+ password: 'Password123',
60
+ email: 'user@example.com',
61
+ },
62
+ userResponse: {
63
+ status: 'success',
64
+ data: {
65
+ token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
66
+ user: {
67
+ _id: '66c377f57f99e5969b81de89',
68
+ email: 'user@example.com',
69
+ emailConfirmed: false,
70
+ username: 'user123',
71
+ role: 'user',
72
+ profileImageId: '66c377f57f99e5969b81de87',
73
+ },
74
+ },
75
+ },
76
+ userUpdateResponse: {
77
+ status: 'success',
78
+ data: {
79
+ _id: '66c377f57f99e5969b81de89',
80
+ email: 'user@example.com',
81
+ emailConfirmed: false,
82
+ username: 'user123222',
83
+ role: 'user',
84
+ profileImageId: '66c377f57f99e5969b81de87',
85
+ },
86
+ },
87
+ userGetResponse: {
88
+ status: 'success',
89
+ data: {
90
+ _id: '66c377f57f99e5969b81de89',
91
+ email: 'user@example.com',
92
+ emailConfirmed: false,
93
+ username: 'user123222',
94
+ role: 'user',
95
+ profileImageId: '66c377f57f99e5969b81de87',
96
+ },
97
+ },
98
+ userLogInRequest: {
99
+ email: 'user@example.com',
100
+ password: 'Password123',
101
+ },
102
+ userBadRequestResponse: {
103
+ status: 'error',
104
+ message: 'Bad request. Please check your inputs, and try again',
105
+ },
106
+ },
107
+ securitySchemes: {
108
+ bearerAuth: {
109
+ type: 'http',
110
+ scheme: 'bearer',
111
+ },
112
+ },
113
+ },
114
+ };
115
+
116
+ logger.warn('build swagger api docs', doc.info);
117
+
118
+ const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
119
+ const routes = [];
120
+ for (const api of apis) {
121
+ if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
122
+ }
123
+
124
+ await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
125
+ };
126
+
127
+ /**
128
+ * Builds JSDoc documentation
129
+ * @param {Object} options - JSDoc build options
130
+ * @param {string} options.host - The hostname for the documentation
131
+ * @param {string} options.path - The base path for the documentation
132
+ * @param {Object} options.metadata - Metadata for the documentation
133
+ */
134
+ const buildJsDocs = async ({ host, path, metadata = {} }) => {
135
+ const logger = loggerFactory(import.meta);
136
+ const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
137
+
138
+ jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
139
+ jsDocsConfig.opts.theme_opts.title = metadata?.title ? metadata.title : undefined;
140
+ jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
141
+
142
+ fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
143
+ logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
144
+
145
+ shellExec(`npm run docs`, { silent: true });
146
+ };
147
+
148
+ /**
149
+ * Builds test coverage documentation
150
+ * @param {Object} options - Coverage build options
151
+ * @param {string} options.host - The hostname for the coverage
152
+ * @param {string} options.path - The base path for the coverage
153
+ */
154
+ const buildCoverage = async ({ host, path }) => {
155
+ const logger = loggerFactory(import.meta);
156
+ const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
157
+
158
+ if (!fs.existsSync(`./coverage`)) {
159
+ shellExec(`npm test`);
160
+ }
161
+
162
+ const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
163
+ fs.mkdirSync(coverageBuildPath, { recursive: true });
164
+ fs.copySync(`./coverage`, coverageBuildPath);
165
+
166
+ logger.warn('build coverage', coverageBuildPath);
167
+ };
168
+
169
+ /**
170
+ * Main function to build all documentation
171
+ * @param {Object} options - Documentation build options
172
+ * @param {string} options.host - The hostname
173
+ * @param {string} options.path - The base path
174
+ * @param {number} options.port - The port number
175
+ * @param {Object} options.metadata - Metadata for the documentation
176
+ * @param {Array<string>} options.apis - List of API modules to document
177
+ * @param {string} options.publicClientId - Client ID for the public documentation
178
+ * @param {string} options.rootClientPath - Root path for client files
179
+ * @param {Object} options.packageData - Package.json data
180
+ */
181
+ const buildDocs = async ({
182
+ host,
183
+ path,
184
+ port,
185
+ metadata = {},
186
+ apis = [],
187
+ publicClientId,
188
+ rootClientPath,
189
+ packageData,
190
+ }) => {
191
+ await buildJsDocs({ host, path, metadata });
192
+ await buildCoverage({ host, path });
193
+ await buildApiDocs({
194
+ host,
195
+ path,
196
+ port,
197
+ metadata,
198
+ apis,
199
+ publicClientId,
200
+ rootClientPath,
201
+ packageData,
202
+ });
203
+ };
204
+
205
+ export { buildDocs };
@@ -17,11 +17,11 @@ import dotenv from 'dotenv';
17
17
  import AdmZip from 'adm-zip';
18
18
  import * as dir from 'path';
19
19
  import { shellExec } from './process.js';
20
- import swaggerAutoGen from 'swagger-autogen';
21
20
  import { SitemapStream, streamToPromise } from 'sitemap';
22
21
  import { Readable } from 'stream';
23
22
  import { buildIcons, buildTextImg, getBufferPngText } from './client-icons.js';
24
23
  import Underpost from '../index.js';
24
+ import { buildDocs } from './client-build-docs.js';
25
25
 
26
26
  dotenv.config();
27
27
 
@@ -557,125 +557,16 @@ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
557
557
  }
558
558
 
559
559
  if (!enableLiveRebuild && !process.argv.includes('l') && !process.argv.includes('deploy') && docsBuild) {
560
- // https://www.pullrequest.com/blog/leveraging-jsdoc-for-better-code-documentation-in-javascript/
561
- // https://jsdoc.app/about-configuring-jsdoc
562
- // https://jsdoc.app/ Block tags
563
-
564
- const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
565
- jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
566
- jsDocsConfig.opts.theme_opts.title = metadata && metadata.title ? metadata.title : undefined;
567
- jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
568
- fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
569
- logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
570
- shellExec(`npm run docs`, { silent: true });
571
-
572
- // coverage
573
- if (!fs.existsSync(`./coverage`)) {
574
- shellExec(`npm test`);
575
- }
576
- const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
577
- fs.mkdirSync(coverageBuildPath, { recursive: true });
578
- fs.copySync(`./coverage`, coverageBuildPath);
579
-
580
- // https://swagger-autogen.github.io/docs/
581
-
582
- const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
583
-
584
- const doc = {
585
- info: {
586
- version: packageData.version, // by default: '1.0.0'
587
- title: metadata?.title ? `${metadata.title}` : 'REST API', // by default: 'REST API'
588
- description: metadata?.description ? metadata.description : '', // by default: ''
589
- },
590
- servers: [
591
- {
592
- url:
593
- process.env.NODE_ENV === 'development'
594
- ? `http://localhost:${port}${path}${basePath}`
595
- : `https://${host}${path}${basePath}`, // by default: 'http://localhost:3000'
596
- description: `${process.env.NODE_ENV} server`, // by default: ''
597
- },
598
- ],
599
- tags: [
600
- // by default: empty Array
601
- {
602
- name: 'user', // Tag name
603
- description: 'User API operations', // Tag description
604
- },
605
- ],
606
- components: {
607
- schemas: {
608
- userRequest: {
609
- username: 'user123',
610
- password: 'Password123',
611
- email: 'user@example.com',
612
- },
613
- userResponse: {
614
- status: 'success',
615
- data: {
616
- token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
617
- user: {
618
- _id: '66c377f57f99e5969b81de89',
619
- email: 'user@example.com',
620
- emailConfirmed: false,
621
- username: 'user123',
622
- role: 'user',
623
- profileImageId: '66c377f57f99e5969b81de87',
624
- },
625
- },
626
- },
627
- userUpdateResponse: {
628
- status: 'success',
629
- data: {
630
- _id: '66c377f57f99e5969b81de89',
631
- email: 'user@example.com',
632
- emailConfirmed: false,
633
- username: 'user123222',
634
- role: 'user',
635
- profileImageId: '66c377f57f99e5969b81de87',
636
- },
637
- },
638
- userGetResponse: {
639
- status: 'success',
640
- data: {
641
- _id: '66c377f57f99e5969b81de89',
642
- email: 'user@example.com',
643
- emailConfirmed: false,
644
- username: 'user123222',
645
- role: 'user',
646
- profileImageId: '66c377f57f99e5969b81de87',
647
- },
648
- },
649
- userLogInRequest: {
650
- email: 'user@example.com',
651
- password: 'Password123',
652
- },
653
- userBadRequestResponse: {
654
- status: 'error',
655
- message: 'Bad request. Please check your inputs, and try again',
656
- },
657
- },
658
- securitySchemes: {
659
- bearerAuth: {
660
- type: 'http',
661
- scheme: 'bearer',
662
- },
663
- },
664
- },
665
- };
666
-
667
- logger.warn('build swagger api docs', doc.info);
668
-
669
- const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
670
- const routes = [];
671
- for (const api of apis) {
672
- if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
673
- }
674
-
675
- /* NOTE: If you are using the express Router, you must pass in the 'routes' only the
676
- root file where the route starts, such as index.js, app.js, routes.js, etc ... */
677
-
678
- await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
560
+ await buildDocs({
561
+ host,
562
+ path,
563
+ port,
564
+ metadata,
565
+ apis,
566
+ publicClientId,
567
+ rootClientPath,
568
+ packageData,
569
+ });
679
570
  }
680
571
 
681
572
  if (client) {