@underpostnet/underpost 2.98.0 → 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-setup.sh +12 -39
- package/src/api/document/document.service.js +1 -1
- 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/Content.js +50 -2
- package/src/client/components/core/Panel.js +91 -22
- package/src/client/components/core/PanelForm.js +1 -0
- package/src/client/components/core/Translate.js +8 -0
- package/src/client/services/default/default.management.js +324 -136
- 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
package/src/cli/run.js
CHANGED
|
@@ -146,111 +146,6 @@ class UnderpostRun {
|
|
|
146
146
|
* @memberof UnderpostRun
|
|
147
147
|
*/
|
|
148
148
|
static RUNNERS = {
|
|
149
|
-
/**
|
|
150
|
-
* @method spark-template
|
|
151
|
-
* @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
|
|
152
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
153
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
154
|
-
* @memberof UnderpostRun
|
|
155
|
-
*/
|
|
156
|
-
'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
157
|
-
const dir = '/home/dd/spark-template';
|
|
158
|
-
shellExec(`sudo rm -rf ${dir}`);
|
|
159
|
-
shellCd('/home/dd');
|
|
160
|
-
|
|
161
|
-
// pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
|
|
162
|
-
// await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
163
|
-
shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
|
|
164
|
-
|
|
165
|
-
shellCd(dir);
|
|
166
|
-
|
|
167
|
-
shellExec(`git init && git add . && git commit -m "Base implementation"`);
|
|
168
|
-
shellExec(`chmod +x ./replace_params.sh`);
|
|
169
|
-
shellExec(`chmod +x ./build.sh`);
|
|
170
|
-
|
|
171
|
-
shellExec(`./replace_params.sh`);
|
|
172
|
-
shellExec(`./build.sh`);
|
|
173
|
-
|
|
174
|
-
shellCd('/home/dd/engine');
|
|
175
|
-
},
|
|
176
|
-
/**
|
|
177
|
-
* @method rmi
|
|
178
|
-
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
179
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
180
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
181
|
-
* @memberof UnderpostRun
|
|
182
|
-
*/
|
|
183
|
-
rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
184
|
-
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
185
|
-
},
|
|
186
|
-
/**
|
|
187
|
-
* @method kill
|
|
188
|
-
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
189
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
190
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
191
|
-
* @memberof UnderpostRun
|
|
192
|
-
*/
|
|
193
|
-
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
194
|
-
if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
|
|
195
|
-
for (const _path of path.split(',')) {
|
|
196
|
-
if (_path.split('+')[1]) {
|
|
197
|
-
let [port, sumPortOffSet] = _path.split('+');
|
|
198
|
-
port = parseInt(port);
|
|
199
|
-
sumPortOffSet = parseInt(sumPortOffSet);
|
|
200
|
-
for (const sumPort of range(0, sumPortOffSet))
|
|
201
|
-
shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
|
|
202
|
-
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
/**
|
|
206
|
-
* @method secret
|
|
207
|
-
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
208
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
209
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
210
|
-
* @memberof UnderpostRun
|
|
211
|
-
*/
|
|
212
|
-
secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
213
|
-
shellExec(
|
|
214
|
-
`underpost secret underpost --create-from-file ${
|
|
215
|
-
path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`
|
|
216
|
-
}`,
|
|
217
|
-
);
|
|
218
|
-
},
|
|
219
|
-
/**
|
|
220
|
-
* @method underpost-config
|
|
221
|
-
* @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
222
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
223
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
224
|
-
* @memberof UnderpostRun
|
|
225
|
-
*/
|
|
226
|
-
'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
227
|
-
UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
|
|
228
|
-
},
|
|
229
|
-
/**
|
|
230
|
-
* @method gpu-env
|
|
231
|
-
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
232
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
233
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
234
|
-
* @memberof UnderpostRun
|
|
235
|
-
*/
|
|
236
|
-
'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
237
|
-
shellExec(
|
|
238
|
-
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
|
|
239
|
-
);
|
|
240
|
-
},
|
|
241
|
-
/**
|
|
242
|
-
* @method tf-gpu-test
|
|
243
|
-
* @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
|
|
244
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
245
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
246
|
-
* @memberof UnderpostRun
|
|
247
|
-
*/
|
|
248
|
-
'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
249
|
-
const { underpostRoot, namespace } = options;
|
|
250
|
-
shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
|
|
251
|
-
shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
|
|
252
|
-
shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
|
|
253
|
-
},
|
|
254
149
|
/**
|
|
255
150
|
* @method dev-cluster
|
|
256
151
|
* @description Resets and deploys a full development cluster including MongoDB, Valkey, exposes services, and updates `/etc/hosts` for local access.
|
|
@@ -899,7 +794,7 @@ EOF
|
|
|
899
794
|
'host-update': async (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
900
795
|
// const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
901
796
|
shellExec(`chmod +x ${options.underpostRoot}/scripts/rocky-setup.sh`);
|
|
902
|
-
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh
|
|
797
|
+
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
903
798
|
},
|
|
904
799
|
|
|
905
800
|
/**
|
|
@@ -1600,6 +1495,113 @@ EOF
|
|
|
1600
1495
|
],
|
|
1601
1496
|
});
|
|
1602
1497
|
},
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* @method spark-template
|
|
1501
|
+
* @description Creates a new Spark template project using `sbt new` in `/home/dd/spark-template`, initializes a Git repository, and runs `replace_params.sh` and `build.sh`.
|
|
1502
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1503
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1504
|
+
* @memberof UnderpostRun
|
|
1505
|
+
*/
|
|
1506
|
+
'spark-template': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1507
|
+
const dir = '/home/dd/spark-template';
|
|
1508
|
+
shellExec(`sudo rm -rf ${dir}`);
|
|
1509
|
+
shellCd('/home/dd');
|
|
1510
|
+
|
|
1511
|
+
// pbcopy(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8`);
|
|
1512
|
+
// await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
1513
|
+
shellExec(`cd /home/dd && sbt new ${process.env.GITHUB_USERNAME}/spark-template.g8 '--name=spark-template'`);
|
|
1514
|
+
|
|
1515
|
+
shellCd(dir);
|
|
1516
|
+
|
|
1517
|
+
shellExec(`git init && git add . && git commit -m "Base implementation"`);
|
|
1518
|
+
shellExec(`chmod +x ./replace_params.sh`);
|
|
1519
|
+
shellExec(`chmod +x ./build.sh`);
|
|
1520
|
+
|
|
1521
|
+
shellExec(`./replace_params.sh`);
|
|
1522
|
+
shellExec(`./build.sh`);
|
|
1523
|
+
|
|
1524
|
+
shellCd('/home/dd/engine');
|
|
1525
|
+
},
|
|
1526
|
+
/**
|
|
1527
|
+
* @method rmi
|
|
1528
|
+
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
1529
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1530
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1531
|
+
* @memberof UnderpostRun
|
|
1532
|
+
*/
|
|
1533
|
+
rmi: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1534
|
+
shellExec(`podman rmi $(podman images -qa) --force`);
|
|
1535
|
+
},
|
|
1536
|
+
/**
|
|
1537
|
+
* @method kill
|
|
1538
|
+
* @description Kills processes listening on the specified port(s). If the `path` contains a `+`, it treats it as a range of ports to kill.
|
|
1539
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the port number).
|
|
1540
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1541
|
+
* @memberof UnderpostRun
|
|
1542
|
+
*/
|
|
1543
|
+
kill: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1544
|
+
if (options.pid) return shellExec(`sudo kill -9 ${options.pid}`);
|
|
1545
|
+
for (const _path of path.split(',')) {
|
|
1546
|
+
if (_path.split('+')[1]) {
|
|
1547
|
+
let [port, sumPortOffSet] = _path.split('+');
|
|
1548
|
+
port = parseInt(port);
|
|
1549
|
+
sumPortOffSet = parseInt(sumPortOffSet);
|
|
1550
|
+
for (const sumPort of range(0, sumPortOffSet))
|
|
1551
|
+
shellExec(`sudo kill -9 $(lsof -t -i:${parseInt(port) + parseInt(sumPort)})`);
|
|
1552
|
+
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
1553
|
+
}
|
|
1554
|
+
},
|
|
1555
|
+
/**
|
|
1556
|
+
* @method secret
|
|
1557
|
+
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
1558
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
1559
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1560
|
+
* @memberof UnderpostRun
|
|
1561
|
+
*/
|
|
1562
|
+
secret: (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1563
|
+
const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
|
|
1564
|
+
const command = options.dev
|
|
1565
|
+
? `node bin secret underpost --create-from-file ${secretPath}`
|
|
1566
|
+
: `underpost secret underpost --create-from-file ${secretPath}`;
|
|
1567
|
+
shellExec(command);
|
|
1568
|
+
},
|
|
1569
|
+
/**
|
|
1570
|
+
* @method underpost-config
|
|
1571
|
+
* @description Calls `UnderpostDeploy.API.configMap` to create a Kubernetes ConfigMap, defaulting to the 'production' environment.
|
|
1572
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional configuration name/environment).
|
|
1573
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1574
|
+
* @memberof UnderpostRun
|
|
1575
|
+
*/
|
|
1576
|
+
'underpost-config': (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1577
|
+
UnderpostDeploy.API.configMap(path ? path : 'production', options.namespace);
|
|
1578
|
+
},
|
|
1579
|
+
/**
|
|
1580
|
+
* @method gpu-env
|
|
1581
|
+
* @description Sets up a dedicated GPU development environment cluster, resetting and then setting up the cluster with `--dedicated-gpu` and monitoring the pods.
|
|
1582
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1583
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1584
|
+
* @memberof UnderpostRun
|
|
1585
|
+
*/
|
|
1586
|
+
'gpu-env': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1587
|
+
shellExec(
|
|
1588
|
+
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu --kubeadm && kubectl get pods --all-namespaces -o wide -w`,
|
|
1589
|
+
);
|
|
1590
|
+
},
|
|
1591
|
+
/**
|
|
1592
|
+
* @method tf-gpu-test
|
|
1593
|
+
* @description Deletes existing `tf-gpu-test-script` ConfigMap and `tf-gpu-test-pod`, and applies the test manifest from `manifests/deployment/tensorflow/tf-gpu-test.yaml`.
|
|
1594
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
1595
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1596
|
+
* @memberof UnderpostRun
|
|
1597
|
+
*/
|
|
1598
|
+
'tf-gpu-test': (path, options = UnderpostRun.DEFAULT_OPTION) => {
|
|
1599
|
+
const { underpostRoot, namespace } = options;
|
|
1600
|
+
shellExec(`kubectl delete configmap tf-gpu-test-script -n ${namespace} --ignore-not-found`);
|
|
1601
|
+
shellExec(`kubectl delete pod tf-gpu-test-pod -n ${namespace} --ignore-not-found`);
|
|
1602
|
+
shellExec(`kubectl apply -f ${underpostRoot}/manifests/deployment/tensorflow/tf-gpu-test.yaml -n ${namespace}`);
|
|
1603
|
+
},
|
|
1604
|
+
|
|
1603
1605
|
/**
|
|
1604
1606
|
* @method deploy-job
|
|
1605
1607
|
* @description Creates and applies a custom Kubernetes Pod manifest (Job) for running arbitrary commands inside a container image (defaulting to a TensorFlow/NVIDIA image).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { marked } from 'marked';
|
|
2
2
|
import { FileService } from '../../services/file/file.service.js';
|
|
3
|
-
import { append, getBlobFromUint8ArrayFile, getRawContentFile, htmls, s } from './VanillaJs.js';
|
|
3
|
+
import { append, getBlobFromUint8ArrayFile, getRawContentFile, htmls, s, sa } from './VanillaJs.js';
|
|
4
4
|
import { s4 } from './CommonJs.js';
|
|
5
5
|
import { Translate } from './Translate.js';
|
|
6
6
|
import { Modal, renderViewTitle } from './Modal.js';
|
|
@@ -12,6 +12,49 @@ import { getQueryParams } from './Router.js';
|
|
|
12
12
|
|
|
13
13
|
const logger = loggerFactory(import.meta);
|
|
14
14
|
|
|
15
|
+
const attachMarkdownLinkHandlers = (containerSelector) => {
|
|
16
|
+
const links = sa(`${containerSelector} a[href]`);
|
|
17
|
+
links.forEach((link) => {
|
|
18
|
+
link.addEventListener('click', async (e) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
const href = link.getAttribute('href');
|
|
21
|
+
|
|
22
|
+
// Check if link is external
|
|
23
|
+
const isExternal = href.startsWith('http://') || href.startsWith('https://');
|
|
24
|
+
|
|
25
|
+
if (isExternal) {
|
|
26
|
+
// Show warning modal for external links
|
|
27
|
+
const result = await Modal.RenderConfirm({
|
|
28
|
+
id: `external-link-${s4()}`,
|
|
29
|
+
html: async () => html`
|
|
30
|
+
<div class="in section-mp" style="text-align: center; padding: 20px;">
|
|
31
|
+
<p>${Translate.Render('external-link-warning')}</p>
|
|
32
|
+
<p style="word-break: break-all; margin-top: 10px;"><strong>${href}</strong></p>
|
|
33
|
+
</div>
|
|
34
|
+
`,
|
|
35
|
+
icon: html`<i class="fas fa-external-link-alt"></i>`,
|
|
36
|
+
style: {
|
|
37
|
+
width: '350px',
|
|
38
|
+
height: '500px',
|
|
39
|
+
overflow: 'auto',
|
|
40
|
+
'z-index': '11',
|
|
41
|
+
resize: 'none',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Only open link if user confirmed (not cancelled or closed)
|
|
46
|
+
if (result && result.status === 'confirm') {
|
|
47
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
48
|
+
}
|
|
49
|
+
// If cancelled, do nothing - don't navigate
|
|
50
|
+
} else {
|
|
51
|
+
// Internal link - navigate normally
|
|
52
|
+
window.location.href = href;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
15
58
|
const Content = {
|
|
16
59
|
Render: async function (options = { idModal: '' }) {
|
|
17
60
|
const { idModal } = options;
|
|
@@ -239,6 +282,11 @@ ${JSON.stringify(JSON.parse(content), null, 4)}</pre
|
|
|
239
282
|
|
|
240
283
|
if (options.raw) return render;
|
|
241
284
|
append(container, render);
|
|
285
|
+
|
|
286
|
+
// Scrape and handle markdown links after DOM insertion
|
|
287
|
+
if (ext === 'md') {
|
|
288
|
+
attachMarkdownLinkHandlers(container);
|
|
289
|
+
}
|
|
242
290
|
},
|
|
243
291
|
|
|
244
292
|
/**
|
|
@@ -274,4 +322,4 @@ ${JSON.stringify(JSON.parse(content), null, 4)}</pre
|
|
|
274
322
|
},
|
|
275
323
|
};
|
|
276
324
|
|
|
277
|
-
export { Content };
|
|
325
|
+
export { Content, attachMarkdownLinkHandlers };
|
|
@@ -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
|
-
|
|
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>`;
|
|
@@ -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);
|
|
@@ -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
|
|