refactor: remove dead code _render_file_viewer from board.py
The standalone file viewer page (with '← Workspace' topbar) was dead code — never called after the rollback. 192 lines removed, zero references left.
This commit is contained in:
@@ -980,198 +980,6 @@ async def view_page(request: Request, page_id: int):
|
||||
return HTMLResponse(content=response, headers={"Cache-Control": "no-store, max-age=0"})
|
||||
|
||||
|
||||
def _render_file_viewer(page: dict, request: Request) -> HTMLResponse:
|
||||
"""Render a file viewer for uploaded files (content_format='file')."""
|
||||
import json
|
||||
page_id = page["id"]
|
||||
title = page.get("title", "File")
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
ws_name = user.get("login", "Bruno") if user else "Bruno"
|
||||
initial = ws_name[0].upper() if ws_name else "B"
|
||||
|
||||
try:
|
||||
meta = json.loads(page.get("content", "{}"))
|
||||
except json.JSONDecodeError:
|
||||
meta = {}
|
||||
file_path = meta.get("file_path", "")
|
||||
mime_type = meta.get("mime_type", "application/octet-stream")
|
||||
size = meta.get("size", 0)
|
||||
|
||||
# Build workspace_id from file_path
|
||||
parts = file_path.split("/")
|
||||
ws_id = ""
|
||||
for p in parts:
|
||||
if p.startswith("workspace_"):
|
||||
ws_id = p.replace("workspace_", "")
|
||||
break
|
||||
filename = parts[-1] if parts else title
|
||||
|
||||
file_url = f"/api/files/{ws_id}/{filename}" if ws_id else ""
|
||||
|
||||
# Determine viewer type
|
||||
is_image = mime_type.startswith("image/")
|
||||
is_pdf = mime_type == "application/pdf"
|
||||
is_text = mime_type.startswith("text/") or mime_type in (
|
||||
"application/json", "application/javascript", "application/xml",
|
||||
"application/x-python", "application/x-sh"
|
||||
)
|
||||
is_code = any(ext in (filename or "").lower() for ext in (
|
||||
".py", ".js", ".ts", ".jsx", ".tsx", ".html", ".htm", ".css",
|
||||
".json", ".xml", ".yaml", ".yml", ".md", ".sql", ".sh", ".bash",
|
||||
".ps1", ".bat", ".cmd", ".rb", ".go", ".rs", ".java", ".c", ".cpp",
|
||||
".h", ".hpp", ".php", ".swift", ".kt", ".scala", ".r", ".toml",
|
||||
".ini", ".cfg", ".conf", ".env", ".dockerfile", ".makefile"
|
||||
))
|
||||
if is_code:
|
||||
is_text = True
|
||||
|
||||
# Determine language class for syntax highlighting
|
||||
ext_map = {
|
||||
".py": "python", ".js": "javascript", ".ts": "typescript",
|
||||
".html": "html", ".css": "css", ".json": "json", ".md": "markdown",
|
||||
".sql": "sql", ".sh": "bash", ".ps1": "powershell", ".rs": "rust",
|
||||
".go": "go", ".java": "java", ".rb": "ruby", ".xml": "xml",
|
||||
".yaml": "yaml", ".yml": "yaml", ".toml": "toml",
|
||||
}
|
||||
lang = "plaintext"
|
||||
for ext, name in ext_map.items():
|
||||
if (filename or "").lower().endswith(ext):
|
||||
lang = name
|
||||
break
|
||||
|
||||
size_str = f"{size:,} bytes" if size < 1024 else f"{size/1024:.1f} KB" if size < 1024*1024 else f"{size/1024/1024:.1f} MB"
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{title} — FlowDeck</title>
|
||||
<style>
|
||||
:root{{--bg:#0d1117;--bg2:#161b22;--text:#c9d1d9;--text2:#8b949e;--accent:#58a6ff;--border:#30363d;--line-num:#484f58;}}
|
||||
*{{margin:0;padding:0;box-sizing:border-box}}
|
||||
body{{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;}}
|
||||
.topbar{{display:flex;align-items:center;gap:10px;padding:8px 16px;background:var(--bg2);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10;}}
|
||||
.topbar a,.topbar button{{color:var(--text2);text-decoration:none;background:var(--bg);border:1px solid var(--border);cursor:pointer;font-size:12px;padding:5px 10px;border-radius:6px;display:inline-flex;align-items:center;gap:4px;transition:all 150ms;}}
|
||||
.topbar a:hover,.topbar button:hover{{background:var(--border);color:var(--text);}}
|
||||
.topbar .title{{font-weight:600;color:var(--text);font-size:14px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}}
|
||||
.topbar .lang-badge{{font-size:11px;padding:2px 8px;background:var(--accent);color:#fff;border-radius:10px;font-weight:500;border:none;}}
|
||||
.meta{{font-size:11px;color:var(--text2);white-space:nowrap;}}
|
||||
.btn-copy{{gap:4px;}}
|
||||
.btn-copy.copied{{background:#238636;border-color:#238636;color:#fff;}}
|
||||
.viewer{{max-width:100%;margin:0 auto;}}
|
||||
.viewer img{{max-width:100%;max-height:85vh;display:block;margin:0 auto;}}
|
||||
.viewer iframe{{width:100%;height:90vh;border:none;}}
|
||||
.code-container{{display:flex;overflow:auto;max-height:90vh;font-family:'Fira Code','Cascadia Code','JetBrains Mono','SF Mono',monospace;font-size:12px;line-height:20px;}}
|
||||
.line-numbers{{flex-shrink:0;padding:12px 10px;text-align:right;color:var(--line-num);background:var(--bg2);border-right:1px solid var(--border);user-select:none;min-width:48px;}}
|
||||
.line-numbers span{{display:block;}}
|
||||
.code-content{{flex:1;padding:12px 16px;overflow:auto;white-space:pre;color:var(--text);background:var(--bg);}}
|
||||
.code-content span{{display:block;}}
|
||||
.code-content .hl-kw{{color:#ff7b72;}}
|
||||
.code-content .hl-str{{color:#a5d6ff;}}
|
||||
.code-content .hl-cmt{{color:#8b949e;font-style:italic;}}
|
||||
.code-content .hl-num{{color:#79c0ff;}}
|
||||
.code-content .hl-fn{{color:#d2a8ff;}}
|
||||
.unsupported{{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 20px;text-align:center;}}
|
||||
.unsupported .icon{{font-size:64px;margin-bottom:16px;}}
|
||||
.unsupported h2{{font-size:20px;margin-bottom:8px;}}
|
||||
.unsupported p{{color:var(--text2);margin-bottom:20px;}}
|
||||
.unsupported a{{display:inline-block;padding:10px 24px;background:var(--accent);color:#fff;text-decoration:none;border-radius:8px;font-weight:500;}}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<a href="/local-workspace">← Workspace</a>
|
||||
<span class="title">{title}</span>
|
||||
<span class="lang-badge">{lang}</span>
|
||||
<span class="meta">{size_str}</span>
|
||||
<button class="btn-copy" onclick="copyCode()" title="Copy to clipboard">📋 Copy</button>
|
||||
<a href="{file_url}" download>⬇ Download</a>
|
||||
</div>
|
||||
<div class="viewer">"""
|
||||
|
||||
if is_image:
|
||||
html += f'<img src="{file_url}" alt="{title}">'
|
||||
elif is_pdf:
|
||||
html += f'<iframe src="{file_url}"></iframe>'
|
||||
elif is_text:
|
||||
html += f'''<div class="code-container">
|
||||
<div class="line-numbers" id="line-numbers"></div>
|
||||
<div class="code-content" id="code-content">Loading...</div>
|
||||
</div>
|
||||
<script>
|
||||
var codeEl=document.getElementById("code-content");
|
||||
var lineEl=document.getElementById("line-numbers");
|
||||
fetch("{file_url}").then(function(r){{return r.text()}}).then(function(t){{
|
||||
var lines=t.split("\\n");
|
||||
if(lines.length>1&&lines[lines.length-1]==="")lines.pop();
|
||||
var numHtml="",codeHtml="";
|
||||
for(var i=0;i<lines.length;i++){{
|
||||
numHtml+="<span>"+(i+1)+"</span>";
|
||||
codeHtml+="<span>"+highlightCode(lines[i])+"</span>";
|
||||
}}
|
||||
lineEl.innerHTML=numHtml;
|
||||
codeEl.innerHTML=codeHtml;
|
||||
}}).catch(function(e){{codeEl.textContent="Error loading file: "+e;}});
|
||||
function escHtml(s){{return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");}}
|
||||
function highlightCode(line){{
|
||||
var s=escHtml(line);
|
||||
var lang="{lang}";
|
||||
// Comments
|
||||
if(lang==="python"||lang==="bash"||lang==="yaml"||lang==="toml"||lang==="powershell"){{
|
||||
s=s.replace(/^(\\s*#.*)$/gm,'<span class="hl-cmt">$1</span>');
|
||||
}}else if(lang==="javascript"||lang==="typescript"||lang==="go"||lang==="rust"||lang==="java"||lang==="sql"){{
|
||||
s=s.replace(/^(\\s*\\/\\/.*)$/gm,'<span class="hl-cmt">$1</span>');
|
||||
s=s.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class="hl-cmt">$1</span>');
|
||||
}}else if(lang==="html"||lang==="xml"){{
|
||||
s=s.replace(/(<!--[\\s\\S]*?-->)/g,'<span class="hl-cmt">$1</span>');
|
||||
}}else if(lang==="css"){{
|
||||
s=s.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class="hl-cmt">$1</span>');
|
||||
}}
|
||||
// Strings
|
||||
s=s.replace(/("(?:[^&]|&(?!quot;))*")/g,'<span class="hl-str">$1</span>');
|
||||
s=s.replace(/('(?:[^&]|&(?!#x27;))*')/g,'<span class="hl-str">$1</span>');
|
||||
s=s.replace(/(`[^`]*`)/g,'<span class="hl-str">$1</span>');
|
||||
// Numbers
|
||||
s=s.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class="hl-num">$1</span>');
|
||||
// Keywords
|
||||
var kw=[];
|
||||
if(lang==="python")kw=["def","class","import","from","return","if","elif","else","for","while","try","except","finally","with","as","yield","raise","pass","break","continue","and","or","not","in","is","None","True","False","lambda","async","await","self"];
|
||||
else if(lang==="javascript"||lang==="typescript")kw=["function","const","let","var","return","if","else","for","while","do","switch","case","break","continue","try","catch","finally","throw","new","class","extends","import","export","default","from","async","await","typeof","instanceof","this","super","null","undefined","true","false","of","in"];
|
||||
else if(lang==="go")kw=["func","package","import","return","if","else","for","range","switch","case","break","continue","defer","go","chan","map","struct","interface","type","var","const","nil","true","false"];
|
||||
else if(lang==="rust")kw=["fn","let","mut","const","struct","enum","impl","trait","pub","use","mod","match","if","else","for","while","loop","return","break","continue","self","super","where","as","in","ref","move","true","false","async","await"];
|
||||
else if(lang==="java")kw=["public","private","protected","class","interface","extends","implements","static","final","void","int","long","double","boolean","char","String","new","return","if","else","for","while","switch","case","break","continue","try","catch","finally","throw","throws","import","package","this","super","null","true","false"];
|
||||
else if(lang==="sql")kw=["SELECT","FROM","WHERE","INSERT","UPDATE","DELETE","CREATE","ALTER","DROP","TABLE","INDEX","VIEW","INTO","VALUES","SET","JOIN","LEFT","RIGHT","INNER","OUTER","ON","AND","OR","NOT","NULL","AS","ORDER","BY","GROUP","HAVING","LIMIT","OFFSET","UNION","ALL","DISTINCT","COUNT","SUM","AVG","MAX","MIN","EXISTS","BETWEEN","LIKE","IN","CASE","WHEN","THEN","ELSE","END","PRIMARY","KEY","FOREIGN","REFERENCES","CONSTRAINT","DEFAULT","CHECK","UNIQUE","CASCADE"];
|
||||
else if(lang==="bash")kw=["if","then","else","elif","fi","for","while","do","done","case","esac","function","return","exit","export","local","readonly","unset","echo","printf","source","exec","trap"];
|
||||
else if(lang==="powershell")kw=["function","param","if","else","elseif","foreach","for","while","do","until","switch","case","break","continue","return","throw","try","catch","finally","trap","begin","process","end","param","Write-Host","Write-Output","Get-Content","Set-Content","Out-File"];
|
||||
if(kw.length>0){{
|
||||
var re=new RegExp("\\\\b("+kw.join("|")+")\\\\b","g");
|
||||
s=s.replace(re,'<span class="hl-kw">$1</span>');
|
||||
}}
|
||||
// Decorators (Python) & function calls
|
||||
if(lang==="python"){{
|
||||
s=s.replace(/(@\\w+)/g,'<span class="hl-fn">$1</span>');
|
||||
}}
|
||||
return s;
|
||||
}}
|
||||
function copyCode(){{
|
||||
var text=Array.from(codeEl.children).map(function(s){{return s.textContent}}).join("\\n");
|
||||
navigator.clipboard.writeText(text).then(function(){{
|
||||
var btn=document.querySelector(".btn-copy");
|
||||
btn.textContent="✓ Copied!";
|
||||
btn.classList.add("copied");
|
||||
setTimeout(function(){{btn.textContent="📋 Copy";btn.classList.remove("copied");}},2000);
|
||||
}});
|
||||
}}
|
||||
</script>'''
|
||||
else:
|
||||
html += f'''<div class="unsupported">
|
||||
<div class="icon">📎</div>
|
||||
<h2>{title}</h2>
|
||||
<p>{size_str} · {mime_type}<br>This file type cannot be previewed.</p>
|
||||
<a href="{file_url}" download>⬇ Download</a>
|
||||
</div>'''
|
||||
|
||||
html += "</div></body></html>"
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
@router.post("/api/sync/{owner}/{repo}")
|
||||
async def sync_project(owner: str, repo: str):
|
||||
"""Full bidirectional sync: fetch Gitea issues → update local DB."""
|
||||
|
||||
Reference in New Issue
Block a user