@underpostnet/underpost 2.97.5 → 2.98.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.
- package/.vscode/settings.json +7 -8
- package/README.md +2 -2
- package/bin/build.js +21 -5
- package/bin/deploy.js +1 -0
- package/bin/file.js +2 -1
- package/bin/util.js +0 -17
- package/cli.md +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +2 -4
- package/scripts/rocky-pwa.sh +200 -0
- package/scripts/rocky-setup.sh +12 -39
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.service.js +88 -98
- package/src/cli/cluster.js +5 -9
- package/src/cli/repository.js +9 -9
- package/src/cli/run.js +108 -106
- package/src/client/components/core/Auth.js +2 -0
- package/src/client/components/core/Content.js +52 -4
- package/src/client/components/core/Css.js +30 -0
- package/src/client/components/core/FileExplorer.js +699 -42
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Panel.js +93 -23
- package/src/client/components/core/PanelForm.js +1 -0
- package/src/client/components/core/Responsive.js +15 -7
- package/src/client/components/core/SearchBox.js +0 -110
- package/src/client/components/core/Translate.js +58 -0
- package/src/client/services/default/default.management.js +327 -148
- package/src/client/sw/default.sw.js +107 -184
- package/src/index.js +58 -20
- package/src/client/components/core/ObjectLayerEngine.js +0 -1520
- package/src/client/components/core/ObjectLayerEngineModal.js +0 -1245
- package/src/client/components/core/ObjectLayerEngineViewer.js +0 -880
- package/src/server/object-layer.js +0 -335
|
@@ -43,7 +43,9 @@ const fileFormDataFactory = (e, extensions) => {
|
|
|
43
43
|
logger.error('Invalid file extension', e.target.files[keyFile]);
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
|
-
form.append(e.target.files[keyFile].name, e.target.files[keyFile]);
|
|
46
|
+
// form.append(e.target.files[keyFile].name, e.target.files[keyFile]);
|
|
47
|
+
// Use standard 'file' field name for all files - server expects this format
|
|
48
|
+
form.append('file', e.target.files[keyFile]);
|
|
47
49
|
}
|
|
48
50
|
return form;
|
|
49
51
|
};
|
|
@@ -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) => {
|
|
@@ -348,7 +394,7 @@ const Panel = {
|
|
|
348
394
|
? 'rgba(255,255,255,0.9)'
|
|
349
395
|
: 'rgba(0,0,0,0.85)'}; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
|
350
396
|
>
|
|
351
|
-
${obj.userInfo.username ||
|
|
397
|
+
${obj.userInfo.username || 'Unknown'}
|
|
352
398
|
</a>
|
|
353
399
|
<span
|
|
354
400
|
style="font-size: 11px; color: ${darkTheme
|
|
@@ -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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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>`;
|
|
@@ -644,6 +710,7 @@ const Panel = {
|
|
|
644
710
|
break;
|
|
645
711
|
case 'file':
|
|
646
712
|
setTimeout(() => {
|
|
713
|
+
if (!s(`.${modelData.id}`)) return;
|
|
647
714
|
s(`.${modelData.id}`).fileNameInputExtDefaultContent = fileNameInputExtDefaultContent;
|
|
648
715
|
s(`.${modelData.id}`).onchange = async (e) => {
|
|
649
716
|
if (!Object.keys(e.target.files).length) return;
|
|
@@ -747,6 +814,8 @@ const Panel = {
|
|
|
747
814
|
append(`.${idPanel}-render`, await renderPanel(doc));
|
|
748
815
|
}
|
|
749
816
|
} else htmls(`.${idPanel}-render`, await renderPanel({ ...obj, ...documents }));
|
|
817
|
+
|
|
818
|
+
attachMarkdownLinkHandlers(`.${idPanel}-render`);
|
|
750
819
|
Input.cleanValues(formData);
|
|
751
820
|
s(`.btn-${idPanel}-close`).click();
|
|
752
821
|
s(`.${scrollClassContainer}`).scrollTop = 0;
|
|
@@ -809,6 +878,7 @@ const Panel = {
|
|
|
809
878
|
if (options.on.initAdd) await options.on.initAdd();
|
|
810
879
|
};
|
|
811
880
|
if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
|
|
881
|
+
attachMarkdownLinkHandlers(`.${idPanel}-render`);
|
|
812
882
|
});
|
|
813
883
|
|
|
814
884
|
if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
|
|
@@ -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) => {
|
|
@@ -746,116 +746,6 @@ const SearchBox = {
|
|
|
746
746
|
}, 0);
|
|
747
747
|
},
|
|
748
748
|
|
|
749
|
-
/**
|
|
750
|
-
* Debounce helper for search-while-typing
|
|
751
|
-
*/
|
|
752
|
-
debounce: function (func, wait) {
|
|
753
|
-
let timeout;
|
|
754
|
-
return function executedFunction(...args) {
|
|
755
|
-
const later = () => {
|
|
756
|
-
clearTimeout(timeout);
|
|
757
|
-
func(...args);
|
|
758
|
-
};
|
|
759
|
-
clearTimeout(timeout);
|
|
760
|
-
timeout = setTimeout(later, wait);
|
|
761
|
-
};
|
|
762
|
-
},
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Sets up a search input element with automatic search on typing.
|
|
766
|
-
* Attaches debounced input event handler and manages search lifecycle.
|
|
767
|
-
* @memberof SearchBoxClient.SearchBox
|
|
768
|
-
* @param {string} inputId - Input element ID or class name.
|
|
769
|
-
* @param {string} resultsContainerId - Results container element ID or class name.
|
|
770
|
-
* @param {object} [context={}] - Configuration context object.
|
|
771
|
-
* @param {number} [context.debounceTime=300] - Debounce delay in milliseconds.
|
|
772
|
-
* @param {number} [context.minQueryLength=1] - Minimum query length to trigger search.
|
|
773
|
-
* @returns {Function} Cleanup function to remove event listeners.
|
|
774
|
-
*/
|
|
775
|
-
setupSearchInput: function (inputId, resultsContainerId, context = {}) {
|
|
776
|
-
const input = s(`#${inputId}`) || s(`.${inputId}`);
|
|
777
|
-
if (!input) {
|
|
778
|
-
logger.warn(`Input ${inputId} not found`);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
const debounceTime = context.debounceTime || 300;
|
|
783
|
-
|
|
784
|
-
const performSearch = this.debounce(async (query) => {
|
|
785
|
-
const trimmedQuery = query ? query.trim() : '';
|
|
786
|
-
const minLength = context.minQueryLength !== undefined ? context.minQueryLength : 1;
|
|
787
|
-
|
|
788
|
-
// Show recent results when query is empty
|
|
789
|
-
if (trimmedQuery.length === 0) {
|
|
790
|
-
const recentResults = this.RecentResults.getAll();
|
|
791
|
-
this.renderResults(recentResults, resultsContainerId, context);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Support single character searches by default (minQueryLength: 1)
|
|
796
|
-
// Can be configured via context.minQueryLength for different use cases
|
|
797
|
-
if (trimmedQuery.length < minLength) {
|
|
798
|
-
this.renderResults([], resultsContainerId, context);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const results = await this.search(trimmedQuery, context);
|
|
803
|
-
this.renderResults(results, resultsContainerId, context);
|
|
804
|
-
}, debounceTime);
|
|
805
|
-
|
|
806
|
-
// Store the handler reference
|
|
807
|
-
const handlerId = `search-handler-${inputId}`;
|
|
808
|
-
if (this.Data[handlerId]) {
|
|
809
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
this.Data[handlerId] = (e) => {
|
|
813
|
-
performSearch(e.target.value);
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
input.addEventListener('input', this.Data[handlerId]);
|
|
817
|
-
|
|
818
|
-
logger.info(`Setup search input: ${inputId}`);
|
|
819
|
-
|
|
820
|
-
return () => {
|
|
821
|
-
input.removeEventListener('input', this.Data[handlerId]);
|
|
822
|
-
delete this.Data[handlerId];
|
|
823
|
-
};
|
|
824
|
-
},
|
|
825
|
-
|
|
826
|
-
/**
|
|
827
|
-
* Debounces a function call to reduce excessive invocations.
|
|
828
|
-
* Used for search input to prevent searching on every keystroke.
|
|
829
|
-
* @memberof SearchBoxClient.SearchBox
|
|
830
|
-
* @param {Function} func - The function to debounce.
|
|
831
|
-
* @param {number} wait - Delay in milliseconds before invoking the function.
|
|
832
|
-
* @returns {Function} Debounced function that delays invocation.
|
|
833
|
-
*/
|
|
834
|
-
debounce: function (func, wait) {
|
|
835
|
-
let timeout;
|
|
836
|
-
|
|
837
|
-
const later = function (...args) {
|
|
838
|
-
timeout = null;
|
|
839
|
-
func(...args);
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
return function (...args) {
|
|
843
|
-
if (timeout) clearTimeout(timeout);
|
|
844
|
-
timeout = setTimeout(() => later(...args), wait);
|
|
845
|
-
};
|
|
846
|
-
},
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* Clears all registered search providers.
|
|
850
|
-
* Useful for cleanup or resetting search functionality.
|
|
851
|
-
* @memberof SearchBoxClient.SearchBox
|
|
852
|
-
* @returns {void}
|
|
853
|
-
*/
|
|
854
|
-
clearProviders: function () {
|
|
855
|
-
this.providers = [];
|
|
856
|
-
logger.info('Cleared all search providers');
|
|
857
|
-
},
|
|
858
|
-
|
|
859
749
|
/**
|
|
860
750
|
* Gets base CSS styles for SearchBox with theme-aware styling.
|
|
861
751
|
* Uses subThemeManager colors for consistent theming across light and dark modes.
|
|
@@ -180,6 +180,7 @@ const TranslateCore = {
|
|
|
180
180
|
Translate.Data['load'] = { en: 'load', es: 'Cargar' };
|
|
181
181
|
Translate.Data['settings'] = { en: 'settings', es: 'configuraciones' };
|
|
182
182
|
Translate.Data['search'] = { en: 'Search', es: 'Buscar' };
|
|
183
|
+
Translate.Data['filter-by-file-name'] = { en: 'Filter by file name', es: 'Filtrar por nombre de archivo' };
|
|
183
184
|
Translate.Data['view'] = { en: 'view', es: 'ver' };
|
|
184
185
|
Translate.Data['user'] = { en: 'User', es: 'Usuario' };
|
|
185
186
|
Translate.Data['pass'] = { en: 'Password', es: 'Contraseña' };
|
|
@@ -397,6 +398,55 @@ const TranslateCore = {
|
|
|
397
398
|
};
|
|
398
399
|
Translate.Data['resend'] = { en: 'Resend', es: 'Reenviar' };
|
|
399
400
|
Translate.Data['delete-account'] = { en: 'Delete Account', es: 'Borrar cuenta' };
|
|
401
|
+
|
|
402
|
+
Translate.Data['doc-title'] = {
|
|
403
|
+
en: 'Doc Title',
|
|
404
|
+
es: 'Título del documento',
|
|
405
|
+
};
|
|
406
|
+
Translate.Data['md-file-name'] = {
|
|
407
|
+
en: 'MD File Name',
|
|
408
|
+
es: 'Nombre de archivo MD',
|
|
409
|
+
};
|
|
410
|
+
Translate.Data['generic-file-name'] = {
|
|
411
|
+
en: 'Generic File Name',
|
|
412
|
+
es: 'Nombre de archivo genérico',
|
|
413
|
+
};
|
|
414
|
+
Translate.Data['edit-document'] = {
|
|
415
|
+
en: 'Edit Document',
|
|
416
|
+
es: 'Editar documento',
|
|
417
|
+
};
|
|
418
|
+
Translate.Data['location'] = {
|
|
419
|
+
en: 'Location',
|
|
420
|
+
es: 'Ubicación',
|
|
421
|
+
};
|
|
422
|
+
Translate.Data['save'] = {
|
|
423
|
+
en: 'Save',
|
|
424
|
+
es: 'Guardar',
|
|
425
|
+
};
|
|
426
|
+
Translate.Data['success-update-document'] = {
|
|
427
|
+
en: 'Document updated successfully.',
|
|
428
|
+
es: 'Documento actualizado con éxito.',
|
|
429
|
+
};
|
|
430
|
+
Translate.Data['error-update-document'] = {
|
|
431
|
+
en: 'Error updating document.',
|
|
432
|
+
es: 'Error al actualizar el documento.',
|
|
433
|
+
};
|
|
434
|
+
Translate.Data['error-title-required'] = {
|
|
435
|
+
en: 'Title is required.',
|
|
436
|
+
es: 'El título es obligatorio.',
|
|
437
|
+
};
|
|
438
|
+
Translate.Data['editing'] = {
|
|
439
|
+
en: 'Editing',
|
|
440
|
+
es: 'Editando',
|
|
441
|
+
};
|
|
442
|
+
Translate.Data['no-md-file-attached'] = {
|
|
443
|
+
en: 'No markdown file attached to this document',
|
|
444
|
+
es: 'No hay archivo markdown adjunto a este documento',
|
|
445
|
+
};
|
|
446
|
+
Translate.Data['no-generic-file-attached'] = {
|
|
447
|
+
en: 'No generic file attached to this document',
|
|
448
|
+
es: 'No hay archivo genérico adjunto a este documento',
|
|
449
|
+
};
|
|
400
450
|
Translate.Data['success-delete-account'] = {
|
|
401
451
|
en: 'Account deleted successfully.',
|
|
402
452
|
es: 'Cuenta borrada con éxito.',
|
|
@@ -629,6 +679,14 @@ const TranslateCore = {
|
|
|
629
679
|
en: 'Public Profile',
|
|
630
680
|
es: 'Perfil Público',
|
|
631
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
|
+
};
|
|
632
690
|
},
|
|
633
691
|
};
|
|
634
692
|
|