@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
|
@@ -34,12 +34,133 @@ const SearchBox = {
|
|
|
34
34
|
* - search: async (query, context) => Promise<Array<result>>
|
|
35
35
|
* - renderResult: (result, index, context) => string (HTML)
|
|
36
36
|
* - onClick: (result, context) => void
|
|
37
|
-
* - priority: number (lower = higher priority)
|
|
37
|
+
* - priority: number (lower number = higher priority)
|
|
38
38
|
* @type {Array<object>}
|
|
39
39
|
* @memberof SearchBoxClient.SearchBox
|
|
40
40
|
*/
|
|
41
41
|
providers: [],
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Recent search results manager with localStorage persistence.
|
|
45
|
+
* Tracks clicked results from all providers (routes and custom).
|
|
46
|
+
* Maintains order of most-recent-first results across sessions.
|
|
47
|
+
* @type {object}
|
|
48
|
+
* @memberof SearchBoxClient.SearchBox
|
|
49
|
+
*/
|
|
50
|
+
RecentResults: {
|
|
51
|
+
/**
|
|
52
|
+
* Storage key for localStorage persistence
|
|
53
|
+
* @type {string}
|
|
54
|
+
*/
|
|
55
|
+
storageKey: 'searchbox_recent_results',
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Maximum number of recent results to keep in history
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
maxResults: 20,
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all cached recent results from localStorage
|
|
65
|
+
* @returns {Array<object>} Array of recent result objects
|
|
66
|
+
*/
|
|
67
|
+
getAll: function () {
|
|
68
|
+
try {
|
|
69
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
70
|
+
return stored ? JSON.parse(stored) : [];
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.warn('Error reading search history from localStorage:', error);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Save recent results to localStorage
|
|
79
|
+
* @param {Array<object>} results - Array of results to save
|
|
80
|
+
*/
|
|
81
|
+
saveAll: function (results) {
|
|
82
|
+
try {
|
|
83
|
+
localStorage.setItem(this.storageKey, JSON.stringify(results.slice(0, this.maxResults)));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.warn('Error saving search history to localStorage:', error);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Add a result to recent history (moves to front if duplicate)
|
|
91
|
+
* Removes duplicates and maintains max size limit.
|
|
92
|
+
* Only stores serializable data (excludes DOM elements).
|
|
93
|
+
* @param {object} result - Result object to add (must have id and providerId/routerId)
|
|
94
|
+
*/
|
|
95
|
+
add: function (result) {
|
|
96
|
+
if (!result || (!result.id && !result.routerId)) {
|
|
97
|
+
logger.warn('SearchBox.RecentResults.add: Invalid result, missing id or routerId');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Create a clean copy excluding DOM elements (fontAwesomeIcon, imgElement)
|
|
102
|
+
const cleanResult = {
|
|
103
|
+
id: result.id,
|
|
104
|
+
routerId: result.routerId,
|
|
105
|
+
type: result.type,
|
|
106
|
+
providerId: result.providerId,
|
|
107
|
+
title: result.title,
|
|
108
|
+
subtitle: result.subtitle,
|
|
109
|
+
tags: result.tags,
|
|
110
|
+
createdAt: result.createdAt,
|
|
111
|
+
data: result.data,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const recent = this.getAll();
|
|
115
|
+
|
|
116
|
+
// Remove duplicate if it exists (based on id and providerId/routerId)
|
|
117
|
+
const filteredRecent = recent.filter((r) => {
|
|
118
|
+
if (cleanResult.providerId && r.providerId) {
|
|
119
|
+
return !(r.id === cleanResult.id && r.providerId === cleanResult.providerId);
|
|
120
|
+
} else if (cleanResult.routerId && r.routerId) {
|
|
121
|
+
return r.routerId !== cleanResult.routerId;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Add new result to front
|
|
127
|
+
filteredRecent.unshift(cleanResult);
|
|
128
|
+
|
|
129
|
+
// Save to localStorage
|
|
130
|
+
this.saveAll(filteredRecent);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clear all recent results from localStorage
|
|
135
|
+
*/
|
|
136
|
+
clear: function () {
|
|
137
|
+
try {
|
|
138
|
+
localStorage.removeItem(this.storageKey);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger.warn('Error clearing search history:', error);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Remove a single result from recent history by ID and provider
|
|
146
|
+
* @param {string} resultId - Result ID to remove
|
|
147
|
+
* @param {string} [providerId] - Provider ID of the result (optional, for routes use null)
|
|
148
|
+
*/
|
|
149
|
+
remove: function (resultId, providerId) {
|
|
150
|
+
const recent = this.getAll();
|
|
151
|
+
const filtered = recent.filter((r) => {
|
|
152
|
+
// Match by ID and providerId (or routerId for routes)
|
|
153
|
+
if (providerId) {
|
|
154
|
+
return !(r.id === resultId && r.providerId === providerId);
|
|
155
|
+
} else {
|
|
156
|
+
// For routes (providerId is null), match by routerId instead
|
|
157
|
+
return !(r.routerId === resultId);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
this.saveAll(filtered);
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
|
|
43
164
|
/**
|
|
44
165
|
* Registers a search provider plugin for extensible search functionality.
|
|
45
166
|
* Replaces any existing provider with the same ID.
|
|
@@ -290,24 +411,56 @@ const SearchBox = {
|
|
|
290
411
|
return;
|
|
291
412
|
}
|
|
292
413
|
|
|
293
|
-
|
|
414
|
+
// Check if this is rendering recently clicked items (not search results)
|
|
415
|
+
// context.isRecentHistory is set when rendering from history, not from search query
|
|
416
|
+
const isRecentHistory = context.isRecentHistory === true;
|
|
417
|
+
|
|
418
|
+
let htmlContent = '';
|
|
294
419
|
results.forEach((result, index) => {
|
|
295
420
|
const provider = this.providers.find((p) => p.id === result.providerId);
|
|
296
421
|
|
|
422
|
+
let resultHtml = '';
|
|
297
423
|
if (result.type === 'route' || !provider) {
|
|
298
424
|
// Default route rendering (backward compatible)
|
|
299
|
-
|
|
425
|
+
resultHtml = this.renderRouteResult(result, index, context);
|
|
300
426
|
} else {
|
|
301
427
|
// Custom provider rendering
|
|
302
|
-
|
|
428
|
+
resultHtml = provider.renderResult(result, index, context);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Only add delete button for recently clicked items (not search results)
|
|
432
|
+
if (isRecentHistory) {
|
|
433
|
+
// Wrapper with relative position for absolute delete button
|
|
434
|
+
htmlContent += `
|
|
435
|
+
<div class="search-result-wrapper search-result-history-item" data-result-id="${result.id || result.routerId}" data-provider-id="${result.providerId || 'default-routes'}">
|
|
436
|
+
${resultHtml}
|
|
437
|
+
<button
|
|
438
|
+
class="search-result-delete-btn"
|
|
439
|
+
data-result-id="${result.id || result.routerId}"
|
|
440
|
+
data-provider-id="${result.providerId || 'default-routes'}"
|
|
441
|
+
title="Remove from history"
|
|
442
|
+
aria-label="Remove from history"
|
|
443
|
+
>
|
|
444
|
+
<i class="fas fa-trash-alt"></i>
|
|
445
|
+
</button>
|
|
446
|
+
</div>
|
|
447
|
+
`;
|
|
448
|
+
} else {
|
|
449
|
+
// Search results: no delete button, no wrapper overhead
|
|
450
|
+
htmlContent += resultHtml;
|
|
303
451
|
}
|
|
304
452
|
});
|
|
305
453
|
|
|
306
|
-
container.innerHTML =
|
|
454
|
+
container.innerHTML = htmlContent;
|
|
307
455
|
|
|
308
456
|
// Attach click handlers
|
|
309
457
|
this.attachClickHandlers(results, containerId, context);
|
|
310
458
|
|
|
459
|
+
// Only attach delete handlers for recently clicked items
|
|
460
|
+
if (isRecentHistory) {
|
|
461
|
+
this.attachDeleteHandlers(container, results, containerId, context);
|
|
462
|
+
}
|
|
463
|
+
|
|
311
464
|
// Call post-render callbacks from providers
|
|
312
465
|
results.forEach((result) => {
|
|
313
466
|
const provider = this.providers.find((p) => p.id === result.providerId);
|
|
@@ -317,6 +470,68 @@ const SearchBox = {
|
|
|
317
470
|
});
|
|
318
471
|
},
|
|
319
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Attaches delete event handlers to result delete buttons within a specific container.
|
|
475
|
+
* Removes only the clicked result from history with smooth animation feedback.
|
|
476
|
+
* Only affects delete buttons within the specified container.
|
|
477
|
+
* @memberof SearchBoxClient.SearchBox
|
|
478
|
+
* @param {HTMLElement} container - The container element to search within.
|
|
479
|
+
* @param {Array<object>} results - Array of search results.
|
|
480
|
+
* @param {string} containerId - Results container element ID or class name.
|
|
481
|
+
* @param {object} [context={}] - Context object.
|
|
482
|
+
* @returns {void}
|
|
483
|
+
*/
|
|
484
|
+
attachDeleteHandlers: function (container, results, containerId, context = {}) {
|
|
485
|
+
// Only select delete buttons within this specific container
|
|
486
|
+
const deleteButtons = container.querySelectorAll('.search-result-delete-btn');
|
|
487
|
+
deleteButtons.forEach((btn) => {
|
|
488
|
+
btn.addEventListener('click', (e) => {
|
|
489
|
+
e.preventDefault();
|
|
490
|
+
e.stopPropagation();
|
|
491
|
+
|
|
492
|
+
const resultId = btn.getAttribute('data-result-id');
|
|
493
|
+
const providerId = btn.getAttribute('data-provider-id');
|
|
494
|
+
|
|
495
|
+
// Animate removal
|
|
496
|
+
const wrapper = btn.closest('.search-result-history-item');
|
|
497
|
+
if (wrapper) {
|
|
498
|
+
wrapper.classList.add('search-result-removing');
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
// Remove only this specific result from history storage
|
|
501
|
+
if (providerId === 'default-routes') {
|
|
502
|
+
this.RecentResults.remove(resultId, null);
|
|
503
|
+
} else {
|
|
504
|
+
this.RecentResults.remove(resultId, providerId);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Filter out only the deleted result from the array
|
|
508
|
+
const remaining = results.filter((r) => {
|
|
509
|
+
if (providerId === 'default-routes') {
|
|
510
|
+
// For routes, match by routerId
|
|
511
|
+
return r.routerId !== resultId;
|
|
512
|
+
} else {
|
|
513
|
+
// For providers, match by id and providerId
|
|
514
|
+
return !(r.id === resultId && r.providerId === providerId);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Re-render with remaining results (context.isRecentHistory should stay true)
|
|
519
|
+
if (remaining.length > 0) {
|
|
520
|
+
this.renderResults(remaining, containerId, context);
|
|
521
|
+
} else {
|
|
522
|
+
// If no results left, clear container and hide clear-all button
|
|
523
|
+
container.innerHTML = '';
|
|
524
|
+
const clearAllBtn = document.querySelector('.btn-search-history-clear-all');
|
|
525
|
+
if (clearAllBtn) {
|
|
526
|
+
clearAllBtn.style.display = 'none';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}, 200);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
|
|
320
535
|
/**
|
|
321
536
|
* Renders a default route search result.
|
|
322
537
|
* Backward compatible with Modal.js search functionality.
|
|
@@ -338,10 +553,35 @@ const SearchBox = {
|
|
|
338
553
|
const imgElement = result.imgElement;
|
|
339
554
|
|
|
340
555
|
let iconHtml = '';
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
556
|
+
|
|
557
|
+
// For route results from history, reconstruct icons from DOM
|
|
558
|
+
if (!fontAwesomeIcon && !imgElement && routerId) {
|
|
559
|
+
const routeBtn = s(`.main-btn-${routerId}`);
|
|
560
|
+
if (routeBtn) {
|
|
561
|
+
const icon = getAllChildNodes(routeBtn).find((e) => {
|
|
562
|
+
return e.classList && Array.from(e.classList).find((e) => e.match('fa-') && !e.match('fa-grip-vertical'));
|
|
563
|
+
});
|
|
564
|
+
const img = getAllChildNodes(routeBtn).find((e) => {
|
|
565
|
+
return (
|
|
566
|
+
e.classList &&
|
|
567
|
+
Array.from(e.classList).find((e) =>
|
|
568
|
+
options.searchCustomImgClass ? e.match(options.searchCustomImgClass) : e.match('img-btn-square-menu'),
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
if (img) {
|
|
573
|
+
iconHtml = img.outerHTML;
|
|
574
|
+
} else if (icon) {
|
|
575
|
+
iconHtml = icon.outerHTML;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
// For fresh search results, use provided DOM elements
|
|
580
|
+
if (imgElement) {
|
|
581
|
+
iconHtml = imgElement.outerHTML;
|
|
582
|
+
} else if (fontAwesomeIcon) {
|
|
583
|
+
iconHtml = fontAwesomeIcon.outerHTML;
|
|
584
|
+
}
|
|
345
585
|
}
|
|
346
586
|
|
|
347
587
|
const translatedText = Translate.Render(routerId);
|
|
@@ -381,6 +621,9 @@ const SearchBox = {
|
|
|
381
621
|
e.preventDefault();
|
|
382
622
|
e.stopPropagation();
|
|
383
623
|
|
|
624
|
+
// Track result in persistent history for all result types
|
|
625
|
+
this.RecentResults.add(result);
|
|
626
|
+
|
|
384
627
|
const provider = this.providers.find((p) => p.id === result.providerId);
|
|
385
628
|
|
|
386
629
|
if (result.type === 'route') {
|
|
@@ -503,109 +746,6 @@ const SearchBox = {
|
|
|
503
746
|
}, 0);
|
|
504
747
|
},
|
|
505
748
|
|
|
506
|
-
/**
|
|
507
|
-
* Debounce helper for search-while-typing
|
|
508
|
-
*/
|
|
509
|
-
debounce: function (func, wait) {
|
|
510
|
-
let timeout;
|
|
511
|
-
return function executedFunction(...args) {
|
|
512
|
-
const later = () => {
|
|
513
|
-
clearTimeout(timeout);
|
|
514
|
-
func(...args);
|
|
515
|
-
};
|
|
516
|
-
clearTimeout(timeout);
|
|
517
|
-
timeout = setTimeout(later, wait);
|
|
518
|
-
};
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Sets up a search input element with automatic search on typing.
|
|
523
|
-
* Attaches debounced input event handler and manages search lifecycle.
|
|
524
|
-
* @memberof SearchBoxClient.SearchBox
|
|
525
|
-
* @param {string} inputId - Input element ID or class name.
|
|
526
|
-
* @param {string} resultsContainerId - Results container element ID or class name.
|
|
527
|
-
* @param {object} [context={}] - Configuration context object.
|
|
528
|
-
* @param {number} [context.debounceTime=300] - Debounce delay in milliseconds.
|
|
529
|
-
* @param {number} [context.minQueryLength=1] - Minimum query length to trigger search.
|
|
530
|
-
* @returns {Function} Cleanup function to remove event listeners.
|
|
531
|
-
*/
|
|
532
|
-
setupSearchInput: function (inputId, resultsContainerId, context = {}) {
|
|
533
|
-
const input = s(`#${inputId}`) || s(`.${inputId}`);
|
|
534
|
-
if (!input) {
|
|
535
|
-
logger.warn(`Input ${inputId} not found`);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const debounceTime = context.debounceTime || 300;
|
|
540
|
-
|
|
541
|
-
const performSearch = this.debounce(async (query) => {
|
|
542
|
-
const trimmedQuery = query ? query.trim() : '';
|
|
543
|
-
const minLength = context.minQueryLength !== undefined ? context.minQueryLength : 1;
|
|
544
|
-
|
|
545
|
-
// Support single character searches by default (minQueryLength: 1)
|
|
546
|
-
// Can be configured via context.minQueryLength for different use cases
|
|
547
|
-
if (trimmedQuery.length < minLength) {
|
|
548
|
-
this.renderResults([], resultsContainerId, context);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const results = await this.search(trimmedQuery, context);
|
|
553
|
-
this.renderResults(results, resultsContainerId, context);
|
|
554
|
-
}, debounceTime);
|
|
555
|
-
|
|
556
|
-
// Store the handler reference
|
|
557
|
-
const handlerId = `search-handler-${inputId}`;
|
|
558
|
-
if (this.Data[handlerId]) {
|
|
559
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
this.Data[handlerId] = (e) => {
|
|
563
|
-
performSearch(e.target.value);
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
input.addEventListener('input', this.Data[handlerId]);
|
|
567
|
-
|
|
568
|
-
logger.info(`Setup search input: ${inputId}`);
|
|
569
|
-
|
|
570
|
-
return () => {
|
|
571
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
572
|
-
delete this.Data[handlerId];
|
|
573
|
-
};
|
|
574
|
-
},
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Debounces a function call to reduce excessive invocations.
|
|
578
|
-
* Used for search input to prevent searching on every keystroke.
|
|
579
|
-
* @memberof SearchBoxClient.SearchBox
|
|
580
|
-
* @param {Function} func - The function to debounce.
|
|
581
|
-
* @param {number} wait - Delay in milliseconds before invoking the function.
|
|
582
|
-
* @returns {Function} Debounced function that delays invocation.
|
|
583
|
-
*/
|
|
584
|
-
debounce: function (func, wait) {
|
|
585
|
-
let timeout;
|
|
586
|
-
|
|
587
|
-
const later = function (...args) {
|
|
588
|
-
timeout = null;
|
|
589
|
-
func(...args);
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
return function (...args) {
|
|
593
|
-
if (timeout) clearTimeout(timeout);
|
|
594
|
-
timeout = setTimeout(() => later(...args), wait);
|
|
595
|
-
};
|
|
596
|
-
},
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Clears all registered search providers.
|
|
600
|
-
* Useful for cleanup or resetting search functionality.
|
|
601
|
-
* @memberof SearchBoxClient.SearchBox
|
|
602
|
-
* @returns {void}
|
|
603
|
-
*/
|
|
604
|
-
clearProviders: function () {
|
|
605
|
-
this.providers = [];
|
|
606
|
-
logger.info('Cleared all search providers');
|
|
607
|
-
},
|
|
608
|
-
|
|
609
749
|
/**
|
|
610
750
|
* Gets base CSS styles for SearchBox with theme-aware styling.
|
|
611
751
|
* Uses subThemeManager colors for consistent theming across light and dark modes.
|
|
@@ -665,13 +805,17 @@ const SearchBox = {
|
|
|
665
805
|
display: flex;
|
|
666
806
|
align-items: center;
|
|
667
807
|
gap: 10px;
|
|
668
|
-
padding:
|
|
808
|
+
padding: 12px 14px;
|
|
669
809
|
margin: 4px 0;
|
|
670
810
|
cursor: pointer;
|
|
671
811
|
border-radius: 4px;
|
|
672
812
|
transition: all 0.15s ease;
|
|
673
813
|
border: 1px solid transparent;
|
|
674
814
|
background: transparent;
|
|
815
|
+
min-height: 44px;
|
|
816
|
+
box-sizing: border-box;
|
|
817
|
+
width: 100%;
|
|
818
|
+
max-width: 100%;
|
|
675
819
|
}
|
|
676
820
|
|
|
677
821
|
.search-result-item:hover {
|
|
@@ -687,16 +831,19 @@ const SearchBox = {
|
|
|
687
831
|
}
|
|
688
832
|
|
|
689
833
|
.search-result-route {
|
|
690
|
-
padding:
|
|
834
|
+
padding: 10px 12px;
|
|
691
835
|
margin: 2px;
|
|
692
836
|
text-align: left;
|
|
837
|
+
min-height: 40px;
|
|
693
838
|
}
|
|
694
839
|
|
|
695
840
|
.search-result-icon {
|
|
696
841
|
display: flex;
|
|
697
842
|
align-items: center;
|
|
698
843
|
justify-content: center;
|
|
699
|
-
min-width:
|
|
844
|
+
min-width: 28px;
|
|
845
|
+
min-height: 28px;
|
|
846
|
+
font-size: 16px;
|
|
700
847
|
color: ${iconColor} !important;
|
|
701
848
|
}
|
|
702
849
|
|
|
@@ -723,8 +870,9 @@ const SearchBox = {
|
|
|
723
870
|
|
|
724
871
|
.search-result-title {
|
|
725
872
|
font-size: 14px;
|
|
726
|
-
font-weight:
|
|
873
|
+
font-weight: 500;
|
|
727
874
|
margin-bottom: 2px;
|
|
875
|
+
line-height: 1.4;
|
|
728
876
|
}
|
|
729
877
|
|
|
730
878
|
.search-result-subtitle {
|
|
@@ -761,6 +909,64 @@ const SearchBox = {
|
|
|
761
909
|
border-color: ${activeBorder};
|
|
762
910
|
color: ${hasThemeColor ? (darkTheme ? lightenHex(themeColor, 0.8) : darkenHex(themeColor, 0.4)) : tagColor};
|
|
763
911
|
}
|
|
912
|
+
|
|
913
|
+
/* Wrapper for history items with delete button - maintains original width */
|
|
914
|
+
.search-result-history-item {
|
|
915
|
+
position: relative;
|
|
916
|
+
box-sizing: border-box;
|
|
917
|
+
width: 100%;
|
|
918
|
+
max-width: 100%;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.search-result-history-item .search-result-item {
|
|
922
|
+
width: 100%;
|
|
923
|
+
box-sizing: border-box;
|
|
924
|
+
padding-right: 30px; /* Make room for delete button */
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/* Delete button - absolute positioned in top-right corner */
|
|
928
|
+
.search-result-delete-btn {
|
|
929
|
+
position: absolute;
|
|
930
|
+
top: 4px;
|
|
931
|
+
right: 4px;
|
|
932
|
+
background: none;
|
|
933
|
+
border: none;
|
|
934
|
+
color: #999;
|
|
935
|
+
cursor: pointer;
|
|
936
|
+
padding: 3px 6px;
|
|
937
|
+
border-radius: 3px;
|
|
938
|
+
transition: all 0.2s ease;
|
|
939
|
+
opacity: 0;
|
|
940
|
+
width: 22px;
|
|
941
|
+
height: 22px;
|
|
942
|
+
display: flex;
|
|
943
|
+
align-items: center;
|
|
944
|
+
justify-content: center;
|
|
945
|
+
font-size: 10px;
|
|
946
|
+
z-index: 5;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.search-result-history-item:hover .search-result-delete-btn {
|
|
950
|
+
opacity: 1;
|
|
951
|
+
color: ${darkTheme ? '#ff8a8a' : '#e53935'};
|
|
952
|
+
background: ${darkTheme ? 'rgba(255, 107, 107, 0.2)' : 'rgba(211, 47, 47, 0.15)'};
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.search-result-delete-btn:hover {
|
|
956
|
+
background: ${darkTheme ? 'rgba(255, 107, 107, 0.35)' : 'rgba(211, 47, 47, 0.25)'};
|
|
957
|
+
transform: scale(1.1);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.search-result-delete-btn:active {
|
|
961
|
+
transform: scale(0.95);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/* Animation for removal */
|
|
965
|
+
.search-result-history-item.search-result-removing {
|
|
966
|
+
opacity: 0;
|
|
967
|
+
transform: translateX(20px);
|
|
968
|
+
transition: all 0.2s ease;
|
|
969
|
+
}
|
|
764
970
|
`;
|
|
765
971
|
},
|
|
766
972
|
|
|
@@ -19,7 +19,7 @@ const SignUp = {
|
|
|
19
19
|
{
|
|
20
20
|
model: 'username',
|
|
21
21
|
id: `sign-up-username`,
|
|
22
|
-
rules: [{ type: 'isEmpty' }, { type: 'isLength', options: { min: 2, max: 20 } }],
|
|
22
|
+
rules: [{ type: 'isEmpty' }, { type: 'isLength', options: { min: 2, max: 20 } }, { type: 'isValidUsername' }],
|
|
23
23
|
},
|
|
24
24
|
{ model: 'email', id: `sign-up-email`, rules: [{ type: 'isEmpty' }, { type: 'isEmail' }] },
|
|
25
25
|
{
|
|
@@ -43,23 +43,42 @@ const SignUp = {
|
|
|
43
43
|
if ('model' in inputData) body[inputData.model] = s(`.${inputData.id}`).value;
|
|
44
44
|
}
|
|
45
45
|
const result = await UserService.post({ body });
|
|
46
|
+
const handleSignUpError = (data) => {
|
|
47
|
+
let error = '';
|
|
48
|
+
if (data.message) {
|
|
49
|
+
if (data.message.match('duplicate')) {
|
|
50
|
+
if (data.message.match('username')) error += Translate.Render('error-username-taken');
|
|
51
|
+
if (data.message.match('email')) error += Translate.Render('error-email-taken');
|
|
52
|
+
} else {
|
|
53
|
+
if (data.message.match('username')) error += Translate.Render('error-username-invalid');
|
|
54
|
+
if (data.message.match('email')) error += Translate.Render('error-email-invalid');
|
|
55
|
+
if (data.message.match('password')) error += Translate.Render('error-password-invalid');
|
|
56
|
+
}
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
return Translate.Render('error-register-user');
|
|
60
|
+
};
|
|
46
61
|
NotificationManager.Push({
|
|
47
62
|
html:
|
|
48
63
|
typeof result.data === 'string'
|
|
49
64
|
? result.data
|
|
50
65
|
: result.status === 'success'
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
? Translate.Render(`success-register-user`)
|
|
67
|
+
: handleSignUpError(result),
|
|
53
68
|
status: result.status,
|
|
54
69
|
});
|
|
55
70
|
if (result.status === 'success') {
|
|
56
71
|
await Auth.sessionIn(result);
|
|
57
|
-
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
if (s(`.btn-close-${options.idModal}`)) s(`.btn-close-${options.idModal}`).click();
|
|
74
|
+
});
|
|
58
75
|
}
|
|
59
76
|
});
|
|
60
|
-
|
|
61
|
-
s(`.
|
|
62
|
-
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
s(`.btn-sign-up-i-have-account`).onclick = () => {
|
|
79
|
+
s(`.main-btn-log-in`).click();
|
|
80
|
+
};
|
|
81
|
+
});
|
|
63
82
|
});
|
|
64
83
|
return html`
|
|
65
84
|
${await BtnIcon.Render({
|
|
@@ -21,9 +21,12 @@ const SocketIo = {
|
|
|
21
21
|
},
|
|
22
22
|
Init: async function (options) {
|
|
23
23
|
if (this.socket) this.socket.disconnect();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const path = getWsBasePath();
|
|
25
|
+
this.host = options.host ? options.host : getWsBaseUrl({ wsBasePath: '' });
|
|
26
|
+
logger.info(`ws host:`, {
|
|
27
|
+
host: this.host,
|
|
28
|
+
path,
|
|
29
|
+
});
|
|
27
30
|
const connectOptions = {
|
|
28
31
|
path: path === '/' ? undefined : path,
|
|
29
32
|
// auth: {
|