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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
use *;
/// Decodes a percent-encoded anchor slug from a hash route.
///
/// Browser URL hash for non-ASCII characters arrives as the
/// percent-encoded form (e.g. `%E7%8E%AF%E5%A2%83%E8%A6%81%E6%B1%82`
/// for `环境要求`), but the heading `<h2 id="…">` elements are
/// written in the raw UTF-8 form by `build.rs`. `getElementById`
/// needs the raw form to match, so decode before querying.
///
/// Falls back to the input verbatim if decoding fails or if
/// `js_sys` is unavailable (e.g. during a unit test outside the
/// browser), so a malformed anchor never panics the page.
///
/// # Arguments
///
/// - `&str` - The percent-encoded anchor slug (already stripped
/// of the leading `#`).
///
/// # Returns
///
/// - `String` - The decoded UTF-8 anchor slug, or the input
/// unchanged if decoding fails.
/// Splits a raw route into its page path and optional in-page anchor.
///
/// `/guide/a.html#install` → `("/guide/a.html", Some("install"))`.
///
/// The anchor is percent-decoded so that links to non-ASCII
/// headings (e.g. `/zh/guide/getting-started.html#环境要求`) match
/// the raw UTF-8 `id` attributes emitted by `build.rs`. Without
/// decoding, `getElementById` returns null and the in-page
/// scroll-to-anchor handler in `schedule_scroll` silently
/// regresses to "back to top".
///
/// # Arguments
///
/// - `&str` - The raw hash route.
///
/// # Returns
///
/// - `(String, Option<String>)` - The page path and optional anchor slug.
pub
/// Finds the locale owning a route (longest prefix match).
///
/// # Arguments
///
/// - `&str` - The page route path.
///
/// # Returns
///
/// - `&'static DocsLocale` - The matched locale (root locale as fallback).
pub
/// Looks up a page by route, normalizing missing trailing forms.
///
/// # Arguments
///
/// - `&str` - The page route path.
///
/// # Returns
///
/// - `Option<&'static DocsPage>` - The page when found.
pub
/// Finds the sidebar scope that contains the given route.
///
/// Returns the level of the sidebar tree where the route appears as a
/// direct child. Group/index pages (items with both a link and children)
/// appear in the scope alongside leaf pages, so clicking a directory
/// README still gets a usable pager anchored inside their own directory.
///
/// When the direct scope has fewer than two navigable items (e.g. a
/// single-content directory whose only sidebar entry is its LICENSE),
/// the result bubbles up one level — the level whose array contains
/// the direct scope as a child. The pager pool then includes the
/// sibling group/index page and other leaves so prev/next can walk
/// instead of being empty. The bubble stops as soon as the parent
/// scope has at least two navigable items, or the recursion reaches
/// the top-level array (where further bubbling would have no parent
/// to consult).
///
/// Walks the tree depth-first. The recursive call does NOT bubble up —
/// the matched level is always the lowest one containing the route.
/// Sibling directories that do not contain the route are NOT pulled in
/// from a higher ancestor, so prev/next stays inside the same branch
/// of the sidebar instead of hopping to unrelated projects.
///
/// # Arguments
///
/// - `&'static [EuvSidebarItem]` - The sidebar tree to search.
/// - `&str` - The current route path.
///
/// # Returns
///
/// - `Some(&'static [EuvSidebarItem])` - The sibling array that contains
/// the matched item as a direct child, possibly bubbled to a higher
/// level so the pool has at least two navigable items.
/// - `None` - The route is not present in this subtree.
pub
/// Flattens a sidebar scope into its ordered navigable items.
///
/// A navigable item is either a leaf (no children) with a link, or a
/// group/index page (children present) with its own link. Pure
/// container groups (no link, only children) recurse transparently so
/// the resulting pool contains every page the reader can actually
/// navigate to via prev/next, in sidebar order.
///
/// # Arguments
///
/// - `&'static [EuvSidebarItem]` - The sidebar scope to flatten.
///
/// # Returns
///
/// - `Vec<&'static EuvSidebarItem>` - Navigable items, in display order.
pub
/// Maps a route to the equivalent route in another locale.
///
/// Falls back to the target locale home when the page has no counterpart.
///
/// # Arguments
///
/// - `&str` - The current page route path.
/// - `&'static DocsLocale` - The target locale.
///
/// # Returns
///
/// - `String` - The target route.
pub