clay-server 5.0.2-beta.1 → 5.1.0-beta.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/lib/project-file-path.js
CHANGED
|
@@ -12,9 +12,8 @@ function inside(base, target) {
|
|
|
12
12
|
return relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// Scope is server-derived for each request.
|
|
16
|
-
//
|
|
17
|
-
// Unmapped multi-user deployments retain the project and symlink boundary.
|
|
15
|
+
// Scope is server-derived for each request. File-viewer operations follow the
|
|
16
|
+
// executing OS identity within the account-derived project boundary, if any.
|
|
18
17
|
function resolveFilePath(cwd, requested, scope) {
|
|
19
18
|
if (typeof requested !== "string" || !requested || requested.indexOf("\0") !== -1) {
|
|
20
19
|
throw failure("INVALID_PATH", "Invalid file path");
|
|
@@ -60,10 +60,22 @@ function attachRequestAccess(ctx) {
|
|
|
60
60
|
return info;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function isSoleAccount(ws) {
|
|
64
|
+
if (!users || typeof users.getAllUsers !== "function") return false;
|
|
65
|
+
try {
|
|
66
|
+
var accounts = users.getAllUsers();
|
|
67
|
+
var user = userFor(ws);
|
|
68
|
+
return !!(user && user.id && Array.isArray(accounts) && accounts.length === 1 &&
|
|
69
|
+
accounts[0] && accounts[0].id === user.id);
|
|
70
|
+
} catch (e) { return false; }
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
function fileScope(ws) {
|
|
64
74
|
if (!canUseFiles(ws)) throw fileFailure("FILE_FORBIDDEN", "File browser access is not permitted");
|
|
65
75
|
var identity = osIdentity(ws);
|
|
66
|
-
|
|
76
|
+
// Recheck the account roster so adding another user restores the boundary
|
|
77
|
+
// for shared installations without OS isolation, including active watches.
|
|
78
|
+
return { identity: identity, projectBound: !identity && isMultiUser() && !isSoleAccount(ws) };
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
return { permitMessage: permitMessage, canAccessProject: canAccessProject, hasPermission: hasPermission,
|
|
@@ -1696,6 +1696,16 @@
|
|
|
1696
1696
|
padding: 0;
|
|
1697
1697
|
}
|
|
1698
1698
|
|
|
1699
|
+
.file-viewer-html-preview {
|
|
1700
|
+
display: block;
|
|
1701
|
+
width: 100%;
|
|
1702
|
+
height: 100%;
|
|
1703
|
+
min-height: 320px;
|
|
1704
|
+
border: 0;
|
|
1705
|
+
background: #fff;
|
|
1706
|
+
color-scheme: light;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1699
1709
|
.file-viewer-body pre {
|
|
1700
1710
|
margin: 0;
|
|
1701
1711
|
padding: 16px 20px;
|
|
@@ -13,6 +13,7 @@ import { initFileViewerTabs, openFileViewerTab, previewFileViewerTab, updateFile
|
|
|
13
13
|
import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-context-menu.js';
|
|
14
14
|
import { registerClayFileLinkOpener } from './clay-file-links.js';
|
|
15
15
|
import { isPlantUmlFile, renderPlantUmlPreview, cancelPlantUmlPreview } from './plantuml-preview.js';
|
|
16
|
+
import { isHtmlFile, renderHtmlPreview } from './html-preview.js';
|
|
16
17
|
|
|
17
18
|
var ctx;
|
|
18
19
|
var showDropHint = function () {};
|
|
@@ -43,7 +44,7 @@ export function initFileBrowser(_ctx) {
|
|
|
43
44
|
onFocus: function(path) {
|
|
44
45
|
claimRightWorkbench("file-viewer");
|
|
45
46
|
ctx.fileViewerEl.classList.remove("hidden");
|
|
46
|
-
if (path !== currentFilePath) requestFileContent(path);
|
|
47
|
+
if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
|
|
47
48
|
},
|
|
48
49
|
onEmpty: function() { teardownFileViewer(); },
|
|
49
50
|
});
|
|
@@ -194,10 +195,14 @@ export function initFileBrowser(_ctx) {
|
|
|
194
195
|
downloadProjectFile(currentFilePath);
|
|
195
196
|
});
|
|
196
197
|
|
|
197
|
-
//
|
|
198
|
+
// File preview/source toggle
|
|
198
199
|
document.getElementById("file-viewer-render").addEventListener("click", function () {
|
|
199
|
-
if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml"))) return;
|
|
200
|
+
if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml") && !store.get("fileViewerIsHtml"))) return;
|
|
200
201
|
isRendered = !isRendered;
|
|
202
|
+
if (store.get("fileViewerIsHtml")) {
|
|
203
|
+
renderHtmlBody();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
201
206
|
if (store.get("fileViewerIsPlantUml")) {
|
|
202
207
|
renderPlantUmlBody();
|
|
203
208
|
return;
|
|
@@ -491,6 +496,8 @@ export function closeFileViewer() {
|
|
|
491
496
|
|
|
492
497
|
function teardownFileViewer() {
|
|
493
498
|
cancelPlantUmlPreview();
|
|
499
|
+
var htmlFrame = document.querySelector(".file-viewer-html-preview");
|
|
500
|
+
if (htmlFrame) htmlFrame.remove();
|
|
494
501
|
if (currentFilePath && isFollowingMarkdown(currentFilePath)) cancelMarkdownFollow();
|
|
495
502
|
sendUnwatch();
|
|
496
503
|
inlineDiffActive = false;
|
|
@@ -528,7 +535,7 @@ export function resetFileBrowser() {
|
|
|
528
535
|
isRendered = false;
|
|
529
536
|
currentIsMarkdown = false;
|
|
530
537
|
currentIsSvg = false;
|
|
531
|
-
store.set({ fileViewerIsPlantUml: false });
|
|
538
|
+
store.set({ fileViewerIsPlantUml: false, fileViewerIsHtml: false });
|
|
532
539
|
cancelPlantUmlPreview();
|
|
533
540
|
historyVisible = false;
|
|
534
541
|
currentHistoryEntries = [];
|
|
@@ -1165,7 +1172,7 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1165
1172
|
currentFilePath = msg.path;
|
|
1166
1173
|
currentIsMarkdown = false;
|
|
1167
1174
|
currentIsSvg = false;
|
|
1168
|
-
store.set({ fileViewerIsPlantUml: false });
|
|
1175
|
+
store.set({ fileViewerIsPlantUml: false, fileViewerIsHtml: false });
|
|
1169
1176
|
cancelPlantUmlPreview();
|
|
1170
1177
|
if (!keepRenderState) isRendered = false;
|
|
1171
1178
|
var requestedExt = msg.path.split(".").pop().toLowerCase();
|
|
@@ -1193,19 +1200,20 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1193
1200
|
lightweightPreview = (msg.size || 0) > FILE_RICH_PREVIEW_MAX_BYTES;
|
|
1194
1201
|
currentIsMarkdown = !lightweightPreview && (ext === "md" || ext === "mdx");
|
|
1195
1202
|
currentIsSvg = !lightweightPreview && ext === "svg";
|
|
1203
|
+
store.set({ fileViewerIsHtml: !lightweightPreview && isHtmlFile(msg.path) });
|
|
1196
1204
|
store.set({ fileViewerIsPlantUml: !lightweightPreview && isPlantUmlFile(msg.path) });
|
|
1197
1205
|
if (lightweightPreview) isRendered = false;
|
|
1198
1206
|
if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
|
|
1199
1207
|
|
|
1200
|
-
if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml")) {
|
|
1208
|
+
if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml") || store.get("fileViewerIsHtml")) {
|
|
1201
1209
|
renderBtn.classList.remove("hidden");
|
|
1202
1210
|
renderBtn.title = currentIsSvg ? "Show SVG source" : "Render markdown";
|
|
1203
|
-
copyBtn.title = store.get("fileViewerIsPlantUml") ? "Copy PlantUML source" : currentIsSvg ? "Copy SVG source" : "Copy Markdown source";
|
|
1211
|
+
copyBtn.title = store.get("fileViewerIsHtml") ? "Copy HTML source" : store.get("fileViewerIsPlantUml") ? "Copy PlantUML source" : currentIsSvg ? "Copy SVG source" : "Copy Markdown source";
|
|
1204
1212
|
} else {
|
|
1205
1213
|
copyBtn.title = "Copy contents";
|
|
1206
1214
|
}
|
|
1207
1215
|
|
|
1208
|
-
// Markdown starts as source; SVG
|
|
1216
|
+
// Markdown starts as source; HTML, SVG and diagrams start as previews.
|
|
1209
1217
|
if (lightweightPreview) {
|
|
1210
1218
|
renderLargeTextFile(bodyEl, msg.content, msg.size);
|
|
1211
1219
|
} else if (currentIsMarkdown) {
|
|
@@ -1214,6 +1222,9 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1214
1222
|
? (previousContent == null ? "" : previousContent)
|
|
1215
1223
|
: null;
|
|
1216
1224
|
renderBody(transitionFrom, refreshSlideIndex);
|
|
1225
|
+
} else if (store.get("fileViewerIsHtml")) {
|
|
1226
|
+
if (!keepRenderState) isRendered = !store.get("pendingFileNavigation");
|
|
1227
|
+
renderHtmlBody();
|
|
1217
1228
|
} else if (store.get("fileViewerIsPlantUml")) {
|
|
1218
1229
|
if (!keepRenderState) isRendered = !store.get("pendingFileNavigation");
|
|
1219
1230
|
renderPlantUmlBody();
|
|
@@ -1283,6 +1294,15 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1283
1294
|
}
|
|
1284
1295
|
}
|
|
1285
1296
|
|
|
1297
|
+
function renderHtmlBody() {
|
|
1298
|
+
var bodyEl = document.getElementById("file-viewer-body");
|
|
1299
|
+
var renderBtn = document.getElementById("file-viewer-render");
|
|
1300
|
+
renderBtn.classList.toggle("active", isRendered);
|
|
1301
|
+
renderBtn.title = isRendered ? "Show HTML source" : "Show HTML preview";
|
|
1302
|
+
if (isRendered) renderHtmlPreview(bodyEl, currentContent, currentFilePath);
|
|
1303
|
+
else renderCodeWithLineNumbers(bodyEl, currentContent, "html");
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1286
1306
|
function renderPlantUmlBody() {
|
|
1287
1307
|
var bodyEl = document.getElementById("file-viewer-body");
|
|
1288
1308
|
var renderBtn = document.getElementById("file-viewer-render");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function isHtmlFile(path) {
|
|
2
|
+
return /\.html?$/i.test(String(path || ""));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function renderHtmlPreview(body, source, path) {
|
|
6
|
+
var frame = document.createElement("iframe");
|
|
7
|
+
frame.className = "file-viewer-html-preview";
|
|
8
|
+
frame.title = "HTML preview of " + path;
|
|
9
|
+
// Scripts can drive the preview, but cannot access Clay's document or cookies.
|
|
10
|
+
frame.setAttribute("sandbox", "allow-scripts");
|
|
11
|
+
frame.setAttribute("referrerpolicy", "no-referrer");
|
|
12
|
+
var doc = new DOMParser().parseFromString(source, "text/html");
|
|
13
|
+
var policy = doc.createElement("meta");
|
|
14
|
+
policy.httpEquiv = "Content-Security-Policy";
|
|
15
|
+
policy.content = "default-src 'none'; script-src 'unsafe-inline' https: http:; style-src 'unsafe-inline' https: http:; img-src data: blob: https: http:; font-src data: https: http:; media-src data: blob: https: http:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'";
|
|
16
|
+
doc.head.prepend(policy);
|
|
17
|
+
frame.srcdoc = "<!doctype html>\n" + doc.documentElement.outerHTML;
|
|
18
|
+
body.replaceChildren(frame);
|
|
19
|
+
}
|
package/package.json
CHANGED