FossilRepo

fossilrepo / templates / includes / sidebar.html
1
<aside aria-label="Sidebar navigation" x-data="{ collapsed: localStorage.getItem('sidebarCollapsed') === 'true', projectsOpen: true, docsOpen: false }"
2
:class="collapsed ? 'w-14' : 'w-60'"
3
class="hidden lg:flex lg:flex-col flex-shrink-0 bg-gray-900 border-r border-gray-700 overflow-y-auto overflow-x-hidden transition-all duration-200">
4
5
<nav class="flex-1 px-2 py-3 space-y-1 overflow-y-auto">
6
7
<!-- Expand button (collapsed state only) — prominent, centered -->
8
<button x-show="collapsed" style="display:none"
9
@click="collapsed = false; localStorage.setItem('sidebarCollapsed', 'false')"
10
class="flex items-center justify-center w-full rounded-md px-2 py-2 mb-1 text-gray-400 hover:text-white hover:bg-gray-800 transition-colors"
11
title="Expand sidebar">
12
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
13
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
14
</svg>
15
</button>
16
17
<!-- Dashboard + collapse toggle (expanded state) -->
18
<div class="flex items-center gap-1" :class="collapsed && 'justify-center'">
19
<a href="{% url 'dashboard' %}"
20
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm font-medium {% if request.path == '/dashboard/' %}bg-gray-800 text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
21
:class="collapsed ? '' : 'flex-1'"
22
:title="collapsed ? 'Dashboard' : ''">
23
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
24
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955a1.126 1.126 0 011.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
25
</svg>
26
<span x-show="!collapsed" class="truncate">Dashboard</span>
27
</a>
28
<button x-show="!collapsed" @click="collapsed = true; localStorage.setItem('sidebarCollapsed', 'true')"
29
class="flex-shrink-0 rounded-md p-1.5 text-gray-500 hover:text-gray-300 hover:bg-gray-800 transition-colors"
30
title="Collapse sidebar">
31
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
32
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
33
</svg>
34
</button>
35
</div>
36
37
<!-- Explore -->
38
<a href="{% url 'explore' %}"
39
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm font-medium {% if request.path == '/explore/' %}bg-gray-800 text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
40
:title="collapsed ? 'Explore' : ''">
41
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
42
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
43
</svg>
44
<span x-show="!collapsed" class="truncate">Explore</span>
45
</a>
46
47
<!-- Projects section -->
48
{% if perms.projects.view_project %}
49
<div>
50
<button @click="collapsed ? (collapsed = false, projectsOpen = true) : (projectsOpen = !projectsOpen)"
51
class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/projects/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
52
:title="collapsed ? 'Projects' : ''">
53
<span class="flex items-center gap-2">
54
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
55
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
56
</svg>
57
<span x-show="!collapsed" class="truncate">Projects</span>
58
</span>
59
<svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="projectsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
60
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
61
</svg>
62
</button>
63
<div x-show="projectsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-2">
64
{% for entry in sidebar_grouped %}
65
<div class="mt-2">
66
<div class="flex items-center gap-1.5 px-2 py-1 text-xs font-semibold text-gray-500 uppercase tracking-wider">
67
<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
68
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" />
69
</svg>
70
{{ entry.group.name }}
71
</div>
72
{% for project in entry.projects %}
73
{% include "includes/_sidebar_project.html" %}
74
{% endfor %}
75
</div>
76
{% endfor %}
77
{% for project in sidebar_ungrouped %}
78
{% include "includes/_sidebar_project.html" %}
79
{% endfor %}
80
{% if perms.projects.add_project %}
81
<a href="{% url 'projects:create' %}"
82
class="block rounded-md px-2 py-1.5 text-sm text-gray-600 hover:text-brand-light">
83
+ New
84
</a>
85
{% endif %}
86
</div>
87
</div>
88
{% endif %}
89
90
<!-- Knowledge Base (org wiki — user-editable) -->
91
{% if perms.pages.view_page %}
92
<div x-data="{ kbOpen: false }">
93
<button @click="collapsed ? (collapsed = false, kbOpen = true) : (kbOpen = !kbOpen)"
94
class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium text-gray-400 hover:bg-gray-800 hover:text-white"
95
:title="collapsed ? 'Knowledge Base' : ''">
96
<span class="flex items-center gap-2">
97
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
98
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776" />
99
</svg>
100
<span x-show="!collapsed" class="truncate">Knowledge Base</span>
101
</span>
102
<svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="kbOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
103
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
104
</svg>
105
</button>
106
<div x-show="kbOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3">
107
{% for p in sidebar_kb_pages %}
108
<a href="{% url 'pages:detail' slug=p.slug %}"
109
class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate">
110
{{ p.name }}
111
</a>
112
{% endfor %}
113
{% if sidebar_kb_pages|length == 0 %}
114
<p class="px-3 py-1.5 text-xs text-gray-600">No articles yet.</p>
115
{% endif %}
116
{% if perms.pages.add_page %}
117
<a href="{% url 'pages:create' %}"
118
class="block rounded-md px-3 py-1.5 text-sm text-gray-600 hover:text-brand-light">
119
+ New
120
</a>
121
{% endif %}
122
</div>
123
</div>
124
{% endif %}
125
126
<!-- FossilRepo Docs (product docs — read-only) -->
127
{% if sidebar_product_docs %}
128
<div x-data="{ docsOpen: false }">
129
<button @click="collapsed ? (collapsed = false, docsOpen = true) : (docsOpen = !docsOpen)"
130
class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/kb/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
131
:title="collapsed ? 'FossilRepo Docs' : ''">
132
<span class="flex items-center gap-2">
133
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
134
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
135
</svg>
136
<span x-show="!collapsed" class="truncate">FossilRepo Docs</span>
137
</span>
138
<svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="docsOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
139
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
140
</svg>
141
</button>
142
<div x-show="docsOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-3">
143
{% for p in sidebar_product_docs %}
144
<a href="{% url 'pages:detail' slug=p.slug %}"
145
class="block rounded-md px-3 py-1.5 text-sm {% if p.slug in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %} truncate">
146
{{ p.name }}
147
</a>
148
{% endfor %}
149
</div>
150
</div>
151
{% endif %}
152
153
<!-- FossilSCM Guide -->
154
<a href="{% url 'fossil:docs' slug='fossil-scm' %}"
155
class="flex items-center gap-2 rounded-md px-2 py-2 text-sm font-medium {% if '/fossil/docs/' in request.path %}bg-gray-800 text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
156
:title="collapsed ? 'FossilSCM Guide' : ''">
157
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
158
<path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 00-.491 6.347A48.62 48.62 0 0112 20.904a48.62 48.62 0 018.232-4.41 60.46 60.46 0 00-.491-6.347m-15.482 0a50.636 50.636 0 00-2.658-.813A59.906 59.906 0 0112 3.493a59.903 59.903 0 0110.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0112 13.489a50.702 50.702 0 017.74-3.342M6.75 15a.75.75 0 100-1.5.75.75 0 000 1.5zm0 0v-3.675A55.378 55.378 0 0112 8.443m-7.007 11.55A5.981 5.981 0 006.75 15.75v-1.5" />
159
</svg>
160
<span x-show="!collapsed" class="truncate">FossilSCM Guide</span>
161
</a>
162
163
<!-- Admin section (collapsible) -->
164
{% if perms.organization.view_organization or user.is_staff %}
165
<div x-data="{ adminOpen: window.location.pathname.includes('/settings/') || window.location.pathname.includes('/projects/groups/') }">
166
<button @click="collapsed ? (collapsed = false, adminOpen = true) : (adminOpen = !adminOpen)"
167
class="flex items-center justify-between w-full rounded-md px-2 py-2 text-sm font-medium {% if '/settings/' in request.path or '/projects/groups/' in request.path %}text-white{% else %}text-gray-400 hover:bg-gray-800 hover:text-white{% endif %}"
168
:title="collapsed ? 'Admin' : ''">
169
<span class="flex items-center gap-2">
170
<svg class="h-4 w-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
171
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
172
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
173
</svg>
174
<span x-show="!collapsed" class="truncate">Admin</span>
175
</span>
176
<svg x-show="!collapsed" class="h-4 w-4 transition-transform" :class="adminOpen && 'rotate-90'" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
177
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
178
</svg>
179
</button>
180
<div x-show="adminOpen && !collapsed" x-collapse class="ml-4 mt-1 space-y-0.5 border-l border-gray-700 pl-2">
181
{% if perms.projects.view_projectgroup %}
182
<a href="{% url 'projects:group_list' %}"
183
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if '/projects/groups/' in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
184
Groups
185
</a>
186
{% endif %}
187
{% if perms.organization.view_team %}
188
<a href="{% url 'organization:team_list' %}"
189
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if '/settings/teams/' in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
190
Teams
191
</a>
192
{% endif %}
193
{% if perms.organization.view_organization %}
194
<a href="{% url 'organization:role_list' %}"
195
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if '/settings/roles/' in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
196
Roles
197
</a>
198
{% endif %}
199
<a href="{% url 'organization:members' %}"
200
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if '/settings/members/' in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
201
Members
202
</a>
203
{% if perms.organization.view_organization %}
204
<a href="{% url 'organization:settings' %}"
205
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if request.path == '/settings/' %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
206
Settings
207
</a>
208
{% endif %}
209
{% if user.is_superuser or perms.organization.change_organization %}
210
<a href="{% url 'organization:audit_log' %}"
211
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs {% if '/settings/audit/' in request.path %}text-brand-light font-medium{% else %}text-gray-500 hover:text-gray-300{% endif %}">
212
Audit Log
213
</a>
214
{% endif %}
215
{% if user.is_staff %}
216
<a href="{% url 'admin:index' %}"
217
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-500 hover:text-gray-300">
218
Super Admin
219
</a>
220
{% endif %}
221
</div>
222
</div>
223
{% endif %}
224
225
</nav>
226
227
</aside>
228

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button