Skip to content

Commit 640c40b

Browse files
committed
fix: preserve side tools state across navigation
- Keep the tools menu choice during single-page navigation - Hide side tools accessibly and recalc visibility on resize - Stack the expanded menu above the gear button without slide animation
1 parent c3dfdf0 commit 640c40b

7 files changed

Lines changed: 70 additions & 47 deletions

File tree

docs/content/docs/en/basic/global.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ Controls whether the gear icon in the right-bottom tools rotates continuously.
172172
| :---: | :---: | :---: |
173173
| Boolean | `true \| false` | `false` |
174174

175-
Controls whether the side tools list automatically expands when the page loads. By default, users need to click the gear button to reveal the tools list.
175+
Controls whether the side tools list starts expanded on a full page load. Users
176+
can toggle the list with the gear button, and their choice is preserved while
177+
navigating with the single-page experience.
176178

177179
## Google Analytics
178180

docs/content/docs/zh/basic/global.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ description: 设置全站的一些功能。
171171
| :----: | :-------------: | :-----: |
172172
| 布尔值 | `true \| false` | `false` |
173173

174-
控制侧边工具列表是否在页面加载时自动展开。默认情况下,用户需要点击齿轮按钮才能显示工具列表
174+
控制侧边工具列表是否在完整加载页面时默认展开。用户可以点击齿轮按钮切换显示状态,使用单页导航时会保留当前选择
175175

176176
## Google Analytics
177177

layout/components/side-tools.ejs

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
<%
2-
const toolClass = "mb-1 flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 text-lg text-rd-gray-1000 shadow-rd last:mb-0 hover:!bg-primary hover:!text-rd-primary-text sm:size-11 sm:text-xl [&_i]:!text-inherit";
2+
const toolClass = "flex size-10 shrink-0 items-center justify-center rounded-2xl border border-rd-gray-alpha-400 bg-rd-background-100 text-lg text-rd-gray-1000 shadow-rd hover:!bg-primary hover:!text-rd-primary-text sm:size-11 sm:text-xl [&_i]:!text-inherit";
33
%>
4-
<div>
5-
<div id="side-tools-menu" data-state="closed" class="translate-x-full opacity-0 transition-[transform,opacity] duration-200 ease-in-out data-[state=open]:translate-x-0 data-[state=open]:opacity-100">
6-
<button id="theme-toggle" data-side-tool type="button" class="<%= toolClass %>" aria-label="<%= __('theme') %>" title="<%= __('theme') %>">
7-
<i class="fa-regular fa-moon" aria-hidden="true"></i>
8-
</button>
4+
<div class="flex flex-col items-end gap-1">
5+
<div class="relative">
6+
<div id="side-tools-menu" data-state="closed" aria-hidden="true" class="invisible absolute right-0 bottom-full mb-1 flex flex-col gap-1 opacity-0 pointer-events-none transition-opacity duration-200 ease-in-out data-[state=open]:visible data-[state=open]:opacity-100 data-[state=open]:pointer-events-auto motion-reduce:transition-none">
7+
<button id="theme-toggle" data-side-tool type="button" class="<%= toolClass %>" aria-label="<%= __('theme') %>" title="<%= __('theme') %>">
8+
<i class="fa-regular fa-moon" aria-hidden="true"></i>
9+
</button>
910

10-
<% if (theme.plugins.feed.enable && config.feed) { %>
11-
<a data-side-tool class="<%= toolClass %>" href="<%= url_for(config.feed.path) %>" target="_blank" rel="noopener noreferrer" aria-label="<%= __('rss_feed') %>" title="<%= __('rss_feed') %>">
12-
<i class="fa-regular fa-rss" aria-hidden="true"></i>
13-
</a>
14-
<% } %>
11+
<% if (theme.plugins.feed.enable && config.feed) { %>
12+
<a data-side-tool class="<%= toolClass %>" href="<%= url_for(config.feed.path) %>" target="_blank" rel="noopener noreferrer" aria-label="<%= __('rss_feed') %>" title="<%= __('rss_feed') %>">
13+
<i class="fa-regular fa-rss" aria-hidden="true"></i>
14+
</a>
15+
<% } %>
1516

16-
<% if (theme.global.scroll_progress.percentage !== true) { %>
17-
<button id="scroll-top" data-side-tool type="button" class="<%= toolClass %>" aria-label="<%= __('top') %>" title="<%= __('top') %>">
18-
<i class="fa-regular fa-arrow-up" aria-hidden="true"></i>
19-
</button>
20-
<% } %>
17+
<% if (theme.global.scroll_progress.percentage !== true) { %>
18+
<button id="scroll-top" data-side-tool type="button" class="<%= toolClass %>" aria-label="<%= __('top') %>" title="<%= __('top') %>">
19+
<i class="fa-regular fa-arrow-up" aria-hidden="true"></i>
20+
</button>
21+
<% } %>
2122

22-
<button id="scroll-bottom" data-side-tool type="button" class="<%= toolClass %>" aria-label="Bottom" title="Bottom">
23-
<i class="fa-regular fa-arrow-down" aria-hidden="true"></i>
24-
</button>
25-
</div>
23+
<button id="scroll-bottom" data-side-tool type="button" class="<%= toolClass %>" aria-label="Bottom" title="Bottom">
24+
<i class="fa-regular fa-arrow-down" aria-hidden="true"></i>
25+
</button>
26+
</div>
2627

27-
<div>
2828
<button id="side-tools-toggle" data-side-tool type="button" class="<%= toolClass %>" aria-controls="side-tools-menu" aria-expanded="false" aria-label="Tools" title="Tools">
2929
<i class="fa-regular fa-cog <% if (theme.global.side_tools && theme.global.side_tools.gear_rotation !== false) { %>fa-spin<% } %>" aria-hidden="true"></i>
3030
</button>
31-
<% if (theme.global.scroll_progress.percentage === true) { %>
32-
<button id="scroll-top" data-side-tool type="button" data-state="hidden" class="group <%= toolClass %> data-[state=hidden]:hidden" aria-label="<%= __('top') %>" title="<%= __('top') %>">
33-
<i data-scroll-arrow class="fas fa-arrow-up !hidden group-hover:!flex" aria-hidden="true"></i>
34-
<span data-scroll-percent class="flex text-base group-hover:!hidden"></span>
35-
</button>
36-
<% } %>
37-
<% if (theme.plugins.aplayer.enable && theme.plugins.aplayer.type === "mini") { %>
38-
<div class="hidden md:block">
39-
<div data-side-tool class="<%= toolClass %>" aria-hidden="true"></div>
40-
</div>
41-
<% } %>
4231
</div>
32+
33+
<% if (theme.global.scroll_progress.percentage === true) { %>
34+
<button id="scroll-top" data-side-tool type="button" data-state="hidden" class="group <%= toolClass %> data-[state=hidden]:hidden" aria-label="<%= __('top') %>" title="<%= __('top') %>">
35+
<i data-scroll-arrow class="fas fa-arrow-up !hidden group-hover:!flex" aria-hidden="true"></i>
36+
<span data-scroll-percent class="flex text-base group-hover:!hidden"></span>
37+
</button>
38+
<% } %>
39+
40+
<% if (theme.plugins.aplayer.enable && theme.plugins.aplayer.type === "mini") { %>
41+
<div class="hidden size-10 sm:size-11 md:block" aria-hidden="true"></div>
42+
<% } %>
4343
</div>

layout/page.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<%- partial('pages/router', {pageKind: currentPageKind, region: 'after-shell'}) %>
2626

27-
<aside id="side-tools" data-state="visible" class="fixed right-3 bottom-6 z-30 opacity-100 transition-opacity duration-200 data-[state=hidden]:pointer-events-none data-[state=hidden]:opacity-0 sm:right-10">
27+
<aside id="side-tools" data-state="visible" class="fixed right-3 bottom-6 z-30 opacity-100 transition-opacity duration-200 data-[state=hidden]:pointer-events-none data-[state=hidden]:opacity-0 motion-reduce:transition-none sm:right-10">
2828
<%- partial('components/side-tools') %>
2929
</aside>
3030

source/js/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const registerScrollHandlers = (signal) => {
4545

4646
didInitScroll = true;
4747
window.addEventListener("scroll", handleScroll, { signal });
48+
window.addEventListener("resize", handleScroll, { signal });
4849
window.addEventListener("scroll", debounce(handleHomeBannerBlur, 20), {
4950
signal,
5051
});

source/js/utils/sideTools.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
let toolsMenuOpen = theme.global.side_tools?.auto_expand === true;
2+
13
export const updateAutoHideTools = () => {
24
const y = window.scrollY;
3-
const height = document.body.scrollHeight;
5+
const height = document.documentElement.scrollHeight;
46
const windowHeight = window.innerHeight;
57
const tools = document.getElementById("side-tools");
68
const aplayer = document.getElementById("aplayer");
@@ -9,27 +11,38 @@ export const updateAutoHideTools = () => {
911
return;
1012
}
1113

14+
const isScrollable = height > windowHeight;
1215
const shouldHide =
1316
(y <= 100 && location.pathname === config.root) ||
14-
y + windowHeight >= height - 20;
15-
tools.dataset.state = shouldHide ? "hidden" : "visible";
16-
aplayer?.classList.toggle("hide", shouldHide);
17+
(isScrollable && y + windowHeight >= height - 20);
18+
const state = shouldHide ? "hidden" : "visible";
19+
20+
if (tools.dataset.state !== state) {
21+
tools.dataset.state = state;
22+
tools.setAttribute("aria-hidden", String(shouldHide));
23+
tools.inert = shouldHide;
24+
}
25+
if (aplayer && aplayer.classList.contains("hide") !== shouldHide) {
26+
aplayer.classList.toggle("hide", shouldHide);
27+
}
1728
};
1829

1930
export const initToolsListToggle = (ctx, signal) => {
2031
if (!ctx?.toolsList || !ctx?.toggleButton) {
2132
return;
2233
}
2334

24-
if (theme.global.side_tools && theme.global.side_tools.auto_expand) {
25-
ctx.toolsList.dataset.state = "open";
26-
ctx.toggleButton.setAttribute("aria-expanded", "true");
27-
}
35+
const applyState = () => {
36+
ctx.toolsList.dataset.state = toolsMenuOpen ? "open" : "closed";
37+
ctx.toolsList.setAttribute("aria-hidden", String(!toolsMenuOpen));
38+
ctx.toggleButton.setAttribute("aria-expanded", String(toolsMenuOpen));
39+
};
40+
41+
applyState();
2842

2943
const handler = () => {
30-
const isOpen = ctx.toolsList.dataset.state === "open";
31-
ctx.toolsList.dataset.state = isOpen ? "closed" : "open";
32-
ctx.toggleButton.setAttribute("aria-expanded", String(!isOpen));
44+
toolsMenuOpen = !toolsMenuOpen;
45+
applyState();
3346
};
3447

3548
if (signal) {

tests/theme-generation.test.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,18 @@ test("theme build and generation configurations", async (t) => {
6262
"data-sidebar-panel",
6363
'class="order-last rounded-2xl border',
6464
'aria-label="Theme Redefine" class="order-first overflow-hidden',
65-
'class="flex w-full items-center gap-2 rounded-lg',
65+
"data-sidebar-link",
6666
'id="page-shell"',
6767
'id="main-content"',
6868
'id="site-footer"',
69+
'id="side-tools" data-state="visible" class="fixed',
70+
'class="flex flex-col items-end gap-1"',
71+
'id="side-tools-menu" data-state="closed" aria-hidden="true" class="invisible absolute right-0 bottom-full mb-1 flex flex-col gap-1 opacity-0 pointer-events-none transition-opacity',
6972
]);
73+
const sideToolsMenu = home.match(/<div id="side-tools-menu"[^>]+>/)?.[0] || "";
74+
assert.ok(!sideToolsMenu.includes("translate-x"));
75+
assert.ok(!sideToolsMenu.includes("transition-[transform,opacity]"));
76+
assert.ok(!home.includes('data-side-tool class="hidden size-10'));
7077
assert.equal(home.split('href="/essays"').length, 3);
7178
includes(post, ['id="article-layout"', 'id="toc-toggle"']);
7279
[home, post].forEach((output) => {

0 commit comments

Comments
 (0)