@gitpagedocs/cli 1.1.53 → 1.1.55

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.
@@ -6,28 +6,23 @@ function sanitizeDocsPath(docsPath) {
6
6
 
7
7
  export async function ensureGitHubPagesWorkflow(getCurrentGitBranch, writeText, docsPath = "") {
8
8
  const pathSegment = sanitizeDocsPath(docsPath);
9
- const buildStepsBlock =
10
- pathSegment
11
- ? ` - name: Build static site with target repository path
12
- run: pnpm exec next build frontend && mv frontend/out out
13
- working-directory: .gitpagedocs-runtime
14
- env:
15
- GITHUB_ACTIONS: "true"
16
- GITHUB_REPOSITORY: \${{ github.repository }}
17
- GITPAGEDOCS_PATH: "${pathSegment}"
18
-
19
- - name: Relocate output to custom docs path
20
- run: |
21
- mkdir -p .gitpagedocs-runtime/out_new/${pathSegment}
9
+ const buildStepsBlock =
10
+ pathSegment
11
+ ? ` - name: Build static site with target repository path
12
+ run: pnpm exec next build frontend && mv frontend/out out
13
+ working-directory: .gitpagedocs-runtime
14
+ env:
15
+ GITPAGEDOCS_PATH: "${pathSegment}"
16
+
17
+ - name: Relocate output to custom docs path
18
+ run: |
19
+ mkdir -p .gitpagedocs-runtime/out_new/${pathSegment}
22
20
  mv .gitpagedocs-runtime/out/* .gitpagedocs-runtime/out_new/${pathSegment}/
23
21
  rm -rf .gitpagedocs-runtime/out
24
22
  mv .gitpagedocs-runtime/out_new .gitpagedocs-runtime/out`
25
- : ` - name: Build static site with target repository path
26
- run: pnpm exec next build frontend && mv frontend/out out
27
- working-directory: .gitpagedocs-runtime
28
- env:
29
- GITHUB_ACTIONS: "true"
30
- GITHUB_REPOSITORY: \${{ github.repository }}`;
23
+ : ` - name: Build static site with target repository path
24
+ run: pnpm exec next build frontend && mv frontend/out out
25
+ working-directory: .gitpagedocs-runtime`;
31
26
  const currentBranch = getCurrentGitBranch();
32
27
  const workflowPath = ".github/workflows/gitpagedocs-pages.yml";
33
28
  const workflowContent = `name: Deploy GitPageDocs
@@ -47,9 +42,13 @@ concurrency:
47
42
  cancel-in-progress: true
48
43
 
49
44
  jobs:
50
- build:
51
- runs-on: ubuntu-latest
52
- steps:
45
+ build:
46
+ runs-on: ubuntu-latest
47
+ env:
48
+ GITHUB_ACTIONS: "true"
49
+ GITHUB_REPOSITORY: \${{ github.repository }}
50
+ GITPAGEDOCS_REPOSITORY_SEARCH: "true"
51
+ steps:
53
52
  - name: Checkout target repository
54
53
  uses: actions/checkout@v4
55
54
 
@@ -85,43 +84,9 @@ jobs:
85
84
  run: pnpm install --frozen-lockfile
86
85
  working-directory: .gitpagedocs-runtime
87
86
 
88
- ${buildStepsBlock}
89
-
90
- - name: Force root URL to docs entrypoint
91
- env:
92
- GITHUB_REPOSITORY: \${{ github.repository }}
93
- run: |
94
- node <<'NODE'
95
- const fs = require('fs');
96
- const path = require('path');
97
- const pathSegment = ${JSON.stringify(pathSegment)};
98
- const repoName = (process.env.GITHUB_REPOSITORY || '').split('/')[1] || '';
99
- const isUserPage = repoName.toLowerCase().endsWith('.github.io');
100
- const rootRedirectBase = pathSegment;
101
- const cfgPath = path.join('.gitpagedocs-runtime', 'gitpagedocs', 'config.json');
102
- if (!fs.existsSync(cfgPath)) process.exit(0);
103
- const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
104
- const defaultVersion = cfg?.site?.docsVersion || cfg?.VersionControl?.versions?.[0]?.id || '1.1.0';
105
- const defaultLang = cfg?.site?.defaultLanguage || 'en';
106
- const redirectTargetRoot = rootRedirectBase
107
- ? './' + rootRedirectBase + '/v/' + defaultVersion + '/?lang=' + defaultLang
108
- : './v/' + defaultVersion + '/?lang=' + defaultLang;
109
- const redirectTargetProject = './v/' + defaultVersion + '/?lang=' + defaultLang;
110
- const rootHtml = '<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=' + redirectTargetRoot + '"/><script>window.location.replace("' + redirectTargetRoot + '" + (window.location.search || ""));</script></head><body>Redirecting...</body></html>';
111
- fs.writeFileSync(path.join('.gitpagedocs-runtime', 'out', 'index.html'), rootHtml);
112
- const projectPathSegments = [];
113
- if (repoName && !isUserPage) projectPathSegments.push(repoName);
114
- if (pathSegment) projectPathSegments.push(pathSegment);
115
- if (projectPathSegments.length > 0) {
116
- const projectIndexPath = path.join('.gitpagedocs-runtime', 'out', ...projectPathSegments, 'index.html');
117
- if (fs.existsSync(path.dirname(projectIndexPath))) {
118
- const projectHtml = '<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=' + redirectTargetProject + '"/><script>window.location.replace("' + redirectTargetProject + '" + (window.location.search || ""));</script></head><body>Redirecting...</body></html>';
119
- fs.writeFileSync(projectIndexPath, projectHtml);
120
- }
121
- }
122
- NODE
123
-
124
- - name: Add .nojekyll
87
+ ${buildStepsBlock}
88
+
89
+ - name: Add .nojekyll
125
90
  run: touch .gitpagedocs-runtime/out/.nojekyll 2>/dev/null || mkdir -p .gitpagedocs-runtime/out && touch .gitpagedocs-runtime/out/.nojekyll
126
91
 
127
92
  - name: Upload artifact
package/start.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "child_process";
3
3
 
4
- const env = { ...process.env, GITPAGEDOCS_REPOSITORY_SEARCH: "false" };
4
+ const env = { ...process.env, GITPAGEDOCS_REPOSITORY_SEARCH: "true" };
5
5
  // The Next app lives in frontend/, so `next build` emits to frontend/.next —
6
6
  // point `next start` at the same directory (matches `next build frontend`).
7
7
  const child = spawn("npx", ["next", "start", "frontend"], {
@@ -1,330 +0,0 @@
1
- /** Generate GitHub-style source code viewer HTML from src/, cli/, and root config files */
2
-
3
- import path from "node:path";
4
- import { readFileSync, readdirSync, existsSync } from "node:fs";
5
-
6
- function buildTreeFromKeys(fileKeys) {
7
- const root = {};
8
- for (const key of fileKeys) {
9
- const parts = key.split("/");
10
- let curr = root;
11
- for (let i = 0; i < parts.length; i++) {
12
- const part = parts[i];
13
- const isLast = i === parts.length - 1;
14
- if (!curr[part]) curr[part] = isLast ? { path: key } : {};
15
- if (isLast) curr[part].path = key;
16
- else curr = curr[part];
17
- }
18
- }
19
- return root;
20
- }
21
-
22
- const ROOT_FILES = ["README.md", "package.json", "package-lock.json", "next.config.ts", "tsconfig.json", ".eslintrc.json"];
23
-
24
- function collectSourceFiles(projectRoot) {
25
- const files = {};
26
- function scan(dir, prefix = "") {
27
- if (!existsSync(dir)) return;
28
- const entries = readdirSync(dir, { withFileTypes: true })
29
- .sort((a, b) => a.name.localeCompare(b.name));
30
- for (const e of entries) {
31
- const name = e.name;
32
- if (name === "node_modules" || name.startsWith(".")) continue;
33
- const rel = prefix ? `${prefix}/${name}` : name;
34
- const fullPath = path.join(dir, name);
35
- if (e.isDirectory()) {
36
- scan(fullPath, rel);
37
- } else {
38
- try {
39
- const content = readFileSync(fullPath, "utf-8");
40
- files[rel] = content;
41
- } catch {
42
- /* skip binary or unreadable */
43
- }
44
- }
45
- }
46
- }
47
- for (const f of ROOT_FILES) {
48
- const p = path.join(projectRoot, f);
49
- if (existsSync(p)) {
50
- try {
51
- files[f] = readFileSync(p, "utf-8");
52
- } catch {}
53
- }
54
- }
55
- const srcDir = path.join(projectRoot, "src");
56
- const cliDir = path.join(projectRoot, "cli");
57
- if (existsSync(srcDir)) scan(srcDir, "src");
58
- if (existsSync(cliDir)) scan(cliDir, "cli");
59
- return files;
60
- }
61
-
62
- /** GitHub-like file viewer: breadcrumb bar, file header, table layout, dark theme */
63
- export function generateSourceViewerHtml(root) {
64
- const files = collectSourceFiles(root);
65
- const fileKeys = Object.keys(files).sort();
66
- const tree = buildTreeFromKeys(fileKeys);
67
- const noFilesMsg = "No files found.";
68
- const dataJson = JSON.stringify({ files, fileKeys, tree });
69
- return `<!DOCTYPE html>
70
- <html lang="en">
71
- <head>
72
- <meta charset="UTF-8"/>
73
- <meta name="viewport" content="width=device-width,initial-scale=1"/>
74
- <title>Source code - GitHub</title>
75
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"/>
76
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
77
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
78
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/tsx.min.js"></script>
79
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
80
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js"></script>
81
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js"></script>
82
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/markdown.min.js"></script>
83
- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
84
- <style>
85
- /* GitHub dark theme - variables */
86
- :root{
87
- --github-bg: #0d1117;
88
- --github-bg-secondary: #161b22;
89
- --github-bg-tertiary: #21262d;
90
- --github-border: #30363d;
91
- --github-border-muted: #21262d;
92
- --github-fg: #e6edf3;
93
- --github-fg-muted: #8b949e;
94
- --github-accent: #58a6ff;
95
- --github-accent-soft: #79c0ff;
96
- }
97
- *{box-sizing:border-box;margin:0;padding:0}
98
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;background:var(--github-bg);color:var(--github-fg);min-height:100vh;font-size:14px;line-height:1.5}
99
-
100
- /* Top bar - GitHub style */
101
- .top-bar{background:var(--github-bg-secondary);border-bottom:1px solid var(--github-border-muted);padding:8px 16px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
102
- .top-bar .crumb{color:var(--github-fg-muted);text-decoration:none}
103
- .top-bar .crumb:hover{color:var(--github-accent)}
104
- .top-bar .crumb-sep{color:var(--github-fg-muted);user-select:none}
105
- .top-bar .file-name{color:var(--github-fg);font-weight:600}
106
-
107
- /* Layout */
108
- .layout{display:flex;height:calc(100vh - 45px)}
109
-
110
- /* Sidebar - GitHub file tree */
111
- .sidebar{width:260px;min-width:200px;background:var(--github-bg-secondary);border-right:1px solid var(--github-border-muted);overflow-y:auto;padding:8px 0;flex-shrink:0}
112
- .sidebar .folder,.sidebar .file{padding:4px 12px 4px 8px;font-size:13px;cursor:pointer;display:flex;align-items:center;gap:6px;border-radius:6px;margin:0 8px}
113
- .sidebar .folder:hover,.sidebar .file:hover{background:var(--github-bg-tertiary)}
114
- .sidebar .file{color:var(--github-fg-muted)}
115
- .sidebar .file.active{color:var(--github-accent);background:var(--github-bg-tertiary)}
116
- .sidebar .icon{width:16px;text-align:center;flex-shrink:0}
117
- .sidebar .icon svg{vertical-align:middle}
118
- .sidebar .folder .icon{color:var(--github-accent-soft)}
119
- .sidebar .chevron{width:16px;transition:transform .15s;flex-shrink:0}
120
- .sidebar .folder.collapsed .chevron{transform:rotate(-90deg)}
121
- .sidebar .children{margin-left:4px}
122
- .sidebar .children.hidden{display:none}
123
- .sidebar .search-wrap{padding:8px;border-bottom:1px solid var(--github-border-muted)}
124
- .sidebar .search-input{width:100%;padding:6px 10px;font-size:12px;border:1px solid var(--github-border);border-radius:6px;background:var(--github-bg);color:var(--github-fg);outline:none}
125
- .sidebar .search-input:focus{border-color:var(--github-accent)}
126
- .sidebar .search-input::placeholder{color:var(--github-fg-muted)}
127
- .sidebar .tree-actions{display:flex;gap:4px;padding:4px 8px;border-bottom:1px solid var(--github-border-muted)}
128
- .sidebar .tree-btn{padding:4px 8px;font-size:11px;border:1px solid var(--github-border);border-radius:4px;background:var(--github-bg-tertiary);color:var(--github-fg-muted);cursor:pointer}
129
- .sidebar .tree-btn:hover{color:var(--github-fg);background:var(--github-border)}
130
-
131
- /* Content - GitHub file view */
132
- .content{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--github-bg)}
133
- .content .file-header{background:var(--github-bg-secondary);border-bottom:1px solid var(--github-border-muted);padding:8px 16px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
134
- .content .file-header .file-path{font-weight:600;color:var(--github-fg)}
135
- .content .file-header .copy-btn{padding:4px 12px;font-size:12px;border:1px solid var(--github-border);border-radius:6px;background:var(--github-bg-tertiary);color:var(--github-fg);cursor:pointer}
136
- .content .file-header .copy-btn:hover{background:var(--github-border);border-color:var(--github-fg-muted)}
137
- .content .code-wrap{flex:1;overflow:auto}
138
- .content .code-table{width:100%;border-collapse:collapse;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.45}
139
- .content .code-table td{vertical-align:top;padding:0}
140
- .content .line-nums{padding:16px 0;text-align:right;user-select:none;color:var(--github-fg-muted);background:var(--github-bg-secondary);border-right:1px solid var(--github-border-muted);width:1%;min-width:50px}
141
- .content .line-nums span{display:block;padding:0 16px}
142
- .content .line-code{padding:16px 0;padding-left:16px}
143
- .content .line-code .line{display:block;min-height:1.45em;white-space:pre}
144
- .content .empty{color:var(--github-fg-muted);padding:24px;font-style:italic}
145
- .content .md-preview{padding:24px;line-height:1.6}
146
- .content .md-preview h1,.content .md-preview h2,.content .md-preview h3{margin-top:1em;margin-bottom:0.5em;font-weight:600}
147
- .content .md-preview code{background:var(--github-bg-tertiary);padding:2px 6px;border-radius:4px;font-size:0.9em}
148
- .content .md-preview pre{background:var(--github-bg-tertiary);padding:16px;overflow-x:auto;border-radius:6px;font-size:13px}
149
- .content .btn-group{display:flex;gap:8px;margin-bottom:16px}
150
- .content .btn{padding:6px 12px;border:1px solid var(--github-border);border-radius:6px;background:var(--github-bg-tertiary);color:var(--github-fg);cursor:pointer;font-size:12px}
151
- .content .btn.active{background:var(--github-accent);border-color:var(--github-accent);color:#fff}
152
- .content .btn:hover{background:var(--github-border)}
153
-
154
- @media(max-width:767px){
155
- .layout{flex-direction:column;height:auto;min-height:100vh}
156
- .sidebar{width:100%;max-height:200px;border-right:none;border-bottom:1px solid var(--github-border-muted)}
157
- .content .code-wrap{min-height:400px}
158
- }
159
- </style>
160
- </head>
161
- <body>
162
- <div class="top-bar">
163
- <span class="crumb">source</span>
164
- <span class="crumb-sep">/</span>
165
- <span class="file-name" id="breadcrumbFile">-</span>
166
- </div>
167
- <div class="layout">
168
- <aside class="sidebar">
169
- <div class="search-wrap"><input type="text" class="search-input" id="searchInput" placeholder="Filter files..." autocomplete="off"/></div>
170
- <div class="tree-actions"><button class="tree-btn" id="expandAll">Expand all</button><button class="tree-btn" id="collapseAll">Collapse all</button></div>
171
- <div id="tree"></div>
172
- </aside>
173
- <main class="content">
174
- <div class="file-header" id="fileHeader" style="display:none">
175
- <span class="file-path" id="headerPath"></span>
176
- <button class="copy-btn" id="copyBtn">Copy</button>
177
- </div>
178
- <div class="code-wrap">
179
- <pre class="content-inner" id="viewer"><code class="empty">${fileKeys.length ? "Select a file from the sidebar" : noFilesMsg}</code></pre>
180
- </div>
181
- </main>
182
- </div>
183
- <script type="application/json" id="filesData">${dataJson.replace(/<\/script>/gi, "<\\/script>")}</script>
184
- <script>
185
- (function(){
186
- var el=document.getElementById("filesData");
187
- var raw=el?el.textContent:"{}";
188
- var data=JSON.parse(raw);
189
- var files=data.files||{}, keys=data.fileKeys||[], treeData=data.tree||{};
190
- function esc(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML}
191
- function getLang(p){
192
- if(p.endsWith(".ts")||p.endsWith(".tsx"))return"typescript";
193
- if(p.endsWith(".js")||p.endsWith(".mjs")||p.endsWith(".cjs"))return"javascript";
194
- if(p.endsWith(".json"))return"json";
195
- if(p.endsWith(".css"))return"css";
196
- if(p.endsWith(".md"))return"markdown";
197
- return"";
198
- }
199
- var viewMode="code";
200
- function setViewMode(mode){viewMode=mode}
201
- function selectFile(path){
202
- document.querySelectorAll(".sidebar .file.active").forEach(function(n){n.classList.remove("active")});
203
- document.querySelectorAll(".sidebar .folder.active").forEach(function(n){n.classList.remove("active")});
204
- var node=document.querySelector('.sidebar .file[data-path="'+path.replace(/"/g,'\\"')+'"]');
205
- if(node){node.classList.add("active");node.scrollIntoView({block:"nearest"})}
206
- document.getElementById("breadcrumbFile").textContent=path;
207
- document.getElementById("fileHeader").style.display="block";
208
- document.getElementById("headerPath").textContent=path;
209
- var content=files[path]||"";
210
- var isReadme=path.toLowerCase()==="readme.md";
211
- var viewer=document.getElementById("viewer");
212
- if(isReadme){
213
- var btnHtml='<div class="btn-group"><button class="btn'+(viewMode==='preview'?' active':'')+'" data-mode="preview">Preview</button><button class="btn'+(viewMode==='code'?' active':'')+'" data-mode="code">Code</button></div>';
214
- if(viewMode==="preview"&&typeof marked!=="undefined"){
215
- viewer.innerHTML=btnHtml+'<div class="md-preview">'+marked.parse(content||"")+'</div>';
216
- }else{
217
- var lines=(content||"").split("\\n");
218
- var lineHtml="";
219
- lines.forEach(function(line,i){lineHtml+="<span class=\\"line-num\\">"+(i+1)+"</span><span class=\\"line\\">"+esc(line)+"\\n</span>"});
220
- viewer.innerHTML=btnHtml+"<code>"+lineHtml+"</code>";
221
- if(typeof hljs!=="undefined"){var c=viewer.querySelector("code");if(c){c.className="language-markdown";hljs.highlightElement(c)}}
222
- }
223
- viewer.classList.remove("empty");
224
- viewer.querySelectorAll(".btn").forEach(function(btn){
225
- btn.onclick=function(){setViewMode(btn.dataset.mode);selectFile(path)};
226
- });
227
- }else{
228
- var lines=content.split("\\n");
229
- var nums="",code="";
230
- lines.forEach(function(line,i){nums+="<span>"+(i+1)+"</span>";code+="<span class=\\"line\\">"+esc(line)+"\\n</span>"});
231
- viewer.innerHTML='<table class="code-table"><tr><td class="line-nums">'+nums+'</td><td class="line-code"><code>'+code+'</code></td></tr></table>';
232
- viewer.classList.remove("empty");
233
- var c=viewer.querySelector("code");if(c&&typeof hljs!=="undefined"){c.className="language-"+getLang(path);hljs.highlightElement(c)}
234
- }
235
- window._currentPath=path;window._currentContent=content;
236
- }
237
- document.getElementById("copyBtn").onclick=function(){
238
- var p=window._currentPath,c=window._currentContent;
239
- if(!c)return;
240
- navigator.clipboard&&navigator.clipboard.writeText(c).then(function(){var b=document.getElementById("copyBtn");var t=b.textContent;b.textContent="Copied!";setTimeout(function(){b.textContent=t},1500)});
241
- };
242
- function folderIcon(){return'<svg width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M.513 1.513A1.75 1.75 0 0 1 1.75 1h3.5c.58 0 1.165.224 1.587.624l.357.364A.75.75 0 0 0 8 2h5.25A1.75 1.75 0 0 1 15 3.75v8.5A1.75 1.75 0 0 1 13.25 14H1.75A1.75 1.75 0 0 1 0 12.25v-9.5C0 2.178.78 1.408 1.513 1.513Z"/></svg>'}
243
- function fileIcon(){return'<svg width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Z"/></svg>'}
244
- function chevronDown(){return'<svg width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"/></svg>'}
245
- function renderTree(node, parentEl, indent){
246
- indent=indent||0;
247
- var keys=Object.keys(node).sort();
248
- for(var i=0;i<keys.length;i++){
249
- var k=keys[i];
250
- var v=node[k];
251
- if(k==="path")continue;
252
- if(v&&typeof v==="object"&&v.path){
253
- var div=document.createElement("div");
254
- div.className="file";
255
- div.dataset.path=v.path;
256
- div.style.marginLeft=(indent*12)+"px";
257
- div.innerHTML='<span class="icon">'+fileIcon()+'</span>'+esc(k);
258
- div.onclick=function(p){return function(){selectFile(p)}}(v.path);
259
- parentEl.appendChild(div);
260
- }else if(v&&typeof v==="object"){
261
- var folderDiv=document.createElement("div");
262
- folderDiv.className="folder";
263
- folderDiv.style.marginLeft=(indent*12)+"px";
264
- folderDiv.innerHTML='<span class="chevron">'+chevronDown()+'</span><span class="icon">'+folderIcon()+'</span>'+esc(k);
265
- var children=document.createElement("div");
266
- children.className="children";
267
- folderDiv.onclick=function(e){
268
- if(e.target.closest(".file"))return;
269
- var self=e.currentTarget;
270
- self.classList.toggle("collapsed");
271
- children.classList.toggle("hidden",self.classList.contains("collapsed"));
272
- };
273
- parentEl.appendChild(folderDiv);
274
- parentEl.appendChild(children);
275
- renderTree(v,children,indent+1);
276
- }
277
- }
278
- }
279
- function buildTreeFromKeys(keys){
280
- var root={};
281
- keys.forEach(function(key){
282
- var parts=key.split("/");
283
- var curr=root;
284
- for(var i=0;i<parts.length;i++){
285
- var part=parts[i];
286
- var isLast=i===parts.length-1;
287
- if(!curr[part])curr[part]=isLast?{path:key}:{};
288
- if(isLast)curr[part].path=key;
289
- else curr=curr[part];
290
- }
291
- });
292
- return root;
293
- }
294
- function buildTree(filterQuery){
295
- var treeEl=document.getElementById("tree");
296
- treeEl.innerHTML="";
297
- var filteredKeys=filterQuery?keys.filter(function(k){return k.toLowerCase().indexOf(filterQuery.toLowerCase())>=0;}):keys;
298
- var data=filteredKeys.length?buildTreeFromKeys(filteredKeys):{};
299
- if(Object.keys(data).length>0){
300
- renderTree(data,treeEl,0);
301
- }else{
302
- filteredKeys.sort();
303
- filteredKeys.forEach(function(k){
304
- var div=document.createElement("div");
305
- div.className="file";
306
- div.dataset.path=k;
307
- div.innerHTML='<span class="icon">'+fileIcon()+'</span>'+esc(k.split("/").pop());
308
- div.onclick=function(){selectFile(k)};
309
- treeEl.appendChild(div);
310
- });
311
- }
312
- }
313
- document.getElementById("searchInput").oninput=function(){
314
- var q=this.value.trim();
315
- buildTree(q);
316
- };
317
- document.getElementById("expandAll").onclick=function(){
318
- document.querySelectorAll(".sidebar .folder.collapsed").forEach(function(f){f.classList.remove("collapsed")});
319
- document.querySelectorAll(".sidebar .children.hidden").forEach(function(c){c.classList.remove("hidden")});
320
- };
321
- document.getElementById("collapseAll").onclick=function(){
322
- document.querySelectorAll(".sidebar .folder").forEach(function(f){f.classList.add("collapsed")});
323
- document.querySelectorAll(".sidebar .children").forEach(function(c){c.classList.add("hidden")});
324
- };
325
- buildTree();
326
- })();
327
- </script>
328
- </body>
329
- </html>`;
330
- }