Merge b08126ec2b44456ccfd8f0846dfc750f15e407bc into f8b414c354bac56277bca454ead7d6958b36acde

This commit is contained in:
Wael Nasreddine 2026-02-01 22:34:45 +02:00 committed by GitHub
commit f9ebd85cca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 213 additions and 95 deletions

View File

@ -5,6 +5,7 @@ import setupSearch from "./modules/search.js";
import setupThemeSelector from "./modules/theme.js"; import setupThemeSelector from "./modules/theme.js";
import setupMermaid from "./modules/mermaid.js"; import setupMermaid from "./modules/mermaid.js";
import setupMath from "./modules/math.js"; import setupMath from "./modules/math.js";
import setupSidebars from "./modules/sidebar.js";
import api from "./modules/api.js"; import api from "./modules/api.js";
import "highlight.js/styles/default.css"; import "highlight.js/styles/default.css";
import "@triliumnext/ckeditor5/src/theme/ck-content.css"; import "@triliumnext/ckeditor5/src/theme/ck-content.css";
@ -24,6 +25,7 @@ $try(setupToC);
$try(setupExpanders); $try(setupExpanders);
$try(setupMobileMenu); $try(setupMobileMenu);
$try(setupSearch); $try(setupSearch);
$try(setupSidebars);
function setupTextNote() { function setupTextNote() {
$try(setupMermaid); $try(setupMermaid);

View File

@ -12,21 +12,45 @@
// } // }
export default function setupExpanders() { 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) { for (const expander of expanders) {
const li = expander.parentElement?.parentElement; const li = expander.closest("li");
if (!li) { if (!li) {
continue; continue;
} }
expander.addEventListener("click", e => { expander.addEventListener("click", e => {
if ((e.target as Element).closest(".submenu-item,.item") !== li) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const ul = li.querySelector("ul")!;
ul.style.height = `${ul.scrollHeight}px`; const ul = li.querySelector("ul");
setTimeout(() => li.classList.toggle("expanded"), 1); if (!ul) {
setTimeout(() => ul.style.height = ``, 200); 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);
}); });
} }
} }

View File

@ -1,25 +1,25 @@
import parents from "../common/parents.js";
export default function setupMobileMenu() { export default function setupMobileMenu() {
function toggleMobileMenu(event: MouseEvent) { function closeMobileMenus() {
event.stopPropagation(); // Don't prevent default for links document.body.classList.remove("menu-open");
document.body.classList.remove("toc-open");
const isOpen = document.body.classList.contains("menu-open");
if (isOpen) return document.body.classList.remove("menu-open");
return document.body.classList.add("menu-open");
} }
const showMenuButton = document.getElementById("show-menu-button");
showMenuButton?.addEventListener("click", toggleMobileMenu);
window.addEventListener("click", e => { window.addEventListener("click", e => {
const isOpen = document.body.classList.contains("menu-open"); const isMenuOpen = document.body.classList.contains("menu-open");
if (!isOpen) return; // This listener is only to close const isTocOpen = document.body.classList.contains("toc-open");
if (!isMenuOpen && !isTocOpen) return;
// If the click was anywhere in the mobile nav, don't close const target = e.target as HTMLElement;
if (parents(e.target as HTMLElement, "#left-pane").length) return;
return toggleMobileMenu(e); // 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();
}); });
} }

View File

@ -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");
}

View File

@ -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;
}

View File

@ -2,48 +2,70 @@ html,
body { body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
} }
#split-pane { #split-pane {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100vw; width: 100vw;
height: 100vh; flex: 1;
overflow: hidden; overflow: hidden;
} }
#left-pane { #left-pane {
display: flex; display: flex;
min-width: fit-content; flex-direction: column;
max-width: 20vw; min-width: 0;
height: calc(100vh); width: 20vw;
height: 100%;
background: var(--background-secondary); background: var(--background-secondary);
border-right: 5px solid var(--background-highlight); border-right: 5px solid var(--background-highlight);
justify-content: flex-end; justify-content: flex-start;
position: sticky; position: sticky;
top: 0; top: 0;
overflow-y: auto; overflow-y: auto;
flex-shrink: 0; 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 { #right-pane {
display: flex;
margin: 0 auto;
flex: 1; flex: 1;
overflow: auto; overflow: auto;
position: relative;
min-height: 100%;
display: flex;
flex-direction: row;
align-items: flex-start;
} }
#main { #main {
order: 2; order: 2;
max-width: 900px; max-width: 1600px;
flex: 1; flex: 1;
padding: 0 20px; min-width: 0;
overflow-x: auto;
margin: 0 auto;
padding: 20px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; 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) { @media (min-width: 1200px) {
#main { #main {
padding: 0 50px; padding: 0 50px;
} }
} }

View File

@ -1,82 +1,104 @@
#mobile-header { #header {
display: none;
background: var(--background-secondary); background: var(--background-secondary);
display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 6px 12px; padding: 6px 12px;
border-bottom: 1px solid var(--background-highlight);
} }
#mobile-header a { #header-logo {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 5px;
font-weight: bold;
} }
#mobile-header a img { #header-logo img {
max-width: 32px; max-width: 32px;
} }
#mobile-header button { .header-button {
color: var(--text-menu); color: var(--text-menu);
background: transparent; background: transparent;
margin: 0; margin: 0;
padding: 0; padding: 4px;
border: 0; border: 0;
outline: 0; outline: 0;
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
border-radius: 6px; border-radius: 6px;
transform: rotate(0);
transition: background-color 200ms ease, transform 200ms ease; 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) { @media (max-width: 48em) {
html, html,
body { body {
width: unset; height: 100%;
height: unset;
} }
#split-pane { #split-pane {
overflow: auto; overflow: hidden;
} }
#right-pane, #main { #right-pane, #main {
width: 100%; width: 100%;
padding: 0; padding: 0;
} }
#main { #main {
padding: 1rem; padding: 1rem;
} }
#mobile-header { #header .header-button svg {
display: flex;
}
#mobile-header button svg {
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
#left-pane { #left-pane, #toc-pane {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; bottom: 0;
width: auto; width: 80%;
transform: translateX(-100%); max-width: 300px;
background: var(--background-secondary);
transition: transform 200ms ease; transition: transform 200ms ease;
z-index: 2; 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); transform: translateX(0);
} }
body::before { body::before {
content: ""; content: "";
display: block; display: block;
@ -90,14 +112,11 @@
transition: background-color 200ms ease; transition: background-color 200ms ease;
z-index: 1; z-index: 1;
} }
body.menu-open::before { body.menu-open::before,
body.toc-open::before {
background: rgba(0,0,0, 0.6); background: rgba(0,0,0, 0.6);
pointer-events: auto;
} }
}
body.menu-open #show-menu-button {
background: var(--background-highlight);
transform: rotate(90deg);
}
}

View File

@ -4,13 +4,6 @@
gap: 20px; gap: 20px;
} }
#site-header > a {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
/* The switch - the box around the slider */ /* The switch - the box around the slider */
.switch { .switch {
@ -138,4 +131,4 @@ input:checked ~ .dark-icon {
position: absolute; position: absolute;
width: 20px; width: 20px;
left: 5px; left: 5px;
} }

View File

@ -61,7 +61,8 @@
} }
#menu li:not(.expanded) > ul { #menu li:not(.expanded) > ul {
height: 0!important; height: 0;
overflow: hidden;
/* transition: height 1000ms ease; */ /* transition: height 1000ms ease; */
} }
@ -145,4 +146,4 @@
.collapse-button svg { .collapse-button svg {
width: 14px; width: 14px;
} }

View File

@ -5,7 +5,24 @@
position: sticky; position: sticky;
top: 0; top: 0;
order: 3; 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 { #toc-pane h3 {
@ -18,7 +35,7 @@
margin: 0; margin: 0;
border-radius: 6px; border-radius: 6px;
padding: 0 0 0 16px; padding: 0 0 0 16px;
max-width: 250px; max-width: 100%;
} }
#toc, #toc ul { #toc, #toc ul {
@ -84,11 +101,3 @@
#content h6 a.toc-anchor { #content h6 a.toc-anchor {
margin-left: 10px; margin-left: 10px;
} }
@media (max-width: 1200px) {
#toc-pane {
display: none;
}
}

View File

@ -66,6 +66,13 @@
isStatic: <%= !!isStatic %>, isStatic: <%= !!isStatic %>,
theme 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");
})();
</script> </script>
<!-- HTML Meta Tags --> <!-- HTML Meta Tags -->
<meta name="description" content="<%= note.getLabelValue("shareDescription") %>"> <meta name="description" content="<%= note.getLabelValue("shareDescription") %>">
@ -104,21 +111,22 @@ content = content.replaceAll(headingRe, (...match) => {
%> %>
<body data-note-id="<%= note.noteId %>" class="type-<%= note.type %>" data-ancestor-note-id="<%= subRoot.note.noteId %>"> <body data-note-id="<%= note.noteId %>" class="type-<%= note.type %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
<%- renderSnippets("body:start") %> <%- renderSnippets("body:start") %>
<div id="mobile-header"> <div id="header">
<a href="<%= shareRootLink %>"> <button aria-label="Toggle Navigation" id="left-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
<a href="<%= shareRootLink %>" id="header-logo">
<img src="<%= logoUrl %>" width="32" height="<%= mobileLogoHeight %>" alt="Logo" /> <img src="<%= logoUrl %>" width="32" height="<%= mobileLogoHeight %>" alt="Logo" />
<%= subRoot.note.title %> <%= subRoot.note.title %>
</a> </a>
<button aria-label="Show Mobile Menu" id="show-menu-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button> <% if (headingMatches.length > 1) { %>
<button aria-label="Toggle Table of Contents" id="toc-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10V7h2v2h-2zm0 6h2v-2h-2v2z"></path></svg></button>
<% } else { %>
<div class="header-button-placeholder"></div>
<% } %>
</div> </div>
<div id="split-pane"> <div id="split-pane">
<div id="left-pane"> <div id="left-pane">
<div id="navigation"> <div id="navigation">
<div id="site-header"> <div id="site-header">
<a href="<%= shareRootLink %>">
<img src="<%= logoUrl %>" width="<%= logoWidth %>" height="<%= logoHeight %>" alt="Logo" />
<%= subRoot.note.title %>
</a>
<div class="theme-selection"> <div class="theme-selection">
<span id="sitetheme"><%= t("share_theme.site-theme") %></span> <span id="sitetheme"><%= t("share_theme.site-theme") %></span>
<label class="switch"> <label class="switch">