diff --git a/src/css/injected.scss b/src/css/injected.scss index 15b0e9c8..fbc90533 100644 --- a/src/css/injected.scss +++ b/src/css/injected.scss @@ -704,20 +704,23 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root), /* Native list may keep `.active` after click-through; force our rows clickable, then lock siblings only while a custom drill `.sub` is open. */ - > #bsplus-sidebar-root > li.item { - pointer-events: auto !important; + > #bsplus-sidebar-root .bsplus-sidebar-item { width: auto !important; max-width: none !important; margin: 0 6px 8px !important; box-sizing: border-box !important; } - &:has(> #bsplus-sidebar-root > li.hasChildren.active > .sub) + > #bsplus-sidebar-root > li.item { + pointer-events: auto !important; + } + + &:has(> #bsplus-sidebar-root > li.hasChildren.bsplus-active > .sub) > #bsplus-sidebar-root - > li:not(.hasChildren.active), - &:has(> #bsplus-sidebar-root > li.hasChildren.active > .sub) + > li:not(.hasChildren.bsplus-active), + &:has(> #bsplus-sidebar-root > li.hasChildren.bsplus-active > .sub) > #bsplus-sidebar-root - > li:not(.hasChildren.active) + > li:not(.hasChildren.bsplus-active) * { pointer-events: none !important; } @@ -733,17 +736,21 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root), pointer-events: none; } - > #bsplus-sidebar-root li.hasChildren.active > .sub { + > #bsplus-sidebar-root li.hasChildren.bsplus-active > .sub { pointer-events: auto; + transform: none; background: transparent !important; background-image: none !important; } - > #bsplus-sidebar-root li.hasChildren.active > .sub:has(.hasChildren.active) { + > #bsplus-sidebar-root li.hasChildren.bsplus-active + > .sub:has(.hasChildren.bsplus-active) { pointer-events: none !important; } - > #bsplus-sidebar-root li.hasChildren.active .hasChildren.active > .sub { + > #bsplus-sidebar-root li.hasChildren.bsplus-active + .hasChildren.bsplus-active + > .sub { pointer-events: auto !important; background: transparent !important; background-image: none !important; @@ -765,17 +772,17 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root), background-image: none !important; } - > #bsplus-sidebar-root > li.hasChildren.active > label, + > #bsplus-sidebar-root > li.hasChildren.bsplus-active > label, > #bsplus-sidebar-root.drilling > li.hasChildren > label { pointer-events: none !important; } /* Theme decorations on root leaves (Beach palm/sand) must not show through the transparent drill `.sub` panel. */ - > #bsplus-sidebar-root:has(> li.hasChildren.active > .sub) - > li:not(.hasChildren.active)::before, - > #bsplus-sidebar-root:has(> li.hasChildren.active > .sub) - > li:not(.hasChildren.active)::after { + > #bsplus-sidebar-root:has(> li.hasChildren.bsplus-active > .sub) + > li:not(.hasChildren.bsplus-active)::before, + > #bsplus-sidebar-root:has(> li.hasChildren.bsplus-active > .sub) + > li:not(.hasChildren.bsplus-active)::after { content: none !important; display: none !important; animation: none !important; @@ -812,8 +819,9 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root), #bsplus-sidebar-root .sub > ul { list-style: none; margin: 0; - padding: 0 0 16px; + padding: 8px 0 16px; } + } #menu li > label > svg, diff --git a/src/css/injected/sidebar-animation.scss b/src/css/injected/sidebar-animation.scss index 03cd1172..d68e97ad 100644 --- a/src/css/injected/sidebar-animation.scss +++ b/src/css/injected/sidebar-animation.scss @@ -1,20 +1,22 @@ -#menu ul:has(li.hasChildren.active) li.hasChildren.active { +#menu ul:has(li.hasChildren:is(.active, .bsplus-active)) + li.hasChildren:is(.active, .bsplus-active) { box-shadow: inset 0px 0 var(--item-colour, transparent) !important; background: transparent !important; } -#menu ul:not(:has(li.hasChildren.active)) { +#menu ul:not(:has(li.hasChildren:is(.active, .bsplus-active))) { overflow-y: scroll !important; } -#menu > ul:has(li.hasChildren.active) > li::before, -#menu > ul:has(li.hasChildren.active) > li::after, -#menu > ul ul:has(li.hasChildren.active) > li::before, -#menu > ul ul:has(li.hasChildren.active) > li::after, -#menu > ul:has(li.hasChildren.active) > li > label, -#menu > ul:has(li.hasChildren.active) > li > svg, -#menu > ul ul:has(li.hasChildren.active) > li > label, -#menu > ul ul:has(li.hasChildren.active) > li > svg { +#menu > ul:has(li.hasChildren:is(.active, .bsplus-active)) > li::before, +#menu > ul:has(li.hasChildren:is(.active, .bsplus-active)) > li::after, +#menu > ul ul:has(li.hasChildren:is(.active, .bsplus-active)) > li::before, +#menu > ul ul:has(li.hasChildren:is(.active, .bsplus-active)) > li::after, +#menu > ul:has(li.hasChildren:is(.active, .bsplus-active)) > li > label, +#menu > ul:has(li.hasChildren:is(.active, .bsplus-active)) > li > svg, +#menu > ul ul:has(li.hasChildren:is(.active, .bsplus-active)) > li > label, +#menu > ul ul:has(li.hasChildren:is(.active, .bsplus-active)) > li > svg { + /* !important beats theme animations (e.g. Beach palm-slide forwards). */ transform: translateX(-320px) !important; animation: none !important; } @@ -28,12 +30,13 @@ transition: transform 0.3s ease, left 0.4s cubic-bezier(0.4, 0, 0.2, 1); } -#menu > ul:has(li.hasChildren.active) > li.active { +#menu > ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li:is(.active, .bsplus-active) { background: transparent !important; } -#menu li.hasChildren:not(.active) li .sub { - display: none; +#menu li.hasChildren:not(:is(.active, .bsplus-active)) li .sub { + display: none; // improves performance } .sub .sub { @@ -41,16 +44,42 @@ padding-top: 2px; } -.sub:has(ul > li.hasChildren.active) > .nav > .back { +.sub:has(ul > li.hasChildren:is(.active, .bsplus-active)) > .nav > .back { display: none !important; } -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li::before, -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li::before, -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li > label, -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li > svg, -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li > label, -body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li > svg { +/* Icon-only collapsed: submenu slides over narrow icons */ +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li::before, +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul + ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li::before, +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li + > label, +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li + > svg, +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul + ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li + > label, +body.icon-only-sidebar:not(:has(#menu li.hasChildren:is(.active, .bsplus-active))) + #menu + > ul + ul:has(li.hasChildren:is(.active, .bsplus-active)) + > li + > svg { transform: translateX(-70px); } diff --git a/src/css/injected/sidebar-styles.scss b/src/css/injected/sidebar-styles.scss index b43d03cb..215d0ee3 100644 --- a/src/css/injected/sidebar-styles.scss +++ b/src/css/injected/sidebar-styles.scss @@ -19,9 +19,9 @@ $sidebar-motion: 0.4s cubic-bezier(0.4, 0, 0.2, 1); } #menu.bsplus-custom-sidebar { - $item: "> #bsplus-sidebar-root > li.item:not(.bsplus-sidebar-edit-header):not(.bsplus-sidebar-edit-actions)"; - $label: "> #bsplus-sidebar-root > li.item > label:not(.toggle)"; - $active: "> #bsplus-sidebar-root > li.item.active:not(.hasChildren)"; + $item: "> #bsplus-sidebar-root .bsplus-sidebar-item"; + $label: "> #bsplus-sidebar-root .bsplus-sidebar-item > label:not(.toggle)"; + $active: "> #bsplus-sidebar-root .bsplus-sidebar-item.bsplus-active:not(.hasChildren)"; /* Look: base radius (pill / sharp / strip override). Density/radius animate with the same easing as sidebar width. */ @@ -37,6 +37,7 @@ $sidebar-motion: 0.4s cubic-bezier(0.4, 0, 0.2, 1); } #{$label} { + min-height: 28px !important; transition: font-size $sidebar-motion, line-height $sidebar-motion, @@ -147,7 +148,7 @@ $sidebar-motion: 0.4s cubic-bezier(0.4, 0, 0.2, 1); margin: 0 !important; font-size: 13px !important; line-height: 1.2 !important; - min-height: 0 !important; + min-height: 20px !important; height: auto !important; white-space: nowrap !important; @@ -169,6 +170,7 @@ $sidebar-motion: 0.4s cubic-bezier(0.4, 0, 0.2, 1); padding: 0 !important; font-size: 17px !important; line-height: 1.25 !important; + min-height: 30px !important; > svg { width: 30px !important; diff --git a/src/plugins/built-in/gradeAnalytics/loadAnalyticsPage.ts b/src/plugins/built-in/gradeAnalytics/loadAnalyticsPage.ts index 3d062b4e..c653d7cb 100644 --- a/src/plugins/built-in/gradeAnalytics/loadAnalyticsPage.ts +++ b/src/plugins/built-in/gradeAnalytics/loadAnalyticsPage.ts @@ -1,3 +1,4 @@ +import { getNativeMenuList } from "@/seqta/ui/sidebar/parseNativeMenu"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { waitForElm } from "@/seqta/utils/waitForElm"; import { renderAnalyticsPage } from "./ui"; @@ -23,10 +24,11 @@ export async function loadAnalyticsPage(): Promise { async function loadAnalyticsPageInner(): Promise { document.title = "Analytics ― SEQTA Learn"; - document.querySelectorAll("#menu .item").forEach((item) => { + const menu = getNativeMenuList(); + menu?.querySelectorAll(".item").forEach((item) => { item.classList.remove("active"); }); - document.querySelector('[data-key="analytics"]')?.classList.add("active"); + menu?.querySelector('[data-key="analytics"]')?.classList.add("active"); let main: HTMLElement; try { diff --git a/src/seqta/ui/sidebar/Sidebar.svelte b/src/seqta/ui/sidebar/Sidebar.svelte index 5c43a338..ed36ae47 100644 --- a/src/seqta/ui/sidebar/Sidebar.svelte +++ b/src/seqta/ui/sidebar/Sidebar.svelte @@ -2,10 +2,7 @@ import { onDestroy } from "svelte"; import Sortable from "sortablejs"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; - import { - restoreCustomMenuActive, - sidebarState, - } from "./sidebarState.svelte"; + import { sidebarState } from "./sidebarState.svelte"; import SidebarItem from "./SidebarItem.svelte"; import type { SidebarDrillFrame, @@ -102,7 +99,7 @@ `${Math.round(width)}px`, ); // Fallback clone is created just after onStart; pin size + drop - // `.active` so theme/active rules don't expand it to full width. + // Active styling makes fallback clones wider; keep the source width. requestAnimationFrame(() => { const dragEl = document.querySelector( ".bsplus-sortable-drag", @@ -110,7 +107,7 @@ if (!dragEl) return; dragEl.style.width = `${Math.round(width)}px`; dragEl.style.maxWidth = `${Math.round(width)}px`; - dragEl.classList.remove("active"); + dragEl.classList.remove("bsplus-active"); }); }, onEnd: (evt) => { @@ -156,15 +153,6 @@ }; }); - // SEQTA strips `.active` from `#menu li` after clicks — re-apply from state. - // Do NOT MutationObserver class changes here: restore writes `.active`, SEQTA - // strips it again, and the feedback loop freezes the tab. - $effect(() => { - void sidebarState.activeKey; - void sidebarState.isDrilling; - restoreCustomMenuActive(); - }); - // Drill `.sub` is position:absolute inside this scrollport — if the list was // scrolled down (e.g. Folios/Goals near the bottom), the panel sits under the // logo until we reset. Also reset when going back up the stack. @@ -269,7 +257,7 @@ {@const folder = current.folder}
  • diff --git a/src/seqta/ui/sidebar/sidebarState.svelte.ts b/src/seqta/ui/sidebar/sidebarState.svelte.ts index 543f8830..984d0e8e 100644 --- a/src/seqta/ui/sidebar/sidebarState.svelte.ts +++ b/src/seqta/ui/sidebar/sidebarState.svelte.ts @@ -37,12 +37,6 @@ function filterVisible(items: SidebarItem[]): SidebarItem[] { return items.filter((item) => menuItems[item.key]?.toggle !== false); } -function ensureActive(el: Element | null | undefined) { - if (el instanceof HTMLElement && !el.classList.contains("active")) { - el.classList.add("active"); - } -} - function resetSidebarScroll() { const root = document.getElementById("bsplus-sidebar-root"); if (!(root instanceof HTMLElement)) return; @@ -52,52 +46,6 @@ function resetSidebarScroll() { }); } -/** - * SEQTA (and some themes) strip `.active` from `#menu li` after navigation. - * Theme decorations and drill `.sub` chrome depend on that class on our list. - * - * While drilling, never re-apply route-active on root leaves — themes like Beach - * paint palm/sand on `#menu > ul > li:not(.hasChildren).active`, and our `.sub` - * is transparent so those decorations show through over folder contents. - */ -export function restoreCustomMenuActive() { - const root = document.getElementById("bsplus-sidebar-root"); - if (!root) return; - - for (const li of root.querySelectorAll("li.hasChildren")) { - if (!(li instanceof HTMLElement)) continue; - if (!li.querySelector(":scope > .sub")) continue; - ensureActive(li); - } - - const activeKey = sidebarState.activeKey; - const drilling = sidebarState.isDrilling; - - if (drilling) { - if (activeKey) { - ensureActive( - root.querySelector(`.sub li.item[data-key="${CSS.escape(activeKey)}"]`), - ); - } - for (const li of root.querySelectorAll( - '.sub li.item[aria-current="page"]', - )) { - ensureActive(li); - } - return; - } - - if (activeKey) { - ensureActive( - root.querySelector(`li.item[data-key="${CSS.escape(activeKey)}"]`), - ); - } - - for (const li of root.querySelectorAll('li.item[aria-current="page"]')) { - ensureActive(li); - } -} - /** Clear native drill state so it cannot steal pointer-events from the custom list. */ export function clearNativeDrillActive(menu: HTMLElement) { getNativeMenuList(menu) @@ -251,7 +199,10 @@ class SidebarState { for (const frame of this.drillStack) { const folder = cursor.find((item) => item.key === frame.key); - if (!folder?.hasChildren) break; + if (!folder?.hasChildren) { + next.push(...this.drillStack.slice(next.length)); + break; + } const children = filterVisible(folder.children); next.push({ key: folder.key, label: folder.label, items: children }); cursor = children; @@ -380,18 +331,7 @@ class SidebarState { const native = findNativeMenuEntry(menu, item); if (native) { - // Clear native drill once only — repeating clearNativeDrillActive fights - // SEQTA (it re-adds .active) and used to freeze the tab via menu sync. - clearNativeDrillActive(menu); native.click(); - clearNativeDrillActive(menu); - restoreCustomMenuActive(); - requestAnimationFrame(() => { - clearNativeDrillActive(menu); - restoreCustomMenuActive(); - }); - // Later pass restores custom `.active` only (no native clear loop). - setTimeout(() => restoreCustomMenuActive(), 50); return; } diff --git a/src/seqta/utils/patchThemeImagesPageContext.ts b/src/seqta/utils/patchThemeImagesPageContext.ts index 4d28c7d2..cf74f5e6 100644 --- a/src/seqta/utils/patchThemeImagesPageContext.ts +++ b/src/seqta/utils/patchThemeImagesPageContext.ts @@ -55,6 +55,10 @@ function sendPayload(payload: Record): void { bridge.setAttribute("data-rev", String(Number(bridge.getAttribute("data-rev") || "0") + 1)); } +function preserveCustomSidebarActive(css: string): string { + return css.replace(/\.active(?![\w-])/g, ":is(.active, .bsplus-active)"); +} + export async function syncThemeToPage(input: ThemePageSyncInput): Promise { if (input.clear) { sendPayload({ clear: true }); @@ -63,8 +67,12 @@ export async function syncThemeToPage(input: ThemePageSyncInput): Promise const payload: Record = {}; if (input.clearPreview) payload.clearPreview = true; - if (input.customCss !== undefined) payload.customCss = input.customCss; - if (input.previewCss !== undefined) payload.previewCss = input.previewCss; + if (input.customCss !== undefined) { + payload.customCss = preserveCustomSidebarActive(input.customCss); + } + if (input.previewCss !== undefined) { + payload.previewCss = preserveCustomSidebarActive(input.previewCss); + } if (input.images !== undefined) { payload.images = await Promise.all( input.images.map(async (image) => ({