Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions wiki/frappe_wiki/doctype/wiki_document/wiki_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ def get_web_context(self) -> dict:
# Render markdown and extract TOC headings in one pass
rendered_content, toc_headings = render_markdown_with_toc(self.content or "")

# Ancestor nodes that should be expanded in the sidebar tree on initial render
expanded_nodes = set(self.get_ancestors()) if self.lft else set()

# Base context with defaults for orphan documents
context = {
"doc": self,
Expand All @@ -335,6 +338,7 @@ def get_web_context(self) -> dict:
"toc_headings": toc_headings,
"raw_markdown": self.content or "",
"nested_tree": [],
"expanded_nodes": expanded_nodes,
"prev_doc": None,
"next_doc": None,
"edit_link": self.get_edit_link(),
Expand Down
2 changes: 1 addition & 1 deletion wiki/templates/wiki/document.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div x-data="wikiPage" x-init="$store.navigation.init()" class="flex-1 min-w-0 flex">
<!-- Main Content Area -->
<main class="flex-1 min-w-0 px-4 py-6 lg:px-6 lg:py-8 xl:px-12 flex justify-center">
<article class="w-full min-w-0 transition-[max-width,opacity] duration-300 xl:max-w-[80ch]"
<article class="w-full min-w-0 transition-opacity duration-300 xl:max-w-[80ch]"
:class="{ 'opacity-50': $store.navigation.loading, 'xl:!max-w-[100ch]': $store.sidebar?.isCollapsed }">
<!-- Page Header -->
<header class="py-6 mb-6 border-b border-[var(--outline-gray-1)]">
Expand Down
2 changes: 1 addition & 1 deletion wiki/templates/wiki/includes/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<button @click="$dispatch('open-search')"
class="inline-flex items-center gap-1 px-2 py-1 rounded-full bg-[var(--surface-gray-2)] ring-1 ring-inset ring-[var(--outline-gray-2)] hover:bg-[var(--surface-gray-3)] transition-colors duration-200 cursor-pointer">
{{ render_icon("search", "-ml-0.5 w-4 h-4 text-[var(--ink-gray-5)]") }}
<kbd class="text-xs/4 text-[var(--ink-gray-4)] font-sans" x-text="navigator.platform.includes('Mac') ? '⌘K' : 'Ctrl + K'"></kbd>
<kbd class="text-xs/4 text-[var(--ink-gray-4)] font-sans" x-text="navigator.platform.includes('Mac') ? '⌘K' : 'Ctrl+K'">⌘K</kbd>
</button>

{# Separator before toggle (only if there are text links) #}
Expand Down
12 changes: 8 additions & 4 deletions wiki/templates/wiki/includes/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{% from "templates/wiki/macros/sidebar_tree.html" import render_wiki_tree %}
{% from "templates/wiki/macros/buttons.html" import ghost_button_full, icon_button, render_icon %}

<div class="wiki-sidebar hidden lg:flex bg-[var(--surface-menu-bar)] border-r border-[var(--outline-gray-1)] flex-col flex-shrink-0 transition-[width,border-width] duration-300 ease-in-out overflow-hidden sticky top-[53px] h-[calc(100vh-53px)]"
<div class="wiki-sidebar hidden lg:flex w-72 bg-[var(--surface-menu-bar)] border-r border-[var(--outline-gray-1)] flex-col flex-shrink-0 transition-[width,border-width] duration-300 ease-in-out overflow-hidden sticky top-[53px] h-[calc(100vh-53px)]"
x-data="wikiSidebar()"
:style="isCollapsed ? 'width: 0; border-right-width: 0;' : 'width: 18rem;'">

<!-- Scrollable Navigation -->
<nav class="px-2 py-2 flex-1 overflow-y-auto">
{{ render_wiki_tree(nested_tree, current_route=doc.route if doc else '') }}
{{ render_wiki_tree(nested_tree, current_route=doc.route if doc else '', expanded_nodes=expanded_nodes or set()) }}
</nav>
</div>

<!-- Sidebar Toggle Button (appears on hover at sidebar edge, always visible when collapsed) -->
<div x-data="{ hovered: false }"
class="hidden lg:flex items-center fixed z-40 top-[53px] h-[calc(100vh-53px)] transition-[left] duration-300 ease-in-out"
class="wiki-sidebar-toggle hidden lg:flex items-center fixed z-40 top-[53px] h-[calc(100vh-53px)] transition-[left] duration-300 ease-in-out"
:style="$store.sidebar.isCollapsed ? 'left: 0.5rem;' : 'left: calc(18rem - 0.25rem);'"
@mouseenter="hovered = true"
@mouseleave="hovered = false">
Expand Down Expand Up @@ -52,7 +52,11 @@

// Sidebar store for collapse state
Alpine.store('sidebar', {
isCollapsed: Alpine.$persist(false),
isCollapsed: Alpine.$persist(false).as('sidebar.isCollapsed'),
init() {
// Remove pre-Alpine CSS attribute — Alpine's :style takes over from here
document.documentElement.removeAttribute('data-sidebar-collapsed');
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
toggle() {
this.isCollapsed = !this.isCollapsed;
}
Expand Down
18 changes: 16 additions & 2 deletions wiki/templates/wiki/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,29 @@
<link rel="stylesheet" href="/assets/wiki/css/hljs-github-light.css">

<!-- Hide x-cloak elements until Alpine loads -->
<style>[x-cloak] { display: none !important; }</style>
<style>
[x-cloak] { display: none !important; }
/* Sidebar collapsed state before Alpine loads */
html[data-sidebar-collapsed] .wiki-sidebar { width: 0 !important; border-right-width: 0 !important; }
html[data-sidebar-collapsed] .wiki-sidebar-toggle { left: 0.5rem !important; }
html:not([data-sidebar-collapsed]) .wiki-sidebar-toggle { left: calc(18rem - 0.25rem); }
</style>

<!-- Theme initialization - must run before page renders to prevent flash -->
<!-- Theme + sidebar initialization - must run before page renders to prevent flash -->
<script>
(function() {
const stored = localStorage.getItem('wiki-theme');
// Default to light theme if no preference is stored
const theme = stored || 'light';
document.documentElement.setAttribute('data-theme', theme);

// Read sidebar collapsed state to prevent layout shift
try {
var sidebarState = localStorage.getItem('sidebar.isCollapsed');
if (sidebarState === 'true') {
document.documentElement.setAttribute('data-sidebar-collapsed', '');
}
} catch(e) {}
})();

// CSRF token for API requests
Expand Down
7 changes: 4 additions & 3 deletions wiki/templates/wiki/macros/sidebar_tree.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% macro render_wiki_tree(nodes, level=0, current_route='') %}
{% macro render_wiki_tree(nodes, level=0, current_route='', expanded_nodes=set()) %}
{% if nodes %}
<ul class="wiki-tree list-none m-0 px-0 py-0.5 space-y-0.5">
{% for node in nodes %}
Expand Down Expand Up @@ -54,8 +54,9 @@
<div id="{{ node.name }}-children"
class="wiki-children overflow-hidden mt-0.5 ml-[14px] pl-2 border-l border-[var(--outline-gray-2)]"
x-show="isExpanded('{{ node.name }}')"
x-collapse>
{{ render_wiki_tree(node.children, level + 1, current_route) }}
x-collapse
{% if node.name not in expanded_nodes %}x-cloak{% endif %}>
{{ render_wiki_tree(node.children, level + 1, current_route, expanded_nodes) }}
Comment on lines 54 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential flash for ancestor nodes on first visit.

The logic correctly uses server-computed expanded_nodes to avoid x-cloak on ancestor nodes. However, there's a timing concern:

  1. Ancestor nodes render visible (no x-cloak)
  2. Alpine initializes, x-show="isExpanded(...)" evaluates to false (empty expandedStates)
  3. expandCurrentPageParents() runs in $nextTick, setting expandedStates[nodeId] = true

Between steps 2 and 3, ancestor nodes might briefly collapse before re-expanding. The x-collapse directive may mitigate this by deferring the animation, and on subsequent visits expandedStates is persisted so this only affects first-time visitors.

Consider initializing expandedStates synchronously (not in $nextTick) in wikiSidebar.init() to eliminate any potential flash.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wiki/templates/wiki/macros/sidebar_tree.html` around lines 54 - 59, The
sidebar can briefly collapse on first render because expandCurrentPageParents()
sets expandedStates in $nextTick; initialize expandedStates synchronously during
wikiSidebar.init() instead so the server-provided expanded_nodes map is applied
before Alpine evaluates x-show; locate wikiSidebar.init(),
expandCurrentPageParents(), and the expandedStates object and ensure
expandedStates is populated from the server-side expanded_nodes (and respects
node IDs used in isExpanded('{{ node.name }}')) immediately rather than inside a
$nextTick callback, removing the timing gap that causes the flash while keeping
x-cloak usage for nodes not in expanded_nodes.

</div>
{% endif %}
</li>
Expand Down
Loading