From ee5a54ea2ce4fed29e0ea93baf66f0e3c821356b Mon Sep 17 00:00:00 2001 From: nudoragon Date: Tue, 26 May 2026 12:23:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=A8=B9=E7=8B=80=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/main.cpython-313.pyc | Bin 30247 -> 34016 bytes main.py | 70 +++++++++++++++++++++++++- static/css/my-style.css | 16 ++++++ templates/project_dashboard.html | 80 ++++++++++++++++++++++++++++- websites/my-website/project.json | 83 ++++++++++++++++++++++++++++++- 5 files changed, 246 insertions(+), 3 deletions(-) diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 44e028f895632e88eb28ce282a484559576555cd..bf25bc90025e5a29422762a0745596bddc315e3d 100644 GIT binary patch delta 8287 zcma)B33yb;k$!J(&FDm<8==uT8lhVV5D1X41VTdMcq1+W@-Q=?!E=b787|x3$d3IP zY-~i@Ho_#fd?W_e{wzHC7UFdhvaxr)UT-3KaHM@Gak4SkZ0yfq_{@fNlCA2exiDX{ z??W~Hy8o`Os;;i8e!TiQ|GUGoi;JMCcS!S*sLUX{=QybP2Oq%P6cC(pjrq=oa*>WrEQR)~XPCgf&7Yws-SnuCP|f zV*QoEIw702s)Y4I4r^5lf{-h9)(B>ySIC>0R=YKSy_NEf>GTU;o3TO2$Nol?9Uf92 z87+`$Ggb)&aib04(E>vwFU=4(3Waf)c@a!e45l~^(-^^&2&FZ=P}U?9HVDQz%=`$Z zJc8LInBp)CVlWjmd^QPl<1h;&m=d9K2D4eHio-NTFx4?@)x=>I#bBaq)y82K$6)Fr z^3=y+nj@Hon6>7`VU|QNjWL+{ahRnsn5aAp;xNl1n1vDDHwjH~n3f1;QOsJ4<1noe zOtTQyYm2ZX4zoOhX^5=VCoGM_+!Mhpi(s}2EpeE(FlKIR#DBI4%i}QZTW1UREZW3z z76Zp^EnNR3`m?T0Xm8>T9Lzt8?1n26k zJl{Z%CT`-F)6WvWnU6<+%m-ppkOue>wfl&}XYcpf_xs2a>`>bLu0gL!PHGU^ill|+ zCL8!w!NtiM8Q(M9>fdHdk84yDYDTE(U*Y9+Aef@)$U3M?o5){(D8rg=5iP(TaeTuQh|KPCVE(3S=NIc@?CZxsbDRq z-EC6Gh-bxF0==Z~;$1;whEAT|ggtDU$_Z>7gJdmj&z{c<^nvUi{{G;HJ(F*yeY%8Mxe#5R;d;4ruH#XTbIW>VbTC~{ zpU>203Np6w3puV&4vg3%4JQinu*dW%Xu--HwVva8`Soi0`;F=OGC{5ca2R2@%XCF} zuulWivjaBR!0x@UkZxuju2&`~kV`M$h?@(Kf$M&Z<8<)5i!&*EM1}9bpj}k#bU5t< zjV)@d0}iK+z|N@10Cs2{UWeQ3v$(BxX(!|s8;s*ndjC$R#pkoT`+dZ25A=ioxxnE> zC2NRUx_(d@-?ndaEvD*miv2*ucoa zP-ewzZIimXkgjezv0zj&x@E#pJ67@Q#3f%PaA}#O7#6e6SOl6Jhsh9x<8O?^PHtCi zS8vxm!}UR1divzFusOHHrHOWhQ;gi~vM#eTJN1Zmar!(DwHGB;? zNbDAwRng56w7sfcb+2ZgT#!Ks(d<^Z)B`L3UJEx;FQWgYpPzyH!7h8`9b z#N+W9O2$g`gBv zw5PB{AKuSCkY0ME$p%3!Xu^YGlYSeNAZY&{s726y8zdygZPZ^lFjO~K6sQ7Jl?zEM z6utNVwa*J+6Y#tk%Q3Ucgk<{t6~)R~VLr2kAJ=G zzi+L;-K+&}nNkf7E1A`Ry8Vi22o3@5k7k^c2j@)fArR^~atH~FZAzcR2XQV3Iz-+e z>bwJ8nFlV_#MRVg&gSh?(-1aX_dr}9H^kCTO5 zQv!LC;XyFJ(?^ctO9IB$ehlPHO(uevY~K%w*WupPk5Skw&VnDGo%HXuI2|@AQWGo8 z_9g&_#dj_`Ve}=dz zqv?=pTAy>MZ7O^A3z`#}(acbG`Jq))DcK_{LMcU4S$W6zAKO2w3S}9G71QbYBeqxW znM^OANH0HS9_#&wZBzO{(WJg?LSHuOyQr_6(&rxUKGr>DD4Q~rOcj>Cn*Cz-DdUgk zomT`Gom+Nx*eOF3$X#Q)gs-rR1s?f0$l= zP0!8B8yN_tmHiD3{q3R=V>OsjYO192x=vM` zd5cpqarLIJ^juob^+qlu_u70eHSh z(a8e0^;o<0*&J419~57ehF151ZQDc@vHSeQOrl8thh!ouIA?}mJl&5tgZK2e{P-4+g`*cCkN&isKbZO0) zB5+P~R&zcxRNL}S$zRbi<*0pnMtLJCQhNGO=_b zv2-eX&Qx~ZbWZ+=^<>Tqb56{e%&A#8kyA4!9}_}3O_MpxCUTaAa$2Vfil+*SMx7G{ z^;4x4;GV{0aL=Ta>vB$Ccx@J!m^rSmG5;q-ss?flWFmd4U7(W9?$rp1tZGRTl4Fx0q=qv3NMTbTrAOqeuVVU;!gb^vfYY;V z5smH`uGw9Jtj1P163iuL@*>cgqms%zTvS5{CN>9=@=QYeeye@Z=kU1MT|-nsYQe!1 z9K`gf`@<|Ri_XN4yz`K-l&}U{k@I3oWTJ{mRzHEd+}Cdd-UIB25-9B8{=!OZX=%ky zzthPw06C3g2`jUBVmC8Lx!lp2qVP|Jf3jzz^FjZA!$YO8WI$roJR|K zk*ncegCTs>Q+D{_&Js;hH zl3zgoEqbwZZ+f!L;vLxGu@D=J#wHo*p%8|f$K}{&7 z7VbIYiaPpS+2stptr9H~=1Pg!+DxA|7OVahnsPq)6XO%Iz`tNO8i!q(Sw+OMf#l_H z-~b-L+v*xGc(%6&_9S{R*v`T|Q#uZ!+)teBNH9%Azboj`lC{9#k`~X5afS9zB zGcoVMdWU=fM2a@ve$S5GNpfmej^@-kV5VNSf%z>IJGx&|JU=pRS(dGtkDO2C^eoVP0v8MRce(Uqx}kBKmUe z@yM~;4f@xis1!u44OY(kP?mZXvCPC-*!m4kUSMcp zPL>1jr)8pg(1MZ8OD1uA5fb=d!ZEM9iU--0Oi)tt1roN+1Ju1Bdlge;3Og&1pq!E` z;GRIZ`4T(W%FLwb1!BSui`U-ob^3RSYOBZZhR97?>8}=;lz)QZdxL*o5aJ8ttqZ!} zVK*1O-c+jm1|~cZ%v)3@+Z{i_Orkn7b^ZuQlkQF~QJt9KnPRg68KVsNcSesccFE3d!&=}T|HH;2!Hk6n58 z#Ff#vFTZ`1+<-wTXE3YFnA)u4?*1=%)#EtppOCP<2>X+qBYy#lmnyX&(wSgaTZ=q3 zUOjS%o?JOQz*a^7_G8S24<46?pZxUUw>~}n7GZHtbw9e?gQC(m;CJmXgUYxYED%D5 zzhg(xG2lGo&5f`~n}XO>Buq}0Jej+!g+7cJ41oSk`jPCV?HzMx*LM7u5G+7W)b?)YGXRHj^uvfvqV$6GjtE4u#DH|5KBsw8NlaKa8vgdS!N6i z0EJH!eQ!;figDXRZ>}kB{Td~^Ygmg77PtyKSVh>vWD$=jhu_cP}w zBl!8+8>t!W6w7c9iyt%L>!zi&fr$_azi@%KV!el#Bdo^Qwu)-EeIM>2OI2haUA^Ow zDjQZIH>tQ|;p)}Mw;Kt|#25>uB#irTbt@2)T2y=eR;%6XW!@sH;ajQ~_JtH88;Ubn zbbFs}w-)g)(ub@Ybfq}Ne5;K9m$jxW3*Yq!#mWKnW@Bs`V}La+d7mz@btW}pb3UTL zJLyxlywWeQ2TuijBZjX5Qq{>^(2gTBq4<}!oQ=)+o`)oWvRInVqTU@KeHDXGL`pXCv*H1_;fM|AF&}Jc>I_>O|)`ny|M+k zd>ypz{Fyolvuo7#Yup(soCyC-(djNyLiNWzMfbSd_+Qh1bQc7eJzmCfY=IS{Z7F?9 z7Gu3Ci7|7m$h_Oh@z?EeG0Xt1$an=RhzvwUkH>8X(V`q=FLQLWXLcS#Gcj3W-HU`4*fbM z{h~rLhFvjBGQ`Z~gK>yjkYSlf9du$aPqzeR&PVbvy*1dt|1+Jt=Rlwun2~xURXFqz z5*BbOu*E`r1GbhUS%qW^5*HFYFVe|jetraB&mh74C@P7^4*~iY*zt2DSj-bFP{}1E zSCFi{h7^k!@(mI!3J4~0f+?C{P9>Pd2&M&sQJY}6B^dk&dOSgQCR6lYQl#AsP58%o wpQlI2nOQe8mHg71sr7v3*SmQWUwv&WUE;0M)!&eFvYcx&8DAgtd*77(A1AuV$p8QV delta 5215 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 diff --git a/main.py b/main.py index 450c178..1207a23 100644 --- a/main.py +++ b/main.py @@ -137,9 +137,74 @@ def _copy_blank_template(dest: Path) -> None: ) +def _flatten_tree_items(items: list[dict[str, Any]], file_names: set[str]) -> None: + for item in items: + if item.get("type") == "file" and isinstance(item.get("name"), str): + file_names.add(item["name"]) + if isinstance(item.get("children"), list): + _flatten_tree_items(item["children"], file_names) + + +def _normalize_page_tree(slug: str, tree: dict[str, Any]) -> dict[str, Any]: + """將已儲存的 page_tree 與實際檔案同步並補齊新頁面。""" + pages = set(_list_pages(slug)) + existing_files: set[str] = set() + root_items = tree.get("root") if isinstance(tree.get("root"), list) else [] + _flatten_tree_items(root_items, existing_files) + + def filter_valid_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] + for item in items: + item_type = item.get("type") + if item_type == "file": + name = item.get("name") + if isinstance(name, str) and name in pages: + existing_files.add(name) + result.append({ + "type": "file", + "name": name, + "title": item.get("title", Path(name).stem.replace("-", " ").title()), + "show_in_nav": bool(item.get("show_in_nav", True)), + "is_homepage": bool(item.get("is_homepage", False)), + "requires_password": bool(item.get("requires_password", False)), + }) + elif item_type == "folder": + name = item.get("name") + if isinstance(name, str): + children = filter_valid_items(item.get("children", []) if isinstance(item.get("children"), list) else []) + result.append({ + "type": "folder", + "name": name, + "title": item.get("title", name.replace("-", " ").title()), + "show_in_nav": bool(item.get("show_in_nav", True)), + "children": children, + }) + else: + continue + return result + + normalized = {"root": filter_valid_items(root_items)} + missing_pages = sorted(pages - existing_files) + if missing_pages: + for rel in missing_pages: + normalized["root"].append({ + "type": "file", + "name": rel, + "title": Path(rel).stem.replace("-", " ").title(), + "show_in_nav": True, + "is_homepage": rel == "index.html", + "requires_password": False, + }) + return normalized + + def build_page_tree(slug: str, max_depth: int = 3) -> dict[str, Any]: - """遞歸建構專案的頁面樹狀結構(根據資料夾結構)。""" + """遞歸建構專案的頁面樹狀結構。""" proj_dir = _project_dir(slug) + data = _load_project(slug) + page_tree = data.get("page_tree") + if isinstance(page_tree, dict) and isinstance(page_tree.get("root"), list): + return _normalize_page_tree(slug, page_tree) def scan_folder(folder: Path, current_depth: int = 0): items: list[dict[str, Any]] = [] @@ -168,6 +233,9 @@ def build_page_tree(slug: str, max_depth: int = 3) -> dict[str, Any]: "type": "file", "name": rel, "title": Path(name).stem.replace("-", " ").title(), + "show_in_nav": True, + "is_homepage": rel == "index.html", + "requires_password": False, }) return items diff --git a/static/css/my-style.css b/static/css/my-style.css index 25b7f55..91212dd 100644 --- a/static/css/my-style.css +++ b/static/css/my-style.css @@ -428,6 +428,22 @@ button { cursor: pointer; font-family: inherit; } background: rgba(99,102,241,0.1); } +.tree-item-meta { + display: flex; + align-items: center; + gap: 0.3rem; + margin-left: 1rem; +} + +.tree-item-meta .btn { + min-width: 2.2rem; +} + +.tree-item-meta .btn.active { + border-color: rgba(59,130,246,0.6); + background: rgba(59,130,246,0.12); +} + .tree-item-actions .btn { padding: 0.35rem 0.7rem; } diff --git a/templates/project_dashboard.html b/templates/project_dashboard.html index 20ff238..dcd45e0 100644 --- a/templates/project_dashboard.html +++ b/templates/project_dashboard.html @@ -340,6 +340,24 @@ const tree = await res.json(); const container = document.getElementById('pagesTree'); container.innerHTML = ''; + container.addEventListener('dragover', e => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + }); + container.addEventListener('drop', e => { + e.preventDefault(); + const nameEnc = e.dataTransfer.getData('text/name'); + if (!nameEnc) return; + const name = decodeURIComponent(nameEnc); + const src = container.querySelector(`[data-name='${CSS.escape(name)}']`); + if (!src) return; + let rootUl = container.querySelector(':scope > ul'); + if (!rootUl) { + rootUl = document.createElement('ul'); + container.appendChild(rootUl); + } + rootUl.appendChild(src); + }); function createLi(item) { const li = document.createElement('li'); @@ -347,6 +365,9 @@ li.draggable = true; li.dataset.type = item.type || 'file'; li.dataset.name = item.name || item.title || ''; + li.dataset.showInNav = item.show_in_nav !== false ? 'true' : 'false'; + li.dataset.isHomepage = item.is_homepage ? 'true' : 'false'; + li.dataset.requiresPassword = item.requires_password ? 'true' : 'false'; const titleWrap = document.createElement('span'); titleWrap.className = 'tree-title'; @@ -356,7 +377,56 @@ btns.className = 'tree-item-actions'; btns.innerHTML = ""; + const meta = document.createElement('span'); + meta.className = 'tree-item-meta'; + meta.innerHTML = ` + + + + `; + + const btnNav = meta.querySelector('.btn-nav'); + const btnHome = meta.querySelector('.btn-homepage'); + const btnPassword = meta.querySelector('.btn-password'); + + const updateMetaState = () => { + const showNav = li.dataset.showInNav === 'true'; + const isHomepage = li.dataset.isHomepage === 'true'; + const requiresPassword = li.dataset.requiresPassword === 'true'; + btnNav.classList.toggle('active', showNav); + btnNav.title = showNav ? '顯示於導覽列' : '從導覽列隱藏'; + btnHome.classList.toggle('active', isHomepage); + btnHome.title = isHomepage ? '首頁' : '設為首頁'; + btnPassword.classList.toggle('active', requiresPassword); + btnPassword.title = requiresPassword ? '需要密碼' : '不需要密碼'; + if (li.dataset.type !== 'file') { + btnHome.disabled = true; + btnHome.title = '僅限單一頁面'; + } + }; + + updateMetaState(); + + btnNav.addEventListener('click', () => { + li.dataset.showInNav = li.dataset.showInNav === 'true' ? 'false' : 'true'; + updateMetaState(); + }); + btnPassword.addEventListener('click', () => { + li.dataset.requiresPassword = li.dataset.requiresPassword === 'true' ? 'false' : 'true'; + updateMetaState(); + }); + btnHome.addEventListener('click', () => { + if (li.dataset.type !== 'file') return; + container.querySelectorAll('.tree-item').forEach(other => { + other.dataset.isHomepage = 'false'; + }); + container.querySelectorAll('.btn-homepage').forEach(button => button.classList.remove('active')); + li.dataset.isHomepage = 'true'; + updateMetaState(); + }); + li.appendChild(titleWrap); + li.appendChild(meta); li.appendChild(btns); if (item.children && item.children.length) { @@ -374,6 +444,7 @@ li.addEventListener('dragleave', () => { li.classList.remove('tree-drop-hover'); }); li.addEventListener('drop', e => { e.preventDefault(); + e.stopPropagation(); li.classList.remove('tree-drop-hover'); const nameEnc = e.dataTransfer.getData('text/name'); if (!nameEnc) return; @@ -422,7 +493,14 @@ const title = li.querySelector('.tree-title')?.textContent || ''; const name = li.dataset.name || ''; const type = li.dataset.type || 'file'; - const obj = { name, title, type }; + const obj = { + name, + title, + type, + show_in_nav: li.dataset.showInNav === 'true', + is_homepage: li.dataset.isHomepage === 'true', + requires_password: li.dataset.requiresPassword === 'true', + }; const childUl = li.querySelector(':scope > ul'); if (childUl) obj.children = walk(childUl); arr.push(obj); diff --git a/websites/my-website/project.json b/websites/my-website/project.json index 534e4cb..cc5a0ac 100644 --- a/websites/my-website/project.json +++ b/websites/my-website/project.json @@ -2,5 +2,86 @@ "name": "My Website", "slug": "my-website", "description": "", - "created_at": "2026-05-17T13:53:12" + "created_at": "2026-05-17T13:53:12", + "page_tree": { + "root": [ + { + "name": "fold.html", + "title": "Fold", + "type": "file" + }, + { + "name": "about-final.html", + "title": "About Final", + "type": "file" + }, + { + "name": "index.html", + "title": "Index", + "type": "file" + }, + { + "name": "my-page.html", + "title": "My Page", + "type": "file" + }, + { + "name": "my-page3.html", + "title": "My Page3", + "type": "file" + }, + { + "name": "my-page4.html", + "title": "My Page4", + "type": "file" + }, + { + "name": "subfolder", + "title": "Subfolder", + "type": "folder", + "children": [ + { + "name": "subfolder/subpage-title.html", + "title": "Subpage Title", + "type": "file" + } + ] + }, + { + "name": "temp", + "title": "Temp", + "type": "folder", + "children": [ + { + "name": "temp/my-page5.html", + "title": "My Page5", + "type": "file" + }, + { + "name": "temp/new.html", + "title": "New", + "type": "file" + }, + { + "name": "subsubfolder", + "title": "Subsubfolder", + "type": "folder", + "children": [ + { + "name": "temp/subsubfolder/myname.html", + "title": "Myname", + "type": "file" + } + ] + } + ] + }, + { + "name": "test.html", + "title": "Test", + "type": "file" + } + ] + }, + "updated_at": "2026-05-26T04:12:23" } \ No newline at end of file