micro-sidebar 2.1.0__py3-none-any.whl → 2.2.0__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-2.1.0.dist-info → micro_sidebar-2.2.0.dist-info}/METADATA +2 -1
- {micro_sidebar-2.1.0.dist-info → micro_sidebar-2.2.0.dist-info}/RECORD +11 -9
- sidebar/static/sidebar/css/reorder.css +91 -0
- sidebar/static/sidebar/js/reorder.js +267 -0
- sidebar/static/sidebar/sidebar.css +2 -0
- sidebar/templates/sidebar/auto.html +2 -0
- sidebar/templates/sidebar/extra_groups.html +1 -1
- sidebar/templates/sidebar/main.html +71 -5
- {micro_sidebar-2.1.0.dist-info → micro_sidebar-2.2.0.dist-info}/LICENSE +0 -0
- {micro_sidebar-2.1.0.dist-info → micro_sidebar-2.2.0.dist-info}/WHEEL +0 -0
- {micro_sidebar-2.1.0.dist-info → micro_sidebar-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: micro-sidebar
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A Reusable RTL Django Sidebar App
|
|
5
5
|
Home-page: https://github.com/debeski/micro-sidebar
|
|
6
6
|
Author: DeBeski
|
|
@@ -230,3 +230,4 @@ While it may theoretically work in LTR environments if standard Bootstrap files
|
|
|
230
230
|
| **v1.2.2** | **CSP Compliance:** Added `nonce` attribute support to inline scripts for Content Security Policy compliance. |
|
|
231
231
|
| **v2.0.0** | **Auto-Discovery:** New feature that introspects Django URL patterns and models to automatically generate sidebar navigation items. Adds `{% auto_sidebar %}` template tag, context processor, and configuration options. |
|
|
232
232
|
| **v2.1.0** | **Refactor & Enhancements:** Decoupled customization from models by introducing `DEFAULT_ITEMS` setting for overriding auto-discovered items' labels/icons/order. Added `EXTRA_ITEMS` setting for manual, permission-aware sidebar links grouped in accordions with `{% extra_sidebar %}` tag. Removed deprecated model-level `sidebar_*` attributes. |
|
|
233
|
+
| **v2.2.0** | **Drag-and-Drop Reordering:** New reorder toggle in sidebar toolbar (visible in expanded mode only). Click to enable reorder mode with shake animation. Drag items to reorder with visual drop indicator. Order persists to localStorage. Default order applies only if no user customization exists. Accordion headers remain fixed. |
|
|
@@ -5,9 +5,11 @@ sidebar/discovery.py,sha256=6rBpji-g057Pwg3fS7Vp1JV61wz3ygmhZfosGMNXjAY,5636
|
|
|
5
5
|
sidebar/urls.py,sha256=UL_9e1RLNMxZXkah65m7GRU1dbViZRGeNPBIiSZpOYg,142
|
|
6
6
|
sidebar/views.py,sha256=MebyJ1ZiylSOPESXFkkQ8QTg-ClrkJn-oYLN6KrcgiM,418
|
|
7
7
|
sidebar/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
sidebar/static/sidebar/sidebar.css,sha256=
|
|
8
|
+
sidebar/static/sidebar/sidebar.css,sha256=fojQ5zofvpCz1SNe4zCHPB3Bx7kF0KiVKb_W3SSrA8g,6044
|
|
9
9
|
sidebar/static/sidebar/sidebar.js,sha256=gcurW6nl0Dt9jPTzk749DX7uQ3U_7De4F6Gj8ci6W40,5233
|
|
10
|
+
sidebar/static/sidebar/css/reorder.css,sha256=zOBzg6nJwbY0QRCQKAVjaKxqCXqpICY1kFmXe2qO-og,2177
|
|
10
11
|
sidebar/static/sidebar/css/theme_picker.css,sha256=S2u9p_4rXZALf7hoCOsgyAU91NP3Hoy54WYnc4aGyww,4345
|
|
12
|
+
sidebar/static/sidebar/js/reorder.js,sha256=BRAI-S9aTBeq7kwg1Z_ixgIrPdHB3A7LIdZbe8poRB4,9478
|
|
11
13
|
sidebar/static/sidebar/js/theme_picker.js,sha256=Kt4S2fd0ocFjqeuOQXDcvWVLrLDAVxs7zANZqzIqA4g,2292
|
|
12
14
|
sidebar/static/themes/blue.css,sha256=_CMBLoX8xuSkEdiSYP4HhM-_M368q5Zus9n2kGILBQw,1651
|
|
13
15
|
sidebar/static/themes/dark.css,sha256=JmLG6UWSB7Erwogz0tCcpZUL1hgwxqgfyx-WSCXBtLU,14854
|
|
@@ -17,13 +19,13 @@ sidebar/static/themes/light.css,sha256=XqStIa8wTzWswWyy2nqbk4tMPKUuIR4967PBoJoPu
|
|
|
17
19
|
sidebar/static/themes/main.css,sha256=NaPBDAPL-PkTphtcIto7Qjb5CkiR9T_X5YztDT5SwwE,121
|
|
18
20
|
sidebar/static/themes/main.js,sha256=DSz07M8c1KTXSlj7woxPq0Kf9VFb2uNCI13Cz87gghs,1412
|
|
19
21
|
sidebar/static/themes/red.css,sha256=t8OTmvgszQXX4YtYxlIM86zjx1jp-SvfVx2G3X0FjDI,1613
|
|
20
|
-
sidebar/templates/sidebar/auto.html,sha256=
|
|
21
|
-
sidebar/templates/sidebar/extra_groups.html,sha256=
|
|
22
|
-
sidebar/templates/sidebar/main.html,sha256=
|
|
22
|
+
sidebar/templates/sidebar/auto.html,sha256=LQRLUJjiodRbRyWKxkj1MGorYrKAF67gsX_LC7LowH4,538
|
|
23
|
+
sidebar/templates/sidebar/extra_groups.html,sha256=uyiT2BcsY1-GM5GXHoudajckccF1cDCrCftm2vxOflo,1568
|
|
24
|
+
sidebar/templates/sidebar/main.html,sha256=djayBODnzWFaZerWG1THyHfvFV-4gYajIrx8BhBF_FY,6512
|
|
23
25
|
sidebar/templatetags/__init__.py,sha256=RC19QrlHdcgslFc_19Se9UhOe-7_WH9GtNGYU_cZlXg,48
|
|
24
26
|
sidebar/templatetags/sidebar_tags.py,sha256=KoZzjqRpMtiGcITKFUQBcs5RdbvtuzCaNrCWEzIbBlk,2245
|
|
25
|
-
micro_sidebar-2.
|
|
26
|
-
micro_sidebar-2.
|
|
27
|
-
micro_sidebar-2.
|
|
28
|
-
micro_sidebar-2.
|
|
29
|
-
micro_sidebar-2.
|
|
27
|
+
micro_sidebar-2.2.0.dist-info/LICENSE,sha256=Fco89ULLSSxKkC2KKnx57SaT0R7WOkZfuk8IYcGiN50,1063
|
|
28
|
+
micro_sidebar-2.2.0.dist-info/METADATA,sha256=NMtlGHTlPMAP1SvLxA2BzGqsbK_XYfWkzfFzYsPT8o0,8607
|
|
29
|
+
micro_sidebar-2.2.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
30
|
+
micro_sidebar-2.2.0.dist-info/top_level.txt,sha256=ih69sjMhU1wOB9HzUV90yEY98aiPuGhzPBBBE-YtJ3w,8
|
|
31
|
+
micro_sidebar-2.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Reorder Toggle Button */
|
|
2
|
+
.reorder-toggle {
|
|
3
|
+
font-size: 1.2rem;
|
|
4
|
+
color: #8c98a4;
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
padding: 5px;
|
|
7
|
+
margin-left: 10px;
|
|
8
|
+
border-radius: 50%;
|
|
9
|
+
transition: all 0.2s ease;
|
|
10
|
+
pointer-events: auto;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.reorder-toggle:hover {
|
|
14
|
+
color: var(--primal, #2363c3);
|
|
15
|
+
transform: scale(1.1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.reorder-toggle.active {
|
|
19
|
+
color: var(--primal, #2363c3);
|
|
20
|
+
background-color: rgba(35, 99, 195, 0.1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Hide reorder toggle in collapsed sidebar */
|
|
24
|
+
.sidebar.collapsed .reorder-toggle {
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Shake Animation for reorder mode */
|
|
29
|
+
@keyframes shake {
|
|
30
|
+
0%, 100% { transform: translateX(0); }
|
|
31
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-2px); }
|
|
32
|
+
20%, 40%, 60%, 80% { transform: translateX(2px); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sidebar.reorder-mode .list-group-item[draggable="true"],
|
|
36
|
+
.sidebar.reorder-mode .accordion-body .list-group-item[draggable="true"] {
|
|
37
|
+
animation: shake 1.5s ease-in-out infinite;
|
|
38
|
+
cursor: grab;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sidebar.reorder-mode .list-group-item[draggable="true"]:hover {
|
|
42
|
+
animation-play-state: paused;
|
|
43
|
+
background-color: rgba(35, 99, 195, 0.08) !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Dragging State */
|
|
47
|
+
.sidebar .list-group-item.dragging {
|
|
48
|
+
opacity: 0.4;
|
|
49
|
+
cursor: grabbing;
|
|
50
|
+
animation: none !important;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Drop Indicator Line */
|
|
54
|
+
.drop-indicator {
|
|
55
|
+
height: 3px;
|
|
56
|
+
background: var(--primal, #2363c3);
|
|
57
|
+
border-radius: 2px;
|
|
58
|
+
margin: 2px 10px;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
box-shadow: 0 0 8px rgba(35, 99, 195, 0.5);
|
|
61
|
+
animation: pulseIndicator 0.8s ease-in-out infinite;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes pulseIndicator {
|
|
65
|
+
0%, 100% { opacity: 1; }
|
|
66
|
+
50% { opacity: 0.6; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Ensure theme indicator stays in position */
|
|
70
|
+
.sidebar.collapsed .sidebar-toolbar {
|
|
71
|
+
justify-content: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Dark Mode Overrides */
|
|
75
|
+
:root.theme-dark .reorder-toggle {
|
|
76
|
+
color: rgba(255, 255, 255, 0.6);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:root.theme-dark .reorder-toggle:hover,
|
|
80
|
+
:root.theme-dark .reorder-toggle.active {
|
|
81
|
+
color: var(--primal, #3b82f6);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:root.theme-dark .reorder-toggle.active {
|
|
85
|
+
background-color: rgba(59, 130, 246, 0.2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:root.theme-dark .drop-indicator {
|
|
89
|
+
background: var(--primal, #3b82f6);
|
|
90
|
+
box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
|
|
91
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY_AUTO = 'sidebar_auto_order';
|
|
5
|
+
const STORAGE_KEY_PREFIX_EXTRA = 'sidebar_extra_';
|
|
6
|
+
|
|
7
|
+
let isReorderMode = false;
|
|
8
|
+
let draggedElement = null;
|
|
9
|
+
let dropIndicator = null;
|
|
10
|
+
|
|
11
|
+
// Expose restore function globally for immediate FOUC fix
|
|
12
|
+
window.restoreSidebarOrder = restoreOrder;
|
|
13
|
+
|
|
14
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
15
|
+
const sidebar = document.getElementById('sidebar');
|
|
16
|
+
const reorderToggle = document.getElementById('sidebarReorderToggle');
|
|
17
|
+
|
|
18
|
+
if (!sidebar || !reorderToggle) return;
|
|
19
|
+
|
|
20
|
+
// Create drop indicator element
|
|
21
|
+
dropIndicator = document.createElement('div');
|
|
22
|
+
dropIndicator.className = 'drop-indicator';
|
|
23
|
+
dropIndicator.style.display = 'none';
|
|
24
|
+
|
|
25
|
+
// Restore is now called immediately via inline script for FOUC prevention
|
|
26
|
+
// But call again here as fallback if inline script didn't run
|
|
27
|
+
if (!window._sidebarOrderRestored) {
|
|
28
|
+
restoreOrder();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Toggle reorder mode
|
|
32
|
+
reorderToggle.addEventListener('click', (e) => {
|
|
33
|
+
e.stopPropagation();
|
|
34
|
+
isReorderMode = !isReorderMode;
|
|
35
|
+
reorderToggle.classList.toggle('active', isReorderMode);
|
|
36
|
+
sidebar.classList.toggle('reorder-mode', isReorderMode);
|
|
37
|
+
|
|
38
|
+
if (isReorderMode) {
|
|
39
|
+
enableDragAndDrop();
|
|
40
|
+
} else {
|
|
41
|
+
disableDragAndDrop();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Close reorder mode when clicking outside
|
|
46
|
+
document.addEventListener('click', (e) => {
|
|
47
|
+
if (isReorderMode && !sidebar.contains(e.target)) {
|
|
48
|
+
isReorderMode = false;
|
|
49
|
+
reorderToggle.classList.remove('active');
|
|
50
|
+
sidebar.classList.remove('reorder-mode');
|
|
51
|
+
disableDragAndDrop();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function enableDragAndDrop() {
|
|
57
|
+
// Auto items in .sidebar-auto-items or direct .list-group children
|
|
58
|
+
const autoContainer = document.getElementById('sidebarAutoItems') ||
|
|
59
|
+
document.querySelector('.sidebar .list-group');
|
|
60
|
+
if (autoContainer) {
|
|
61
|
+
setupDraggableContainer(autoContainer, STORAGE_KEY_AUTO);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Extra group items in accordion bodies
|
|
65
|
+
const accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
|
|
66
|
+
accordionBodies.forEach(body => {
|
|
67
|
+
const groupName = body.dataset.groupName || body.closest('.accordion-item')?.querySelector('.accordion-button span')?.textContent?.trim();
|
|
68
|
+
if (groupName) {
|
|
69
|
+
const key = STORAGE_KEY_PREFIX_EXTRA + slugify(groupName);
|
|
70
|
+
setupDraggableContainer(body, key);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function disableDragAndDrop() {
|
|
76
|
+
const items = document.querySelectorAll('.sidebar .list-group-item[draggable="true"]');
|
|
77
|
+
items.forEach(item => {
|
|
78
|
+
item.removeAttribute('draggable');
|
|
79
|
+
item.removeEventListener('dragstart', handleDragStart);
|
|
80
|
+
item.removeEventListener('dragend', handleDragEnd);
|
|
81
|
+
item.removeEventListener('dragover', handleDragOver);
|
|
82
|
+
item.removeEventListener('drop', handleDrop);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Hide drop indicator
|
|
86
|
+
if (dropIndicator) {
|
|
87
|
+
dropIndicator.style.display = 'none';
|
|
88
|
+
if (dropIndicator.parentNode) {
|
|
89
|
+
dropIndicator.parentNode.removeChild(dropIndicator);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setupDraggableContainer(container, storageKey) {
|
|
95
|
+
const items = container.querySelectorAll(':scope > .list-group-item');
|
|
96
|
+
|
|
97
|
+
items.forEach(item => {
|
|
98
|
+
// Skip accordion buttons - they're not reorderable
|
|
99
|
+
if (item.classList.contains('accordion-button')) return;
|
|
100
|
+
|
|
101
|
+
item.setAttribute('draggable', 'true');
|
|
102
|
+
item.dataset.storageKey = storageKey;
|
|
103
|
+
|
|
104
|
+
item.addEventListener('dragstart', handleDragStart);
|
|
105
|
+
item.addEventListener('dragend', handleDragEnd);
|
|
106
|
+
item.addEventListener('dragover', handleDragOver);
|
|
107
|
+
item.addEventListener('drop', handleDrop);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Add event listeners to container for drag events
|
|
111
|
+
container.addEventListener('dragover', handleContainerDragOver);
|
|
112
|
+
container.addEventListener('drop', handleContainerDrop);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleDragStart(e) {
|
|
116
|
+
draggedElement = this;
|
|
117
|
+
this.classList.add('dragging');
|
|
118
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
119
|
+
e.dataTransfer.setData('text/plain', ''); // Required for Firefox
|
|
120
|
+
|
|
121
|
+
// Add drop indicator to DOM
|
|
122
|
+
if (dropIndicator && this.parentNode) {
|
|
123
|
+
this.parentNode.appendChild(dropIndicator);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function handleDragEnd(e) {
|
|
128
|
+
this.classList.remove('dragging');
|
|
129
|
+
|
|
130
|
+
// Hide drop indicator
|
|
131
|
+
if (dropIndicator) {
|
|
132
|
+
dropIndicator.style.display = 'none';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Save new order
|
|
136
|
+
if (draggedElement) {
|
|
137
|
+
saveOrder(draggedElement.parentNode, draggedElement.dataset.storageKey);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
draggedElement = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleDragOver(e) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
e.dataTransfer.dropEffect = 'move';
|
|
146
|
+
|
|
147
|
+
if (!draggedElement || draggedElement === this) return;
|
|
148
|
+
if (draggedElement.dataset.storageKey !== this.dataset.storageKey) return;
|
|
149
|
+
|
|
150
|
+
const rect = this.getBoundingClientRect();
|
|
151
|
+
const midY = rect.top + rect.height / 2;
|
|
152
|
+
|
|
153
|
+
// Show drop indicator
|
|
154
|
+
if (dropIndicator) {
|
|
155
|
+
dropIndicator.style.display = 'block';
|
|
156
|
+
if (e.clientY < midY) {
|
|
157
|
+
this.parentNode.insertBefore(dropIndicator, this);
|
|
158
|
+
} else {
|
|
159
|
+
this.parentNode.insertBefore(dropIndicator, this.nextSibling);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleContainerDragOver(e) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleDrop(e) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
|
|
172
|
+
if (!draggedElement || draggedElement === this) return;
|
|
173
|
+
if (draggedElement.dataset.storageKey !== this.dataset.storageKey) return;
|
|
174
|
+
|
|
175
|
+
const rect = this.getBoundingClientRect();
|
|
176
|
+
const midY = rect.top + rect.height / 2;
|
|
177
|
+
|
|
178
|
+
if (e.clientY < midY) {
|
|
179
|
+
this.parentNode.insertBefore(draggedElement, this);
|
|
180
|
+
} else {
|
|
181
|
+
this.parentNode.insertBefore(draggedElement, this.nextSibling);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleContainerDrop(e) {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
// Item drops are handled by individual items
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function saveOrder(container, storageKey) {
|
|
191
|
+
if (!container || !storageKey) return;
|
|
192
|
+
|
|
193
|
+
const items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
|
|
194
|
+
const order = Array.from(items).map(item => item.dataset.urlName);
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
localStorage.setItem(storageKey, JSON.stringify(order));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.warn('Could not save sidebar order:', e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function restoreOrder() {
|
|
204
|
+
// Restore auto items order
|
|
205
|
+
const autoContainer = document.getElementById('sidebarAutoItems') ||
|
|
206
|
+
document.querySelector('.sidebar .list-group');
|
|
207
|
+
if (autoContainer) {
|
|
208
|
+
restoreContainerOrder(autoContainer, STORAGE_KEY_AUTO);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Restore extra group items order
|
|
212
|
+
const accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
|
|
213
|
+
accordionBodies.forEach(body => {
|
|
214
|
+
const groupName = body.dataset.groupName || body.closest('.accordion-item')?.querySelector('.accordion-button span')?.textContent?.trim();
|
|
215
|
+
if (groupName) {
|
|
216
|
+
const key = STORAGE_KEY_PREFIX_EXTRA + slugify(groupName);
|
|
217
|
+
restoreContainerOrder(body, key);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Mark as restored
|
|
222
|
+
window._sidebarOrderRestored = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function restoreContainerOrder(container, storageKey) {
|
|
226
|
+
let savedOrder;
|
|
227
|
+
try {
|
|
228
|
+
const saved = localStorage.getItem(storageKey);
|
|
229
|
+
if (!saved) return; // No saved order, use default
|
|
230
|
+
savedOrder = JSON.parse(saved);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return; // Invalid JSON, use default
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(savedOrder) || savedOrder.length === 0) return;
|
|
236
|
+
|
|
237
|
+
const items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
|
|
238
|
+
const itemMap = new Map();
|
|
239
|
+
items.forEach(item => {
|
|
240
|
+
itemMap.set(item.dataset.urlName, item);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Reorder based on saved order
|
|
244
|
+
savedOrder.forEach(urlName => {
|
|
245
|
+
const item = itemMap.get(urlName);
|
|
246
|
+
if (item) {
|
|
247
|
+
container.appendChild(item);
|
|
248
|
+
itemMap.delete(urlName);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Append any remaining items (new items not in saved order)
|
|
253
|
+
itemMap.forEach(item => {
|
|
254
|
+
container.appendChild(item);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function slugify(text) {
|
|
259
|
+
return text
|
|
260
|
+
.toString()
|
|
261
|
+
.toLowerCase()
|
|
262
|
+
.trim()
|
|
263
|
+
.replace(/\s+/g, '-')
|
|
264
|
+
.replace(/[^\w\-]+/g, '')
|
|
265
|
+
.replace(/\-\-+/g, '-');
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<div class="sidebar-auto-items" id="sidebarAutoItems">
|
|
1
2
|
{% for item in items %}
|
|
2
3
|
<a href="{{ item.url }}"
|
|
3
4
|
class="list-group-item list-group-item-action{% if item.active %} active{% endif %}"
|
|
@@ -11,3 +12,4 @@
|
|
|
11
12
|
<p class="mb-0">لا توجد عناصر</p>
|
|
12
13
|
</div>
|
|
13
14
|
{% endfor %}
|
|
15
|
+
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<div id="extraGroup{{ forloop.counter }}"
|
|
17
17
|
class="accordion-collapse collapse{% if group.has_active %} show{% endif %}"
|
|
18
18
|
data-bs-parent="#sidebarExtraAccordion">
|
|
19
|
-
<div class="accordion-body p-0">
|
|
19
|
+
<div class="accordion-body p-0" data-group-name="{{ group_name|slugify }}">
|
|
20
20
|
{% for item in group.items %}
|
|
21
21
|
<a href="{{ item.url }}"
|
|
22
22
|
class="list-group-item list-group-item-action{% if item.active %} active{% endif %}"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{% load static %}
|
|
2
2
|
<link rel="stylesheet" href="{% static 'sidebar/sidebar.css' %}">
|
|
3
3
|
<link rel="stylesheet" href="{% static 'sidebar/css/theme_picker.css' %}">
|
|
4
|
+
<link rel="stylesheet" href="{% static 'sidebar/css/reorder.css' %}">
|
|
4
5
|
<link rel="stylesheet" href="{% static 'themes/main.css' %}">
|
|
5
6
|
<script src="{% static 'sidebar/sidebar.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
|
|
6
7
|
<script src="{% static 'themes/main.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
|
|
7
8
|
<script src="{% static 'sidebar/js/theme_picker.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
|
|
9
|
+
<script src="{% static 'sidebar/js/reorder.js' %}" nonce="{{ request.csp_nonce }}" defer></script>
|
|
8
10
|
<!-- Ghost Sidebar for small screens layout stability -->
|
|
9
11
|
{% if request.user.is_authenticated %}
|
|
10
12
|
<!-- <div class="sidebar-ghost"></div> -->
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
{% block items %}
|
|
32
34
|
|
|
33
35
|
<!-- DEFAULT CONTENT / INSTRUCTIONS -->
|
|
34
|
-
<div class="p-3 text-center text-muted">
|
|
36
|
+
<!-- <div class="p-3 text-center text-muted">
|
|
35
37
|
<i class="bi bi-info-circle mb-2" style="font-size: 24px;"></i>
|
|
36
38
|
<p class="small">
|
|
37
39
|
<strong>Default Sidebar</strong><br>
|
|
@@ -39,18 +41,82 @@
|
|
|
39
41
|
<code>sidebar/main.html</code><br>
|
|
40
42
|
and override the <code>items</code> block.
|
|
41
43
|
</p>
|
|
42
|
-
</div>
|
|
44
|
+
</div> -->
|
|
43
45
|
|
|
44
|
-
<a href="#" class="list-group-item list-group-item-action">
|
|
46
|
+
<!-- <a href="#" class="list-group-item list-group-item-action">
|
|
45
47
|
<i class="bi bi-house me-2" style="font-size: 24px;"></i>
|
|
46
48
|
<span>Example Home</span>
|
|
47
|
-
</a>
|
|
49
|
+
</a> -->
|
|
48
50
|
|
|
49
51
|
{% endblock %}
|
|
50
52
|
</div>
|
|
53
|
+
<script nonce="{{ request.csp_nonce }}">
|
|
54
|
+
// Immediate order restore to prevent FOUC - runs before browser paint
|
|
55
|
+
(function() {
|
|
56
|
+
var STORAGE_KEY_AUTO = 'sidebar_auto_order';
|
|
57
|
+
var STORAGE_KEY_PREFIX_EXTRA = 'sidebar_extra_';
|
|
58
|
+
|
|
59
|
+
function restoreContainer(container, storageKey) {
|
|
60
|
+
var saved;
|
|
61
|
+
try {
|
|
62
|
+
saved = localStorage.getItem(storageKey);
|
|
63
|
+
if (!saved) return;
|
|
64
|
+
saved = JSON.parse(saved);
|
|
65
|
+
} catch(e) { return; }
|
|
66
|
+
if (!Array.isArray(saved) || saved.length === 0) return;
|
|
67
|
+
|
|
68
|
+
var items = container.querySelectorAll(':scope > .list-group-item[data-url-name]');
|
|
69
|
+
var itemMap = {};
|
|
70
|
+
for (var i = 0; i < items.length; i++) {
|
|
71
|
+
itemMap[items[i].dataset.urlName] = items[i];
|
|
72
|
+
}
|
|
73
|
+
for (var j = 0; j < saved.length; j++) {
|
|
74
|
+
var item = itemMap[saved[j]];
|
|
75
|
+
if (item) {
|
|
76
|
+
container.appendChild(item);
|
|
77
|
+
delete itemMap[saved[j]];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (var key in itemMap) {
|
|
81
|
+
container.appendChild(itemMap[key]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function slugify(text) {
|
|
86
|
+
return text.toString().toLowerCase().trim()
|
|
87
|
+
.replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Restore auto items
|
|
91
|
+
var autoContainer = document.getElementById('sidebarAutoItems');
|
|
92
|
+
if (autoContainer) {
|
|
93
|
+
restoreContainer(autoContainer, STORAGE_KEY_AUTO);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Restore extra groups
|
|
97
|
+
var accordionBodies = document.querySelectorAll('.sidebar .accordion-body');
|
|
98
|
+
for (var k = 0; k < accordionBodies.length; k++) {
|
|
99
|
+
var body = accordionBodies[k];
|
|
100
|
+
var groupName = body.dataset.groupName;
|
|
101
|
+
if (!groupName) {
|
|
102
|
+
var btn = body.closest('.accordion-item');
|
|
103
|
+
if (btn) {
|
|
104
|
+
var span = btn.querySelector('.accordion-button span');
|
|
105
|
+
if (span) groupName = span.textContent.trim();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (groupName) {
|
|
109
|
+
restoreContainer(body, STORAGE_KEY_PREFIX_EXTRA + slugify(groupName));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
window._sidebarOrderRestored = true;
|
|
114
|
+
})();
|
|
115
|
+
</script>
|
|
51
116
|
|
|
52
|
-
<!-- Sidebar Toolbar (Theme Picker, etc.) -->
|
|
117
|
+
<!-- Sidebar Toolbar (Theme Picker, Reorder, etc.) -->
|
|
53
118
|
<div class="sidebar-toolbar no-print">
|
|
119
|
+
<i class="bi bi-arrows-move reorder-toggle" id="sidebarReorderToggle" title="إعادة الترتيب"></i>
|
|
54
120
|
<i class="bi bi-chevron-up theme-arrow" id="sidebarThemeArrow"></i>
|
|
55
121
|
<div class="current-theme-indicator" id="sidebarThemeIndicator" title="تغيير المظهر"></div>
|
|
56
122
|
<div class="theme-popup" id="sidebarThemePopup">
|
|
File without changes
|
|
File without changes
|
|
File without changes
|