File size: 3,352 Bytes
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
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",
        },
    )