import { computed, ref, onMounted, watch, onUnmounted, defineComponent, mergeProps, unref, withCtx, openBlock, createBlock, createVNode, toDisplayString, Fragment, renderList, createCommentVNode, createTextVNode, useSSRContext } from "vue"; import { ssrRenderAttrs, ssrRenderList, ssrRenderClass, ssrInterpolate, ssrRenderAttr, ssrRenderComponent } from "vue/server-renderer"; import { OverlayScrollbarsComponent } from "overlayscrollbars-vue"; import { i as useViewerStore, g as getApiBase, w as ws, l as lastViewerPath, s as scrollbarOptions, _ as _export_sfc } from "../main.mjs"; import { ArrowDownTrayIcon, FolderOpenIcon, FolderIcon, DocumentIcon } from "@heroicons/vue/20/solid"; import { useRoute, useRouter } from "vue-router"; import { marked } from "marked"; import "@unhead/vue/server"; import "pinia"; import "@heroicons/vue/24/outline"; import "overlayscrollbars"; function useViewer() { const route = useRoute(); const router = useRouter(); const { onMessage } = ws; const viewerStore = useViewerStore(); const fstoken = computed(() => viewerStore.fstoken); const viewerRoots = computed(() => viewerStore.roots); const token = fstoken; function normalizePath(path) { if (!path) return path; const roots = viewerStore.roots; if (!roots.length) return path; const [prefix, ...rest] = path.split("/"); if (roots.includes(prefix)) return path; const canonical = roots.find((r) => r === `workspace-${prefix}` || r.endsWith(`-${prefix}`)); return canonical ? [canonical, ...rest].join("/") : path; } const currentPath = ref(normalizePath( route.query.path || localStorage.getItem("viewer_last_path") || "" )); const sidebarCollapsed = ref(window.innerWidth < 768); const content = ref(""); const fileType = ref(""); const dirFiles = ref([]); const dirDirs = ref([]); const loading = ref(false); const showLoading = ref(false); let loadingTimer = null; const fetchError = ref(""); const pdfCacheBust = ref(Date.now()); const mdRaw = ref(false); const mdLineCount = computed(() => { if (!content.value) return 0; return content.value.split("\n").length; }); const renderedMd = computed(() => { if (fileType.value !== "md" || !content.value) return ""; return marked.parse(content.value); }); const pdfSrc = computed(() => { if (fileType.value !== "pdf" || !currentPath.value || !token.value) return ""; const base = getBaseUrl(); return `${base}/api/viewer/file?path=${encodeURIComponent(currentPath.value)}&token=${encodeURIComponent(token.value)}&t=${pdfCacheBust.value}`; }); function getBaseUrl() { return getApiBase(); } function extOf(p) { const m = p.match(/\.([^./]+)$/); return m ? m[1].toLowerCase() : ""; } function startLoading(silent) { if (silent) return; loading.value = true; showLoading.value = false; if (loadingTimer) clearTimeout(loadingTimer); loadingTimer = setTimeout(() => { showLoading.value = true; }, 3e3); } function stopLoading() { loading.value = false; showLoading.value = false; if (loadingTimer) { clearTimeout(loadingTimer); loadingTimer = null; } } async function fetchContent(path, silent = false, _retry = false) { startLoading(silent); try { const base = getBaseUrl(); const url = `${base}/api/viewer/file?path=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`; const res = await fetch(url); if (res.status === 401 && !_retry) { viewerStore.invalidate(); await viewerStore.acquire(true); return fetchContent(path, silent, true); } if (!res.ok) { fetchError.value = `${res.status}: ${await res.text()}`; return; } fetchError.value = ""; content.value = await res.text(); } catch (e) { fetchError.value = e.message || "Fetch failed"; } finally { stopLoading(); } } async function openFile(path) { path = normalizePath(path); currentPath.value = path; localStorage.setItem("viewer_last_path", path); lastViewerPath.value = path; if (route.query.path !== path) { router.push({ name: "viewer", query: { path } }); } const ext = extOf(path); if (!path) { fileType.value = "dir"; content.value = ""; fetchError.value = ""; dirDirs.value = viewerRoots.value.length ? viewerRoots.value : ["shared", "workspace-titan"]; dirFiles.value = []; return; } if (!ext) { fileType.value = "dir"; content.value = ""; fetchError.value = ""; startLoading(false); try { const base = getBaseUrl(); const url = `${base}/api/viewer/tree?root=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`; const res = await fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); const data = await res.json(); dirDirs.value = data.dirs || []; dirFiles.value = data.files || []; } catch (e) { dirDirs.value = []; dirFiles.value = []; fetchError.value = e.message || "Failed to load directory"; } finally { stopLoading(); } return; } if (ext === "pdf") { fileType.value = "pdf"; content.value = ""; fetchError.value = ""; pdfCacheBust.value = Date.now(); fetch(`${getBaseUrl()}/api/viewer/file?path=${encodeURIComponent(path)}&token=${encodeURIComponent(token.value)}`, { method: "HEAD" }).catch(() => { }); return; } fileType.value = ext === "md" ? "md" : "text"; fetchError.value = ""; await fetchContent(path, false); } function onCopy(e) { const sel = window.getSelection(); if (!sel || sel.isCollapsed) return; const html = (() => { const div = document.createElement("div"); div.appendChild(sel.getRangeAt(0).cloneContents()); return div.innerHTML; })(); const clean = html.replace(/background(-color)?:[^;"]*(;|(?="))/gi, ""); const plain = sel.toString(); e.clipboardData.setData("text/html", clean); e.clipboardData.setData("text/plain", plain); e.preventDefault(); } async function refreshFile(path) { const ext = extOf(path); if (ext === "pdf") { pdfCacheBust.value = Date.now(); return; } await fetchContent(path, true); } let unsubscribe = null; onMounted(() => { unsubscribe = onMessage((data) => { if (data.type === "viewer_file_changed" && data.path === currentPath.value) { refreshFile(currentPath.value); } if (data.type === "viewer_tree_changed" && data.path === currentPath.value && fileType.value === "dir") { openFile(currentPath.value); } }); viewerStore.acquire(); if (currentPath.value) openFile(currentPath.value); }); watch(() => route.query.path, (newPath) => { if (newPath && newPath !== currentPath.value) { openFile(newPath); } }); onUnmounted(() => { if (unsubscribe) unsubscribe(); }); return { fstoken, viewerRoots, currentPath, sidebarCollapsed, content, fileType, loading, fetchError, mdRaw, mdLineCount, renderedMd, pdfSrc, openFile, onCopy, dirFiles, dirDirs, showLoading }; } const _sfc_main = /* @__PURE__ */ defineComponent({ ...{ name: "ViewerView" }, __name: "ViewerView", __ssrInlineRender: true, setup(__props) { const { fstoken, currentPath, content, fileType, showLoading, fetchError, mdRaw, renderedMd, pdfSrc, openFile, onCopy, dirFiles, dirDirs } = useViewer(); const contentLines = computed(() => content.value.split("\n")); const fileName = computed(() => { if (!currentPath.value) return ""; return currentPath.value.split("/").pop() || ""; }); const downloadUrl = computed(() => { if (!currentPath.value || !fstoken.value) return ""; return `${getApiBase()}/api/viewer/file?path=${encodeURIComponent(currentPath.value)}&token=${encodeURIComponent(fstoken.value)}&dl=1`; }); const breadcrumbs = computed(() => { const crumbs = [{ label: "files", path: "" }]; if (!currentPath.value) return crumbs; const parts = currentPath.value.split("/"); for (let i = 0; i < parts.length; i++) { crumbs.push({ label: parts[i], path: parts.slice(0, i + 1).join("/") }); } return crumbs; }); return (_ctx, _push, _parent, _attrs) => { _push(`
`); if (unref(currentPath) || unref(fileType) === "dir") { _push(`
`); if (unref(fileType) === "md") { _push(`
`); } else { _push(``); } if (unref(fileType) && unref(fileType) !== "dir") { _push(``); _push(ssrRenderComponent(unref(ArrowDownTrayIcon), { class: "w-4 h-4" }, null, _parent)); _push(``); } else { _push(``); } _push(`
`); } else { _push(``); } _push(ssrRenderComponent(unref(OverlayScrollbarsComponent), { class: "content flex-1 min-h-0", options: unref(scrollbarOptions), element: "div" }, { default: withCtx((_, _push2, _parent2, _scopeId) => { if (_push2) { if (!unref(currentPath) && unref(fileType) !== "dir") { _push2(`
`); _push2(ssrRenderComponent(unref(FolderOpenIcon), { class: "w-8 h-8 text-text-dim" }, null, _parent2, _scopeId)); _push2(`

Select a file from the tree

`); } else if (unref(showLoading)) { _push2(`
loading…
`); } else if (unref(fetchError)) { _push2(`
${ssrInterpolate(unref(fetchError))}
`); } else if (unref(fileType) === "dir") { _push2(`
`); ssrRenderList(unref(dirDirs), (d) => { _push2(`
`); _push2(ssrRenderComponent(unref(FolderIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId)); _push2(`${ssrInterpolate(d)}/
`); }); _push2(``); ssrRenderList(unref(dirFiles), (f) => { _push2(`
`); _push2(ssrRenderComponent(unref(DocumentIcon), { class: "w-4 h-4" }, null, _parent2, _scopeId)); _push2(`${ssrInterpolate(f.name)}
`); }); _push2(``); if (!unref(dirDirs).length && !unref(dirFiles).length) { _push2(`

Empty directory

`); } else { _push2(``); } _push2(`
`); } else if (unref(fileType) === "pdf") { _push2(``); } else if (unref(fileType) === "md" && !unref(mdRaw)) { _push2(`
${unref(renderedMd) ?? ""}
`); } else if (unref(fileType) === "md" && unref(mdRaw)) { _push2(`
`);
              ssrRenderList(contentLines.value, (line, i) => {
                _push2(`${ssrInterpolate(i + 1)}${ssrInterpolate(line)}
`);
              });
              _push2(`
`); } else { _push2(`
`);
              ssrRenderList(contentLines.value, (line, i) => {
                _push2(`${ssrInterpolate(i + 1)}${ssrInterpolate(line)}
`);
              });
              _push2(`
`); } } else { return [ !unref(currentPath) && unref(fileType) !== "dir" ? (openBlock(), createBlock("div", { key: 0, class: "viewer-empty" }, [ createVNode(unref(FolderOpenIcon), { class: "w-8 h-8 text-text-dim" }), createVNode("p", null, "Select a file from the tree") ])) : unref(showLoading) ? (openBlock(), createBlock("div", { key: 1, class: "viewer-loading" }, "loading…")) : unref(fetchError) ? (openBlock(), createBlock("div", { key: 2, class: "viewer-error" }, toDisplayString(unref(fetchError)), 1)) : unref(fileType) === "dir" ? (openBlock(), createBlock("div", { key: 3, class: "viewer-dir" }, [ (openBlock(true), createBlock(Fragment, null, renderList(unref(dirDirs), (d) => { return openBlock(), createBlock("div", { key: d, class: "dir-entry dir-entry--dir", onClick: ($event) => unref(openFile)(unref(currentPath) ? unref(currentPath) + "/" + d : d) }, [ createVNode(unref(FolderIcon), { class: "w-4 h-4" }), createVNode("span", null, toDisplayString(d) + "/", 1) ], 8, ["onClick"]); }), 128)), (openBlock(true), createBlock(Fragment, null, renderList(unref(dirFiles), (f) => { return openBlock(), createBlock("div", { key: f.path, class: "dir-entry dir-entry--file", onClick: ($event) => unref(openFile)(f.path) }, [ createVNode(unref(DocumentIcon), { class: "w-4 h-4" }), createVNode("span", null, toDisplayString(f.name), 1) ], 8, ["onClick"]); }), 128)), !unref(dirDirs).length && !unref(dirFiles).length ? (openBlock(), createBlock("div", { key: 0, class: "viewer-empty" }, [ createVNode("p", null, "Empty directory") ])) : createCommentVNode("", true) ])) : unref(fileType) === "pdf" ? (openBlock(), createBlock("iframe", { key: 4, class: "viewer-pdf", src: unref(pdfSrc), frameborder: "0" }, null, 8, ["src"])) : unref(fileType) === "md" && !unref(mdRaw) ? (openBlock(), createBlock("div", { key: 5, class: "viewer-md md-body", innerHTML: unref(renderedMd), onCopy: unref(onCopy) }, null, 40, ["innerHTML", "onCopy"])) : unref(fileType) === "md" && unref(mdRaw) ? (openBlock(), createBlock("div", { key: 6, class: "viewer-raw-md" }, [ createVNode("pre", { class: "viewer-raw-code" }, [ createVNode("code", null, [ (openBlock(true), createBlock(Fragment, null, renderList(contentLines.value, (line, i) => { return openBlock(), createBlock("span", { key: i, class: "code-line" }, [ createVNode("span", { class: "line-num" }, toDisplayString(i + 1), 1), createTextVNode(toDisplayString(line) + "\n", 1) ]); }), 128)) ]) ]) ])) : (openBlock(), createBlock("pre", { key: 7, class: "viewer-raw-code" }, [ createVNode("code", null, [ (openBlock(true), createBlock(Fragment, null, renderList(contentLines.value, (line, i) => { return openBlock(), createBlock("span", { key: i, class: "code-line" }, [ createVNode("span", { class: "line-num" }, toDisplayString(i + 1), 1), createTextVNode(toDisplayString(line) + "\n", 1) ]); }), 128)) ]) ])) ]; } }), _: 1 }, _parent)); _push(`
`); }; } }); const _sfc_setup = _sfc_main.setup; _sfc_main.setup = (props, ctx) => { const ssrContext = useSSRContext(); (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/views/ViewerView.vue"); return _sfc_setup ? _sfc_setup(props, ctx) : void 0; }; const ViewerView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6b5cb038"]]); export { ViewerView as default };