micro-sidebar 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- micro_sidebar-1.0.2.dist-info/METADATA +111 -0
- micro_sidebar-1.0.2.dist-info/RECORD +12 -0
- micro_sidebar-1.0.2.dist-info/WHEEL +5 -0
- micro_sidebar-1.0.2.dist-info/top_level.txt +1 -0
- sidebar/__init__.py +0 -0
- sidebar/apps.py +5 -0
- sidebar/migrations/__init__.py +0 -0
- sidebar/static/sidebar/js/sidebar.js +163 -0
- sidebar/static/sidebar/style.css +93 -0
- sidebar/templates/sidebar/content.html +31 -0
- sidebar/urls.py +6 -0
- sidebar/views.py +9 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: micro_sidebar
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A reusable Django sidebar for Web Apps
|
|
5
|
+
Home-page: https://github.com/debeski/micro-sidebar
|
|
6
|
+
Author: DeBeski
|
|
7
|
+
Author-email: DeBeski <debeski1@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/debeski/micro-sidebar
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 5
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: Django>=5.1
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
|
|
29
|
+
# Micro Sidebar
|
|
30
|
+
|
|
31
|
+
A reusable RTL Django sidebar app for Web Apps.
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- **Django**: >= 5.1
|
|
36
|
+
- **Bootstrap**: 5 (Required for consistent styling)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
1. **Install the package:**
|
|
41
|
+
```bash
|
|
42
|
+
pip install micro-sidebar
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Add to `INSTALLED_APPS`:**
|
|
46
|
+
In your `settings.py`:
|
|
47
|
+
```python
|
|
48
|
+
INSTALLED_APPS = [
|
|
49
|
+
...
|
|
50
|
+
'sidebar',
|
|
51
|
+
...
|
|
52
|
+
]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. **Configure URLs:**
|
|
56
|
+
In your project's `urls.py`:
|
|
57
|
+
```python
|
|
58
|
+
from django.urls import path, include
|
|
59
|
+
|
|
60
|
+
urlpatterns = [
|
|
61
|
+
...
|
|
62
|
+
path('sidebar/', include('sidebar.urls')),
|
|
63
|
+
...
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. **Add to your Base Template:**
|
|
68
|
+
In your `base.html` (or equivalent), include the sidebar. It is designed to sit to the right of your main content.
|
|
69
|
+
|
|
70
|
+
Example structure using Flexbox:
|
|
71
|
+
```html
|
|
72
|
+
<body>
|
|
73
|
+
<div class="d-flex">
|
|
74
|
+
<!-- Sidebar -->
|
|
75
|
+
{% include "sidebar/content.html" %}
|
|
76
|
+
|
|
77
|
+
<!-- Main Content -->
|
|
78
|
+
<div class="flex-grow-1">
|
|
79
|
+
{% block content %}{% endblock %}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Bootstrap JS (Required) -->
|
|
84
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
85
|
+
</body>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Customization
|
|
89
|
+
|
|
90
|
+
### Overriding Content
|
|
91
|
+
The sidebar comes with a default template. To customize the links and content, create a file named `content.html` inside `templates/sidebar/` in your project's `templates` directory.
|
|
92
|
+
|
|
93
|
+
**Path:** `your_project/templates/sidebar/content.html`
|
|
94
|
+
|
|
95
|
+
The default sidebar logic expects specific classes like `.list-group-item` and `.accordion-item` for the collapsible features to work correctly with the provided JS.
|
|
96
|
+
|
|
97
|
+
### Positioning
|
|
98
|
+
The sidebar is sticky by default. If your app has a top navigation bar (titlebar), the sidebar will automatically adjust its position below it on small screens. If no titlebar is detected, it will stick to the top of the viewport.
|
|
99
|
+
|
|
100
|
+
## RTL / LTR Support
|
|
101
|
+
This sidebar is primarily designed for **RTL (Right-to-Left)** interfaces (e.g., Arabic).
|
|
102
|
+
While it may theoretically work in LTR environments if standard Bootstrap files are used instead of RTL versions, this has **not been fully tested**.
|
|
103
|
+
> *Future updates are planned to support dynamic language switching between RTL and LTR.*
|
|
104
|
+
|
|
105
|
+
## Version History
|
|
106
|
+
|
|
107
|
+
| Version | Changes |
|
|
108
|
+
| :--- | :--- |
|
|
109
|
+
| **v1.0.0** | Initial Release. |
|
|
110
|
+
| **v1.0.1** | Fixed titlebar positioning bug causing overlap/gaps. |
|
|
111
|
+
| **v1.0.2** | Improved documentation clarity and added usage instructions. |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
sidebar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
sidebar/apps.py,sha256=4UjKXHoBGKRLxpC9AY6eaq8YZx8unirUz_8u-IrlfVQ,145
|
|
3
|
+
sidebar/urls.py,sha256=UL_9e1RLNMxZXkah65m7GRU1dbViZRGeNPBIiSZpOYg,142
|
|
4
|
+
sidebar/views.py,sha256=MebyJ1ZiylSOPESXFkkQ8QTg-ClrkJn-oYLN6KrcgiM,418
|
|
5
|
+
sidebar/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
sidebar/static/sidebar/style.css,sha256=aEbc4yPFaQBpMoYnJi575u077pHzej7FLFP8kgRnfBk,2105
|
|
7
|
+
sidebar/static/sidebar/js/sidebar.js,sha256=jLspMcoRDpsw_KpIegGwSL-FRi_uz_8pQ95zDXMnTZ0,6221
|
|
8
|
+
sidebar/templates/sidebar/content.html,sha256=hSqgzvuWZRxlUN1-zC-Oovppd_rbnCylxQmfRdGIbhY,1298
|
|
9
|
+
micro_sidebar-1.0.2.dist-info/METADATA,sha256=fctkEC7TQqt0tDm2oAQzXalyb058WNMLWtoLA-gwJH4,3607
|
|
10
|
+
micro_sidebar-1.0.2.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
11
|
+
micro_sidebar-1.0.2.dist-info/top_level.txt,sha256=ih69sjMhU1wOB9HzUV90yEY98aiPuGhzPBBBE-YtJ3w,8
|
|
12
|
+
micro_sidebar-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sidebar
|
sidebar/__init__.py
ADDED
|
File without changes
|
sidebar/apps.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
2
|
+
const sidebar = document.getElementById("sidebar");
|
|
3
|
+
const sidebarToggle = document.getElementById("sidebarToggle");
|
|
4
|
+
|
|
5
|
+
// Read config from data attribute on sidebar or a global config
|
|
6
|
+
// Assuming sidebar has data attributes, or we can look for a meta tag
|
|
7
|
+
const sidebarConfigStr = sidebar.getAttribute('data-sidebar-config');
|
|
8
|
+
let sidebarConfig = {};
|
|
9
|
+
if (sidebarConfigStr) {
|
|
10
|
+
try {
|
|
11
|
+
sidebarConfig = JSON.parse(sidebarConfigStr.replace(/'/g, '"')); // Simple parse attempt, better to use valid JSON in attribute
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.error("Error parsing sidebar config", e);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const toggleUrl = sidebar.dataset.toggleUrl;
|
|
18
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
19
|
+
let isSessionCollapsed = sidebar.dataset.sessionCollapsed === "true";
|
|
20
|
+
|
|
21
|
+
// Function to handle sidebar collapsing based on window size
|
|
22
|
+
function adjustSidebarForWindowSize() {
|
|
23
|
+
const screenWidth = window.innerWidth;
|
|
24
|
+
const titlebar = document.querySelector('.titlebar');
|
|
25
|
+
const titlebarHeight = titlebar ? titlebar.offsetHeight : 0;
|
|
26
|
+
|
|
27
|
+
if (screenWidth < 1100) {
|
|
28
|
+
// Always collapse sidebar on small screens
|
|
29
|
+
sidebar.classList.add("collapsed");
|
|
30
|
+
initializeTooltips();
|
|
31
|
+
|
|
32
|
+
// Dynamic positioning to anchor to titlebar
|
|
33
|
+
// Dynamic positioning to anchor to titlebar
|
|
34
|
+
if (titlebarHeight > 0) {
|
|
35
|
+
sidebar.style.top = titlebarHeight + 'px';
|
|
36
|
+
sidebar.style.height = (window.innerHeight - titlebarHeight) + 'px';
|
|
37
|
+
} else {
|
|
38
|
+
// Failsafe: Stick to top if no titlebar
|
|
39
|
+
sidebar.style.top = '0px';
|
|
40
|
+
sidebar.style.height = '100vh';
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Reset styles for large screens to let CSS take over (sticky)
|
|
44
|
+
sidebar.style.top = '';
|
|
45
|
+
sidebar.style.height = '';
|
|
46
|
+
|
|
47
|
+
// Use session state for larger screens
|
|
48
|
+
if (isSessionCollapsed) {
|
|
49
|
+
sidebar.classList.add("collapsed");
|
|
50
|
+
initializeTooltips();
|
|
51
|
+
} else {
|
|
52
|
+
sidebar.classList.remove("collapsed");
|
|
53
|
+
deinitializeTooltips();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Adjust sidebar state on load
|
|
59
|
+
adjustSidebarForWindowSize();
|
|
60
|
+
|
|
61
|
+
// Check if sidebar is collapsed on load and initialize tooltips if so
|
|
62
|
+
if (sidebar.classList.contains("collapsed")) {
|
|
63
|
+
initializeTooltips();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Listen for window resize
|
|
67
|
+
window.addEventListener("resize", adjustSidebarForWindowSize);
|
|
68
|
+
|
|
69
|
+
// Toggle sidebar and update session via AJAX
|
|
70
|
+
if (sidebarToggle) {
|
|
71
|
+
sidebarToggle.addEventListener("click", function () {
|
|
72
|
+
sidebar.classList.toggle("collapsed");
|
|
73
|
+
const isCollapsed = sidebar.classList.contains("collapsed");
|
|
74
|
+
|
|
75
|
+
// Update tooltips immediately for all screen sizes
|
|
76
|
+
if (isCollapsed) {
|
|
77
|
+
initializeTooltips();
|
|
78
|
+
} else {
|
|
79
|
+
deinitializeTooltips();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Only update session if screen width is >= 1100px
|
|
83
|
+
if (window.innerWidth >= 1100) {
|
|
84
|
+
fetch(toggleUrl, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"X-CSRFToken": csrfToken,
|
|
88
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
89
|
+
},
|
|
90
|
+
body: `collapsed=${isCollapsed}`
|
|
91
|
+
}).then(response => response.json())
|
|
92
|
+
.then(data => {
|
|
93
|
+
if (data.status === "success") {
|
|
94
|
+
isSessionCollapsed = isCollapsed; // Update local state
|
|
95
|
+
}
|
|
96
|
+
}).catch(error => console.error("Error updating sidebar state:", error));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setTimeout(triggerAutoscale, 250);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Close sidebar when clicking outside (only for small screens)
|
|
105
|
+
document.addEventListener("click", function (event) {
|
|
106
|
+
const sidebar = document.getElementById("sidebar");
|
|
107
|
+
const sidebarToggle = document.getElementById("sidebarToggle");
|
|
108
|
+
const screenWidth = window.innerWidth;
|
|
109
|
+
|
|
110
|
+
if (sidebar && sidebarToggle && screenWidth < 1100 && !sidebar.contains(event.target) && !sidebarToggle.contains(event.target)) {
|
|
111
|
+
if (!sidebar.classList.contains("collapsed")) {
|
|
112
|
+
sidebar.classList.add("collapsed");
|
|
113
|
+
|
|
114
|
+
const toggleUrl = sidebar.dataset.toggleUrl;
|
|
115
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
116
|
+
|
|
117
|
+
fetch(toggleUrl, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
"X-CSRFToken": csrfToken,
|
|
121
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
122
|
+
},
|
|
123
|
+
body: "collapsed=true"
|
|
124
|
+
}).then(response => response.json())
|
|
125
|
+
.catch(error => console.error("Error updating sidebar state:", error));
|
|
126
|
+
|
|
127
|
+
initializeTooltips();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function initializeTooltips() {
|
|
133
|
+
const sidebarItems = document.querySelectorAll(".sidebar.collapsed .list-group-item, .sidebar.collapsed .accordion-button");
|
|
134
|
+
sidebarItems.forEach(item => {
|
|
135
|
+
if (item._tooltip) {
|
|
136
|
+
item._tooltip.dispose();
|
|
137
|
+
}
|
|
138
|
+
item._tooltip = new bootstrap.Tooltip(item, {
|
|
139
|
+
title: item.querySelector("span").textContent,
|
|
140
|
+
placement: "right",
|
|
141
|
+
customClass: "tooltip-custom"
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function deinitializeTooltips() {
|
|
147
|
+
const sidebarItems = document.querySelectorAll(".sidebar .list-group-item, .sidebar .accordion-button");
|
|
148
|
+
sidebarItems.forEach(item => {
|
|
149
|
+
if (item._tooltip) {
|
|
150
|
+
item._tooltip.dispose();
|
|
151
|
+
delete item._tooltip;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function triggerAutoscale() {
|
|
157
|
+
if (window.innerWidth > 1100) {
|
|
158
|
+
const autoscaleButton = document.querySelector('.modebar-btn[data-title="Reset axes"]');
|
|
159
|
+
if (autoscaleButton) {
|
|
160
|
+
autoscaleButton.click();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
.sidebar {
|
|
3
|
+
z-index: 900;
|
|
4
|
+
transition: width 0.15s ease-in-out;
|
|
5
|
+
background-color: white;
|
|
6
|
+
position: -webkit-sticky; /* For Safari */
|
|
7
|
+
position: sticky;
|
|
8
|
+
top: 0; /* Sticks the sidebar at the top of the parent container */
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
overflow-x: hidden;
|
|
11
|
+
height: calc(100vh - 45px); /* Fallback/Base height for sticky behavior */
|
|
12
|
+
/* Custom Scrollbar for Webkit */
|
|
13
|
+
scrollbar-width: thin;
|
|
14
|
+
scrollbar-color: rgba(0,0,0,0.2) transparent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sidebar::-webkit-scrollbar {
|
|
18
|
+
width: 6px;
|
|
19
|
+
}
|
|
20
|
+
.sidebar::-webkit-scrollbar-track {
|
|
21
|
+
background: transparent;
|
|
22
|
+
}
|
|
23
|
+
.sidebar::-webkit-scrollbar-thumb {
|
|
24
|
+
background-color: rgba(0,0,0,0.2);
|
|
25
|
+
border-radius: 20px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sidebar.collapsed {
|
|
29
|
+
width: 52px !important;
|
|
30
|
+
}
|
|
31
|
+
.sidebar.collapsed .list-group-item span {
|
|
32
|
+
display: none !important;
|
|
33
|
+
}
|
|
34
|
+
.sidebar.collapsed .accordion-button span {
|
|
35
|
+
display: none !important;
|
|
36
|
+
}
|
|
37
|
+
.sidebar.collapsed .accordion-button::after {
|
|
38
|
+
background-image: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
.sidebar-ghost {
|
|
43
|
+
display: none;
|
|
44
|
+
width: 52px;
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@media (max-width: 1100px) {
|
|
49
|
+
.sidebar-ghost {
|
|
50
|
+
display: block;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.sidebar {
|
|
54
|
+
position: fixed;
|
|
55
|
+
top: 45px; /* Fallback, JS calculates exact height */
|
|
56
|
+
right: 0;
|
|
57
|
+
width: 250px;
|
|
58
|
+
transition: width 0.15s ease-in-out;
|
|
59
|
+
height: calc(100vh - 45px); /* Fallback */
|
|
60
|
+
z-index: 1000;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.sidebar.collapsed {
|
|
64
|
+
width: 52px !important;
|
|
65
|
+
right: 0;
|
|
66
|
+
position: fixed;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
.sidebar .list-group-item {
|
|
70
|
+
border: none !important;
|
|
71
|
+
font-size: 18px !important;
|
|
72
|
+
font-weight: 600 !important;
|
|
73
|
+
height: 60px !important;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
padding: 12px;
|
|
77
|
+
align-items: center;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sidebar .accordion-button {
|
|
81
|
+
font-size: 18px !important;
|
|
82
|
+
font-weight: 600 !important;
|
|
83
|
+
border: none !important;
|
|
84
|
+
height: 60px !important;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
padding: 12px;
|
|
88
|
+
align-items: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.sidebar .accordion-button:not(.collapsed) {
|
|
92
|
+
background-color: var(--body) !important;
|
|
93
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{% load static %}
|
|
2
|
+
<link rel="stylesheet" href="{% static 'sidebar/style.css' %}">
|
|
3
|
+
<script src="{% static 'sidebar/js/sidebar.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
|
|
4
|
+
<!-- Ghost Sidebar for small screens layout stability -->
|
|
5
|
+
<div class="sidebar-ghost"></div>
|
|
6
|
+
|
|
7
|
+
<!-- sidebar.html -->
|
|
8
|
+
<div class="col-2 flex-column shadow-sm sidebar {% if request.session.sidebarCollapsed %}collapsed{% endif %} no-print"
|
|
9
|
+
id="sidebar"
|
|
10
|
+
data-toggle-url="{% url 'toggle_sidebar' %}"
|
|
11
|
+
data-session-collapsed="{{ request.session.sidebarCollapsed|yesno:'true,false' }}">
|
|
12
|
+
<div class="list-group flex-shrink-0">
|
|
13
|
+
|
|
14
|
+
<!-- DEFAULT CONTENT / INSTRUCTIONS -->
|
|
15
|
+
<div class="p-3 text-center text-muted">
|
|
16
|
+
<i class="bi bi-info-circle mb-2" style="font-size: 24px;"></i>
|
|
17
|
+
<p class="small">
|
|
18
|
+
<strong>Default Sidebar</strong><br>
|
|
19
|
+
To customize this menu, create a file at:<br>
|
|
20
|
+
<code>templates/sidebar/content.html</code><br>
|
|
21
|
+
in your Django project.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<a href="#" class="list-group-item list-group-item-action">
|
|
26
|
+
<i class="bi bi-house me-2" style="font-size: 24px;"></i>
|
|
27
|
+
<span>Example Home</span>
|
|
28
|
+
</a>
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
sidebar/urls.py
ADDED
sidebar/views.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from django.http import JsonResponse
|
|
2
|
+
|
|
3
|
+
# Helper Function that handles the sidebar toggle and state
|
|
4
|
+
def toggle_sidebar(request):
|
|
5
|
+
if request.method == "POST" and request.user.is_authenticated:
|
|
6
|
+
collapsed = request.POST.get("collapsed") == "true"
|
|
7
|
+
request.session["sidebarCollapsed"] = collapsed
|
|
8
|
+
return JsonResponse({"status": "success"})
|
|
9
|
+
return JsonResponse({"status": "error"}, status=400)
|