@underpostnet/underpost 2.96.1 → 2.97.1

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 (51) hide show
  1. package/.dockerignore +1 -2
  2. package/.env.development +0 -3
  3. package/.env.production +0 -3
  4. package/.env.test +0 -3
  5. package/.prettierignore +1 -2
  6. package/README.md +31 -31
  7. package/baremetal/commission-workflows.json +94 -17
  8. package/bin/deploy.js +1 -1
  9. package/cli.md +75 -41
  10. package/conf.js +1 -0
  11. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  12. package/manifests/deployment/dd-test-development/deployment.yaml +4 -4
  13. package/package.json +3 -2
  14. package/packer/scripts/fuse-tar-root +3 -3
  15. package/scripts/disk-clean.sh +128 -187
  16. package/scripts/gpu-diag.sh +2 -2
  17. package/scripts/ip-info.sh +11 -11
  18. package/scripts/ipxe-setup.sh +197 -0
  19. package/scripts/maas-upload-boot-resource.sh +1 -1
  20. package/scripts/nvim.sh +1 -1
  21. package/scripts/packer-setup.sh +13 -13
  22. package/scripts/ports-ls.sh +31 -0
  23. package/scripts/quick-tftp.sh +19 -0
  24. package/scripts/rocky-setup.sh +2 -2
  25. package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
  26. package/scripts/ssl.sh +7 -7
  27. package/src/api/document/document.controller.js +15 -0
  28. package/src/api/document/document.model.js +44 -1
  29. package/src/api/document/document.router.js +2 -0
  30. package/src/api/document/document.service.js +398 -26
  31. package/src/cli/baremetal.js +2001 -463
  32. package/src/cli/cloud-init.js +354 -231
  33. package/src/cli/cluster.js +51 -53
  34. package/src/cli/db.js +22 -0
  35. package/src/cli/deploy.js +7 -3
  36. package/src/cli/image.js +1 -0
  37. package/src/cli/index.js +40 -37
  38. package/src/cli/lxd.js +3 -3
  39. package/src/cli/run.js +78 -12
  40. package/src/cli/ssh.js +1 -1
  41. package/src/client/components/core/Css.js +16 -2
  42. package/src/client/components/core/Input.js +3 -1
  43. package/src/client/components/core/Modal.js +125 -159
  44. package/src/client/components/core/Panel.js +436 -31
  45. package/src/client/components/core/PanelForm.js +222 -37
  46. package/src/client/components/core/SearchBox.js +801 -0
  47. package/src/client/components/core/Translate.js +11 -0
  48. package/src/client/services/document/document.service.js +42 -0
  49. package/src/index.js +1 -1
  50. package/src/server/dns.js +12 -6
  51. package/src/server/start.js +14 -6
@@ -2,8 +2,8 @@ import { getId, isValidDate, newInstance } from './CommonJs.js';
2
2
  import { LoadingAnimation } from '../core/LoadingAnimation.js';
3
3
  import { Validator } from '../core/Validator.js';
4
4
  import { Input } from '../core/Input.js';
5
- import { darkTheme, ThemeEvents } from './Css.js';
6
- import { append, getDataFromInputFile, htmls, s } from './VanillaJs.js';
5
+ import { darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from './Css.js';
6
+ import { append, copyData, getDataFromInputFile, htmls, s, sa } from './VanillaJs.js';
7
7
  import { BtnIcon } from './BtnIcon.js';
8
8
  import { Translate } from './Translate.js';
9
9
  import { DropDown } from './DropDown.js';
@@ -14,6 +14,9 @@ import { RichText } from './RichText.js';
14
14
  import { loggerFactory } from './Logger.js';
15
15
  import { Badge } from './Badge.js';
16
16
  import { Content } from './Content.js';
17
+ import { DocumentService } from '../../services/document/document.service.js';
18
+ import { NotificationManager } from './NotificationManager.js';
19
+ import { getApiBaseUrl } from '../../services/core/core.service.js';
17
20
 
18
21
  const logger = loggerFactory(import.meta);
19
22
 
@@ -30,6 +33,10 @@ const Panel = {
30
33
  originData: () => [],
31
34
  filesData: () => [],
32
35
  onClick: () => {},
36
+ share: {
37
+ copyLink: false,
38
+ },
39
+ showCreatorProfile: false,
33
40
  },
34
41
  ) {
35
42
  const idPanel = options?.idPanel ? options.idPanel : getId(this.Tokens, `${idPanel}-`);
@@ -87,6 +94,62 @@ const Panel = {
87
94
  htmls(`.${idPanel}-cell-col-a-${id}`, render);
88
95
  },
89
96
  });
97
+ if (options.share && options.share.copyLink) {
98
+ EventsUI.onClick(
99
+ `.${idPanel}-btn-copy-share-${id}`,
100
+ async (e) => {
101
+ try {
102
+ const shareUrl = `${window.location.origin}${window.location.pathname}?cid=${obj._id}`;
103
+ await copyData(shareUrl);
104
+ await NotificationManager.Push({
105
+ status: 'success',
106
+ html: html`<div>${Translate.Render('link-copied')}</div>`,
107
+ });
108
+ // Track the copy share link event
109
+ await DocumentService.patch({ id: obj._id, action: 'copy-share-link' });
110
+ // Update the count in the UI - read current value from span first
111
+ const countSpan = s(`.${idPanel}-share-count-${id}`);
112
+ if (countSpan) {
113
+ const currentCount = parseInt(countSpan.textContent) || 0;
114
+ const newCount = currentCount + 1;
115
+ htmls(`.${idPanel}-share-count-${id}`, newCount);
116
+ } else {
117
+ // Create count badge if it didn't exist before (was 0)
118
+ const btn = s(`.${idPanel}-btn-copy-share-${id}`);
119
+ if (btn) {
120
+ const countBadge = document.createElement('span');
121
+ countBadge.className = `${idPanel}-share-count-${id}`;
122
+ countBadge.style.cssText =
123
+ 'position: absolute; top: -4px; right: -4px; background: #666; color: white; border-radius: 10px; padding: 1px 5px; font-size: 10px; font-weight: bold; min-width: 16px; text-align: center;';
124
+ countBadge.textContent = '1';
125
+ btn.appendChild(countBadge);
126
+ }
127
+ }
128
+ } catch (error) {
129
+ logger.error('Error copying share link:', error);
130
+ await NotificationManager.Push({
131
+ status: 'error',
132
+ html: html`<div>${Translate.Render('error-copying-link')}</div>`,
133
+ });
134
+ }
135
+ },
136
+ { context: 'modal' },
137
+ );
138
+
139
+ // Add tooltip hover effect
140
+ setTimeout(() => {
141
+ const btn = s(`.${idPanel}-btn-copy-share-${id}`);
142
+ const tooltip = s(`.${idPanel}-share-tooltip-${id}`);
143
+ if (btn && tooltip) {
144
+ btn.addEventListener('mouseenter', () => {
145
+ tooltip.style.opacity = '1';
146
+ });
147
+ btn.addEventListener('mouseleave', () => {
148
+ tooltip.style.opacity = '0';
149
+ });
150
+ }
151
+ });
152
+ }
90
153
  EventsUI.onClick(
91
154
  `.${idPanel}-btn-delete-${id}`,
92
155
  async (e) => {
@@ -99,6 +162,8 @@ const Panel = {
99
162
  );
100
163
  EventsUI.onClick(`.${idPanel}-btn-edit-${id}`, async () => {
101
164
  logger.warn('edit', obj);
165
+ const searchId = String(obj._id || obj.id);
166
+
102
167
  if (obj._id) Panel.Tokens[idPanel].editId = obj._id;
103
168
  else if (obj.id) Panel.Tokens[idPanel].editId = obj.id;
104
169
 
@@ -106,14 +171,11 @@ const Panel = {
106
171
  s(`.btn-${idPanel}-label-add`).classList.add('hide');
107
172
 
108
173
  openPanelForm();
109
- // s(`.btn-${idPanel}-add`).click();
110
- s(`.${scrollClassContainer}`).scrollTop = 0;
111
-
174
+ // s(`.${scrollClassContainer}`).scrollTop = 0;
112
175
  const originData = options.originData();
113
176
  const filesData = options.filesData();
114
177
 
115
178
  // Convert IDs to strings for comparison to handle ObjectId vs string issues
116
- const searchId = String(obj._id || obj.id);
117
179
  const foundOrigin = originData.find((d) => String(d._id || d.id) === searchId);
118
180
  const foundFiles = filesData.find((d) => String(d._id || d.id) === searchId);
119
181
 
@@ -133,6 +195,8 @@ const Panel = {
133
195
  );
134
196
  }
135
197
 
198
+ // Clear previous form values then populate with the current item's data
199
+ Input.cleanValues(formData);
136
200
  Input.setValues(formData, obj, foundOrigin, foundFiles);
137
201
  if (options.on.initEdit) await options.on.initEdit({ data: obj });
138
202
  });
@@ -144,34 +208,120 @@ const Panel = {
144
208
  e.preventDefault();
145
209
  // if (options.onClick) await options.onClick({ payload });
146
210
  };
211
+
212
+ // Add theme change handler for creator profile header
213
+ if (options.showCreatorProfile && obj.userInfo) {
214
+ const updateCreatorProfileTheme = () => {
215
+ const profileHeader = s(`.creator-profile-header-${id}`);
216
+ if (profileHeader) {
217
+ profileHeader.style.borderBottom = `1px solid ${darkTheme ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.08)'}`;
218
+ profileHeader.style.background = `${darkTheme ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)'}`;
219
+
220
+ // Update avatar border if it's an image
221
+ const avatarImg = profileHeader.querySelector('.creator-avatar');
222
+ if (avatarImg && avatarImg.tagName === 'IMG') {
223
+ avatarImg.style.border = `2px solid ${darkTheme ? 'rgba(102, 126, 234, 0.5)' : 'rgba(102, 126, 234, 0.3)'}`;
224
+ }
225
+
226
+ // Update username color
227
+ const username = profileHeader.querySelector('.creator-username');
228
+ if (username) {
229
+ username.style.color = `${darkTheme ? 'rgba(255,255,255,0.9)' : 'rgba(0,0,0,0.85)'}`;
230
+ }
231
+
232
+ // Update "Creator" label color
233
+ const creatorLabel = username?.nextElementSibling;
234
+ if (creatorLabel) {
235
+ creatorLabel.style.color = `${darkTheme ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.45)'}`;
236
+ }
237
+ }
238
+ };
239
+
240
+ // Register theme change handler
241
+ const profileThemeHandlerId = `${id}-creator-profile-theme`;
242
+ ThemeEvents[profileThemeHandlerId] = updateCreatorProfileTheme;
243
+ }
147
244
  });
148
245
  if (s(`.${idPanel}-${id}`)) s(`.${idPanel}-${id}`).remove();
149
- return html` <div class="in box-shadow ${idPanel} ${idPanel}-${id}">
246
+
247
+ // Check if document is public (from obj.isPublic field)
248
+ const isPublic = obj.isPublic === true;
249
+ // Visibility icon: globe for public, padlock for private
250
+ const visibilityIcon = isPublic
251
+ ? '<i class="fas fa-globe" title="Public document"></i>'
252
+ : '<i class="fas fa-lock" title="Private document"></i>';
253
+
254
+ return html` <div class="in box-shadow ${idPanel} ${idPanel}-${id}" style="position: relative;">
150
255
  <div class="fl ${idPanel}-tools session-fl-log-in ${obj.tools ? '' : 'hide'}">
151
256
  ${await BtnIcon.Render({
152
- class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-edit-${id}`,
153
- label: html`<div class="abs center"><i class="fas fa-edit"></i></div>`,
257
+ class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-delete-${id}`,
258
+ label: html`<div class="abs center"><i class="fas fa-trash"></i></div>`,
154
259
  useVisibilityHover: true,
155
260
  tooltipHtml: await Badge.Render({
156
261
  id: `tooltip-${idPanel}-${id}`,
157
- text: `${Translate.Render(`edit`)}`,
262
+ text: `${Translate.Render(`delete`)}`,
158
263
  classList: '',
159
- style: { top: `-22px`, left: '-5px' },
264
+ style: { top: `-22px`, left: '-13px' },
160
265
  }),
161
266
  })}
162
267
  ${await BtnIcon.Render({
163
- class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-delete-${id}`,
164
- label: html`<div class="abs center"><i class="fas fa-trash"></i></div>`,
268
+ class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-edit-${id}`,
269
+ label: html`<div class="abs center"><i class="fas fa-edit"></i></div>`,
165
270
  useVisibilityHover: true,
166
271
  tooltipHtml: await Badge.Render({
167
272
  id: `tooltip-${idPanel}-${id}`,
168
- text: `${Translate.Render(`delete`)}`,
273
+ text: `${Translate.Render(`edit`)}`,
169
274
  classList: '',
170
- style: { top: `-22px`, left: '-13px' },
275
+ style: { top: `-22px`, left: '-5px' },
171
276
  }),
172
277
  })}
173
278
  </div>
174
279
  <div class="in container-${idPanel}-${id}">
280
+ <div class="panel-visibility-icon">${visibilityIcon}</div>
281
+ ${options.showCreatorProfile && obj.userInfo
282
+ ? html`<div
283
+ class="creator-profile-header creator-profile-header-${id}"
284
+ style="padding: 10px 12px; margin-bottom: 10px; border-bottom: 1px solid ${darkTheme
285
+ ? 'rgba(255,255,255,0.1)'
286
+ : 'rgba(0,0,0,0.08)'}; display: flex; align-items: center; gap: 10px; background: ${darkTheme
287
+ ? 'rgba(255,255,255,0.02)'
288
+ : 'rgba(0,0,0,0.02)'}; border-radius: 4px 4px 0 0;"
289
+ >
290
+ ${obj.userInfo.profileImageId && obj.userInfo.profileImageId._id
291
+ ? html`<img
292
+ class="creator-avatar"
293
+ src="${getApiBaseUrl({ id: obj.userInfo.profileImageId._id, endpoint: 'file/blob' })}"
294
+ alt="${obj.userInfo.username || obj.userInfo.email}"
295
+ style="width: 36px; height: 36px; border-radius: 50%; object-fit: cover; border: 2px solid ${darkTheme
296
+ ? 'rgba(102, 126, 234, 0.5)'
297
+ : 'rgba(102, 126, 234, 0.3)'}; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"
298
+ title="${obj.userInfo.email || obj.userInfo.username}"
299
+ />`
300
+ : html`<div
301
+ class="creator-avatar"
302
+ style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 16px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"
303
+ title="${obj.userInfo.email || obj.userInfo.username}"
304
+ >
305
+ ${(obj.userInfo.username || obj.userInfo.email || 'U').charAt(0).toUpperCase()}
306
+ </div>`}
307
+ <div style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
308
+ <span
309
+ class="creator-username"
310
+ style="font-size: 14px; font-weight: 600; color: ${darkTheme
311
+ ? 'rgba(255,255,255,0.9)'
312
+ : 'rgba(0,0,0,0.85)'}; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
313
+ >
314
+ ${obj.userInfo.username || obj.userInfo.email || 'Unknown'}
315
+ </span>
316
+ <span
317
+ style="font-size: 11px; color: ${darkTheme
318
+ ? 'rgba(255,255,255,0.5)'
319
+ : 'rgba(0,0,0,0.45)'}; line-height: 1.3; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 500;"
320
+ >Creator</span
321
+ >
322
+ </div>
323
+ </div>`
324
+ : ''}
175
325
  <div class="in ${idPanel}-head">
176
326
  <div class="in ${idPanel}-title">
177
327
  ${options.titleIcon}
@@ -201,18 +351,79 @@ const Panel = {
201
351
  }
202
352
 
203
353
  if (formData.find((f) => f.model === infoKey && f.panel && f.panel.type === 'tags')) {
204
- setTimeout(async () => {
354
+ // Function to render tags with current theme
355
+ const renderTags = async () => {
205
356
  let tagRender = html``;
206
357
  for (const tag of obj[infoKey]) {
358
+ // Use subThemeManager colors for consistent theming
359
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
360
+ const hasThemeColor = themeColor && themeColor !== null;
361
+
362
+ let tagBg, tagColor;
363
+ if (darkTheme) {
364
+ tagBg = hasThemeColor ? darkenHex(themeColor, 0.6) : '#4a4a4a';
365
+ tagColor = hasThemeColor ? lightenHex(themeColor, 0.7) : '#ffffff';
366
+ } else {
367
+ tagBg = hasThemeColor ? lightenHex(themeColor, 0.7) : '#a2a2a2';
368
+ tagColor = hasThemeColor ? darkenHex(themeColor, 0.5) : '#ffffff';
369
+ }
370
+
207
371
  tagRender += await Badge.Render({
208
372
  text: tag,
209
- style: { color: 'white' },
210
- classList: 'inl',
211
- style: { margin: '3px', background: `#a2a2a2` },
373
+ style: { color: tagColor },
374
+ classList: 'inl panel-tag-clickable',
375
+ style: {
376
+ margin: '3px',
377
+ background: tagBg,
378
+ color: tagColor,
379
+ cursor: 'pointer',
380
+ transition: 'all 0.2s ease',
381
+ },
212
382
  });
213
383
  }
214
- if (s(`.tag-render-${id}`)) htmls(`.tag-render-${id}`, tagRender);
215
- });
384
+ if (s(`.tag-render-${id}`)) {
385
+ htmls(`.tag-render-${id}`, tagRender);
386
+
387
+ // Add click handlers to tags for search integration
388
+ setTimeout(() => {
389
+ const tagElements = sa(`.tag-render-${id} .panel-tag-clickable`);
390
+ tagElements.forEach((tagEl) => {
391
+ tagEl.onclick = (e) => {
392
+ e.stopPropagation();
393
+ const tagText = tagEl.textContent.trim();
394
+
395
+ // Open search bar if closed
396
+ if (
397
+ !s('.main-body-btn-ui-bar-custom-open').classList.contains('hide') ||
398
+ !s(`.main-body-btn-ui-open`).classList.contains('hide')
399
+ )
400
+ s('.main-body-btn-bar-custom').click();
401
+
402
+ // Find and populate search box if it exists
403
+ const searchBox = s('.top-bar-search-box');
404
+ if (searchBox) {
405
+ searchBox.value = tagText;
406
+ searchBox.focus();
407
+
408
+ // Trigger input event to start search
409
+ const inputEvent = new Event('input', { bubbles: true });
410
+ searchBox.dispatchEvent(inputEvent);
411
+
412
+ logger.info(`Tag clicked: ${tagText} - search triggered`);
413
+ }
414
+ };
415
+ });
416
+ }, 100);
417
+ }
418
+ };
419
+
420
+ // Initial render
421
+ setTimeout(renderTags);
422
+
423
+ // Add theme change handler for this tag set
424
+ const tagThemeHandlerId = `${id}-tags-${infoKey}-theme`;
425
+ ThemeEvents[tagThemeHandlerId] = renderTags;
426
+
216
427
  return html``;
217
428
  }
218
429
  {
@@ -266,6 +477,32 @@ const Panel = {
266
477
  </div>
267
478
  </div>
268
479
  </div>
480
+ ${options.share && options.share.copyLink
481
+ ? html`<div
482
+ class="${idPanel}-share-btn-container ${idPanel}-share-btn-container-${id}"
483
+ style="position: absolute; bottom: 8px; right: 8px; z-index: 2;"
484
+ >
485
+ <button
486
+ class="btn-icon ${idPanel}-btn-copy-share-${id}"
487
+ style="background: transparent; color: #888; border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; display: flex; align-items: center; justify-content: center; position: relative; transition: all 0.3s ease;"
488
+ >
489
+ <i class="fas fa-link" style="font-size: 20px;"></i>
490
+ ${obj.totalCopyShareLinkCount && obj.totalCopyShareLinkCount > 0
491
+ ? html`<span
492
+ class="${idPanel}-share-count-${id}"
493
+ style="position: absolute; top: -4px; right: -4px; background: #666; color: white; border-radius: 10px; padding: 1px 5px; font-size: 10px; font-weight: bold; min-width: 16px; text-align: center;"
494
+ >${obj.totalCopyShareLinkCount}</span
495
+ >`
496
+ : ''}
497
+ </button>
498
+ <div
499
+ class="${idPanel}-share-tooltip-${id}"
500
+ style="position: absolute; bottom: 50px; right: 0; background: rgba(0,0,0,0.8); color: white; padding: 6px 10px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.3s ease;"
501
+ >
502
+ ${Translate.Render('copy-share-link')}
503
+ </div>
504
+ </div>`
505
+ : ''}
269
506
  </div>`;
270
507
  };
271
508
 
@@ -410,6 +647,11 @@ const Panel = {
410
647
  </div>`,
411
648
  // disabled: true,
412
649
  // disabledEye: true,
650
+ })}
651
+ ${await BtnIcon.Render({
652
+ class: `inl section-mp btn-custom btn-${idPanel}-clean-file`,
653
+ label: html`<i class="fa-solid fa-file-circle-xmark"></i> ${Translate.Render('clear-file')}`,
654
+ type: 'button',
413
655
  })}`;
414
656
  break;
415
657
  default:
@@ -476,6 +718,15 @@ const Panel = {
476
718
  s(`.btn-${idPanel}-clean`).onclick = () => {
477
719
  Input.cleanValues(formData);
478
720
  };
721
+ s(`.btn-${idPanel}-clean-file`).onclick = () => {
722
+ // Clear file input specifically
723
+ const fileFormData = formData.find((f) => f.inputType === 'file');
724
+ if (fileFormData && s(`.${fileFormData.id}`)) {
725
+ s(`.${fileFormData.id}`).value = '';
726
+ s(`.${fileFormData.id}`).inputFiles = null;
727
+ htmls(`.file-name-render-${fileFormData.id}`, `${fileNameInputExtDefaultContent}`);
728
+ }
729
+ };
479
730
  s(`.btn-${idPanel}-close`).onclick = (e) => {
480
731
  e.preventDefault();
481
732
  s(`.${idPanel}-form-body`).style.opacity = 0;
@@ -494,10 +745,26 @@ const Panel = {
494
745
  };
495
746
  s(`.btn-${idPanel}-add`).onclick = async (e) => {
496
747
  e.preventDefault();
497
- // s(`.btn-${idPanel}-clean`).click();
748
+
749
+ // Clean all form inputs and reset data scope
750
+ Input.cleanValues(formData);
751
+
752
+ // Clean file input specifically
753
+ const fileFormData = formData.find((f) => f.inputType === 'file');
754
+ if (fileFormData && s(`.${fileFormData.id}`)) {
755
+ s(`.${fileFormData.id}`).value = '';
756
+ s(`.${fileFormData.id}`).inputFiles = null;
757
+ htmls(`.file-name-render-${fileFormData.id}`, `${fileNameInputExtDefaultContent}`);
758
+ }
759
+
760
+ // Reset edit ID to ensure we're in "add" mode
498
761
  Panel.Tokens[idPanel].editId = undefined;
762
+
763
+ // Update button labels
499
764
  s(`.btn-${idPanel}-label-add`).classList.remove('hide');
500
765
  s(`.btn-${idPanel}-label-edit`).classList.add('hide');
766
+
767
+ // Scroll to top
501
768
  s(`.${scrollClassContainer}`).scrollTop = 0;
502
769
 
503
770
  openPanelForm();
@@ -544,6 +811,29 @@ const Panel = {
544
811
  ? getDarkStyles(idPanel, scrollClassContainer)
545
812
  : getLightStyles(idPanel, scrollClassContainer);
546
813
  }
814
+
815
+ // Update tag hover styles
816
+ const tagStyleElement = s(`.${idPanel}-tag-styles`);
817
+ if (tagStyleElement) {
818
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
819
+ const hasThemeColor = themeColor && themeColor !== null;
820
+ let hoverBg;
821
+ if (darkTheme) {
822
+ hoverBg = hasThemeColor ? darkenHex(themeColor, 0.5) : '#5a5a5a';
823
+ } else {
824
+ hoverBg = hasThemeColor ? lightenHex(themeColor, 0.6) : '#8a8a8a';
825
+ }
826
+
827
+ tagStyleElement.textContent = css`
828
+ .panel-tag-clickable:hover {
829
+ background: ${hoverBg} !important;
830
+ transform: scale(1.05);
831
+ }
832
+ .panel-tag-clickable:active {
833
+ transform: scale(0.98);
834
+ }
835
+ `;
836
+ }
547
837
  };
548
838
 
549
839
  // Add theme change listener
@@ -562,15 +852,39 @@ const Panel = {
562
852
  width: 100%;
563
853
  }
564
854
  .${idPanel}-title {
565
- color: rgba(109, 104, 255, 1);
855
+ color: ${(() => {
856
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
857
+ const hasThemeColor = themeColor && themeColor !== null;
858
+ if (hasThemeColor) {
859
+ return darkTheme ? lightenHex(themeColor, 0.3) : darkenHex(themeColor, 0.2);
860
+ } else {
861
+ return darkTheme ? '#8a85ff' : 'rgba(109, 104, 255, 1)';
862
+ }
863
+ })()};
566
864
  font-size: 24px;
567
865
  padding: 5px;
568
866
  }
569
867
  .a-title-${idPanel} {
570
- color: rgba(109, 104, 255, 1);
868
+ color: ${(() => {
869
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
870
+ const hasThemeColor = themeColor && themeColor !== null;
871
+ if (hasThemeColor) {
872
+ return darkTheme ? lightenHex(themeColor, 0.3) : darkenHex(themeColor, 0.2);
873
+ } else {
874
+ return darkTheme ? '#8a85ff' : 'rgba(109, 104, 255, 1)';
875
+ }
876
+ })()};
571
877
  }
572
878
  .a-title-${idPanel}:hover {
573
- color: #e89f4c;
879
+ color: ${(() => {
880
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
881
+ const hasThemeColor = themeColor && themeColor !== null;
882
+ if (hasThemeColor) {
883
+ return darkTheme ? lightenHex(themeColor, 0.5) : lightenHex(themeColor, 0.3);
884
+ } else {
885
+ return darkTheme ? '#ffb74d' : '#e89f4c';
886
+ }
887
+ })()};
574
888
  }
575
889
  .${idPanel}-row {
576
890
  padding: 5px;
@@ -587,6 +901,7 @@ const Panel = {
587
901
  margin-left: 10px;
588
902
  top: -7px;
589
903
  }
904
+
590
905
  .${idPanel}-row-key {
591
906
  }
592
907
  .${idPanel}-row-value {
@@ -617,8 +932,53 @@ const Panel = {
617
932
  color: #000000 !important;
618
933
  font-size: 17px !important;
619
934
  }
935
+ .${idPanel}-share-btn-container button:hover {
936
+ background: transparent !important;
937
+ transform: scale(1.1);
938
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4) !important;
939
+ }
940
+ .${idPanel}-share-btn-container button:focus {
941
+ outline: none;
942
+ background: transparent !important;
943
+ }
944
+ .${idPanel}-share-btn-container button:focus {
945
+ outline: none;
946
+ background: transparent !important;
947
+ }
948
+ .${idPanel}-share-btn-container button:active {
949
+ transform: scale(0.95);
950
+ }
951
+ .${idPanel}-share-btn-container span[class*='share-count'] {
952
+ animation: ${idPanel}-share-pulse 2s infinite;
953
+ }
954
+ @keyframes ${idPanel}-share-pulse {
955
+ 0%,
956
+ 100% {
957
+ transform: scale(1);
958
+ }
959
+ 50% {
960
+ transform: scale(1.1);
961
+ }
962
+ }
620
963
  </style>
621
964
  <style class="${idPanel}-styles"></style>
965
+ <style class="${idPanel}-tag-styles">
966
+ .panel-tag-clickable:hover {
967
+ background: ${(() => {
968
+ const themeColor = darkTheme ? subThemeManager.darkColor : subThemeManager.lightColor;
969
+ const hasThemeColor = themeColor && themeColor !== null;
970
+ if (darkTheme) {
971
+ return hasThemeColor ? darkenHex(themeColor, 0.5) : '#5a5a5a';
972
+ } else {
973
+ return hasThemeColor ? lightenHex(themeColor, 0.6) : '#8a8a8a';
974
+ }
975
+ })()} !important;
976
+ transform: scale(1.05);
977
+ }
978
+ .panel-tag-clickable:active {
979
+ transform: scale(0.98);
980
+ }
981
+ </style>
622
982
  <div class="${idPanel}-container">
623
983
  <div class="in modal ${idPanel}-form-container ${options.formContainerClass ? options.formContainerClass : ''}">
624
984
  <div class="in ${idPanel}-form-header">
@@ -710,6 +1070,19 @@ function getBaseStyles(idPanel, scrollClassContainer) {
710
1070
  .${idPanel}-dropdown {
711
1071
  min-height: 100px;
712
1072
  }
1073
+ .panel-visibility-icon {
1074
+ position: absolute;
1075
+ top: 34px;
1076
+ left: 0px;
1077
+ font-size: 14px;
1078
+ opacity: 0.7;
1079
+ transition: opacity 0.2s ease;
1080
+ pointer-events: none;
1081
+ z-index: 10;
1082
+ }
1083
+ .${idPanel}:hover .panel-visibility-icon {
1084
+ opacity: 1;
1085
+ }
713
1086
  `;
714
1087
  }
715
1088
 
@@ -726,15 +1099,27 @@ function getLightStyles(idPanel, scrollClassContainer) {
726
1099
  background: #ffffff;
727
1100
  }
728
1101
  .${idPanel}-title {
729
- color: rgba(109, 104, 255, 1);
1102
+ color: ${(() => {
1103
+ const themeColor = subThemeManager.lightColor;
1104
+ const hasThemeColor = themeColor && themeColor !== null;
1105
+ return hasThemeColor ? darkenHex(themeColor, 0.2) : 'rgba(109, 104, 255, 1)';
1106
+ })()};
730
1107
  font-size: 24px;
731
1108
  padding: 5px;
732
1109
  }
733
1110
  .a-title-${idPanel} {
734
- color: rgba(109, 104, 255, 1);
1111
+ color: ${(() => {
1112
+ const themeColor = subThemeManager.lightColor;
1113
+ const hasThemeColor = themeColor && themeColor !== null;
1114
+ return hasThemeColor ? darkenHex(themeColor, 0.2) : 'rgba(109, 104, 255, 1)';
1115
+ })()};
735
1116
  }
736
1117
  .a-title-${idPanel}:hover {
737
- color: #e89f4c;
1118
+ color: ${(() => {
1119
+ const themeColor = subThemeManager.lightColor;
1120
+ const hasThemeColor = themeColor && themeColor !== null;
1121
+ return hasThemeColor ? lightenHex(themeColor, 0.3) : '#e89f4c';
1122
+ })()};
738
1123
  }
739
1124
  .${idPanel}-row-pin-value {
740
1125
  font-size: 20px;
@@ -748,6 +1133,10 @@ function getLightStyles(idPanel, scrollClassContainer) {
748
1133
  color: #000000 !important;
749
1134
  font-size: 17px !important;
750
1135
  }
1136
+ .panel-visibility-icon .fa-globe,
1137
+ .panel-visibility-icon .fa-lock {
1138
+ color: #666;
1139
+ }
751
1140
  `;
752
1141
  }
753
1142
 
@@ -764,15 +1153,27 @@ function getDarkStyles(idPanel, scrollClassContainer) {
764
1153
  background: #3a3a3a;
765
1154
  }
766
1155
  .${idPanel}-title {
767
- color: #8a85ff;
1156
+ color: ${(() => {
1157
+ const themeColor = subThemeManager.darkColor;
1158
+ const hasThemeColor = themeColor && themeColor !== null;
1159
+ return hasThemeColor ? lightenHex(themeColor, 0.3) : '#8a85ff';
1160
+ })()};
768
1161
  font-size: 24px;
769
1162
  padding: 5px;
770
1163
  }
771
1164
  .a-title-${idPanel} {
772
- color: #8a85ff;
1165
+ color: ${(() => {
1166
+ const themeColor = subThemeManager.darkColor;
1167
+ const hasThemeColor = themeColor && themeColor !== null;
1168
+ return hasThemeColor ? lightenHex(themeColor, 0.3) : '#8a85ff';
1169
+ })()};
773
1170
  }
774
1171
  .a-title-${idPanel}:hover {
775
- color: #ffb74d;
1172
+ color: ${(() => {
1173
+ const themeColor = subThemeManager.darkColor;
1174
+ const hasThemeColor = themeColor && themeColor !== null;
1175
+ return hasThemeColor ? lightenHex(themeColor, 0.5) : '#ffb74d';
1176
+ })()};
776
1177
  }
777
1178
  .${idPanel}-row-pin-value {
778
1179
  font-size: 20px;
@@ -786,6 +1187,10 @@ function getDarkStyles(idPanel, scrollClassContainer) {
786
1187
  color: #ffffff !important;
787
1188
  font-size: 17px !important;
788
1189
  }
1190
+ .panel-visibility-icon .fa-globe,
1191
+ .panel-visibility-icon .fa-lock {
1192
+ color: #999;
1193
+ }
789
1194
  `;
790
1195
  }
791
1196