diff --git a/packages/share-theme/src/scripts/index.ts b/packages/share-theme/src/scripts/index.ts index 4769ea76e..89d368a96 100644 --- a/packages/share-theme/src/scripts/index.ts +++ b/packages/share-theme/src/scripts/index.ts @@ -5,6 +5,7 @@ import setupSearch from "./modules/search.js"; import setupThemeSelector from "./modules/theme.js"; import setupMermaid from "./modules/mermaid.js"; import setupMath from "./modules/math.js"; +import setupSidebars from "./modules/sidebar.js"; import api from "./modules/api.js"; import "highlight.js/styles/default.css"; import "@triliumnext/ckeditor5/src/theme/ck-content.css"; @@ -24,6 +25,7 @@ $try(setupToC); $try(setupExpanders); $try(setupMobileMenu); $try(setupSearch); +$try(setupSidebars); function setupTextNote() { $try(setupMermaid); diff --git a/packages/share-theme/src/scripts/modules/expanders.ts b/packages/share-theme/src/scripts/modules/expanders.ts index c72fe8a1c..b3addce1c 100644 --- a/packages/share-theme/src/scripts/modules/expanders.ts +++ b/packages/share-theme/src/scripts/modules/expanders.ts @@ -12,21 +12,45 @@ // } export default function setupExpanders() { - const expanders = Array.from(document.querySelectorAll("#menu .submenu-item .collapse-button")); + const expanders = document.querySelectorAll("#menu .submenu-item .collapse-button"); for (const expander of expanders) { - const li = expander.parentElement?.parentElement; + const li = expander.closest("li"); if (!li) { continue; } expander.addEventListener("click", e => { - if ((e.target as Element).closest(".submenu-item,.item") !== li) return; e.preventDefault(); e.stopPropagation(); - const ul = li.querySelector("ul")!; - ul.style.height = `${ul.scrollHeight}px`; - setTimeout(() => li.classList.toggle("expanded"), 1); - setTimeout(() => ul.style.height = ``, 200); + + const ul = li.querySelector("ul"); + if (!ul) { + return; + } + + const isExpanded = li.classList.contains("expanded"); + + if (isExpanded) { + // Collapsing + ul.style.height = `${ul.scrollHeight}px`; + // Force reflow + ul.offsetHeight; + + li.classList.remove("expanded"); + ul.style.height = "0"; + } else { + // Expanding + ul.style.height = "0"; + // Force reflow + ul.offsetHeight; + + li.classList.add("expanded"); + ul.style.height = `${ul.scrollHeight}px`; + } + + setTimeout(() => { + ul.style.height = ""; + }, 200); }); } } diff --git a/packages/share-theme/src/scripts/modules/mobile.ts b/packages/share-theme/src/scripts/modules/mobile.ts index f311f5618..36f600e8c 100644 --- a/packages/share-theme/src/scripts/modules/mobile.ts +++ b/packages/share-theme/src/scripts/modules/mobile.ts @@ -1,25 +1,25 @@ -import parents from "../common/parents.js"; - export default function setupMobileMenu() { - function toggleMobileMenu(event: MouseEvent) { - event.stopPropagation(); // Don't prevent default for links - - const isOpen = document.body.classList.contains("menu-open"); - if (isOpen) return document.body.classList.remove("menu-open"); - return document.body.classList.add("menu-open"); + function closeMobileMenus() { + document.body.classList.remove("menu-open"); + document.body.classList.remove("toc-open"); } - const showMenuButton = document.getElementById("show-menu-button"); - showMenuButton?.addEventListener("click", toggleMobileMenu); - window.addEventListener("click", e => { - const isOpen = document.body.classList.contains("menu-open"); - if (!isOpen) return; // This listener is only to close + const isMenuOpen = document.body.classList.contains("menu-open"); + const isTocOpen = document.body.classList.contains("toc-open"); + if (!isMenuOpen && !isTocOpen) return; - // If the click was anywhere in the mobile nav, don't close - if (parents(e.target as HTMLElement, "#left-pane").length) return; - return toggleMobileMenu(e); + const target = e.target as HTMLElement; + + // If the click was anywhere in the mobile nav or TOC, don't close + if (target.closest("#left-pane")) return; + if (target.closest("#toc-pane")) return; + + // If the click was on one of the toggle buttons, the button's own listener will handle it + if (target.closest(".header-button")) return; + + return closeMobileMenus(); }); } diff --git a/packages/share-theme/src/scripts/modules/sidebar.ts b/packages/share-theme/src/scripts/modules/sidebar.ts new file mode 100644 index 000000000..e56a312ff --- /dev/null +++ b/packages/share-theme/src/scripts/modules/sidebar.ts @@ -0,0 +1,22 @@ +const MOBILE_BREAKPOINT = 768; // 48em + +function setupToggle(buttonId: string, className: string, mobileClass: string, otherMobileClass: string) { + const button = document.getElementById(buttonId); + if (!button) return; + + button.addEventListener("click", () => { + const isMobile = window.innerWidth <= MOBILE_BREAKPOINT; + if (isMobile) { + document.body.classList.toggle(mobileClass); + document.body.classList.remove(otherMobileClass); + } else { + const isCollapsed = document.documentElement.classList.toggle(className); + localStorage.setItem(className, String(isCollapsed)); + } + }); +} + +export default function setupSidebars() { + setupToggle("left-pane-toggle-button", "left-pane-collapsed", "menu-open", "toc-open"); + setupToggle("toc-pane-toggle-button", "toc-pane-collapsed", "toc-open", "menu-open"); +} diff --git a/packages/share-theme/src/styles/content.css b/packages/share-theme/src/styles/content.css index 38547dc3e..309ab0e21 100644 --- a/packages/share-theme/src/styles/content.css +++ b/packages/share-theme/src/styles/content.css @@ -80,3 +80,21 @@ body.type-webView { } } } + +/* table styles */ +.ck-content table { + border-collapse: collapse; + width: 100%; +} + +.ck-content table td, +.ck-content table th { + min-width: 120px; + border: 1px solid var(--background-highlight); + padding: 8px; +} + +.ck-content table th { + background-color: var(--background-secondary); + font-weight: bold; +} diff --git a/packages/share-theme/src/styles/layout.css b/packages/share-theme/src/styles/layout.css index 46cabca6e..6b8a30b79 100644 --- a/packages/share-theme/src/styles/layout.css +++ b/packages/share-theme/src/styles/layout.css @@ -2,48 +2,70 @@ html, body { width: 100vw; height: 100vh; + overflow: hidden; + display: flex; + flex-direction: column; } #split-pane { display: flex; flex-direction: row; width: 100vw; - height: 100vh; + flex: 1; overflow: hidden; } #left-pane { display: flex; - min-width: fit-content; - max-width: 20vw; - height: calc(100vh); + flex-direction: column; + min-width: 0; + width: 20vw; + height: 100%; background: var(--background-secondary); border-right: 5px solid var(--background-highlight); - justify-content: flex-end; + justify-content: flex-start; position: sticky; top: 0; overflow-y: auto; flex-shrink: 0; + transition: width 0.3s ease, margin-left 0.3s ease; +} + +.left-pane-collapsed #left-pane { + width: 0; + min-width: 0; + margin-left: -5px; /* offset the border */ + overflow: hidden; + border-right-width: 0; } #right-pane { - display: flex; - margin: 0 auto; flex: 1; overflow: auto; + position: relative; + min-height: 100%; + display: flex; + flex-direction: row; + align-items: flex-start; } #main { order: 2; - max-width: 900px; + max-width: 1600px; flex: 1; - padding: 0 20px; + min-width: 0; + overflow-x: auto; + margin: 0 auto; + padding: 20px; box-sizing: border-box; width: 100%; + transition: max-width 0.3s ease, margin 0.3s ease; } +/* #main always has max-width: 1600px and margin: 0 auto for stability */ + @media (min-width: 1200px) { #main { padding: 0 50px; } -} \ No newline at end of file +} diff --git a/packages/share-theme/src/styles/mobile.css b/packages/share-theme/src/styles/mobile.css index 3b8d95051..09bba1ba6 100644 --- a/packages/share-theme/src/styles/mobile.css +++ b/packages/share-theme/src/styles/mobile.css @@ -1,82 +1,104 @@ -#mobile-header { - display: none; +#header { background: var(--background-secondary); + display: flex; justify-content: space-between; align-items: center; padding: 6px 12px; + border-bottom: 1px solid var(--background-highlight); } -#mobile-header a { +#header-logo { display: flex; align-items: center; gap: 5px; + font-weight: bold; } -#mobile-header a img { +#header-logo img { max-width: 32px; } -#mobile-header button { +.header-button { color: var(--text-menu); background: transparent; margin: 0; - padding: 0; + padding: 4px; border: 0; outline: 0; display: flex; align-items: center; cursor: pointer; border-radius: 6px; - transform: rotate(0); transition: background-color 200ms ease, transform 200ms ease; } +.header-button:hover { + background-color: var(--background-highlight); +} +.header-button-placeholder { + width: 32px; +} @media (max-width: 48em) { - + html, body { - width: unset; - height: unset; + height: 100%; } #split-pane { - overflow: auto; + overflow: hidden; } #right-pane, #main { width: 100%; padding: 0; } - + #main { padding: 1rem; } - - #mobile-header { - display: flex; - } - - #mobile-header button svg { + + #header .header-button svg { width: 32px; height: 32px; } - - #left-pane { + + #left-pane, #toc-pane { position: fixed; top: 0; - left: 0; - width: auto; - transform: translateX(-100%); + bottom: 0; + width: 80%; + max-width: 300px; + background: var(--background-secondary); transition: transform 200ms ease; z-index: 2; + overflow-y: auto; } - - .menu-open #left-pane { + + #left-pane { + left: 0; + transform: translateX(-100%); + } + + #toc-pane { + display: flex; + right: 0; + transform: translateX(100%); + padding: 20px; + } + + #toc-pane h3, + #toc-pane #toc { + display: block; + } + + .menu-open #left-pane, + .toc-open #toc-pane { transform: translateX(0); } - + body::before { content: ""; display: block; @@ -90,14 +112,11 @@ transition: background-color 200ms ease; z-index: 1; } - - body.menu-open::before { + + body.menu-open::before, + body.toc-open::before { background: rgba(0,0,0, 0.6); + pointer-events: auto; } - - - body.menu-open #show-menu-button { - background: var(--background-highlight); - transform: rotate(90deg); - } -} \ No newline at end of file + +} diff --git a/packages/share-theme/src/styles/navbar/header.css b/packages/share-theme/src/styles/navbar/header.css index 0cb93c2c2..c29cc2c5a 100644 --- a/packages/share-theme/src/styles/navbar/header.css +++ b/packages/share-theme/src/styles/navbar/header.css @@ -4,13 +4,6 @@ gap: 20px; } -#site-header > a { - display: flex; - align-items: center; - justify-content: center; - gap: 10px; -} - /* The switch - the box around the slider */ .switch { @@ -138,4 +131,4 @@ input:checked ~ .dark-icon { position: absolute; width: 20px; left: 5px; -} \ No newline at end of file +} diff --git a/packages/share-theme/src/styles/navbar/navbar.css b/packages/share-theme/src/styles/navbar/navbar.css index e409d7813..c9dc47af8 100644 --- a/packages/share-theme/src/styles/navbar/navbar.css +++ b/packages/share-theme/src/styles/navbar/navbar.css @@ -61,7 +61,8 @@ } #menu li:not(.expanded) > ul { - height: 0!important; + height: 0; + overflow: hidden; /* transition: height 1000ms ease; */ } @@ -145,4 +146,4 @@ .collapse-button svg { width: 14px; -} \ No newline at end of file +} diff --git a/packages/share-theme/src/styles/toc.css b/packages/share-theme/src/styles/toc.css index 3e02d6577..d9d6fc52f 100644 --- a/packages/share-theme/src/styles/toc.css +++ b/packages/share-theme/src/styles/toc.css @@ -5,7 +5,24 @@ position: sticky; top: 0; order: 3; -/* padding: 16px 16px 16px 32px; */ + transition: width 0.3s ease, margin-right 0.3s ease; + width: 250px; + flex-shrink: 0; + max-height: 100vh; + overflow-y: auto; + padding-top: 20px; + z-index: 1; + background: var(--background-primary); +} + +.toc-pane-collapsed #toc-pane { + width: 0; + overflow: hidden; +} + +.toc-pane-collapsed #toc-pane h3, +.toc-pane-collapsed #toc-pane #toc { + display: none; } #toc-pane h3 { @@ -18,7 +35,7 @@ margin: 0; border-radius: 6px; padding: 0 0 0 16px; - max-width: 250px; + max-width: 100%; } #toc, #toc ul { @@ -84,11 +101,3 @@ #content h6 a.toc-anchor { margin-left: 10px; } - - - -@media (max-width: 1200px) { - #toc-pane { - display: none; - } -} \ No newline at end of file diff --git a/packages/share-theme/src/templates/page.ejs b/packages/share-theme/src/templates/page.ejs index 7d387cff3..5505fcf5c 100644 --- a/packages/share-theme/src/templates/page.ejs +++ b/packages/share-theme/src/templates/page.ejs @@ -66,6 +66,13 @@ isStatic: <%= !!isStatic %>, theme }; + + (function() { + const leftCollapsed = localStorage.getItem("left-pane-collapsed") === "true"; + const tocCollapsed = localStorage.getItem("toc-pane-collapsed") === "true"; + if (leftCollapsed) document.documentElement.classList.add("left-pane-collapsed"); + if (tocCollapsed) document.documentElement.classList.add("toc-pane-collapsed"); + })(); "> @@ -104,21 +111,22 @@ content = content.replaceAll(headingRe, (...match) => { %>
<%- renderSnippets("body:start") %> -