django-fast-treenode 2.0.11__py3-none-any.whl → 2.1.1__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.
- {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.1.dist-info}/LICENSE +2 -2
- django_fast_treenode-2.1.1.dist-info/METADATA +158 -0
- django_fast_treenode-2.1.1.dist-info/RECORD +64 -0
- {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.1.dist-info}/WHEEL +1 -1
- treenode/admin/__init__.py +9 -0
- treenode/admin/admin.py +295 -0
- treenode/admin/changelist.py +65 -0
- treenode/admin/mixins.py +302 -0
- treenode/apps.py +12 -1
- treenode/cache.py +2 -2
- treenode/forms.py +8 -10
- treenode/managers/__init__.py +21 -0
- treenode/managers/adjacency.py +203 -0
- treenode/managers/closure.py +278 -0
- treenode/models/__init__.py +2 -1
- treenode/models/adjacency.py +343 -0
- treenode/models/classproperty.py +3 -0
- treenode/models/closure.py +23 -24
- treenode/models/factory.py +12 -2
- treenode/models/mixins/__init__.py +23 -0
- treenode/models/mixins/ancestors.py +65 -0
- treenode/models/mixins/children.py +81 -0
- treenode/models/mixins/descendants.py +66 -0
- treenode/models/mixins/family.py +63 -0
- treenode/models/mixins/logical.py +68 -0
- treenode/models/mixins/node.py +210 -0
- treenode/models/mixins/properties.py +156 -0
- treenode/models/mixins/roots.py +96 -0
- treenode/models/mixins/siblings.py +99 -0
- treenode/models/mixins/tree.py +344 -0
- treenode/signals.py +26 -0
- treenode/static/treenode/css/tree_widget.css +201 -31
- treenode/static/treenode/css/treenode_admin.css +48 -41
- treenode/static/treenode/js/tree_widget.js +269 -131
- treenode/static/treenode/js/treenode_admin.js +131 -171
- treenode/templates/admin/tree_node_changelist.html +6 -0
- treenode/templates/admin/treenode_ajax_rows.html +7 -0
- treenode/tests/tests.py +488 -0
- treenode/urls.py +10 -6
- treenode/utils/__init__.py +2 -0
- treenode/utils/aid.py +46 -0
- treenode/utils/base16.py +38 -0
- treenode/utils/base36.py +3 -1
- treenode/utils/db.py +116 -0
- treenode/utils/exporter.py +2 -0
- treenode/utils/importer.py +0 -1
- treenode/utils/radix.py +61 -0
- treenode/version.py +2 -2
- treenode/views.py +118 -43
- treenode/widgets.py +91 -43
- django_fast_treenode-2.0.11.dist-info/METADATA +0 -698
- django_fast_treenode-2.0.11.dist-info/RECORD +0 -42
- treenode/admin.py +0 -439
- treenode/docs/Documentation +0 -636
- treenode/managers.py +0 -419
- treenode/models/proxy.py +0 -669
- {django_fast_treenode-2.0.11.dist-info → django_fast_treenode-2.1.1.dist-info}/top_level.txt +0 -0
@@ -11,9 +11,10 @@ Features:
|
|
11
11
|
- Smooth hover effects and animations.
|
12
12
|
- Consistent layout adjustments for better UI interaction.
|
13
13
|
|
14
|
-
Version: 2.
|
14
|
+
Version: 2.1.0
|
15
15
|
Author: Timur Kady
|
16
16
|
Email: timurkady@yandex.com
|
17
|
+
|
17
18
|
*/
|
18
19
|
|
19
20
|
|
@@ -22,13 +23,17 @@ Email: timurkady@yandex.com
|
|
22
23
|
100% {width: 100px; height: 100px;}
|
23
24
|
}
|
24
25
|
|
26
|
+
.field-drag, .field-toggle {
|
27
|
+
width: 18px !important;
|
28
|
+
text-align: center !important;
|
29
|
+
padding: 8px 0px !important;
|
30
|
+
}
|
31
|
+
|
25
32
|
.treenode-space {
|
26
33
|
display: inline-block;
|
27
|
-
width:
|
28
|
-
height:
|
29
|
-
margin
|
30
|
-
margin-bottom: 0px;
|
31
|
-
margin-right: 7px !important;
|
34
|
+
width: 18px;
|
35
|
+
height: 18px;
|
36
|
+
margin: 0px 3px !important;
|
32
37
|
background-color: transparent;
|
33
38
|
border: 1px solid transparent;
|
34
39
|
padding: 1px;
|
@@ -36,25 +41,28 @@ Email: timurkady@yandex.com
|
|
36
41
|
|
37
42
|
.treenode-toggle {
|
38
43
|
display: inline-block;
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
width: 18px;
|
45
|
+
height: 18px;
|
46
|
+
background: var(--button-bg);
|
47
|
+
color: var(--button-fg);
|
48
|
+
border-radius: 3px;
|
49
|
+
border: none;
|
50
|
+
margin: 0px 5px;
|
46
51
|
cursor: pointer;
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
font-size: 12px;
|
53
|
+
line-height: 18px;
|
54
|
+
padding: 0px;
|
55
|
+
opacity: 0.8;
|
56
|
+
transition: opacity 0.2s ease, color 0.2s ease;
|
57
|
+
}
|
58
|
+
|
59
|
+
.treenode-toggle[expanded="true"] {
|
60
|
+
color: green;
|
53
61
|
}
|
54
62
|
|
63
|
+
|
55
64
|
.treenode-toggle:hover {
|
56
|
-
|
57
|
-
color: #007bff;
|
65
|
+
opacity: 1.0;
|
58
66
|
}
|
59
67
|
|
60
68
|
.dark-theme .treenode-toggle {
|
@@ -68,24 +76,6 @@ Email: timurkady@yandex.com
|
|
68
76
|
color: #fff;
|
69
77
|
}
|
70
78
|
|
71
|
-
.treenode-toggle {
|
72
|
-
font-weight: bold;
|
73
|
-
display: inline-block;
|
74
|
-
|
75
|
-
text-align: center;
|
76
|
-
cursor: pointer;
|
77
|
-
margin-right: 5px;
|
78
|
-
border-radius: 4px;
|
79
|
-
|
80
|
-
padding: 2px;
|
81
|
-
transition: background-color 0.2s ease, color 0.2s ease;
|
82
|
-
}
|
83
|
-
|
84
|
-
.treenode-toggle:hover {
|
85
|
-
background-color: #e0e0e0;
|
86
|
-
color: #007bff;
|
87
|
-
}
|
88
|
-
|
89
79
|
.dark-theme .treenode-toggle {
|
90
80
|
color: #ccc;
|
91
81
|
background-color: #444;
|
@@ -97,10 +87,27 @@ Email: timurkady@yandex.com
|
|
97
87
|
color: #fff;
|
98
88
|
}
|
99
89
|
|
100
|
-
.treenode-
|
90
|
+
.treenode-drag-handle {
|
101
91
|
display: inline-block;
|
92
|
+
text-align: center;
|
93
|
+
font-weight: bold;
|
94
|
+
font-size: 10px;
|
95
|
+
width: 10px;
|
96
|
+
height: 10px;
|
97
|
+
line-height: 10px;
|
98
|
+
padding: 1px;
|
99
|
+
cursor: ns-resize;
|
100
|
+
opacity: 0.25;
|
102
101
|
}
|
103
102
|
|
104
|
-
.
|
103
|
+
.treenode-drag-handle:hover {
|
104
|
+
opacity: 1.0;
|
105
|
+
}
|
106
|
+
|
107
|
+
.dark-theme treenode-drag-handle {
|
105
108
|
color: #ccc;
|
106
109
|
}
|
110
|
+
|
111
|
+
.treenode-wrapper {
|
112
|
+
display: inline-block;
|
113
|
+
}
|
@@ -1,161 +1,299 @@
|
|
1
1
|
/*
|
2
2
|
TreeNode Select2 Widget
|
3
3
|
|
4
|
-
This script
|
5
|
-
|
6
|
-
a
|
4
|
+
This script replaces Django's Select widget for presenting hierarchical data
|
5
|
+
in the Django admin panel. The widget is intended for a parent field, but
|
6
|
+
can be used to select a tree node with a different purpose. It provides
|
7
|
+
structured tree display.
|
8
|
+
The widget supports AJAX fetching, which avoids loading the entire tree.
|
7
9
|
|
8
10
|
Features:
|
9
|
-
- Dynamically initializes
|
10
|
-
-
|
11
|
-
- Supports dark mode and automatically applies theme
|
11
|
+
- Dynamically initializes select-like elements with the `tree-widget` class.
|
12
|
+
- Fetches data via AJAX and displays it with the correct indentation.
|
13
|
+
- Supports dark mode and automatically applies theme styles.
|
12
14
|
- Handles parent-child relationships and updates node priorities.
|
13
15
|
|
14
|
-
Version: 2.
|
16
|
+
Version: 2.1.0
|
15
17
|
Author: Timur Kady
|
16
18
|
Email: timurkady@yandex.com
|
17
19
|
*/
|
18
20
|
|
19
|
-
|
20
21
|
(function ($) {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
return;
|
35
|
-
}
|
22
|
+
"use strict";
|
23
|
+
|
24
|
+
var TreeWidget = {
|
25
|
+
// Initialize each widget by container with class .tree-widget
|
26
|
+
init: function (selector) {
|
27
|
+
$(selector).each(function () {
|
28
|
+
var $widget = $(this);
|
29
|
+
|
30
|
+
// Find the hidden input, dropdown list and display area inside
|
31
|
+
// the container
|
32
|
+
var $select = $widget.find('input[type="hidden"]').first();
|
33
|
+
var $dropdown = $widget.find('.tree-widget-dropdown').first();
|
34
|
+
var $display = $widget.find('.tree-widget-display').first();
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
model: forwardData.model || null, // Pass the model name
|
48
|
-
};
|
49
|
-
},
|
50
|
-
processResults: function (data) {
|
51
|
-
if (!data.results) {
|
52
|
-
return { results: [] }; // Return an empty array if no results exist
|
53
|
-
}
|
54
|
-
return { results: data.results };
|
55
|
-
},
|
56
|
-
},
|
57
|
-
minimumInputLength: 0, // Allows opening the dropdown without typing
|
58
|
-
allowClear: true, // Enables the "clear selection" button
|
59
|
-
width: "100%", // Expands the dropdown to fit the parent container
|
60
|
-
templateResult: formatTreeResult, // Custom rendering function for hierarchical display
|
61
|
-
});
|
62
|
-
|
63
|
-
// Immediately apply theme styling after Select2 initialization
|
64
|
-
var select2Instance = $widget.data("select2");
|
65
|
-
if (select2Instance && isDarkTheme()) {
|
66
|
-
select2Instance.$container
|
67
|
-
.find(".select2-selection--single")
|
68
|
-
.addClass("dark-theme"); // Apply dark mode styling if enabled
|
36
|
+
// Get URL for AJAX and model data from data attributes
|
37
|
+
var ajaxUrl = $select.data('url');
|
38
|
+
var ajaxUrlChildren = $select.data('url-children');
|
39
|
+
var forwardData = $select.attr("data-forward");
|
40
|
+
if (typeof forwardData === "string" && forwardData.trim().length > 0) {
|
41
|
+
try {
|
42
|
+
forwardData = JSON.parse(forwardData.replace(/"/g, '"'));
|
43
|
+
} catch (e) {
|
44
|
+
console.error("Invalid JSON in data-forward:", forwardData, e);
|
45
|
+
forwardData = {};
|
69
46
|
}
|
47
|
+
} else {
|
48
|
+
forwardData = {};
|
49
|
+
}
|
50
|
+
|
51
|
+
var selectedId = $select.data('selected'); // Получаем значение data-selected
|
52
|
+
if (selectedId === undefined) {
|
53
|
+
selectedId = "";
|
54
|
+
}
|
55
|
+
|
56
|
+
var widgetData = {
|
57
|
+
$widget: $widget,
|
58
|
+
$select: $select,
|
59
|
+
$dropdown: $dropdown,
|
60
|
+
$display: $display,
|
61
|
+
ajaxUrl: ajaxUrl,
|
62
|
+
urlChildren: ajaxUrlChildren,
|
63
|
+
model: forwardData.model || '',
|
64
|
+
selectedId: selectedId,
|
65
|
+
mode: selectedId ? 'selected' : 'default'
|
66
|
+
};
|
67
|
+
|
68
|
+
$widget.data('widgetData', widgetData);
|
69
|
+
|
70
|
+
// Load data for the current mode (default, selected or search)
|
71
|
+
TreeWidget.loadData(widgetData);
|
72
|
+
|
73
|
+
// Bind event handlers
|
74
|
+
TreeWidget.bindEvents(widgetData);
|
75
|
+
});
|
76
|
+
},
|
77
|
+
|
78
|
+
// Method of loading data via AJAX
|
79
|
+
loadData: function (widgetData, searchQuery) {
|
80
|
+
var params = {model: widgetData.model};
|
81
|
+
if (searchQuery) {
|
82
|
+
params.q = searchQuery;
|
83
|
+
widgetData.mode = 'search';
|
84
|
+
} else if (widgetData.selectedId) {
|
85
|
+
params.select_id = widgetData.selectedId;
|
86
|
+
widgetData.mode = 'selected';
|
87
|
+
} else {
|
88
|
+
widgetData.mode = 'default';
|
89
|
+
}
|
90
|
+
|
91
|
+
$.ajax({
|
92
|
+
url: widgetData.ajaxUrl,
|
93
|
+
data: params,
|
94
|
+
dataType: 'json',
|
95
|
+
success: function (response) {
|
96
|
+
var $treeList = widgetData.$dropdown.find('.tree-list');
|
97
|
+
$treeList.empty();
|
98
|
+
TreeWidget.renderNodes(response.results, $treeList);
|
99
|
+
// If needed, you can show a dropdown after loading the data
|
100
|
+
// widgetData.$dropdown.show();
|
101
|
+
},
|
102
|
+
error: function (error) {
|
103
|
+
console.error("Error loading data:", params, error);
|
104
|
+
}
|
105
|
+
});
|
106
|
+
},
|
107
|
+
|
108
|
+
// Method for formatting a node
|
109
|
+
formatNode: function(node, levelOverride) {
|
110
|
+
// If levelOverride is passed, use it, otherwise take node.level
|
111
|
+
var level = (typeof levelOverride !== 'undefined') ? levelOverride : parseInt(node.level, 10);
|
112
|
+
var $li = $('<li></li>')
|
113
|
+
.addClass('tree-node')
|
114
|
+
.attr('data-id', node.id)
|
115
|
+
.attr('data-level', level);
|
116
|
+
var indent = level * 20;
|
117
|
+
$li.css('margin-left', indent + 'px');
|
118
|
+
|
119
|
+
// If the node is not a leaf node, add a button to expand it,
|
120
|
+
// otherwise insert an empty element for alignment
|
121
|
+
if (!node.is_leaf) {
|
122
|
+
var $expandBtn = $('<button type="button" class="expand-button">⏵</button>');
|
123
|
+
$li.append($expandBtn);
|
124
|
+
$li.append('<span class="node-icon">📁</span>').css({
|
125
|
+
display: 'inline-block'
|
126
|
+
})
|
127
|
+
} else {
|
128
|
+
$li.append($('<span class="no-expand"></span>').css({
|
129
|
+
display: 'inline-block'
|
130
|
+
}));
|
131
|
+
$li.append('<span class="node-icon">📄</span>').css({
|
132
|
+
display: 'inline-block'
|
133
|
+
})
|
134
|
+
}
|
135
|
+
|
136
|
+
$li.append($('<span class="node-text"></span>').text(node.text));
|
137
|
+
|
138
|
+
return $li;
|
139
|
+
},
|
140
|
+
|
141
|
+
// Method of drawing a node
|
142
|
+
renderNodes: function (nodes, $container) {
|
143
|
+
$container.empty();
|
144
|
+
$.each(nodes, function (index, node) {
|
145
|
+
var $nodeElem = TreeWidget.formatNode(node);
|
146
|
+
$container.append($nodeElem);
|
147
|
+
});
|
148
|
+
},
|
149
|
+
|
150
|
+
// Handler for clicking on the node's expand button
|
151
|
+
expandNode: function ($button, widgetData) {
|
152
|
+
var $li = $button.closest('li.tree-node');
|
153
|
+
var nodeId = $li.data('id');
|
154
|
+
if ($button.data('expanded')) {
|
155
|
+
TreeWidget.collapseNode($li);
|
156
|
+
$button.text('⏵').data('expanded', false);
|
157
|
+
} else {
|
158
|
+
$.ajax({
|
159
|
+
url: widgetData.urlChildren,
|
160
|
+
data: { model: widgetData.model, reference_id: nodeId },
|
161
|
+
dataType: 'json',
|
162
|
+
success: function (response) {
|
163
|
+
var parentLevel = parseInt($li.data('level'), 10);
|
164
|
+
var $childrenFragment = $();
|
165
|
+
$.each(response.results, function (index, node) {
|
166
|
+
var $childLi = TreeWidget.formatNode(node, parentLevel + 1);
|
167
|
+
$childrenFragment = $childrenFragment.add($childLi);
|
168
|
+
});
|
169
|
+
$li.after($childrenFragment);
|
170
|
+
$button.text('⏷').data('expanded', true);
|
171
|
+
},
|
172
|
+
error: function (data, error) {
|
173
|
+
console.error("Error loading children: ", data, error);
|
174
|
+
}
|
70
175
|
});
|
71
|
-
|
176
|
+
}
|
177
|
+
},
|
72
178
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
179
|
+
// Node collapse method
|
180
|
+
// Remove child elements that are higher than the parent
|
181
|
+
collapseNode: function ($li) {
|
182
|
+
var currentLevel = parseInt($li.data('level'), 10);
|
183
|
+
var $next = $li.next();
|
184
|
+
while ($next.length && parseInt($next.data('level'), 10) > currentLevel) {
|
185
|
+
var $temp = $next.next();
|
186
|
+
$next.remove();
|
187
|
+
$next = $temp;
|
188
|
+
}
|
189
|
+
},
|
81
190
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
191
|
+
// Binding events for widget operation
|
192
|
+
bindEvents: function (widgetData) {
|
193
|
+
var $dropdown = widgetData.$dropdown;
|
194
|
+
var $widget = widgetData.$widget;
|
195
|
+
var $select = widgetData.$select;
|
196
|
+
var $display = widgetData.$display;
|
197
|
+
|
198
|
+
// Handle click on node expand button
|
199
|
+
$dropdown.on('click', '.expand-button', function (e) {
|
200
|
+
e.preventDefault();
|
201
|
+
e.stopPropagation();
|
202
|
+
TreeWidget.expandNode($(this), widgetData);
|
203
|
+
});
|
204
|
+
|
205
|
+
// Handle node selection (click on node text)
|
206
|
+
$dropdown.on('click', 'li.tree-node', function (e) {
|
207
|
+
e.preventDefault();
|
208
|
+
e.stopPropagation();
|
209
|
+
var $li = $(this).closest('li.tree-node');
|
210
|
+
var nodeId = $li.data('id');
|
211
|
+
$select.val(nodeId);
|
212
|
+
$select.data('selected', nodeId);
|
213
|
+
// Update the displayed selected value
|
214
|
+
$widget.find('.selected-node').text($(this).text());
|
215
|
+
$dropdown.hide();
|
216
|
+
});
|
217
|
+
|
218
|
+
// Processing input in the search field
|
219
|
+
$dropdown.on('keyup', '.tree-search', function (e) {
|
220
|
+
var query = $(this).val();
|
221
|
+
if (query.length > 0) {
|
222
|
+
TreeWidget.loadData(widgetData, query);
|
94
223
|
} else {
|
95
|
-
|
96
|
-
$container.removeClass("dark-theme");
|
224
|
+
TreeWidget.loadData(widgetData);
|
97
225
|
}
|
98
|
-
|
226
|
+
});
|
99
227
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
228
|
+
// Handle clicking on the search field clear button
|
229
|
+
$dropdown.on('click', '.tree-search-clear', function (e) {
|
230
|
+
e.preventDefault();
|
231
|
+
e.stopPropagation();
|
232
|
+
var $search = $dropdown.find('.tree-search');
|
233
|
+
$search.val('');
|
234
|
+
TreeWidget.loadData(widgetData);
|
235
|
+
});
|
236
|
+
|
237
|
+
// Toggle the visibility of the dropdown list when clicking on the
|
238
|
+
// display area
|
239
|
+
$display.on('click', function (e) {
|
240
|
+
e.preventDefault();
|
241
|
+
$dropdown.toggle();
|
242
|
+
});
|
243
|
+
|
244
|
+
// Hide the dropdown when clicking outside the widget
|
245
|
+
$(document).on('click', function (e) {
|
246
|
+
if (!$widget.is(e.target) && $widget.has(e.target).length === 0) {
|
247
|
+
$dropdown.hide();
|
109
248
|
}
|
110
|
-
|
111
|
-
var is_leaf = result.is_leaf || false; // Determine if it's a leaf node
|
112
|
-
var indent = " ".repeat(level); // Create indentation based on depth
|
113
|
-
var icon = is_leaf ? "📄 " : "📂 "; // Use 📄 for leaves, 📂 for parent nodes
|
114
|
-
return $("<span>" + indent + icon + result.text + "</span>"); // Return formatted text
|
249
|
+
});
|
115
250
|
}
|
251
|
+
};
|
116
252
|
|
117
|
-
/**
|
118
|
-
* Binds event listeners and initializes Select2.
|
119
|
-
* Ensures correct theme application on page load and during interactions.
|
120
|
-
*/
|
121
|
-
$(document).ready(function () {
|
122
|
-
initializeSelect2();
|
123
|
-
applyTheme();
|
124
|
-
|
125
|
-
// When a Select2 dropdown opens, update its theme
|
126
|
-
$(document).on("select2:open", function () {
|
127
|
-
applyTheme();
|
128
|
-
});
|
129
253
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
254
|
+
/**
|
255
|
+
* Checks whether dark mode is enabled.
|
256
|
+
* It relies on the presence of the `data-theme="dark"` attribute on the
|
257
|
+
* <html> tag.
|
258
|
+
* @returns {boolean} - True if dark mode is active, false otherwise.
|
259
|
+
*/
|
260
|
+
function isDarkTheme() {
|
261
|
+
return document.documentElement.getAttribute("data-theme") === "dark";
|
262
|
+
}
|
134
263
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
264
|
+
/**
|
265
|
+
* Applies or removes the `.dark-theme` class to the Select2 dropdown and
|
266
|
+
* container. Ensures the dropdown styling follows the selected theme.
|
267
|
+
*/
|
268
|
+
function applyTheme() {
|
269
|
+
var dark = isDarkTheme(); // Check if dark mode is enabled
|
270
|
+
var $container = $(".tree-widget");
|
271
|
+
var $dropdown = $(".tree-widget-dropdown");
|
139
272
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
273
|
+
if (dark) {
|
274
|
+
$dropdown.addClass("dark-theme");
|
275
|
+
$container.addClass("dark-theme");
|
276
|
+
} else {
|
277
|
+
$dropdown.removeClass("dark-theme");
|
278
|
+
$container.removeClass("dark-theme");
|
279
|
+
}
|
280
|
+
}
|
144
281
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
});
|
282
|
+
// Initialize the widget when the page loads using the .tree-widget container
|
283
|
+
$(document).ready(function () {
|
284
|
+
applyTheme();
|
285
|
+
TreeWidget.init('.tree-widget');
|
286
|
+
|
287
|
+
// When a widget dropdown opens, update its theme
|
288
|
+
// $(document).on("select2:open", function () {
|
289
|
+
// applyTheme();
|
290
|
+
// });
|
291
|
+
|
292
|
+
// When the theme toggle button is clicked, reapply the theme
|
293
|
+
$(document).on("click", ".theme-toggle", function () {
|
294
|
+
applyTheme();
|
159
295
|
});
|
296
|
+
|
297
|
+
});
|
160
298
|
|
161
299
|
})(django.jQuery || window.jQuery);
|