@underpostnet/underpost 2.97.1 → 2.98.0
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 +2 -2
- package/cli.md +3 -1
- package/conf.js +2 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/rocky-pwa.sh +200 -0
- package/src/api/core/core.service.js +0 -5
- package/src/api/default/default.service.js +7 -5
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.router.js +5 -0
- package/src/api/document/document.service.js +176 -128
- package/src/api/file/file.model.js +112 -4
- package/src/api/file/file.ref.json +42 -0
- package/src/api/file/file.service.js +380 -32
- package/src/api/user/user.model.js +38 -1
- package/src/api/user/user.router.js +96 -63
- package/src/api/user/user.service.js +81 -48
- package/src/cli/db.js +424 -166
- package/src/cli/index.js +8 -0
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +1 -0
- package/src/cli/ssh.js +10 -10
- package/src/client/components/core/Account.js +327 -36
- package/src/client/components/core/AgGrid.js +3 -0
- package/src/client/components/core/Auth.js +11 -3
- package/src/client/components/core/Chat.js +2 -2
- package/src/client/components/core/Content.js +161 -80
- package/src/client/components/core/Css.js +30 -0
- package/src/client/components/core/CssCore.js +16 -12
- package/src/client/components/core/FileExplorer.js +813 -49
- package/src/client/components/core/Input.js +207 -12
- package/src/client/components/core/LogIn.js +42 -20
- package/src/client/components/core/Modal.js +138 -24
- package/src/client/components/core/Panel.js +71 -32
- package/src/client/components/core/PanelForm.js +262 -77
- package/src/client/components/core/PublicProfile.js +888 -0
- package/src/client/components/core/Responsive.js +15 -7
- package/src/client/components/core/Router.js +117 -15
- package/src/client/components/core/SearchBox.js +322 -116
- package/src/client/components/core/SignUp.js +26 -7
- package/src/client/components/core/SocketIo.js +6 -3
- package/src/client/components/core/Translate.js +148 -0
- package/src/client/components/core/Validator.js +15 -0
- package/src/client/components/core/windowGetDimensions.js +6 -6
- package/src/client/components/default/MenuDefault.js +59 -12
- package/src/client/components/default/RoutesDefault.js +1 -0
- package/src/client/services/core/core.service.js +163 -1
- package/src/client/services/default/default.management.js +454 -76
- package/src/client/services/default/default.service.js +13 -6
- package/src/client/services/file/file.service.js +43 -16
- package/src/client/services/user/user.service.js +13 -9
- package/src/client/sw/default.sw.js +107 -184
- package/src/db/DataBaseProvider.js +1 -1
- package/src/db/mongo/MongooseDB.js +1 -1
- package/src/index.js +1 -1
- package/src/mailer/MailerProvider.js +4 -4
- package/src/runtime/express/Express.js +2 -1
- package/src/runtime/lampp/Lampp.js +2 -2
- package/src/server/auth.js +3 -6
- package/src/server/data-query.js +449 -0
- package/src/server/object-layer.js +0 -3
- package/src/ws/IoInterface.js +2 -2
|
@@ -46,13 +46,21 @@ const Responsive = {
|
|
|
46
46
|
// alternative option
|
|
47
47
|
// this.Observer = new ResizeObserver(this.resizeCallback);
|
|
48
48
|
// this.Observer.observe(document.documentElement);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
|
|
50
|
+
// Check if screen.orientation is available before adding event listener
|
|
51
|
+
if (
|
|
52
|
+
typeof screen !== 'undefined' &&
|
|
53
|
+
screen.orientation &&
|
|
54
|
+
typeof screen.orientation.addEventListener === 'function'
|
|
55
|
+
) {
|
|
56
|
+
screen.orientation.addEventListener('change', (event) => {
|
|
57
|
+
const type = event.target.type; // landscape-primary | portrait-primary
|
|
58
|
+
const angle = event.target.angle; // 90 degrees.
|
|
59
|
+
logger.info(`ScreenOrientation change: ${type}, ${angle} degrees.`);
|
|
60
|
+
setTimeout(() => window.onresize({}, true));
|
|
61
|
+
Responsive.triggerEventsOrientation();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
56
64
|
Responsive.matchMediaOrientationInstance = matchMedia('screen and (orientation:portrait)');
|
|
57
65
|
|
|
58
66
|
Responsive.matchMediaOrientationInstance.onchange = (e) => {
|
|
@@ -55,17 +55,24 @@ const getProxyPath = () => {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Sets the browser's path using the History API. It sanitizes the path, handles query strings and hashes,
|
|
58
|
-
* and prevents pushing the same state twice.
|
|
58
|
+
* and prevents pushing the same state twice unless forced or using replace mode.
|
|
59
59
|
* @param {string} [path='/'] - The new path to set. Can include query strings and hashes.
|
|
60
|
-
* @param {object} [options={ removeSearch: false, removeHash: false }] - Options for path manipulation.
|
|
60
|
+
* @param {object} [options={ removeSearch: false, removeHash: false, replace: false, force: false }] - Options for path manipulation.
|
|
61
61
|
* @param {boolean} [options.removeSearch=false] - If true, removes the search part of the URL.
|
|
62
62
|
* @param {boolean} [options.removeHash=false] - If true, removes the hash part of the URL. Defaults to `false`.
|
|
63
|
+
* @param {boolean} [options.replace=false] - If true, uses replaceState instead of pushState.
|
|
64
|
+
* @param {boolean} [options.force=false] - If true, allows navigation to the same path (useful for query param changes).
|
|
63
65
|
* @param {object} [stateStorage={}] - State object to associate with the history entry.
|
|
64
66
|
* @param {string} [title=''] - The title for the new history entry.
|
|
65
67
|
* @memberof PwaRouter
|
|
66
|
-
* @returns {void | undefined} Returns `undefined` if the new path is the same as the current path, otherwise `void` (result of `history.pushState`).
|
|
68
|
+
* @returns {void | undefined} Returns `undefined` if the new path is the same as the current path and not forced, otherwise `void` (result of `history.pushState` or `history.replaceState`).
|
|
67
69
|
*/
|
|
68
|
-
const setPath = (
|
|
70
|
+
const setPath = (
|
|
71
|
+
path = '/',
|
|
72
|
+
options = { removeSearch: false, removeHash: false, replace: false, force: false },
|
|
73
|
+
stateStorage = {},
|
|
74
|
+
title = '',
|
|
75
|
+
) => {
|
|
69
76
|
// logger.warn(`Set path input`, `${path}`);
|
|
70
77
|
if (!path) path = '/';
|
|
71
78
|
|
|
@@ -94,11 +101,16 @@ const setPath = (path = '/', options = { removeSearch: false, removeHash: false
|
|
|
94
101
|
// currentFullPath,
|
|
95
102
|
// newFullPath,
|
|
96
103
|
// });
|
|
97
|
-
if (currentFullPath === newFullPath) {
|
|
104
|
+
if (currentFullPath === newFullPath && !options.force) {
|
|
98
105
|
// logger.warn('Prevent overwriting same path', { currentFullPath, newFullPath });
|
|
99
106
|
return;
|
|
100
107
|
}
|
|
101
|
-
|
|
108
|
+
|
|
109
|
+
if (options.replace) {
|
|
110
|
+
return history.replaceState.call(history, stateStorage, title, newFullPath);
|
|
111
|
+
} else {
|
|
112
|
+
return history.pushState.call(history, stateStorage, title, newFullPath);
|
|
113
|
+
}
|
|
102
114
|
};
|
|
103
115
|
|
|
104
116
|
/**
|
|
@@ -139,13 +151,15 @@ const sanitizeRoute = (route) =>
|
|
|
139
151
|
* @memberof PwaRouter
|
|
140
152
|
*/
|
|
141
153
|
const setDocTitle = (route) => {
|
|
142
|
-
|
|
154
|
+
let _route = sanitizeRoute(route);
|
|
143
155
|
// logger.warn('setDocTitle', _route);
|
|
144
156
|
const title = titleFormatted(_route);
|
|
145
157
|
htmls('title', html`${title}${title.match(Worker.title.toLowerCase()) ? '' : ` | ${Worker.title}`}`);
|
|
146
|
-
|
|
158
|
+
|
|
159
|
+
const btnSelector = _route === 'u' ? 'public-profile' : _route;
|
|
160
|
+
if (s(`.main-btn-${btnSelector}`)) {
|
|
147
161
|
if (s(`.main-btn-menu-active`)) s(`.main-btn-menu-active`).classList.remove(`main-btn-menu-active`);
|
|
148
|
-
if (s(`.main-btn-${
|
|
162
|
+
if (s(`.main-btn-${btnSelector}`)) s(`.main-btn-${btnSelector}`).classList.add(`main-btn-menu-active`);
|
|
149
163
|
}
|
|
150
164
|
};
|
|
151
165
|
|
|
@@ -168,11 +182,19 @@ const Router = function (options = { Routes: () => {}, e: new PopStateEvent() })
|
|
|
168
182
|
let pushPath = `${proxyPath}${route}`;
|
|
169
183
|
|
|
170
184
|
if (path[path.length - 1] !== '/') path = `${path}/`;
|
|
185
|
+
// Handle clean profile URLs: match /u/username with /u route
|
|
186
|
+
let matchPath = path;
|
|
187
|
+
if (route === 'u' && path.startsWith(`${proxyPath}u/`) && path !== `${proxyPath}u/`) {
|
|
188
|
+
handleCleanProfileUrl(path);
|
|
189
|
+
matchPath = `${proxyPath}u/`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (matchPath[matchPath.length - 1] !== '/') matchPath = `${matchPath}/`;
|
|
171
193
|
if (pushPath[pushPath.length - 1] !== '/') pushPath = `${pushPath}/`;
|
|
172
194
|
|
|
173
|
-
const routerEvent = { path, pushPath, route };
|
|
195
|
+
const routerEvent = { path: matchPath, pushPath, route };
|
|
174
196
|
|
|
175
|
-
if (
|
|
197
|
+
if (matchPath === pushPath) {
|
|
176
198
|
for (const event of Object.keys(RouterEvents)) RouterEvents[event](routerEvent);
|
|
177
199
|
subMenuHandler(Object.keys(Routes()), route);
|
|
178
200
|
setDocTitle(route);
|
|
@@ -206,15 +228,81 @@ const LoadRouter = function (RouterInstance) {
|
|
|
206
228
|
* This function constructs a new URI based on the proxy path, a given path, and an optional query parameter.
|
|
207
229
|
* @param {object} [options={ path: '', queryPath: '' }] - The path options.
|
|
208
230
|
* @param {string} [options.path=''] - The base path segment.
|
|
231
|
+
* @param {string} [options.queryPath=''] - The query parameter value.
|
|
232
|
+
* @param {string} [queryKey='cid'] - The query parameter key.
|
|
209
233
|
* @memberof PwaRouter
|
|
210
234
|
*/
|
|
211
|
-
const setQueryPath = (options = { path: '', queryPath: '' }, queryKey = 'cid') => {
|
|
235
|
+
const setQueryPath = (options = { path: '', queryPath: '' }, queryKey = 'cid', navOptions = {}) => {
|
|
212
236
|
const { queryPath, path } = options;
|
|
213
|
-
const
|
|
237
|
+
const { replace = false } = navOptions;
|
|
238
|
+
const newUri = `${getProxyPath()}${path === 'home' ? '' : `${path}`}${
|
|
214
239
|
typeof queryPath === 'string' && queryPath ? `?${queryKey}=${queryPath}` : ''
|
|
215
240
|
}`;
|
|
216
241
|
const currentUri = `${window.location.pathname}${location.search}`;
|
|
217
|
-
|
|
242
|
+
|
|
243
|
+
// For query parameter changes on the same path, force the navigation to ensure proper history
|
|
244
|
+
const isSamePath = window.location.pathname === new URL(newUri, window.location.origin).pathname;
|
|
245
|
+
const isDifferentQuery = window.location.search !== new URL(newUri, window.location.origin).search;
|
|
246
|
+
const shouldForce = isSamePath && isDifferentQuery;
|
|
247
|
+
|
|
248
|
+
if (currentUri !== newUri && currentUri !== `${newUri}/`) {
|
|
249
|
+
setPath(newUri, { force: shouldForce, replace }, '');
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Extracts username from clean public profile URLs like /u/username.
|
|
255
|
+
* @param {string} [pathname] - The pathname to extract from (defaults to current pathname).
|
|
256
|
+
* @returns {string|null} The username if found, null otherwise.
|
|
257
|
+
* @memberof PwaRouter
|
|
258
|
+
*/
|
|
259
|
+
const extractUsernameFromPath = (pathname = window.location.pathname) => {
|
|
260
|
+
const proxyPath = getProxyPath();
|
|
261
|
+
const cleanPathPrefix = `${proxyPath}u/`.replace(/\/+/g, '/');
|
|
262
|
+
|
|
263
|
+
if (pathname.startsWith(cleanPathPrefix)) {
|
|
264
|
+
const username = pathname.slice(cleanPathPrefix.length).split('/')[0];
|
|
265
|
+
return username || null;
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Handles direct navigation to clean public profile URLs.
|
|
272
|
+
* Converts clean URLs like /u/username to internal query format for SPA.
|
|
273
|
+
* @param {string} [pathname] - The pathname to handle (defaults to current pathname).
|
|
274
|
+
* @returns {boolean} True if this was a public profile URL that was handled.
|
|
275
|
+
* @memberof PwaRouter
|
|
276
|
+
*/
|
|
277
|
+
const handleCleanProfileUrl = (pathname = window.location.pathname) => {
|
|
278
|
+
const username = extractUsernameFromPath(pathname);
|
|
279
|
+
if (username) {
|
|
280
|
+
// Convert clean URL to internal query format for data fetching
|
|
281
|
+
// Don't modify history - just return the username for the caller to use
|
|
282
|
+
return username;
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Navigates to a public profile URL without adding intermediate query URLs to history.
|
|
289
|
+
* This ensures clean back/forward navigation between profiles.
|
|
290
|
+
* @param {string} username - The username to navigate to.
|
|
291
|
+
* @param {object} [options={}] - Navigation options.
|
|
292
|
+
* @param {boolean} [options.replace=false] - If true, replaces current history entry instead of pushing.
|
|
293
|
+
* @memberof PwaRouter
|
|
294
|
+
*/
|
|
295
|
+
const navigateToProfile = (username, options = {}) => {
|
|
296
|
+
const { replace = false } = options;
|
|
297
|
+
if (!username) return;
|
|
298
|
+
|
|
299
|
+
const cleanPath = `${getProxyPath()}u/${username}`;
|
|
300
|
+
const currentPath = window.location.pathname;
|
|
301
|
+
|
|
302
|
+
// If we're already on this profile's clean URL, no navigation needed
|
|
303
|
+
if (currentPath === cleanPath) return;
|
|
304
|
+
// Navigate directly to clean URL, avoiding intermediate ?cid= URLs in history
|
|
305
|
+
setPath(cleanPath, { replace });
|
|
218
306
|
};
|
|
219
307
|
|
|
220
308
|
/**
|
|
@@ -304,7 +392,12 @@ const handleModalViewRoute = (options = { RouterInstance: { Routes: () => {} },
|
|
|
304
392
|
const newPath = `${proxyPath}${route}`;
|
|
305
393
|
if (RouterInstance && RouterInstance.Routes) subMenuHandler(Object.keys(RouterInstance.Routes()), route);
|
|
306
394
|
|
|
307
|
-
if
|
|
395
|
+
// Check if we're already on this route or a sub-path of it (e.g., /u/username for route 'u')
|
|
396
|
+
// Don't push to history if already on the route or its sub-path
|
|
397
|
+
const routeBasePath = `${proxyPath}${route}`;
|
|
398
|
+
const isOnRouteOrSubPath = path === newPath || path.startsWith(`${routeBasePath}/`);
|
|
399
|
+
|
|
400
|
+
if (!isOnRouteOrSubPath) {
|
|
308
401
|
setPath(newPath);
|
|
309
402
|
setDocTitle(newPath);
|
|
310
403
|
}
|
|
@@ -331,6 +424,12 @@ const setQueryParams = (newParams, options = { replace: true }) => {
|
|
|
331
424
|
});
|
|
332
425
|
|
|
333
426
|
const newPath = url.pathname + url.search + url.hash;
|
|
427
|
+
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
|
428
|
+
|
|
429
|
+
// Only update history and trigger listeners if the URL actually changed
|
|
430
|
+
if (newPath === currentPath) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
334
433
|
|
|
335
434
|
if (options.replace) {
|
|
336
435
|
history.replaceState(history.state, '', newPath);
|
|
@@ -348,12 +447,15 @@ const setQueryParams = (newParams, options = { replace: true }) => {
|
|
|
348
447
|
|
|
349
448
|
export {
|
|
350
449
|
RouterEvents,
|
|
450
|
+
navigateToProfile,
|
|
351
451
|
closeModalRouteChangeEvents,
|
|
352
452
|
coreUI,
|
|
353
453
|
Router,
|
|
354
454
|
setDocTitle,
|
|
355
455
|
LoadRouter,
|
|
356
456
|
setQueryPath,
|
|
457
|
+
extractUsernameFromPath,
|
|
458
|
+
handleCleanProfileUrl,
|
|
357
459
|
listenQueryPathInstance,
|
|
358
460
|
closeModalRouteChangeEvent,
|
|
359
461
|
handleModalViewRoute,
|