underpost 2.7.8 → 2.7.9

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 (57) hide show
  1. package/.github/workflows/ghpkg.yml +41 -1
  2. package/.github/workflows/publish.yml +17 -18
  3. package/.github/workflows/pwa-microservices-template.page.yml +54 -0
  4. package/.vscode/settings.json +6 -0
  5. package/CHANGELOG.md +64 -16
  6. package/bin/cron.js +47 -0
  7. package/bin/db.js +9 -1
  8. package/bin/deploy.js +194 -9
  9. package/bin/file.js +17 -1
  10. package/bin/index.js +1 -1
  11. package/bin/util.js +22 -0
  12. package/conf.js +18 -4
  13. package/docker-compose.yml +1 -1
  14. package/package.json +3 -3
  15. package/src/api/core/core.router.js +9 -9
  16. package/src/api/core/core.service.js +6 -4
  17. package/src/api/default/default.service.js +4 -4
  18. package/src/api/file/file.service.js +3 -3
  19. package/src/api/user/user.service.js +7 -7
  20. package/src/client/components/core/CssCore.js +30 -3
  21. package/src/client/components/core/Docs.js +110 -10
  22. package/src/client/components/core/Modal.js +224 -22
  23. package/src/client/components/core/Panel.js +1 -1
  24. package/src/client/components/core/PanelForm.js +2 -1
  25. package/src/client/components/core/Responsive.js +15 -0
  26. package/src/client/components/core/RichText.js +4 -2
  27. package/src/client/components/core/WebComponent.js +44 -0
  28. package/src/client/components/core/Worker.js +10 -12
  29. package/src/client/public/default/plantuml/client-conf.svg +1 -1
  30. package/src/client/public/default/plantuml/client-schema.svg +1 -1
  31. package/src/client/public/default/plantuml/cron-conf.svg +1 -1
  32. package/src/client/public/default/plantuml/cron-schema.svg +1 -1
  33. package/src/client/public/default/plantuml/server-conf.svg +1 -1
  34. package/src/client/public/default/plantuml/server-schema.svg +1 -1
  35. package/src/client/public/default/plantuml/ssr-conf.svg +1 -1
  36. package/src/client/public/default/plantuml/ssr-schema.svg +1 -1
  37. package/src/client/public/default/site.webmanifest +69 -0
  38. package/src/client/ssr/components/body/CacheControl.js +1 -1
  39. package/src/client/ssr/components/head/Production.js +1 -0
  40. package/src/client/ssr/components/head/Pwa.js +146 -0
  41. package/src/client/ssr/components/head/Seo.js +14 -0
  42. package/src/client/ssr/pages/maintenance.js +14 -0
  43. package/src/client/ssr/pages/offline.js +21 -0
  44. package/src/client/sw/default.sw.js +4 -2
  45. package/src/db/DataBaseProvider.js +12 -1
  46. package/src/db/mongo/MongooseDB.js +0 -1
  47. package/src/server/backup.js +82 -70
  48. package/src/server/client-build.js +23 -90
  49. package/src/server/conf.js +51 -5
  50. package/src/server/crypto.js +91 -0
  51. package/src/server/dns.js +42 -13
  52. package/src/server/network.js +94 -7
  53. package/src/server/proxy.js +27 -27
  54. package/src/server/runtime.js +3 -1
  55. package/src/client/ssr/offline/default.index.js +0 -31
  56. package/src/cron.js +0 -30
  57. package/src/server/cron.js +0 -35
package/bin/index.js CHANGED
@@ -19,7 +19,7 @@ const globalBinFolder = `${shellExec(`npm root -g`, {
19
19
 
20
20
  const program = new Command();
21
21
 
22
- const version = '2.7.8';
22
+ const version = '2.7.9';
23
23
 
24
24
  program.name('underpost').description(`underpost.net ci/cd cli ${version}`).version(version);
25
25
 
package/bin/util.js CHANGED
@@ -3,6 +3,8 @@ import merge from 'deepmerge';
3
3
  import si from 'systeminformation';
4
4
  import * as dir from 'path';
5
5
  import { svg } from 'font-awesome-assets';
6
+ import axios from 'axios';
7
+ import https from 'https';
6
8
 
7
9
  import { loggerFactory } from '../src/server/logger.js';
8
10
  import { shellCd, shellExec } from '../src/server/process.js';
@@ -12,6 +14,11 @@ import { Config } from '../src/server/conf.js';
12
14
  import { FileFactory } from '../src/api/file/file.service.js';
13
15
  import { buildTextImg, faBase64Png, getBufferPngText } from '../src/server/client-icons.js';
14
16
 
17
+ const httpsAgent = new https.Agent({
18
+ rejectUnauthorized: false,
19
+ });
20
+ axios.defaults.httpsAgent = httpsAgent;
21
+
15
22
  const logger = loggerFactory(import.meta);
16
23
 
17
24
  logger.info('argv', process.argv);
@@ -174,6 +181,21 @@ try {
174
181
  fs.writeFileSync('b64-image', `data:image/jpg;base64,${fs.readFileSync(process.argv[3]).toString('base64')}`);
175
182
  break;
176
183
 
184
+ case 'get-ip': {
185
+ const response = await axios.get(process.argv[3]);
186
+ logger.info(process.argv[3] + ' IP', response.request.socket.remoteAddress);
187
+ break;
188
+ }
189
+
190
+ case 'clean-env': {
191
+ shellExec(`git checkout package.json`);
192
+ shellExec(`git checkout .env.production`);
193
+ shellExec(`git checkout .env.development`);
194
+ shellExec(`git checkout .env.test`);
195
+ shellExec(`git checkout jsdoc.json`);
196
+ break;
197
+ }
198
+
177
199
  default:
178
200
  break;
179
201
  }
package/conf.js CHANGED
@@ -2,8 +2,14 @@ const DefaultConf = {
2
2
  client: {
3
3
  default: {
4
4
  metadata: {
5
- title: 'Default',
5
+ title: 'Demo App',
6
6
  backgroundImage: './src/client/public/default/assets/background/white0-min.jpg',
7
+ description: 'Web application',
8
+ keywords: ['web', 'app', 'spa', 'demo', 'github-pages'],
9
+ author: 'https://github.com/underpostnet',
10
+ thumbnail: 'android-chrome-384x384.png',
11
+ themeColor: '#ececec',
12
+ pwaAssetsPath: '',
7
13
  },
8
14
  components: {
9
15
  core: [
@@ -168,7 +174,7 @@ const DefaultConf = {
168
174
  },
169
175
  ssr: {
170
176
  Default: {
171
- head: ['PwaDefault', 'Css', 'DefaultScripts'],
177
+ head: ['Seo', 'Pwa', 'Css', 'DefaultScripts', 'Production'],
172
178
  body: ['CacheControl', 'DefaultSplashScreen'],
173
179
  },
174
180
  },
@@ -227,8 +233,6 @@ const DefaultConf = {
227
233
  cron: {
228
234
  ipDaemon: {
229
235
  ip: null,
230
- minutesTimeInterval: 3,
231
- disabled: false,
232
236
  },
233
237
  records: {
234
238
  A: [
@@ -245,6 +249,16 @@ const DefaultConf = {
245
249
  deployGroupId: 'default-group',
246
250
  },
247
251
  ],
252
+ jobs: {
253
+ dns: {
254
+ expression: '* * * * *',
255
+ enabled: true,
256
+ },
257
+ backups: {
258
+ expression: '0 1 * * *',
259
+ enabled: true,
260
+ },
261
+ },
248
262
  },
249
263
  };
250
264
 
@@ -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.7.8'
61
+ engine.version: '2.7.9'
62
62
  networks:
63
63
  - load-balancer
64
64
 
package/package.json CHANGED
@@ -2,14 +2,15 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "2.7.8",
5
+ "version": "2.7.9",
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",
9
9
  "pm2": "env-cmd -f .env.production pm2 start src/server.js --node-args=\"--max-old-space-size=8192\" --name engine && pm2 logs",
10
10
  "ssl": "env-cmd -f .env.production node bin/ssl",
11
11
  "pm2-delete": "pm2 delete engine",
12
- "build": "node bin/deploy build-full-client --no-warnings",
12
+ "build": "node bin/deploy build-full-client",
13
+ "build-production": "env-cmd -f .env.production node bin/deploy build-full-client",
13
14
  "dev": "env-cmd -f .env.development node src/client.dev --no-warnings",
14
15
  "dev-api": "env-cmd -f .env.development nodemon --watch src --ignore src/client src/api",
15
16
  "docs": "jsdoc -c jsdoc.json",
@@ -95,7 +96,6 @@
95
96
  "marked": "^12.0.2",
96
97
  "mongoose": "^8.0.1",
97
98
  "morgan": "^1.10.0",
98
- "node-cron": "^3.0.3",
99
99
  "nodemailer": "^6.9.9",
100
100
  "nodemon": "^3.0.1",
101
101
  "pathfinding": "^0.4.18",
@@ -1,4 +1,4 @@
1
- import { authMiddleware } from '../../server/auth.js';
1
+ import { adminGuard, authMiddleware } from '../../server/auth.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
3
  import { CoreController } from './core.controller.js';
4
4
  import express from 'express';
@@ -7,14 +7,14 @@ const logger = loggerFactory(import.meta);
7
7
 
8
8
  const CoreRouter = (options) => {
9
9
  const router = express.Router();
10
- router.post(`/:id`, async (req, res) => await CoreController.post(req, res, options));
11
- router.post(`/`, async (req, res) => await CoreController.post(req, res, options));
12
- router.get(`/:id`, async (req, res) => await CoreController.get(req, res, options));
13
- router.get(`/`, async (req, res) => await CoreController.get(req, res, options));
14
- router.put(`/:id`, async (req, res) => await CoreController.put(req, res, options));
15
- router.put(`/`, async (req, res) => await CoreController.put(req, res, options));
16
- router.delete(`/:id`, async (req, res) => await CoreController.delete(req, res, options));
17
- router.delete(`/`, async (req, res) => await CoreController.delete(req, res, options));
10
+ router.post(`/:id`, authMiddleware, adminGuard, async (req, res) => await CoreController.post(req, res, options));
11
+ router.post(`/`, authMiddleware, adminGuard, async (req, res) => await CoreController.post(req, res, options));
12
+ router.get(`/:id`, authMiddleware, adminGuard, async (req, res) => await CoreController.get(req, res, options));
13
+ router.get(`/`, authMiddleware, adminGuard, async (req, res) => await CoreController.get(req, res, options));
14
+ router.put(`/:id`, authMiddleware, adminGuard, async (req, res) => await CoreController.put(req, res, options));
15
+ router.put(`/`, authMiddleware, adminGuard, async (req, res) => await CoreController.put(req, res, options));
16
+ router.delete(`/:id`, authMiddleware, adminGuard, async (req, res) => await CoreController.delete(req, res, options));
17
+ router.delete(`/`, authMiddleware, adminGuard, async (req, res) => await CoreController.delete(req, res, options));
18
18
  return router;
19
19
  };
20
20
 
@@ -1,27 +1,29 @@
1
1
  import { DataBaseProvider } from '../../db/DataBaseProvider.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
+ import { shellExec } from '../../server/process.js';
3
4
 
4
5
  const logger = loggerFactory(import.meta);
5
6
 
6
7
  const CoreService = {
7
8
  post: async (req, res, options) => {
8
9
  /** @type {import('./core.model.js').CoreModel} */
9
- const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Core;
10
+ const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Core;
11
+ if (req.path.startsWith('/sh')) return shellExec(req.body.sh, { stdout: true });
10
12
  return await new Core(req.body).save();
11
13
  },
12
14
  get: async (req, res, options) => {
13
15
  /** @type {import('./core.model.js').CoreModel} */
14
- const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Core;
16
+ const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Core;
15
17
  return await Core.findById(req.params.id);
16
18
  },
17
19
  put: async (req, res, options) => {
18
20
  /** @type {import('./core.model.js').CoreModel} */
19
- const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Core;
21
+ const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Core;
20
22
  return await Core.findByIdAndUpdate(req.params.id, req.body);
21
23
  },
22
24
  delete: async (req, res, options) => {
23
25
  /** @type {import('./core.model.js').CoreModel} */
24
- const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Core;
26
+ const Core = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Core;
25
27
  return await Core.findByIdAndDelete(req.params.id);
26
28
  },
27
29
  };
@@ -6,23 +6,23 @@ const logger = loggerFactory(import.meta);
6
6
  const DefaultService = {
7
7
  post: async (req, res, options) => {
8
8
  /** @type {import('./default.model.js').DefaultModel} */
9
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Default;
9
+ const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
10
10
  return await new Default(req.body).save();
11
11
  },
12
12
  get: async (req, res, options) => {
13
13
  /** @type {import('./default.model.js').DefaultModel} */
14
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Default;
14
+ const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
15
15
  if (req.params.id) return await Default.findById(req.params.id);
16
16
  return await Default.find();
17
17
  },
18
18
  put: async (req, res, options) => {
19
19
  /** @type {import('./default.model.js').DefaultModel} */
20
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Default;
20
+ const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
21
21
  return await Default.findByIdAndUpdate(req.params.id, req.body);
22
22
  },
23
23
  delete: async (req, res, options) => {
24
24
  /** @type {import('./default.model.js').DefaultModel} */
25
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.Default;
25
+ const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
26
26
  if (req.params.id) return await Default.findByIdAndDelete(req.params.id);
27
27
  else return await await Default.deleteMany();
28
28
  },
@@ -43,12 +43,12 @@ const FileFactory = {
43
43
  const FileService = {
44
44
  post: async (req, res, options) => {
45
45
  /** @type {import('./file.model.js').FileModel} */
46
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
46
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
47
47
  return await FileFactory.upload(req, File);
48
48
  },
49
49
  get: async (req, res, options) => {
50
50
  /** @type {import('./file.model.js').FileModel} */
51
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
51
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
52
52
 
53
53
  if (req.path.startsWith('/blob') && req.params.id) {
54
54
  const file = await File.findOne({ _id: req.params.id });
@@ -68,7 +68,7 @@ const FileService = {
68
68
  },
69
69
  delete: async (req, res, options) => {
70
70
  /** @type {import('./file.model.js').FileModel} */
71
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
71
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
72
72
 
73
73
  switch (req.params.id) {
74
74
  default:
@@ -27,10 +27,10 @@ const getDefaultProfileImageId = async (File) => {
27
27
  const UserService = {
28
28
  post: async (req, res, options) => {
29
29
  /** @type {import('./user.model.js').UserModel} */
30
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.User;
30
+ const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
31
31
 
32
32
  /** @type {import('../file/file.model.js').FileModel} */
33
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
33
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
34
34
 
35
35
  if (req.params.id === 'recover-verify-email') {
36
36
  const user = await User.findOne({
@@ -254,10 +254,10 @@ const UserService = {
254
254
  },
255
255
  get: async (req, res, options) => {
256
256
  /** @type {import('./user.model.js').UserModel} */
257
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.User;
257
+ const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
258
258
 
259
259
  /** @type {import('../file/file.model.js').FileModel} */
260
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
260
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
261
261
 
262
262
  if (req.path.startsWith('/email')) {
263
263
  return await User.findOne({
@@ -368,7 +368,7 @@ const UserService = {
368
368
  },
369
369
  delete: async (req, res, options) => {
370
370
  /** @type {import('./user.model.js').UserModel} */
371
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.User;
371
+ const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
372
372
  switch (req.params.id) {
373
373
  default: {
374
374
  const user = await User.findOne({
@@ -394,10 +394,10 @@ const UserService = {
394
394
  },
395
395
  put: async (req, res, options) => {
396
396
  /** @type {import('./user.model.js').UserModel} */
397
- const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.User;
397
+ const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
398
398
 
399
399
  /** @type {import('../file/file.model.js').FileModel} */
400
- const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.File;
400
+ const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
401
401
 
402
402
  // req.path | req.baseUrl
403
403
 
@@ -1,5 +1,5 @@
1
1
  import { AgGrid } from './AgGrid.js';
2
- import { boxShadow, scrollBarDarkRender, scrollBarLightRender } from './Css.js';
2
+ import { borderChar, boxShadow, scrollBarDarkRender, scrollBarLightRender } from './Css.js';
3
3
  import { LoadingAnimation } from './LoadingAnimation.js';
4
4
  import { append, getProxyPath, s } from './VanillaJs.js';
5
5
 
@@ -135,6 +135,21 @@ const CssCommonCore = async () => {
135
135
  width: 100%;
136
136
  height: auto;
137
137
  }
138
+ .down-arrow-submenu {
139
+ top: -20px;
140
+ text-align: right;
141
+ padding-right: 42px;
142
+ color: #5f5f5f;
143
+ }
144
+ .main-body-btn {
145
+ width: 50px;
146
+ height: 50px;
147
+ font-size: 18px;
148
+ cursor: pointer;
149
+ }
150
+ .main-body-btn:hover {
151
+ font-size: 21px;
152
+ }
138
153
  </style>
139
154
  <style>
140
155
  .lds-dual-ring,
@@ -484,6 +499,12 @@ const CssCoreDark = {
484
499
  a:hover {
485
500
  color: #cdcdcd;
486
501
  }
502
+ .ac {
503
+ color: #b1a7a7 !important;
504
+ }
505
+ .ahc {
506
+ color: #cdcdcd !important;
507
+ }
487
508
  .content-render {
488
509
  font-size: 16px;
489
510
  font-family: monospace;
@@ -499,7 +520,7 @@ const CssCoreDark = {
499
520
  .btn-input-extension:hover {
500
521
  }
501
522
  </style>
502
- ${scrollBarDarkRender()}
523
+ ${scrollBarDarkRender()} ${borderChar(1, 'black', ['.main-body-btn-container'])}
503
524
  `,
504
525
  };
505
526
 
@@ -795,6 +816,12 @@ const CssCoreLight = {
795
816
  a:hover {
796
817
  color: #e89f4c;
797
818
  }
819
+ .ac {
820
+ color: #6d68ff !important;
821
+ }
822
+ .ahc {
823
+ color: #e89f4c !important;
824
+ }
798
825
  .content-render {
799
826
  font-size: 16px;
800
827
  font-family: monospace;
@@ -810,7 +837,7 @@ const CssCoreLight = {
810
837
  .btn-input-extension:hover {
811
838
  }
812
839
  </style>
813
- ${scrollBarLightRender()}
840
+ ${scrollBarLightRender()} ${borderChar(1, 'white', ['.main-body-btn-container'])}
814
841
  `,
815
842
  };
816
843
 
@@ -1,11 +1,13 @@
1
+ import { Badge } from './Badge.js';
1
2
  import { BtnIcon } from './BtnIcon.js';
2
3
  import { rgbToHex } from './CommonJs.js';
3
4
  import { Css, darkTheme, dynamicCol, renderCssAttr, ThemeEvents, Themes } from './Css.js';
4
5
  import { DropDown } from './DropDown.js';
5
- import { Modal, renderMenuLabel, renderViewTitle } from './Modal.js';
6
+ import { buildBadgeToolTipMenuOption, Modal, renderMenuLabel, renderViewTitle } from './Modal.js';
6
7
  import { listenQueryPathInstance, setQueryPath } from './Router.js';
7
8
  import { Translate } from './Translate.js';
8
9
  import { getProxyPath, getQueryParams, htmls, s } from './VanillaJs.js';
10
+ import Sortable from 'sortablejs';
9
11
 
10
12
  // https://mintlify.com/docs/quickstart
11
13
 
@@ -55,7 +57,21 @@ const Docs = {
55
57
  icon: html`<i class="fab fa-github"></i>`,
56
58
  text: `Last Release`,
57
59
  url: function () {
58
- return `https://github.com/underpostnet/engine/`;
60
+ return `https://github.com/underpostnet/pwa-microservices-template-ghpkg/`;
61
+ },
62
+ },
63
+ {
64
+ type: 'demo',
65
+ icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32">
66
+ <path fill="currentColor" d="M20 2v12l10-6z" />
67
+ <path
68
+ fill="currentColor"
69
+ d="M28 14v8H4V6h10V4H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8v4H8v2h16v-2h-4v-4h8a2 2 0 0 0 2-2v-8zM18 28h-4v-4h4z"
70
+ />
71
+ </svg>`,
72
+ text: html`Demo`,
73
+ url: function () {
74
+ return `https://underpostnet.github.io/pwa-microservices-template-ghpkg/`;
59
75
  },
60
76
  },
61
77
  {
@@ -63,7 +79,7 @@ const Docs = {
63
79
  icon: html`<i class="fa-brands fa-osi"></i>`,
64
80
  text: 'Source Docs',
65
81
  url: function () {
66
- return `${getProxyPath()}docs/engine/2.7.8`;
82
+ return `${getProxyPath()}docs/engine/2.7.9`;
67
83
  },
68
84
  },
69
85
  {
@@ -109,6 +125,9 @@ const Docs = {
109
125
  type: umlId,
110
126
  icon: html`<i class="fas fa-sitemap"></i>`,
111
127
  text: Translate.Render(`${umlType} config uml`),
128
+ url: function () {
129
+ return `/docs/?cid=${umlId}`;
130
+ },
112
131
  renderHtml: function () {
113
132
  return html` <div class="in section-mp">
114
133
  <div class="in sub-title-modal"><i class="fas fa-project-diagram"></i> Schema</div>
@@ -130,19 +149,36 @@ const Docs = {
130
149
  };
131
150
  }),
132
151
  ),
152
+ Tokens: {},
133
153
  Init: async function (options) {
134
154
  const { idModal } = options;
155
+ this.Tokens[idModal] = options;
135
156
  setTimeout(() => {
157
+ const cleanActive = () => {
158
+ s(`.btn-docs-src`).classList.remove('main-btn-menu-active');
159
+ s(`.btn-docs-api`).classList.remove('main-btn-menu-active');
160
+ s(`.btn-docs-coverage`).classList.remove('main-btn-menu-active');
161
+ for (const umlType of umlTypes) {
162
+ const umlId = `uml-${umlType}`;
163
+ s(`.btn-docs-${umlId}`).classList.remove('main-btn-menu-active');
164
+ }
165
+ };
136
166
  s(`.btn-docs-src`).onclick = async () => {
137
167
  setQueryPath({ path: 'docs', queryPath: 'src' });
168
+ cleanActive();
169
+ s(`.btn-docs-src`).classList.add('main-btn-menu-active');
138
170
  await this.RenderModal('src', options.modalOptions);
139
171
  };
140
172
  s(`.btn-docs-api`).onclick = async () => {
141
173
  setQueryPath({ path: 'docs', queryPath: 'api' });
174
+ cleanActive();
175
+ s(`.btn-docs-api`).classList.add('main-btn-menu-active');
142
176
  await this.RenderModal('api', options.modalOptions);
143
177
  };
144
178
  s(`.btn-docs-coverage`).onclick = async () => {
145
179
  setQueryPath({ path: 'docs', queryPath: 'coverage' });
180
+ cleanActive();
181
+ s(`.btn-docs-coverage`).classList.add('main-btn-menu-active');
146
182
  await this.RenderModal('coverage', options.modalOptions);
147
183
  };
148
184
 
@@ -154,10 +190,16 @@ const Docs = {
154
190
  const docData = this.Data.find((d) => d.type === 'repo');
155
191
  location.href = docData.url();
156
192
  };
193
+ s(`.btn-docs-demo`).onclick = () => {
194
+ const docData = this.Data.find((d) => d.type === 'demo');
195
+ location.href = docData.url();
196
+ };
157
197
 
158
198
  for (const umlType of umlTypes) {
159
199
  const umlId = `uml-${umlType}`;
160
200
  s(`.btn-docs-${umlId}`).onclick = async () => {
201
+ cleanActive();
202
+ s(`.btn-docs-${umlId}`).classList.add('main-btn-menu-active');
161
203
  setQueryPath({ path: 'docs', queryPath: umlId });
162
204
  await this.RenderModal(umlId, { ...options.modalOptions, handleType: 'bar' });
163
205
  };
@@ -181,7 +223,6 @@ const Docs = {
181
223
  switch (docData.type) {
182
224
  case 'repo':
183
225
  case 'coverage-link':
184
- tabHref = docData.url();
185
226
  style = renderCssAttr({ style: { height: '45px' } });
186
227
  labelStyle = renderCssAttr({ style: { top: '8px', left: '9px' } });
187
228
  break;
@@ -189,16 +230,75 @@ const Docs = {
189
230
  default:
190
231
  break;
191
232
  }
192
- docMenuRender += html` <div class="in">
233
+ tabHref = docData.url();
234
+ docMenuRender += html`
193
235
  ${await BtnIcon.Render({
194
- class: `inl section-mp btn-custom btn-docs-${docData.type}`,
195
- label: html`${docData.icon} ${docData.text}`,
236
+ class: `in wfa main-btn-menu btn-docs-${docData.type}`,
237
+ label: html`<span class="menu-btn-icon">${docData.icon}</span
238
+ ><span class="menu-label-text"> ${docData.text} </span>`,
196
239
  tabHref,
197
- style,
198
- labelStyle,
240
+ handleContainerClass: 'handle-btn-container',
241
+ tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption(docData.text, 'right')),
242
+ attrs: `data-id="${docData.type}"`,
243
+ handleContainerClass: 'handle-btn-container',
199
244
  })}
200
- </div>`;
245
+ `;
201
246
  }
247
+
248
+ htmls('.menu-btn-container-children', html` <div class="fl menu-btn-container-docs">${docMenuRender}</div>`);
249
+ if (s(`.menu-btn-container-main`)) s(`.menu-btn-container-main`).classList.add('hide');
250
+ htmls(`.nav-path-display-${'modal-menu'}`, location.pathname);
251
+
252
+ this.Tokens[idModal] = new Sortable(s(`.menu-btn-container-docs`), {
253
+ animation: 150,
254
+ group: `docs-sortable`,
255
+ forceFallback: true,
256
+ fallbackOnBody: true,
257
+ handle: '.handle-btn-container',
258
+ store: {
259
+ /**
260
+ * Get the order of elements. Called once during initialization.
261
+ * @param {Sortable} sortable
262
+ * @returns {Array}
263
+ */
264
+ get: function (sortable) {
265
+ const order = localStorage.getItem(sortable.options.group.name);
266
+ return order ? order.split('|') : [];
267
+ },
268
+
269
+ /**
270
+ * Save the order of elements. Called onEnd (when the item is dropped).
271
+ * @param {Sortable} sortable
272
+ */
273
+ set: function (sortable) {
274
+ const order = sortable.toArray();
275
+ localStorage.setItem(sortable.options.group.name, order.join('|'));
276
+ },
277
+ },
278
+ // chosenClass: 'css-class',
279
+ // ghostClass: 'css-class',
280
+ // Element dragging ended
281
+ onEnd: function (/**Event*/ evt) {
282
+ // console.log('Sortable onEnd', evt);
283
+ // console.log('evt.oldIndex', evt.oldIndex);
284
+ // console.log('evt.newIndex', evt.newIndex);
285
+ const slotId = Array.from(evt.item.classList).pop();
286
+ // console.log('slotId', slotId);
287
+ if (evt.oldIndex === evt.newIndex) s(`.${slotId}`).click();
288
+
289
+ // var itemEl = evt.item; // dragged HTMLElement
290
+ // evt.to; // target list
291
+ // evt.from; // previous list
292
+ // evt.oldIndex; // element's old index within old parent
293
+ // evt.newIndex; // element's new index within new parent
294
+ // evt.oldDraggableIndex; // element's old index within old parent, only counting draggable elements
295
+ // evt.newDraggableIndex; // element's new index within new parent, only counting draggable elements
296
+ // evt.clone; // the clone element
297
+ // evt.pullMode; // when item is in another sortable: `"clone"` if cloning, `true` if moving
298
+ },
299
+ });
300
+
301
+ return '';
202
302
  return html` <div class="in section-mp">${docMenuRender}</div>`;
203
303
  return html` <div class="in section-mp">
204
304
  ${await DropDown.Render({