Spaces:
Sleeping
Sleeping
| """ | |
| layout/page/sections.py | |
| ------------------------ | |
| Section(分段)相關的資料邏輯與 UI 元件。 | |
| 只依賴 Dash / DMC,不含任何 @callback。 | |
| 公開 API: | |
| split_blocks_into_sections(blocks) → List[section_dict] | |
| get_section_blocks(sections, section_id) → List[block_dict] | |
| make_section_control(sections, active_id) → html.Div | None | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from dash import html | |
| import dash_mantine_components as dmc | |
| # --------------------------------------------------------------------------- | |
| # 資料邏輯 | |
| # --------------------------------------------------------------------------- | |
| def split_blocks_into_sections(blocks: list[dict]) -> list[dict]: | |
| """ | |
| 把平坦的 block 清單按 H2 heading 切割成 section 清單。 | |
| 回傳格式: | |
| [ | |
| {"id": "sec-all", "title": "全部", "blocks": [...]}, # 永遠第一筆 | |
| {"id": "sec-1", "title": "標題文字", "blocks": [...]}, | |
| ... | |
| ] | |
| """ | |
| sections: list[dict] = [ | |
| {"id": "sec-all", "title": "全部", "blocks": blocks[:]} | |
| ] | |
| current: dict | None = None | |
| sec_idx = 0 | |
| for b in blocks: | |
| if b.get("type") == "heading" and int(b.get("level", 0)) == 2: | |
| if current: | |
| sections.append(current) | |
| sec_idx += 1 | |
| current = { | |
| "id": f"sec-{sec_idx}", | |
| "title": (b.get("content") or f"段落 {sec_idx}").strip(), | |
| "blocks": [b], | |
| } | |
| else: | |
| if current is not None: | |
| current["blocks"].append(b) | |
| if current: | |
| sections.append(current) | |
| return sections | |
| def get_section_blocks( | |
| sections: list[dict], section_id: str | None | |
| ) -> list[dict]: | |
| """ | |
| 取得指定 section_id 的 block 清單。 | |
| 找不到時退回第一個 section;sections 為空時回傳空清單。 | |
| """ | |
| if not sections: | |
| return [] | |
| if not section_id: | |
| return sections[0].get("blocks") or [] | |
| for s in sections: | |
| if (s.get("id") or "") == section_id: | |
| return s.get("blocks") or [] | |
| return sections[0].get("blocks") or [] | |
| # --------------------------------------------------------------------------- | |
| # UI 元件 | |
| # --------------------------------------------------------------------------- | |
| def make_section_control( | |
| sections: list[dict], active_id: str | None | |
| ) -> html.Div | None: | |
| """ | |
| 產生水平捲動的 section 切換按鈕列。 | |
| 若 sections 為空,回傳 None。 | |
| """ | |
| if not sections: | |
| return None | |
| buttons: list[Any] = [] | |
| for i, s in enumerate(sections): | |
| sid = s.get("id") or f"sec-{i + 1}" | |
| title = s.get("title") or f"段落 {i + 1}" | |
| buttons.append( | |
| dmc.Button( | |
| title, | |
| id={"type": "section-btn", "sid": sid}, | |
| variant="filled" if sid == active_id else "light", | |
| size="sm", | |
| style={"flex": "0 0 auto"}, | |
| ) | |
| ) | |
| return html.Div( | |
| dmc.Group(buttons, gap="sm", wrap="nowrap"), | |
| style={ | |
| "overflowX": "auto", | |
| "overflowY": "hidden", | |
| "whiteSpace": "nowrap", | |
| "paddingBottom": "6px", | |
| }, | |
| ) | |