1- /** Generate GitHub-style source code viewer HTML from src/ and README.md */
1+ /** Generate GitHub-style source code viewer HTML from src/, cli/, and root config files */
22
33import path from "node:path" ;
44import { readFileSync , readdirSync , existsSync } from "node:fs" ;
@@ -19,7 +19,9 @@ function buildTreeFromKeys(fileKeys) {
1919 return root ;
2020}
2121
22- function collectSourceFiles ( root ) {
22+ const ROOT_FILES = [ "README.md" , "package.json" , "package-lock.json" , "next.config.ts" , "tsconfig.json" , ".eslintrc.json" ] ;
23+
24+ function collectSourceFiles ( projectRoot ) {
2325 const files = { } ;
2426 function scan ( dir , prefix = "" ) {
2527 if ( ! existsSync ( dir ) ) return ;
@@ -33,21 +35,26 @@ function collectSourceFiles(root) {
3335 scan ( fullPath , rel ) ;
3436 } else {
3537 try {
36- files [ rel ] = readFileSync ( fullPath , "utf-8" ) ;
38+ const content = readFileSync ( fullPath , "utf-8" ) ;
39+ files [ rel ] = content ;
3740 } catch {
38- // skip binary or unreadable files
41+ /* skip binary or unreadable */
3942 }
4043 }
4144 }
4245 }
43- const readmePath = path . join ( root , "README.md" ) ;
44- if ( existsSync ( readmePath ) ) {
45- try {
46- files [ "README.md" ] = readFileSync ( readmePath , "utf-8" ) ;
47- } catch { }
46+ for ( const f of ROOT_FILES ) {
47+ const p = path . join ( projectRoot , f ) ;
48+ if ( existsSync ( p ) ) {
49+ try {
50+ files [ f ] = readFileSync ( p , "utf-8" ) ;
51+ } catch { }
52+ }
4853 }
49- const srcDir = path . join ( root , "src" ) ;
50- scan ( srcDir , "src" ) ;
54+ const srcDir = path . join ( projectRoot , "src" ) ;
55+ const cliDir = path . join ( projectRoot , "cli" ) ;
56+ if ( existsSync ( srcDir ) ) scan ( srcDir , "src" ) ;
57+ if ( existsSync ( cliDir ) ) scan ( cliDir , "cli" ) ;
5158 return files ;
5259}
5360
@@ -112,6 +119,13 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,san
112119.sidebar .folder.collapsed .chevron{transform:rotate(-90deg)}
113120.sidebar .children{margin-left:4px}
114121.sidebar .children.hidden{display:none}
122+ .sidebar .search-wrap{padding:8px;border-bottom:1px solid var(--github-border-muted)}
123+ .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}
124+ .sidebar .search-input:focus{border-color:var(--github-accent)}
125+ .sidebar .search-input::placeholder{color:var(--github-fg-muted)}
126+ .sidebar .tree-actions{display:flex;gap:4px;padding:4px 8px;border-bottom:1px solid var(--github-border-muted)}
127+ .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}
128+ .sidebar .tree-btn:hover{color:var(--github-fg);background:var(--github-border)}
115129
116130/* Content - GitHub file view */
117131.content{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--github-bg)}
@@ -150,7 +164,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,san
150164 <span class="file-name" id="breadcrumbFile">-</span>
151165</div>
152166<div class="layout">
153- <aside class="sidebar" id="tree"></aside>
167+ <aside class="sidebar">
168+ <div class="search-wrap"><input type="text" class="search-input" id="searchInput" placeholder="Filter files..." autocomplete="off"/></div>
169+ <div class="tree-actions"><button class="tree-btn" id="expandAll">Expand all</button><button class="tree-btn" id="collapseAll">Collapse all</button></div>
170+ <div id="tree"></div>
171+ </aside>
154172<main class="content">
155173 <div class="file-header" id="fileHeader" style="display:none">
156174 <span class="file-path" id="headerPath"></span>
@@ -169,12 +187,12 @@ var raw=el?el.textContent:"{}";
169187var data=JSON.parse(raw);
170188var files=data.files||{}, keys=data.fileKeys||[], treeData=data.tree||{};
171189function esc(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML}
172- function getLang(path ){
173- if(path .endsWith(".ts")||path .endsWith(".tsx"))return"typescript";
174- if(path .endsWith(".js")||path .endsWith(".mjs")||path .endsWith(".cjs"))return"javascript";
175- if(path .endsWith(".json"))return"json";
176- if(path .endsWith(".css"))return"css";
177- if(path .endsWith(".md"))return"markdown";
190+ function getLang(p ){
191+ if(p .endsWith(".ts")||p .endsWith(".tsx"))return"typescript";
192+ if(p .endsWith(".js")||p .endsWith(".mjs")||p .endsWith(".cjs"))return"javascript";
193+ if(p .endsWith(".json"))return"json";
194+ if(p .endsWith(".css"))return"css";
195+ if(p .endsWith(".md"))return"markdown";
178196 return"";
179197}
180198var viewMode="code";
@@ -257,22 +275,52 @@ function renderTree(node, parentEl, indent){
257275 }
258276 }
259277}
260- function buildTree(){
261- var tree=document.getElementById("tree");
262- if(Object.keys(treeData).length>0){
263- renderTree(treeData,tree,0);
278+ function buildTreeFromKeys(keys){
279+ var root={};
280+ keys.forEach(function(key){
281+ var parts=key.split("/");
282+ var curr=root;
283+ for(var i=0;i<parts.length;i++){
284+ var part=parts[i];
285+ var isLast=i===parts.length-1;
286+ if(!curr[part])curr[part]=isLast?{path:key}:{};
287+ if(isLast)curr[part].path=key;
288+ else curr=curr[part];
289+ }
290+ });
291+ return root;
292+ }
293+ function buildTree(filterQuery){
294+ var treeEl=document.getElementById("tree");
295+ treeEl.innerHTML="";
296+ var filteredKeys=filterQuery?keys.filter(function(k){return k.toLowerCase().indexOf(filterQuery.toLowerCase())>=0;}):keys;
297+ var data=filteredKeys.length?buildTreeFromKeys(filteredKeys):{};
298+ if(Object.keys(data).length>0){
299+ renderTree(data,treeEl,0);
264300 }else{
265- keys .sort();
266- keys .forEach(function(k){
301+ filteredKeys .sort();
302+ filteredKeys .forEach(function(k){
267303 var div=document.createElement("div");
268304 div.className="file";
269305 div.dataset.path=k;
270- div.innerHTML='<span class="icon">'+fileIcon()+'</span>'+esc(k);
306+ div.innerHTML='<span class="icon">'+fileIcon()+'</span>'+esc(k.split("/").pop() );
271307 div.onclick=function(){selectFile(k)};
272- tree .appendChild(div);
308+ treeEl .appendChild(div);
273309 });
274310 }
275311}
312+ document.getElementById("searchInput").oninput=function(){
313+ var q=this.value.trim();
314+ buildTree(q);
315+ };
316+ document.getElementById("expandAll").onclick=function(){
317+ document.querySelectorAll(".sidebar .folder.collapsed").forEach(function(f){f.classList.remove("collapsed")});
318+ document.querySelectorAll(".sidebar .children.hidden").forEach(function(c){c.classList.remove("hidden")});
319+ };
320+ document.getElementById("collapseAll").onclick=function(){
321+ document.querySelectorAll(".sidebar .folder").forEach(function(f){f.classList.add("collapsed")});
322+ document.querySelectorAll(".sidebar .children").forEach(function(c){c.classList.add("hidden")});
323+ };
276324buildTree();
277325})();
278326</script>
0 commit comments