@underpostnet/underpost 2.98.0 → 2.98.3

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.
@@ -13,7 +13,7 @@ import { ToggleSwitch } from './ToggleSwitch.js';
13
13
  import { RichText } from './RichText.js';
14
14
  import { loggerFactory } from './Logger.js';
15
15
  import { Badge } from './Badge.js';
16
- import { Content } from './Content.js';
16
+ import { Content, attachMarkdownLinkHandlers } from './Content.js';
17
17
  import { DocumentService } from '../../services/document/document.service.js';
18
18
  import { NotificationManager } from './NotificationManager.js';
19
19
  import { getApiBaseUrl } from '../../services/core/core.service.js';
@@ -37,6 +37,7 @@ const Panel = {
37
37
  onClick: () => {},
38
38
  share: {
39
39
  copyLink: false,
40
+ copySourceMd: false,
40
41
  },
41
42
  showCreatorProfile: false,
42
43
  },
@@ -152,6 +153,51 @@ const Panel = {
152
153
  }
153
154
  });
154
155
  }
156
+ if (options.share && options.share.copySourceMd) {
157
+ EventsUI.onClick(
158
+ `.${idPanel}-btn-copy-source-md-${id}`,
159
+ async (e) => {
160
+ try {
161
+ const filesData = options.filesData();
162
+ const foundFiles = filesData.find((d) => String(d._id || d.id) === String(obj._id || obj.id));
163
+
164
+ if (foundFiles && foundFiles.mdFileId && foundFiles.mdFileId.mdPlain) {
165
+ await copyData(foundFiles.mdFileId.mdPlain);
166
+ await NotificationManager.Push({
167
+ status: 'success',
168
+ html: html`<div>${Translate.Render('markdown-source-copied')}</div>`,
169
+ });
170
+ } else {
171
+ await NotificationManager.Push({
172
+ status: 'warning',
173
+ html: html`<div>No markdown source available</div>`,
174
+ });
175
+ }
176
+ } catch (error) {
177
+ logger.error('Error copying markdown source:', error);
178
+ await NotificationManager.Push({
179
+ status: 'error',
180
+ html: html`<div>${Translate.Render('error-copying-markdown')}</div>`,
181
+ });
182
+ }
183
+ },
184
+ { context: 'modal' },
185
+ );
186
+
187
+ // Add tooltip hover effect
188
+ setTimeout(() => {
189
+ const btn = s(`.${idPanel}-btn-copy-source-md-${id}`);
190
+ const tooltip = s(`.${idPanel}-source-md-tooltip-${id}`);
191
+ if (btn && tooltip) {
192
+ btn.addEventListener('mouseenter', () => {
193
+ tooltip.style.opacity = '1';
194
+ });
195
+ btn.addEventListener('mouseleave', () => {
196
+ tooltip.style.opacity = '0';
197
+ });
198
+ }
199
+ });
200
+ }
155
201
  EventsUI.onClick(
156
202
  `.${idPanel}-btn-delete-${id}`,
157
203
  async (e) => {
@@ -514,30 +560,50 @@ const Panel = {
514
560
  </div>
515
561
  </div>
516
562
  </div>
517
- ${options.share && options.share.copyLink
563
+ ${options.share && (options.share.copyLink || options.share.copySourceMd)
518
564
  ? html`<div
519
565
  class="${idPanel}-share-btn-container ${idPanel}-share-btn-container-${id}"
520
- style="position: absolute; bottom: 8px; right: 8px; z-index: 2;"
566
+ style="position: absolute; bottom: 8px; right: 8px; z-index: 2; display: flex; gap: 8px;"
521
567
  >
522
- <button
523
- class="btn-icon ${idPanel}-btn-copy-share-${id}"
524
- 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;"
525
- >
526
- <i class="fas fa-link" style="font-size: 20px;"></i>
527
- ${obj.totalCopyShareLinkCount && obj.totalCopyShareLinkCount > 0
528
- ? html`<span
529
- class="${idPanel}-share-count-${id}"
530
- 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;"
531
- >${obj.totalCopyShareLinkCount}</span
532
- >`
533
- : ''}
534
- </button>
535
- <div
536
- class="${idPanel}-share-tooltip-${id}"
537
- 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;"
538
- >
539
- ${Translate.Render('copy-share-link')}
540
- </div>
568
+ ${options.share.copyLink
569
+ ? html`<div style="position: relative;">
570
+ <button
571
+ class="btn-icon ${idPanel}-btn-copy-share-${id}"
572
+ 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;"
573
+ >
574
+ <i class="fas fa-link" style="font-size: 20px;"></i>
575
+ ${obj.totalCopyShareLinkCount && obj.totalCopyShareLinkCount > 0
576
+ ? html`<span
577
+ class="${idPanel}-share-count-${id}"
578
+ 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;"
579
+ >${obj.totalCopyShareLinkCount}</span
580
+ >`
581
+ : ''}
582
+ </button>
583
+ <div
584
+ class="${idPanel}-share-tooltip-${id}"
585
+ 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;"
586
+ >
587
+ ${Translate.Render('copy-share-link')}
588
+ </div>
589
+ </div>`
590
+ : ''}
591
+ ${options.share.copySourceMd
592
+ ? html`<div style="position: relative;">
593
+ <button
594
+ class="btn-icon ${idPanel}-btn-copy-source-md-${id}"
595
+ 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;"
596
+ >
597
+ <i class="fas fa-code" style="font-size: 20px;"></i>
598
+ </button>
599
+ <div
600
+ class="${idPanel}-source-md-tooltip-${id}"
601
+ 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;"
602
+ >
603
+ Copy Source MD
604
+ </div>
605
+ </div>`
606
+ : ''}
541
607
  </div>`
542
608
  : ''}
543
609
  </div>`;
@@ -748,6 +814,8 @@ const Panel = {
748
814
  append(`.${idPanel}-render`, await renderPanel(doc));
749
815
  }
750
816
  } else htmls(`.${idPanel}-render`, await renderPanel({ ...obj, ...documents }));
817
+
818
+ attachMarkdownLinkHandlers(`.${idPanel}-render`);
751
819
  Input.cleanValues(formData);
752
820
  s(`.btn-${idPanel}-close`).click();
753
821
  s(`.${scrollClassContainer}`).scrollTop = 0;
@@ -810,6 +878,7 @@ const Panel = {
810
878
  if (options.on.initAdd) await options.on.initAdd();
811
879
  };
812
880
  if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
881
+ attachMarkdownLinkHandlers(`.${idPanel}-render`);
813
882
  });
814
883
 
815
884
  if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
@@ -92,6 +92,7 @@ const PanelForm = {
92
92
  firsUpdateEvent: async () => {},
93
93
  share: {
94
94
  copyLink: false,
95
+ copySourceMd: false,
95
96
  },
96
97
  showCreatorProfile: false,
97
98
  },
@@ -454,7 +455,9 @@ const PanelForm = {
454
455
 
455
456
  const baseNewDoc = newInstance(data);
456
457
  baseNewDoc.tags = tags.filter((t) => !prefixTags.includes(t));
457
- baseNewDoc.mdFileId = hasMdContent ? marked.parse(data.mdFileId) : null;
458
+ baseNewDoc.mdFileId = hasMdContent
459
+ ? `<div class="markdown-content">${marked.parse(data.mdFileId)}</div>`
460
+ : null;
458
461
  baseNewDoc.userId = Elements.Data.user?.main?.model?.user?._id;
459
462
 
460
463
  // Ensure profileImageId is properly formatted as object with _id property
@@ -676,7 +679,7 @@ const PanelForm = {
676
679
  mdPlain = await blobArray[0].text();
677
680
  // Parse markdown with proper error handling
678
681
  try {
679
- parsedMarkdown = mdPlain ? marked.parse(mdPlain) : '';
682
+ parsedMarkdown = mdPlain ? `<div class="markdown-content">${marked.parse(mdPlain)}</div>` : '';
680
683
  } catch (parseError) {
681
684
  logger.error('Error parsing markdown for document:', documentObject._id, parseError);
682
685
  parsedMarkdown = `<p><strong>Error rendering markdown:</strong> ${parseError.message}</p>`;
@@ -679,6 +679,14 @@ const TranslateCore = {
679
679
  en: 'Public Profile',
680
680
  es: 'Perfil Público',
681
681
  };
682
+ Translate.Data['external-link-warning'] = {
683
+ en: 'You are about to open an external link. Do you want to continue?',
684
+ es: 'Está a punto de abrir un enlace externo. ¿Desea continuar?',
685
+ };
686
+ Translate.Data['markdown-source-copied'] = {
687
+ en: 'Markdown source copied to clipboard',
688
+ es: 'Fuente de Markdown copiada al portapapeles',
689
+ };
682
690
  },
683
691
  };
684
692
 
@@ -174,52 +174,103 @@ const disableOptionsClick = (element, types) => {
174
174
 
175
175
  /**
176
176
  * The function `checkFullScreen` checks if the document is in full screen mode and returns a boolean
177
- * value accordingly.
178
- * @returns The function `checkFullScreen` is returning `true` if `document.fullscreenElement` is
179
- * truthy, otherwise it returns `false`.
177
+ * value accordingly. Checks all vendor-prefixed APIs for maximum compatibility.
178
+ * @returns The function `checkFullScreen` is returning `true` if any fullscreen API indicates
179
+ * fullscreen mode is active, otherwise it returns `false`.
180
180
  * @memberof VanillaJS
181
181
  */
182
182
  const checkFullScreen = () => {
183
- // !(!window.screenTop && !window.screenY) ||
184
- return document.fullscreenElement ? true : false;
183
+ return !!(
184
+ document.fullscreenElement ||
185
+ document.webkitFullscreenElement ||
186
+ document.mozFullScreenElement ||
187
+ document.msFullscreenElement ||
188
+ document.fullscreen ||
189
+ document.webkitIsFullScreen ||
190
+ document.mozFullScreen
191
+ );
185
192
  };
186
193
 
187
194
  /**
188
195
  * The function `fullScreenOut` is used to exit full screen mode in a web browser.
196
+ * Handles all vendor-prefixed APIs and returns a promise for better control flow.
197
+ * @returns {Promise<boolean>} Promise that resolves to true if exit was attempted
189
198
  * @memberof VanillaJS
190
199
  */
191
200
  const fullScreenOut = () => {
192
- if (document.exitFullscreen) {
193
- document.exitFullscreen();
194
- } else if (document.mozCancelFullScreen) {
195
- document.mozCancelFullScreen();
196
- } else if (document.webkitExitFullscreen) {
197
- document.webkitExitFullscreen();
198
- } else if (document.msExitFullscreen) {
199
- window.top.document.msExitFullscreen();
200
- }
201
+ return new Promise((resolve) => {
202
+ if (!checkFullScreen()) {
203
+ resolve(true);
204
+ return;
205
+ }
206
+
207
+ try {
208
+ if (document.exitFullscreen) {
209
+ document
210
+ .exitFullscreen()
211
+ .then(() => resolve(true))
212
+ .catch(() => resolve(false));
213
+ } else if (document.webkitExitFullscreen) {
214
+ document.webkitExitFullscreen();
215
+ setTimeout(() => resolve(true), 100);
216
+ } else if (document.mozCancelFullScreen) {
217
+ document.mozCancelFullScreen();
218
+ setTimeout(() => resolve(true), 100);
219
+ } else if (document.msExitFullscreen) {
220
+ document.msExitFullscreen();
221
+ setTimeout(() => resolve(true), 100);
222
+ } else {
223
+ resolve(false);
224
+ }
225
+ } catch (err) {
226
+ console.warn('Fullscreen exit error:', err);
227
+ resolve(false);
228
+ }
229
+ });
201
230
  };
202
231
 
203
232
  /**
204
233
  * The `fullScreenIn` function is used to request full screen mode in a web browser using different
205
- * vendor-specific methods.
234
+ * vendor-specific methods. Returns a promise for better control flow.
235
+ * @returns {Promise<boolean>} Promise that resolves to true if fullscreen was requested
206
236
  * @memberof VanillaJS
207
237
  */
208
238
  const fullScreenIn = () => {
209
- const elem = document.documentElement;
210
- if (elem.requestFullscreen) {
211
- elem.requestFullscreen();
212
- } else if (elem.mozRequestFullScreen) {
213
- /* Firefox */
214
- elem.mozRequestFullScreen();
215
- } else if (elem.webkitRequestFullscreen) {
216
- /* Chrome, Safari & Opera */
217
- elem.webkitRequestFullscreen();
218
- } else if (elem.msRequestFullscreen) {
219
- /* IE/Edge */
220
- elem = window.top.document.body; //To break out of frame in IE
221
- elem.msRequestFullscreen();
222
- }
239
+ return new Promise((resolve) => {
240
+ if (checkFullScreen()) {
241
+ resolve(true);
242
+ return;
243
+ }
244
+
245
+ const elem = document.documentElement;
246
+
247
+ try {
248
+ if (elem.requestFullscreen) {
249
+ elem
250
+ .requestFullscreen()
251
+ .then(() => resolve(true))
252
+ .catch(() => resolve(false));
253
+ } else if (elem.webkitRequestFullscreen) {
254
+ /* Chrome, Safari & Opera */
255
+ elem.webkitRequestFullscreen();
256
+ setTimeout(() => resolve(true), 100);
257
+ } else if (elem.mozRequestFullScreen) {
258
+ /* Firefox */
259
+ elem.mozRequestFullScreen();
260
+ setTimeout(() => resolve(true), 100);
261
+ } else if (elem.msRequestFullscreen) {
262
+ /* IE/Edge */
263
+ const msElem = window.top.document.body;
264
+ msElem.msRequestFullscreen();
265
+ setTimeout(() => resolve(true), 100);
266
+ } else {
267
+ resolve(false);
268
+ }
269
+ } catch (err) {
270
+ console.warn('Fullscreen request error:', err);
271
+ resolve(false);
272
+ }
273
+ });
223
274
  };
224
275
 
225
276
  /**