underpost 2.8.852 → 2.8.854

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/src/cli/db.js CHANGED
@@ -3,6 +3,7 @@ import { loggerFactory } from '../server/logger.js';
3
3
  import { shellExec } from '../server/process.js';
4
4
  import fs from 'fs-extra';
5
5
  import UnderpostDeploy from './deploy.js';
6
+ import UnderpostCron from './cron.js';
6
7
 
7
8
  const logger = loggerFactory(import.meta);
8
9
 
@@ -216,6 +217,129 @@ class UnderpostDB {
216
217
  }
217
218
  }
218
219
  },
220
+ async updateDashboardData(
221
+ deployId = process.env.DEFAULT_DEPLOY_ID,
222
+ host = process.env.DEFAULT_DEPLOY_HOST,
223
+ path = process.env.DEFAULT_DEPLOY_PATH,
224
+ ) {
225
+ try {
226
+ deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
227
+ host = host ?? process.env.DEFAULT_DEPLOY_HOST;
228
+ path = path ?? process.env.DEFAULT_DEPLOY_PATH;
229
+
230
+ const { db } = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'))[host][
231
+ path
232
+ ];
233
+
234
+ await DataBaseProvider.load({ apis: ['instance'], host, path, db });
235
+
236
+ /** @type {import('../api/instance/instance.model.js').InstanceModel} */
237
+ const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
238
+
239
+ await Instance.deleteMany();
240
+
241
+ for (const _deployId of deployList.split(',')) {
242
+ const deployId = _deployId.trim();
243
+ if (!deployId) continue;
244
+ const confServer = loadReplicas(
245
+ JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
246
+ 'proxy',
247
+ );
248
+ const router = await UnderpostDeploy.API.routerFactory(deployId, env);
249
+ const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
250
+
251
+ for (const host of Object.keys(confServer)) {
252
+ for (const { path, port } of pathPortAssignmentData[host]) {
253
+ if (!confServer[host][path]) continue;
254
+
255
+ const { client, runtime, apis } = confServer[host][path];
256
+
257
+ const body = {
258
+ deployId,
259
+ host,
260
+ path,
261
+ port,
262
+ client,
263
+ runtime,
264
+ apis,
265
+ };
266
+
267
+ logger.info('save', body);
268
+
269
+ await new Instance(body).save();
270
+ }
271
+ }
272
+ }
273
+
274
+ await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
275
+ } catch (error) {
276
+ logger.error(error, error.stack);
277
+ }
278
+
279
+ try {
280
+ const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
281
+ const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
282
+ const { db } = confServer[host][path];
283
+
284
+ await DataBaseProvider.load({ apis: ['cron'], host, path, db });
285
+
286
+ /** @type {import('../api/cron/cron.model.js').CronModel} */
287
+ const Cron = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Cron;
288
+
289
+ await Cron.deleteMany();
290
+
291
+ for (const cronInstance of UnderpostCron.NETWORK) {
292
+ logger.info('save', cronInstance);
293
+ await new Cron(cronInstance).save();
294
+ }
295
+
296
+ await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
297
+ } catch (error) {
298
+ logger.error(error, error.stack);
299
+ }
300
+ },
301
+ clusterMetadataBackupCallback(
302
+ deployId = process.env.DEFAULT_DEPLOY_ID,
303
+ host = process.env.DEFAULT_DEPLOY_HOST,
304
+ path = process.env.DEFAULT_DEPLOY_PATH,
305
+ options = {
306
+ import: false,
307
+ export: false,
308
+ instances: false,
309
+ crons: false,
310
+ },
311
+ ) {
312
+ deployId = deployId ?? process.env.DEFAULT_DEPLOY_ID;
313
+ host = host ?? process.env.DEFAULT_DEPLOY_HOST;
314
+ path = path ?? process.env.DEFAULT_DEPLOY_PATH;
315
+
316
+ if (options.instances === true) {
317
+ const outputPath = './engine-private/instances';
318
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
319
+ const collection = 'instances';
320
+ if (options.export === true)
321
+ shellExec(
322
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
323
+ );
324
+ if (options.import === true)
325
+ shellExec(
326
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
327
+ );
328
+ }
329
+ if (options.crons === true) {
330
+ const outputPath = './engine-private/crons';
331
+ if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
332
+ const collection = 'crons';
333
+ if (options.export === true)
334
+ shellExec(
335
+ `node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
336
+ );
337
+ if (options.import === true)
338
+ shellExec(
339
+ `node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
340
+ );
341
+ }
342
+ },
219
343
  };
220
344
  }
221
345
 
package/src/cli/deploy.js CHANGED
@@ -242,12 +242,10 @@ spec:
242
242
  cert: false,
243
243
  versions: '',
244
244
  traffic: '',
245
- dashboardUpdate: false,
246
245
  replicas: '',
247
246
  restoreHosts: false,
248
247
  disableUpdateDeployment: false,
249
248
  infoTraffic: false,
250
- rebuildClientsBundle: false,
251
249
  },
252
250
  ) {
253
251
  if (options.infoUtil === true)
@@ -311,18 +309,20 @@ Password: <Your Key>
311
309
  deployId,
312
310
  env,
313
311
  traffic: UnderpostDeploy.API.getCurrentTraffic(deployId),
312
+ router: await UnderpostDeploy.API.routerFactory(deployId, env),
313
+ pods: await UnderpostDeploy.API.get(deployId),
314
314
  });
315
315
  }
316
316
  return;
317
317
  }
318
- if (options.rebuildClientsBundle === true) await UnderpostDeploy.API.rebuildClientsBundle(deployList);
319
318
  if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
320
319
  if (!options.replicas) options.replicas = 1;
321
320
  if (options.sync) UnderpostDeploy.API.sync(deployList, options);
322
321
  if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options);
323
- if (options.infoRouter === true) logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
324
- if (options.dashboardUpdate === true) await UnderpostDeploy.API.updateDashboardData(deployList, env, options);
325
- if (options.infoRouter === true) return;
322
+ if (options.infoRouter === true) {
323
+ logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
324
+ return;
325
+ }
326
326
  shellExec(`kubectl delete configmap underpost-config`);
327
327
  shellExec(
328
328
  `kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
@@ -435,23 +435,6 @@ Password: <Your Key>
435
435
 
436
436
  return result;
437
437
  },
438
- rebuildClientsBundle(deployList) {
439
- for (const _deployId of deployList.split(',')) {
440
- const deployId = _deployId.trim();
441
- const repoName = `engine-${deployId.split('-')[1]}`;
442
-
443
- shellExec(`underpost script set ${deployId}-client-build '
444
- cd /home/dd/engine &&
445
- git checkout . &&
446
- underpost pull . underpostnet/${repoName} &&
447
- underpost pull ./engine-private underpostnet/${repoName}-private &&
448
- underpost env ${deployId} production &&
449
- node bin/deploy build-full-client ${deployId}
450
- '`);
451
-
452
- shellExec(`node bin script run ${deployId}-client-build --itc --pod-name ${deployId}`);
453
- }
454
- },
455
438
  resourcesFactory() {
456
439
  return {
457
440
  requests: {
@@ -465,60 +448,6 @@ node bin/deploy build-full-client ${deployId}
465
448
  totalPods: UnderpostRootEnv.API.get('total-pods'),
466
449
  };
467
450
  },
468
- async updateDashboardData(deployList, env, options) {
469
- try {
470
- const deployId = process.env.DEFAULT_DEPLOY_ID;
471
- const host = process.env.DEFAULT_DEPLOY_HOST;
472
- const path = process.env.DEFAULT_DEPLOY_PATH;
473
- const { db } = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'))[host][
474
- path
475
- ];
476
-
477
- await DataBaseProvider.load({ apis: ['instance'], host, path, db });
478
-
479
- /** @type {import('../api/instance/instance.model.js').InstanceModel} */
480
- const Instance = DataBaseProvider.instance[`${host}${path}`].mongoose.models.Instance;
481
-
482
- await Instance.deleteMany();
483
-
484
- for (const _deployId of deployList.split(',')) {
485
- const deployId = _deployId.trim();
486
- if (!deployId) continue;
487
- const confServer = loadReplicas(
488
- JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
489
- 'proxy',
490
- );
491
- const router = await UnderpostDeploy.API.routerFactory(deployId, env);
492
- const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
493
-
494
- for (const host of Object.keys(confServer)) {
495
- for (const { path, port } of pathPortAssignmentData[host]) {
496
- if (!confServer[host][path]) continue;
497
-
498
- const { client, runtime, apis } = confServer[host][path];
499
-
500
- const body = {
501
- deployId,
502
- host,
503
- path,
504
- port,
505
- client,
506
- runtime,
507
- apis,
508
- };
509
-
510
- logger.info('save', body);
511
-
512
- await new Instance(body).save();
513
- }
514
- }
515
- }
516
-
517
- await DataBaseProvider.instance[`${host}${path}`].mongoose.close();
518
- } catch (error) {
519
- logger.error(error, error.stack);
520
- }
521
- },
522
451
  existsContainerFile({ podName, path }) {
523
452
  return JSON.parse(
524
453
  shellExec(`kubectl exec ${podName} -- test -f ${path} && echo "true" || echo "false"`, {
package/src/cli/index.js CHANGED
@@ -123,6 +123,11 @@ program
123
123
  .option('--full', 'Initializes the cluster with all available statefulsets and services.')
124
124
  .option('--ns-use <ns-name>', 'Switches the current Kubernetes context to the specified namespace.')
125
125
  .option('--kubeadm', 'Initializes the cluster using kubeadm for control plane management.')
126
+ .option('--grafana', 'Initializes the cluster with a Grafana deployment.')
127
+ .option(
128
+ '--prom [hosts]',
129
+ 'Initializes the cluster with a Prometheus Operator deployment and monitor scrap for specified hosts.',
130
+ )
126
131
  .option('--dev', 'Initializes a development-specific cluster configuration.')
127
132
  .option('--list-pods', 'Displays detailed information about all pods.')
128
133
  .option('--info-capacity', 'Displays the current total machine capacity information.')
@@ -155,7 +160,6 @@ program
155
160
  '--build-manifest',
156
161
  'Builds Kubernetes YAML manifests, including deployments, services, proxies, and secrets.',
157
162
  )
158
- .option('--dashboard-update', 'Updates dashboard instance data with the current router configuration.')
159
163
  .option('--replicas <replicas>', 'Sets a custom number of replicas for deployments.')
160
164
  .option('--versions <deployment-versions>', 'A comma-separated list of custom deployment versions.')
161
165
  .option('--traffic <traffic-versions>', 'A comma-separated list of custom deployment traffic weights.')
@@ -163,10 +167,6 @@ program
163
167
  .option('--info-traffic', 'Retrieves traffic configuration from current resource deployments.')
164
168
  .option('--kubeadm', 'Enables the kubeadm context for deployment operations.')
165
169
  .option('--restore-hosts', 'Restores default `/etc/hosts` entries.')
166
- .option(
167
- '--rebuild-clients-bundle',
168
- 'Inside the container, rebuilds client bundles (only static public or storage client files).',
169
- )
170
170
  .description('Manages application deployments, defaulting to deploying development pods.')
171
171
  .action(Underpost.deploy.callback);
172
172
 
@@ -240,6 +240,18 @@ program
240
240
  .description('Manages database operations, including import, export, and collection management.')
241
241
  .action(Underpost.db.callback);
242
242
 
243
+ program
244
+ .command('metadata')
245
+ .argument('[deploy-id]', 'The deployment ID to manage metadata.')
246
+ .argument('[host]', 'The host to manage metadata.')
247
+ .argument('[path]', 'The path to manage metadata.')
248
+ .option('--import', 'Imports from local storage.')
249
+ .option('--export', 'Exports to local storage.')
250
+ .option('--crons', 'Apply to cron data collection')
251
+ .option('--instances', 'Apply to instance data collection')
252
+ .description('Manages cluster metadata operations, including import and export.')
253
+ .action(Underpost.db.clusterMetadataBackupCallback);
254
+
243
255
  // 'script' command: Execute scripts
244
256
  program
245
257
  .command('script')
@@ -268,7 +280,6 @@ program
268
280
  .option('--itc', 'Executes cron jobs within the container execution context.')
269
281
  .option('--init', 'Initializes cron jobs for the default deployment ID.')
270
282
  .option('--git', 'Uploads cron job configurations to GitHub.')
271
- .option('--dashboard-update', 'Updates dashboard cron data with the current job configurations.')
272
283
  .description('Manages cron jobs, including initialization, execution, and configuration updates.')
273
284
  .action(Underpost.cron.callback);
274
285
 
package/src/cli/run.js CHANGED
@@ -76,6 +76,11 @@ class UnderpostRun {
76
76
  shellExec(`${baseCommand} deploy --expose mongo`, { async: true });
77
77
  shellExec(`${baseCommand} deploy --expose valkey`, { async: true });
78
78
  },
79
+ 'ssh-cluster-info': (path, options = UnderpostRun.DEFAULT_OPTION) => {
80
+ const { underpostRoot } = options;
81
+ shellExec(`chmod +x ${underpostRoot}/manifests/maas/ssh-cluster-info.sh`);
82
+ shellExec(`${underpostRoot}/manifests/maas/ssh-cluster-info.sh`);
83
+ },
79
84
  'cyberia-ide': (path, options = UnderpostRun.DEFAULT_OPTION) => {
80
85
  const baseCommand = options.dev ? 'node bin' : 'underpost';
81
86
  shellExec(`${baseCommand} run ide /home/dd/cyberia-server`);
@@ -93,6 +98,19 @@ class UnderpostRun {
93
98
  shellExec(`${baseCommand} cmt . --empty ci package-pwa-microservices-template`);
94
99
  shellExec(`${baseCommand} push . underpostnet/engine`);
95
100
  },
101
+ clean: (path, options = UnderpostRun.DEFAULT_OPTION) => {
102
+ shellCd(path ?? `/home/dd/engine`);
103
+ shellExec(`node bin/deploy clean-core-repo`);
104
+ },
105
+ upgrade: (path, options = UnderpostRun.DEFAULT_OPTION) => {
106
+ const { underpostRoot } = options;
107
+ shellExec(`npm install -g underpost`);
108
+ shellExec(`underpost run secret`);
109
+ shellCd(`/home/dd/engine`);
110
+ shellExec(`node bin/deploy clean-core-repo`);
111
+ shellExec(`underpost pull . underpostnet/engine`);
112
+ shellExec(`underpost pull engine-private underpostnet/engine-private`, { silent: true });
113
+ },
96
114
  'ssh-deploy': (path, options = UnderpostRun.DEFAULT_OPTION) => {
97
115
  const baseCommand = options.dev || true ? 'node bin' : 'underpost';
98
116
  shellCd('/home/dd/engine');
@@ -15,7 +15,6 @@ import { DefaultParams } from './components/default/CommonDefault.js';
15
15
  import { SocketIo } from './components/core/SocketIo.js';
16
16
  import { SocketIoDefault } from './components/default/SocketIoDefault.js';
17
17
  import { ElementsDefault } from './components/default/ElementsDefault.js';
18
- import { Scroll } from './components/core/Scroll.js';
19
18
  import { CssDefaultDark, CssDefaultLight } from './components/default/CssDefault.js';
20
19
 
21
20
  const htmlMainBody = async () => {
@@ -39,7 +38,6 @@ window.onload = () =>
39
38
  await LogInDefault();
40
39
  await LogOutDefault();
41
40
  await SignUpDefault();
42
- await Scroll.pullTopRefresh();
43
41
  await Keyboard.Init({ callBackTime: DefaultParams.EVENT_CALLBACK_TIME });
44
42
  },
45
43
  });
@@ -10,7 +10,7 @@ import { Modal } from './Modal.js';
10
10
  import { NotificationManager } from './NotificationManager.js';
11
11
  import { Translate } from './Translate.js';
12
12
  import { Validator } from './Validator.js';
13
- import { append, htmls, s } from './VanillaJs.js';
13
+ import { append, getProxyPath, htmls, s } from './VanillaJs.js';
14
14
 
15
15
  const Account = {
16
16
  UpdateEvent: {},
@@ -502,7 +502,7 @@ 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">
505
+ <a class="a-link-top-banner fl">
506
506
  <div class="inl">${await options.slideMenuTopBarBannerFix()}</div></a
507
507
  >
508
508
  </div>`
@@ -1160,7 +1160,7 @@ const Modal = {
1160
1160
  if (s(`.slide-menu-top-bar-fix`)) {
1161
1161
  htmls(
1162
1162
  `.slide-menu-top-bar-fix`,
1163
- html`<a class="a-link-top-banner">${await options.slideMenuTopBarBannerFix()}</a>`,
1163
+ html`<a class="a-link-top-banner fl">${await options.slideMenuTopBarBannerFix()}</a>`,
1164
1164
  );
1165
1165
  Modal.setTopBannerLink();
1166
1166
  }
@@ -80,9 +80,10 @@ const Recover = {
80
80
  }
81
81
  switch (mode) {
82
82
  case 'recover-verify-email': {
83
- body.proxyPath = getProxyPath();
84
- body.hostname = `${location.hostname}`;
85
- const result = await UserService.post({ id: 'recover-verify-email', body });
83
+ const result = await UserService.post({
84
+ id: 'recover-verify-email',
85
+ body: { ...body, proxyPath: getProxyPath(), hostname: `${location.hostname}` },
86
+ });
86
87
  NotificationManager.Push({
87
88
  html:
88
89
  result.status === 'error' ? result.message : Translate.Render(`${result.status}-recover-verify-email`),
@@ -1,131 +1,76 @@
1
- import { borderChar } from './Css.js';
2
- import { Modal } from './Modal.js';
3
- import { append, s } from './VanillaJs.js';
1
+ import { s } from './VanillaJs.js';
4
2
 
5
- const Scroll = {
6
- data: {},
7
- init: function (selector) {
8
- s(selector).addEventListener('scroll', Scroll.scrollHandler);
9
- Scroll.data[selector] = {
10
- element: s(selector),
3
+ class Scroll {
4
+ /**
5
+ * Attach scroll listener to an element (resolved with s(selector)).
6
+ * @param {string} selector - selector passed to s(selector)
7
+ * @param {function} [callback] - callback function to be called on scroll
8
+ * @param {object} options
9
+ * @param {number} [options.threshold=1] - px margin to treat as bottom
10
+ * @param {number} [options.precision=3] - decimal places for percentages
11
+ */
12
+ static setEvent(selector, callback = async () => {}, options = { threshold: 1, precision: 3 }) {
13
+ const el = s(selector);
14
+ if (!el) return;
15
+
16
+ const threshold = options.threshold ?? 1; // px tolerance for bottom detection
17
+ const precision = options.precision ?? 3;
18
+ let ticking = false;
19
+
20
+ const round = (v) => {
21
+ const m = Math.pow(10, precision);
22
+ return Math.round(v * m) / m;
11
23
  };
12
- return Scroll.data[selector];
13
- },
14
- getScrollPosition: function (selector) {
15
- // Scroll.data[selector].element.clientHeight -
16
- return Scroll.data[selector].element.scrollTop;
17
- },
18
- scrollHandler: async function () {
19
- for (const selector in Scroll.data) await Scroll.data[selector].callback(Scroll.getScrollPosition(selector));
20
- },
21
- addEvent: function (selector = '', callback = (position = 0) => {}) {
22
- Scroll.data[selector].callback = callback;
23
- },
24
- removeEvent: function (selector) {
25
- delete Scroll.data[selector];
26
- },
27
- to: function (elector = '', options = { top: 100, left: 100, behavior: 'smooth' }) {
28
- Scroll.data[selector].element.scrollTo({
29
- top: options.top || Scroll.getScrollPosition(selector),
30
- left: options.left || 0,
31
- behavior: options.behavior || 'smooth',
32
- });
33
- },
34
- topRefreshEvents: {},
35
- addTopRefreshEvent: function (options = { id: '', callback: () => {}, condition: () => {} }) {
36
- this.topRefreshEvents[options.id] = options;
37
- },
38
- removeTopRefreshEvent: function (id = '') {
39
- delete this.topRefreshEvents[id];
40
- },
41
- pullTopRefresh: function () {
42
- return;
43
- append(
44
- 'body',
45
- html` <style>
46
- .pull-refresh-icon-container {
47
- height: 60px;
48
- width: 100%;
49
- z-index: 10;
50
- transition: 0.3s;
51
- left: 0px;
52
- }
53
- .pull-refresh-icon {
54
- width: 60px;
55
- height: 60px;
56
- margin: auto;
57
- color: white;
58
- font-size: 30px;
59
- }
60
- </style>
61
- ${borderChar(2, 'black', [' .pull-refresh-icon-container'])}
62
- <div style="top: -60px" class="abs pull-refresh-icon-container">
63
- <div class="in pull-refresh-icon">
64
- <div class="abs center"><i class="fa-solid fa-arrows-rotate"></i></div>
65
- </div>
66
- </div>`,
67
- );
68
24
 
69
- let touchstartY = 0;
70
- let reload = false;
71
- const minHeightDragReload = 3;
72
- const maxHeightDragReload = 20;
25
+ const listener = (event) => {
26
+ if (ticking) return;
27
+ ticking = true;
28
+
29
+ requestAnimationFrame(() => {
30
+ const scrollHeight = el.scrollHeight;
31
+ const clientHeight = el.clientHeight;
32
+ const scrollTop = el.scrollTop;
33
+
34
+ // pixels left to scroll (clamped to >= 0)
35
+ const remaining = Math.max(0, scrollHeight - clientHeight - scrollTop);
73
36
 
74
- document.addEventListener('touchstart', (e) => {
75
- touchstartY = e.touches[0].clientY;
76
- // console.warn('touchstart', touchstartY);
77
- });
37
+ // maximum possible remaining (0 if content fits without scrolling)
38
+ const maxRemaining = Math.max(0, scrollHeight - clientHeight);
78
39
 
79
- document.addEventListener('touchmove', (e) => {
80
- if (
81
- !Object.keys(Scroll.topRefreshEvents).find((event) => Scroll.topRefreshEvents[event].condition()) ||
82
- (!s(`.btn-bar-center-icon-close`).classList.contains('hide') &&
83
- !s(
84
- `.btn-icon-menu-mode-${Modal.Data['modal-menu'].options.mode !== 'slide-menu-right' ? 'left' : 'right'}`,
85
- ).classList.contains('hide'))
86
- )
87
- return;
40
+ // percentRemaining: 1 = top (all remaining), 0 = bottom (none remaining)
41
+ let percentRemaining = maxRemaining === 0 ? 0 : remaining / maxRemaining;
42
+ percentRemaining = Math.max(0, Math.min(1, percentRemaining));
88
43
 
89
- const touchY = e.touches[0].clientY;
90
- const touchDiff = touchY - touchstartY;
44
+ // percentScrolled: complementary value (0 = top, 1 = bottom)
45
+ let percentScrolled = 1 - percentRemaining;
46
+ percentScrolled = Math.max(0, Math.min(1, percentScrolled));
91
47
 
92
- // console.warn('touchDiff', touchDiff, maxHeightDragReload);
48
+ const payload = {
49
+ scrollHeight,
50
+ clientHeight,
51
+ scrollTop,
52
+ remaining, // px left (>= 0)
53
+ scrollBottom: remaining <= threshold ? 0 : remaining,
54
+ atBottom: remaining <= threshold,
55
+ percentRemaining: round(percentRemaining), // 0..1
56
+ percentScrolled: round(percentScrolled), // 0..1
57
+ };
93
58
 
94
- if (touchDiff > maxHeightDragReload)
95
- s(`.pull-refresh-icon-container`).style.top = 60 + maxHeightDragReload + 'px';
96
- else s(`.pull-refresh-icon-container`).style.top = 60 + touchDiff + 'px';
59
+ // replace this with an event dispatch or callback if you prefer
60
+ // console.warn('scroll', event, JSON.stringify(payload, null, 2));
61
+ callback(payload);
97
62
 
98
- if (touchDiff > minHeightDragReload && window.scrollY === 0) {
99
- reload = true;
100
- } else {
101
- reload = false;
102
- }
103
- });
104
- document.addEventListener('touchend', (e) => {
105
- // console.warn('touchend');
106
- s(`.pull-refresh-icon-container`).style.top = '-60px';
107
- if (reload) {
108
- for (const event of Object.keys(Scroll.topRefreshEvents))
109
- if (Scroll.topRefreshEvents[event].condition()) Scroll.topRefreshEvents[event].callback();
110
- }
111
- reload = false;
112
- });
113
- Scroll.addTopRefreshEvent({
114
- id: 'main-body',
115
- callback: () => {
116
- location.reload();
117
- },
118
- condition: () => {
119
- return (
120
- s('.main-body') &&
121
- s('.main-body').scrollTop === 0 &&
122
- !Object.keys(Modal.Data).find(
123
- (idModal) => !['modal-menu', 'main-body', 'bottom-bar', 'main-body-top'].includes(idModal),
124
- )
125
- );
126
- },
127
- });
128
- },
129
- };
63
+ ticking = false;
64
+ });
65
+ };
66
+
67
+ el.addEventListener('scroll', listener, { passive: true });
68
+
69
+ return {
70
+ removeEvent: () => el.removeEventListener('scroll', listener),
71
+ };
72
+ }
73
+ }
130
74
 
131
75
  export { Scroll };
76
+ export default Scroll;
package/src/index.js CHANGED
@@ -35,7 +35,7 @@ class Underpost {
35
35
  * @type {String}
36
36
  * @memberof Underpost
37
37
  */
38
- static version = 'v2.8.852';
38
+ static version = 'v2.8.854';
39
39
  /**
40
40
  * Repository cli API
41
41
  * @static