guanghulab/homepage/brain.html

120 lines
7.2 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>光湖 · 语言层数据库</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root{--bg:#fff;--sidebar-bg:#f7f6f3;--text:#37352f;--text-light:#6b6b6b;--border:#e3e2e0;--accent:#2383e2;--accent-light:#e7f3ff;--hover:#efefef;--radius:6px;--shadow:0 1px 3px rgba(0,0,0,.05);--shadow-hover:0 4px 12px rgba(0,0,0,.08)}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","SF Pro Display",Roboto,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;flex-direction:column;font-size:16px;line-height:1.6}
.header{height:60px;border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 24px;background:var(--bg);flex-shrink:0;gap:12px}
.header h1{font-size:18px;font-weight:600;letter-spacing:-.3px}
.header .actions{margin-left:auto;display:flex;gap:8px}
.btn{padding:8px 18px;border-radius:var(--radius);border:none;background:var(--accent);color:#fff;font-size:15px;font-weight:500;cursor:pointer;transition:all .2s ease;box-shadow:var(--shadow)}
.btn:hover{transform:translateY(-1px);box-shadow:var(--shadow-hover);opacity:.9}
.btn-outline{background:transparent;color:var(--text);border:1px solid var(--border);box-shadow:none}
.btn-outline:hover{background:var(--hover);border-color:var(--accent);color:var(--accent)}
.main{display:flex;flex:1;overflow:hidden}
.sidebar{width:300px;background:var(--sidebar-bg);border-right:1px solid var(--border);overflow-y:auto;padding:16px 12px}
.tree-item{display:flex;align-items:center;padding:10px 14px;margin:4px 0;cursor:pointer;font-size:16px;color:var(--text);border-radius:var(--radius);transition:all .15s ease}
.tree-item:hover{background:var(--hover)}
.tree-item.active{background:var(--accent-light);color:var(--accent);font-weight:500}
.content{flex:1;overflow-y:auto;padding:48px 80px;max-width:900px}
.content h1{font-size:40px;font-weight:700;margin-bottom:12px;letter-spacing:-.5px;line-height:1.2}
.content h2{font-size:28px;font-weight:600;margin:36px 0 16px}
.content h3{font-size:22px;font-weight:600;margin:28px 0 12px}
.content p{margin:14px 0;line-height:1.8;font-size:17px}
.content ul,.content ol{margin:16px 0;padding-left:28px}
.content li{margin:10px 0;font-size:17px;line-height:1.7}
.content code{background:#f1f1f0;padding:3px 8px;border-radius:4px;font-size:15px;font-family:"SF Mono",Monaco,monospace;color:#eb5757}
.content pre{background:#f7f6f3;padding:20px;border-radius:8px;overflow-x:auto;margin:20px 0;font-size:15px;line-height:1.6}
.content pre code{background:none;padding:0;color:var(--text)}
.content blockquote{border-left:3px solid var(--accent);padding:8px 20px;margin:20px 0;color:var(--text-light);font-size:17px;background:#f9f9f8;border-radius:0 8px 8px 0}
.content table{border-collapse:collapse;width:100%;margin:20px 0;font-size:16px}
.content td,.content th{border:1px solid var(--border);padding:12px 16px;text-align:left}
.content th{background:#f7f6f3;font-weight:600}
.meta-bar{display:flex;gap:16px;margin-bottom:32px;padding-bottom:20px;border-bottom:1px solid var(--border);font-size:14px;color:var(--text-light)}
.empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-light);font-size:18px;flex-direction:column;gap:12px}
.loading{text-align:center;padding:60px;color:var(--text-light);font-size:16px}
.upload-area{margin:24px 0;padding:32px;border:2px dashed var(--border);border-radius:12px;text-align:center;cursor:pointer;transition:all .2s;background:#fafafa}
.upload-area:hover{border-color:var(--accent);background:var(--accent-light)}
.upload-area p{color:var(--text-light);font-size:15px;margin:8px 0}
#toast{position:fixed;top:20px;right:20px;background:#37352f;color:#fff;padding:12px 24px;border-radius:8px;font-size:15px;z-index:999;opacity:0;transition:opacity .3s}
</style>
</head>
<body>
<div class="header">
<h1>🌊 语言层数据库</h1>
<div class="actions">
<button class="btn btn-outline" onclick="document.getElementById('fileInput').click()">📦 导入 Notion</button>
<input type="file" id="fileInput" accept=".zip" style="display:none" onchange="uploadFile()">
<button class="btn" onclick="createPage()">+ 新建页面</button>
</div>
</div>
<div class="main">
<div class="sidebar" id="sidebar"><div class="loading">加载中…</div></div>
<div class="content" id="content"><div class="empty"><div style="font-size:64px;opacity:.2">📄</div><div>选择一个页面,或新建一个</div></div></div>
</div>
<div id="toast"></div>
<script>
const API_BASE='/brain/documents';
let currentDoc=null;
function toast(msg,dur=3000){
const t=document.getElementById('toast');
t.textContent=msg;t.style.opacity='1';
setTimeout(()=>t.style.opacity='0',dur);
}
async function uploadFile(){
const f=document.getElementById('fileInput').files[0];
if(!f)return;
toast('正在导入…');
const fd=new FormData();fd.append('file',f);
try{
const r=await fetch(API_BASE+'/import',{method:'POST',body:fd});
const j=await r.json();
if(j.ok){toast('✅ '+j.message,5000);loadTree();}
else{toast('❌ '+j.error)}
}catch(e){toast('导入失败: '+e.message)}
}
async function loadTree(){
try{
const r=await fetch(API_BASE);const j=await r.json();const docs=j.data||[];
const active=docs.filter(d=>!d.document_id.startsWith('deleted_'));
const rootDocs=active.filter(d=>!d.parent_id);const childMap={};
active.forEach(d=>{if(d.parent_id){if(!childMap[d.parent_id])childMap[d.parent_id]=[];childMap[d.parent_id].push(d)}});
let h='';function rt(list,l){list.forEach(doc=>{const ind=l*16;
h+=`<div class="tree-item${currentDoc&&currentDoc.document_id===doc.document_id?' active':''}" style="padding-left:${ind+14}px" onclick="selectDoc('${doc.document_id}')"><span style="margin-right:10px;font-size:18px;opacity:.7">${childMap[doc.document_id]?'📁':'📄'}</span>${doc.title}</div>`;
if(childMap[doc.document_id])rt(childMap[doc.document_id],l+1)})}
rt(rootDocs,0);
document.getElementById('sidebar').innerHTML=h||'<div class="loading">还没有页面</div>';
}catch(e){document.getElementById('sidebar').innerHTML='<div class="loading">加载失败</div>'}
}
async function selectDoc(id){
try{const r=await fetch(API_BASE+'/'+id);const j=await r.json();currentDoc=j.data;renderContent();loadTree()}catch(e){}}
function renderContent(){
if(!currentDoc)return;
let h=`<h1>${currentDoc.title}</h1><div class="meta-bar"><span>📍 ${currentDoc.path}</span><span>📋 v${currentDoc.version}</span></div>`;
h+=currentDoc.content?marked.parse(currentDoc.content):'<p style="color:#999">暂无内容</p>';
document.getElementById('content').innerHTML=h;
}
async function createPage(){
const t=prompt('页面标题:');if(!t)return;
const pid=currentDoc?currentDoc.document_id:null;
try{const r=await fetch(API_BASE,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:t,parent_id:pid,content:'# '+t})});
const j=await r.json();if(j.data){currentDoc=j.data;renderContent();loadTree()}}catch(e){alert('创建失败')}
}
loadTree();
</script>
</body>
</html>