File size: 3,824 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
117
118
119
"""
layout/page/page_callbacks.py
------------------------------
頁面級 Dash callback。
callback 定義在 module 層級,import 即生效。
register_page_callbacks() 保留為空殼供相容呼叫。
"""

from __future__ import annotations
import hashlib, json
from dash import callback, Input, Output, State, ALL, ctx, no_update
from ..renderer.renderer import render_doc
from .sections import get_section_blocks
from .loader import build_toc_titles
from ..toc import make_toc

# render_doc 結果快取(key = section blocks 的 hash)
_render_cache: dict[str, object] = {}

def _cached_render(blocks: list) -> object:
    """同樣的 blocks 只渲染一次。"""
    try:
        key = hashlib.md5(
            json.dumps(blocks, ensure_ascii=False, sort_keys=True).encode()
        ).hexdigest()
    except Exception:
        return render_doc(blocks)
    if key not in _render_cache:
        _render_cache[key] = render_doc(blocks)
    return _render_cache[key]


# ------------------------------------------------------------------
# Callback 1:切換 section → 更新內容 + TOC + Grid 欄寬
# ------------------------------------------------------------------
@callback(
    Output("section-content-wrapper", "children"),
    Output("section-toc-wrapper", "children"),
    Output("main-content-col", "span"),
    Output("toc-col", "span"),
    Output("toc-col", "style"),
    Output("page-active-section-store", "data"),
    Input({"type": "section-btn", "sid": ALL}, "n_clicks"),
    State("page-sections-store", "data"),
    State("page-raw-toc-store", "data"),
    prevent_initial_call=True,
)
def _cb_switch_section(_, sections, raw_toc):
    if not sections:
        return (no_update,) * 6

    trig = ctx.triggered_id
    if not trig:
        return (no_update,) * 6

    section_id = trig.get("sid")
    current_blocks = get_section_blocks(sections, section_id)
    content = _cached_render(current_blocks)

    if section_id == "sec-all":
        toc_titles = build_toc_titles(current_blocks, raw_toc or [])
        toc_component = (
            make_toc(
                titles=toc_titles,
                top_offset=90,
                col_span={"xs": 0, "sm": 0, "md": 3, "lg": 3, "xl": 3},
                visible_from="md",
            )
            if toc_titles
            else None
        )
        return (
            content,
            toc_component,
            {"base": 12, "md": 9},
            {"base": 12, "md": 3},
            {"display": "block"},
            section_id,
        )

    return (
        content,
        None,
        {"base": 12, "md": 12},
        {"base": 12, "md": 0},
        {"display": "none"},
        section_id,
    )


# ------------------------------------------------------------------
# Callback 2:同步 section 按鈕的 active 樣式
# ------------------------------------------------------------------
@callback(
    Output({"type": "section-btn", "sid": ALL}, "variant"),
    Output({"type": "section-btn", "sid": ALL}, "color"),
    Input("page-active-section-store", "data"),
    State("page-sections-store", "data"),
)
def _cb_highlight_section_buttons(active_sid, sections):
    if not sections:
        return [], []

    variants, colors = [], []
    for s in sections:
        sid = s.get("id") or ""
        is_active = sid == active_sid
        variants.append("filled" if is_active else "light")
        colors.append("blue" if is_active else "gray")
    return variants, colors


# ------------------------------------------------------------------
# 相容 shim:讓舊的 register_page_callbacks() 呼叫不報錯
# ------------------------------------------------------------------
def register_page_callbacks() -> None:
    """Callbacks 在 import 時已全部掛載,此函式保留供舊版呼叫。"""
    pass