|
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
|
|