refactor: remove dead code _render_file_viewer from board.py
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

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:
2026-07-18 10:03:09 -04:00
parent 298617af58
commit 37c8e99151
-192
View File
@@ -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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");}}
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(/(&lt;!--[\\s\\S]*?--&gt;)/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;(?:[^&]|&(?!quot;))*&quot;)/g,'<span class="hl-str">$1</span>');
s=s.replace(/(&#x27;(?:[^&]|&(?!#x27;))*&#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."""