django-spire 0.22.0__py3-none-any.whl → 0.22.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.
Files changed (26) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/contrib/generic_views/portal_views.py +43 -0
  3. django_spire/core/models.py +9 -0
  4. django_spire/core/static/django_spire/css/bootstrap-extension.css +41 -0
  5. django_spire/core/static/django_spire/css/bootstrap-override.css +28 -4
  6. django_spire/core/tag/models.py +0 -1
  7. django_spire/core/templates/django_spire/dropdown/ellipsis_table_dropdown.html +26 -0
  8. django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +4 -4
  9. django_spire/core/templates/django_spire/modal/content/dispatch_modal_delete_confirmation_content.html +6 -4
  10. django_spire/core/templates/django_spire/table/base.html +409 -0
  11. django_spire/core/templates/django_spire/table/element/child_row.html +59 -0
  12. django_spire/core/templates/django_spire/table/element/expandable_row.html +45 -0
  13. django_spire/core/templates/django_spire/table/element/footer.html +17 -0
  14. django_spire/core/templates/django_spire/table/element/header.html +9 -0
  15. django_spire/core/templates/django_spire/table/element/loading_skeleton.html +13 -0
  16. django_spire/core/templates/django_spire/table/element/refreshing_skeleton.html +13 -0
  17. django_spire/core/templates/django_spire/table/element/row.html +94 -0
  18. django_spire/core/templates/django_spire/table/element/trigger.html +1 -0
  19. django_spire/core/templates/django_spire/table/item/no_data_item.html +11 -0
  20. django_spire/knowledge/models.py +12 -4
  21. django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +1 -1
  22. {django_spire-0.22.0.dist-info → django_spire-0.22.1.dist-info}/METADATA +1 -1
  23. {django_spire-0.22.0.dist-info → django_spire-0.22.1.dist-info}/RECORD +26 -14
  24. {django_spire-0.22.0.dist-info → django_spire-0.22.1.dist-info}/WHEEL +0 -0
  25. {django_spire-0.22.0.dist-info → django_spire-0.22.1.dist-info}/licenses/LICENSE.md +0 -0
  26. {django_spire-0.22.0.dist-info → django_spire-0.22.1.dist-info}/top_level.txt +0 -0
django_spire/consts.py CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.22.0'
1
+ __VERSION__ = '0.22.1'
2
2
 
3
3
  MAINTENANCE_MODE_SETTINGS_NAME = 'MAINTENANCE_MODE'
4
4
 
@@ -270,6 +270,49 @@ def model_form_view(
270
270
  )
271
271
 
272
272
 
273
+ def table_view(
274
+ request: WSGIRequest,
275
+ *,
276
+ queryset: QuerySet,
277
+ queryset_name: str,
278
+ template: str,
279
+ context_data: dict[str, Any] | None = None
280
+ ) -> TemplateResponse:
281
+ if context_data is None:
282
+ context_data = {}
283
+
284
+ default_batch_size = 25
285
+
286
+ page = int(request.GET.get('page', 1))
287
+
288
+ batch_size = (
289
+ context_data.get('batch_size')
290
+ if 'batch_size' in context_data
291
+ else request.GET.get('batch_size', default_batch_size)
292
+ )
293
+
294
+ batch_size = int(batch_size)
295
+ offset = (page - 1) * batch_size
296
+
297
+ total_count = queryset.count()
298
+ object_list = queryset[offset:offset + batch_size]
299
+ has_next = offset + batch_size < total_count
300
+
301
+ base_context_data = {
302
+ queryset_name: object_list,
303
+ 'has_next': has_next,
304
+ 'total_count': total_count,
305
+ }
306
+
307
+ context_data.update(base_context_data)
308
+
309
+ return TemplateResponse(
310
+ request,
311
+ context=context_data,
312
+ template=template
313
+ )
314
+
315
+
273
316
  def template_view(
274
317
  request: WSGIRequest,
275
318
  page_title: str,
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ # These imports are for the migrations to work as a single app "django_spire_core"
4
+
5
+ from django_spire.core.tag import models
6
+
7
+ # Must assert to stop unused import removals.
8
+
9
+ assert models
@@ -378,6 +378,47 @@ h1, h2, h3, h4, h5, h6 {
378
378
  transform: translateX(0);
379
379
  }
380
380
 
381
+ .skeleton-box {
382
+ background: linear-gradient(
383
+ 90deg,
384
+ rgba(var(--bs-secondary-rgb), 0.1) 0%,
385
+ rgba(var(--bs-secondary-rgb), 0.2) 50%,
386
+ rgba(var(--bs-secondary-rgb), 0.1) 100%
387
+ );
388
+ background-size: 200% 100%;
389
+ animation: skeleton-loading 1.5s ease-in-out infinite;
390
+ border-radius: 4px;
391
+ display: inline-block;
392
+ }
393
+
394
+ @keyframes skeleton-loading {
395
+ 0% {
396
+ background-position: 200% 0;
397
+ }
398
+ 100% {
399
+ background-position: -200% 0;
400
+ }
401
+ }
402
+
403
+ .skeleton-row {
404
+ pointer-events: none;
405
+ user-select: none;
406
+ }
407
+
408
+ .table tbody {
409
+ transition: opacity 0.15s ease-in-out;
410
+ }
411
+
412
+ .table-fixed {
413
+ table-layout: fixed;
414
+ }
415
+
416
+ .table-fixed th,
417
+ .table-fixed td {
418
+ overflow: hidden;
419
+ text-overflow: ellipsis;
420
+ }
421
+
381
422
  .text-200 {
382
423
  font-weight: 200 !important;
383
424
  }
@@ -135,11 +135,11 @@
135
135
  --bs-popover-header-bg: var(--app-layer-two);
136
136
  --bs-popover-header-color: var(--app-default-text-color);
137
137
 
138
- --bs-table-bg: transparent;
139
- --bs-table-border-color: var(--app-layer-three);
138
+ --bs-table-bg: var(--app-table-row-color);
139
+ --bs-table-border-color: var(--bs-border-color);
140
140
  --bs-table-color: var(--app-default-text-color);
141
- --bs-table-hover-bg: var(--app-layer-two);
142
- --bs-table-striped-bg: var(--app-layer-two);
141
+ --bs-table-hover-bg: var(--app-table-row-hover-color);
142
+ --bs-table-striped-bg: var(--app-table-row-alt-color);
143
143
 
144
144
  --bs-tooltip-bg: var(--app-dark);
145
145
  --bs-tooltip-color: var(--app-light);
@@ -263,6 +263,30 @@
263
263
  background-color: var(--app-primary);
264
264
  }
265
265
 
266
+ .table > :not(caption) > * > * {
267
+ color: var(--app-default-text-color);
268
+ }
269
+
270
+ .table-striped tbody tr:nth-of-type(4n+1),
271
+ .table-striped tbody tr:nth-of-type(4n+2) {
272
+ --bs-table-bg-type: var(--app-table-row-alt-color);
273
+ background-color: var(--app-table-row-alt-color);
274
+ color: var(--app-default-text-color);
275
+ }
276
+
277
+ .table-striped tbody tr:nth-of-type(4n+3),
278
+ .table-striped tbody tr:nth-of-type(4n+4) {
279
+ --bs-table-bg-type: color-mix(in srgb, var(--app-secondary) 10%, transparent);
280
+ background-color: color-mix(in srgb, var(--app-secondary) 10%, transparent);
281
+ color: var(--app-default-text-color);
282
+ }
283
+
284
+ .table-hover > tbody > tr:hover > * {
285
+ --bs-table-bg-state: var(--app-table-row-hover-color);
286
+ background-color: var(--app-table-row-hover-color);
287
+ color: var(--app-default-text-color);
288
+ }
289
+
266
290
  .ce-block__content [data-placeholder]:empty::before,
267
291
  .ce-block__content [data-placeholder][data-empty="true"]::before {
268
292
  color: var(--app-primary) !important;
@@ -35,4 +35,3 @@ class Tag(models.Model):
35
35
  db_table = 'django_spire_core_tag'
36
36
  verbose_name = 'Tag'
37
37
  verbose_name_plural = 'Tags'
38
-
@@ -0,0 +1,26 @@
1
+ {% extends 'django_spire/dropdown/dropdown.html' %}
2
+
3
+ {% block dropdown_position %}start-0 top-100{% endblock %}
4
+
5
+ {% block dropdown_trigger %}
6
+ <span
7
+ class="bi bi-three-dots-vertical px-1 {% block ellipsis_class %}{% endblock %}"
8
+ style="overflow: visible;"
9
+ x-bind="trigger"
10
+ >
11
+ </span>
12
+ {% endblock %}
13
+
14
+ {% block dropdown_content %}
15
+ {% if view_url %}
16
+ {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=view_url link_text='View' %}
17
+ {% endif %}
18
+
19
+ {% if edit_url %}
20
+ {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=edit_url link_text='Edit' %}
21
+ {% endif %}
22
+
23
+ {% if delete_url %}
24
+ {% include 'django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html' with view_url=delete_url link_text='Delete' link_css='text-app-danger' %}
25
+ {% endif %}
26
+ {% endblock %}
@@ -1,7 +1,7 @@
1
1
  <form
2
- method="get"
3
- action="{% block session_filter_url %}{% endblock %}"
4
- class="row border-bottom"
2
+ method="get"
3
+ action="{% block session_filter_url %}{% endblock %}"
4
+ class="row border-bottom"
5
5
  >
6
6
  <div class="col-12">
7
7
  <input type="text" hidden value="{% block session_filter_key %}{% endblock %}" name="session_filter_key">
@@ -9,7 +9,7 @@
9
9
  {% block filter_content %}{% endblock %}
10
10
 
11
11
  {% block filter_buttons %}
12
- <div class="py-2">
12
+ <div class="pt-2 pb-5">
13
13
  <input
14
14
  type="submit"
15
15
  name="action"
@@ -1,7 +1,7 @@
1
1
  {% extends 'django_spire/modal/content/modal_title_content.html' %}
2
2
 
3
3
  {% block modal_content_title %}
4
- {{ form_title }}
4
+ {{ form_title|truncatechars:75 }}
5
5
  {% endblock %}
6
6
 
7
7
  {% block modal_content_content %}
@@ -16,14 +16,16 @@
16
16
  }"
17
17
  >
18
18
  {% csrf_token %}
19
+
19
20
  {{ form_description }}
20
21
 
21
22
  {% include 'django_glue/form/field/input_field.html' with glue_field='should_delete' %}
22
23
 
23
24
  <div class="d-flex mt-3">
24
- {% include 'django_spire/contrib/form/button/form_submit_button.html' with button_text='Delete' button_class='btn-app-danger btn-sm shadow-sm' %}
25
- <span class="ms-2" @click="close_modal()" >
26
- {% include 'django_spire/button/secondary_button.html' with button_text='Cancel' %}
25
+ {% include 'django_spire/contrib/form/button/form_submit_button.html' with button_text='Delete' button_class='btn-app-danger shadow-sm' %}
26
+
27
+ <span class="ms-2" @click="close_modal()">
28
+ {% include 'django_spire/button/secondary_button.html' with button_text='Cancel' button_class='btn' %}
27
29
  </span>
28
30
  </div>
29
31
  </form>
@@ -0,0 +1,409 @@
1
+ <div
2
+ x-data="{
3
+ current_page: parseInt('{{ current_page|default:0 }}'),
4
+ endpoint: '{{ endpoint }}',
5
+ has_next: {{ has_next|default:'true'|yesno:'true,false' }},
6
+ batch_size: parseInt('{{ batch_size|default:25 }}'),
7
+ responsive_mode: '{{ responsive_mode|default:"collapse" }}',
8
+ shared_payload: {},
9
+ skeleton_row: parseInt('{{ batch_size|default:25 }}'),
10
+ sort_column: {% if sort_column %}'{{ sort_column }}'{% else %}null{% endif %},
11
+ sort_direction: '{{ sort_direction|default:'asc' }}',
12
+ table_id: $id('table'),
13
+ total_count: parseInt('{{ total_count|default:0 }}'),
14
+
15
+ any_row_has_children: false,
16
+ average_row_height: 0,
17
+ column_breakpoints: [],
18
+ column_count: 0,
19
+ is_loading: false,
20
+ is_refreshing: false,
21
+ is_row_open: {},
22
+ loaded_count: 0,
23
+ observer: null,
24
+ select_all: false,
25
+ selected_rows: new Set(),
26
+ skeleton_count: 0,
27
+ prevent_auto_load: false,
28
+
29
+ {% block table_xdata %}{% endblock %}
30
+
31
+ async init() {
32
+ await this.$nextTick();
33
+ this.reset_scroll_position();
34
+
35
+ if (this.should_load_initial_data()) {
36
+ this.skeleton_count = this.skeleton_row;
37
+ await this.load_more();
38
+ }
39
+
40
+ if (this.has_next) {
41
+ setTimeout(() => this.setup_observer(), 500);
42
+ }
43
+ },
44
+
45
+ apply_column_breakpoints(row_element) {
46
+ if (this.responsive_mode !== 'collapse') return;
47
+
48
+ let cells = Array.from(row_element.children);
49
+
50
+ cells.forEach((cell, index) => {
51
+ let breakpoint = this.column_breakpoints[index];
52
+
53
+ if (breakpoint && cell.tagName === 'TD') {
54
+ cell.classList.add('d-none', `d-${breakpoint}-table-cell`);
55
+ }
56
+ });
57
+ },
58
+
59
+ build_request_params(next_page) {
60
+ let params = new URLSearchParams({
61
+ page: next_page,
62
+ batch_size: this.batch_size
63
+ });
64
+
65
+ if (this.sort_column) {
66
+ params.set('sort', this.sort_column);
67
+ params.set('direction', this.sort_direction);
68
+ }
69
+
70
+ return params;
71
+ },
72
+
73
+ async check_container_height() {
74
+ let table_container = this.$refs.table_container;
75
+ let trigger = this.$refs.infinite_scroll_trigger;
76
+
77
+ if (!table_container || !trigger || !this.has_next || this.is_refreshing || this.prevent_auto_load || this.is_loading) {
78
+ return;
79
+ }
80
+
81
+ await new Promise(resolve => setTimeout(resolve, 50));
82
+
83
+ let container_rect = table_container.getBoundingClientRect();
84
+ let trigger_rect = trigger.getBoundingClientRect();
85
+
86
+ if (trigger_rect.top <= container_rect.bottom) {
87
+ await this.load_more();
88
+ }
89
+ },
90
+
91
+ cleanup_loading_state() {
92
+ this.is_loading = false;
93
+ this.skeleton_count = 0;
94
+ },
95
+
96
+ disconnect_observer() {
97
+ if (this.observer) {
98
+ this.observer.disconnect();
99
+ }
100
+ },
101
+
102
+ async fetch_rows(next_page) {
103
+ if (!this.endpoint) {
104
+ return { success: false, error: 'No endpoint provided' };
105
+ }
106
+
107
+ let params = this.build_request_params(next_page);
108
+ let url = `${this.endpoint}?${params}`;
109
+ let view = new ViewGlue(url, this.$refs.shared_payload ? JSON.parse(this.$refs.shared_payload.textContent) : {});
110
+
111
+ if (next_page === 1) {
112
+ this.$dispatch('clear-rows', { table_id: this.table_id });
113
+ this.$refs.table_body.innerHTML = '';
114
+ }
115
+
116
+ let previous_count = this.loaded_count;
117
+
118
+ await view.render_insert_adjacent(this.$refs.table_body, {}, 'beforeend');
119
+
120
+ let added = this.loaded_count - previous_count;
121
+
122
+ return { success: true, added: added };
123
+ },
124
+
125
+ get_sort_icon(column) {
126
+ if (this.sort_column !== column) {
127
+ return 'bi-chevron-expand';
128
+ }
129
+ return this.sort_direction === 'asc' ? 'bi-chevron-up' : 'bi-chevron-down';
130
+ },
131
+
132
+ handle_header_mounted(event) {
133
+ this.column_count = event.detail.count;
134
+ },
135
+
136
+ handle_header_registered(event) {
137
+ this.column_breakpoints[event.detail.index] = event.detail.breakpoint;
138
+ },
139
+
140
+ handle_row_deselected(event) {
141
+ this.selected_rows.delete(event.detail.row_id);
142
+ },
143
+
144
+ handle_row_mounted(event) {
145
+ this.loaded_count++;
146
+
147
+ if (event.detail.row_element) {
148
+ this.apply_column_breakpoints(event.detail.row_element);
149
+
150
+ if (this.average_row_height === 0) {
151
+ this.average_row_height = event.detail.row_element.offsetHeight;
152
+ }
153
+ }
154
+ },
155
+
156
+ handle_row_selected(event) {
157
+ this.selected_rows.add(event.detail.row_id);
158
+ },
159
+
160
+ handle_rows_cleared() {
161
+ this.loaded_count = 0;
162
+ this.selected_rows.clear();
163
+ this.select_all = false;
164
+ },
165
+
166
+ handle_toggle_all() {
167
+ this.select_all = !this.select_all;
168
+
169
+ if (this.select_all) {
170
+ this.$dispatch('select-all-rows', { table_id: this.table_id });
171
+ } else {
172
+ this.selected_rows.clear();
173
+ this.$dispatch('deselect-all-rows', { table_id: this.table_id });
174
+ }
175
+ },
176
+
177
+ handle_toggle_row(event) {
178
+ let row_id = event.detail.row_id;
179
+ this.is_row_open[row_id] = !this.is_row_open[row_id];
180
+
181
+ this.$dispatch('toggle-row-state', { row_id: row_id, is_open: this.is_row_open[row_id], table_id: this.table_id });
182
+ },
183
+
184
+ handle_total_count_updated(event) {
185
+ this.total_count = event.detail.total_count;
186
+
187
+ if (event.detail.batch_size) {
188
+ this.batch_size = event.detail.batch_size;
189
+ }
190
+ },
191
+
192
+ async load_more() {
193
+ if (!this.endpoint) {
194
+ return;
195
+ }
196
+
197
+ this.is_loading = true;
198
+ this.skeleton_count = this.batch_size;
199
+
200
+ if (!this.$refs.table_body) {
201
+ this.cleanup_loading_state();
202
+ return;
203
+ }
204
+
205
+ let result = await this.fetch_rows(this.current_page + 1);
206
+
207
+ if (!result.success) {
208
+ this.cleanup_loading_state();
209
+ return;
210
+ }
211
+
212
+ if (result.added > 0) {
213
+ this.current_page++;
214
+ }
215
+
216
+ if (result.added < this.batch_size) {
217
+ this.has_next = false;
218
+ this.disconnect_observer();
219
+ }
220
+
221
+ this.cleanup_loading_state();
222
+
223
+ await this.$nextTick();
224
+ await this.check_container_height();
225
+ },
226
+
227
+ async refresh_table() {
228
+ if (this.is_refreshing || this.is_loading) {
229
+ return;
230
+ }
231
+
232
+ this.disconnect_observer();
233
+
234
+ this.prevent_auto_load = true;
235
+ this.skeleton_count = this.batch_size;
236
+ this.is_refreshing = true;
237
+ this.select_all = false;
238
+
239
+ await this.$nextTick();
240
+
241
+ this.reset_table_state();
242
+
243
+ let result = await this.fetch_rows(1);
244
+ this.update_counts_after_refresh(result);
245
+
246
+ await this.$nextTick();
247
+ await new Promise(resolve => setTimeout(resolve, 150));
248
+
249
+ this.is_refreshing = false;
250
+ this.skeleton_count = 0;
251
+
252
+ if (this.has_next) {
253
+ setTimeout(() => {
254
+ this.setup_observer();
255
+
256
+ setTimeout(() => {
257
+ this.prevent_auto_load = false;
258
+ }, 100);
259
+ }, 500);
260
+ }
261
+ },
262
+
263
+ reset_scroll_position() {
264
+ requestAnimationFrame(() => {
265
+ if (this.$refs.table_container) {
266
+ this.$refs.table_container.scrollTop = 0;
267
+ }
268
+ });
269
+ },
270
+
271
+ reset_table_state() {
272
+ this.current_page = 1;
273
+ this.has_next = true;
274
+ this.is_row_open = {};
275
+ },
276
+
277
+ async setup_observer() {
278
+ let trigger = this.$refs.infinite_scroll_trigger;
279
+
280
+ if (!trigger) {
281
+ return;
282
+ }
283
+
284
+ let options = {
285
+ root: null,
286
+ rootMargin: '200px',
287
+ threshold: 0.01
288
+ };
289
+
290
+ this.observer = new IntersectionObserver(
291
+ (entries) => {
292
+ entries.forEach(async entry => {
293
+ if (entry.isIntersecting && this.has_next && !this.is_loading && !this.prevent_auto_load) {
294
+ await this.load_more();
295
+ }
296
+ });
297
+ },
298
+ options
299
+ );
300
+
301
+ this.observer.observe(trigger);
302
+ },
303
+
304
+ should_load_initial_data() {
305
+ return this.loaded_count === 0 && this.current_page === 0;
306
+ },
307
+
308
+ async sort_by(column) {
309
+ if (this.is_refreshing || this.is_loading) {
310
+ return;
311
+ }
312
+
313
+ if (this.sort_column === column) {
314
+ this.sort_direction = this.sort_direction === 'asc' ? 'desc' : 'asc';
315
+ } else {
316
+ this.sort_column = column;
317
+ this.sort_direction = 'asc';
318
+ }
319
+
320
+ await this.refresh_table();
321
+ },
322
+
323
+ update_counts_after_refresh(result) {
324
+ if (result && result.added < this.batch_size) {
325
+ this.has_next = false;
326
+ }
327
+ },
328
+ }"
329
+ @clear-rows.window="if (!$event.detail || $event.detail.table_id === table_id) handle_rows_cleared()"
330
+ @filter-applied.window="if (!$event.detail || $event.detail.table_id === table_id) refresh_table()"
331
+ @header-mounted="handle_header_mounted($event)"
332
+ @header-registered="handle_header_registered($event)"
333
+ @row-deselected.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_deselected($event)"
334
+ @row-mounted.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_mounted($event)"
335
+ @row-selected.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_selected($event)"
336
+ @toggle-row.window="if (!$event.detail || $event.detail.table_id === table_id) handle_toggle_row($event)"
337
+ @total-count-updated.window="if (!$event.detail || $event.detail.table_id === table_id) handle_total_count_updated($event)"
338
+ >
339
+ <script type="application/json" x-ref="shared_payload">{% if shared_payload %}{{ shared_payload|safe }}{% else %}{}{% endif %}</script>
340
+
341
+ {% block table_toolbar %}{% endblock %}
342
+
343
+ {% block table_container %}
344
+ <div
345
+ class="position-relative table-container"
346
+ style="height: {{ table_height|default:'600px' }}; overflow-y: auto; overflow-x: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;"
347
+ x-ref="table_container"
348
+ :data-table-id="table_id"
349
+ >
350
+ <table
351
+ class="table table-striped table-hover fs-7 w-100 {% block table_class %}{% endblock %}"
352
+ :style="'min-width: ' + (responsive_mode === 'scroll' ? '100%' : '800px') + '; ' + (any_row_has_children ? 'table-layout: fixed;' : 'table-layout: auto;')"
353
+ >
354
+ <thead class="z-1 {% block table_head_class %}bg-app-layer-two sticky-top{% endblock %}">
355
+ <tr
356
+ x-init="column_count = $el.children.length; $dispatch('header-mounted', { count: column_count })"
357
+ x-ref="header_row"
358
+ >
359
+ {% block table_header_container %}
360
+ {% block table_header_start %}
361
+ <th style="min-width: 30px; width: 30px;">
362
+ <input
363
+ :checked="select_all"
364
+ :disabled="is_refreshing || is_loading"
365
+ @change="handle_toggle_all()"
366
+ type="checkbox"
367
+ >
368
+ </th>
369
+
370
+ <th style="min-width: 30px; width: 30px;"></th>
371
+ {% endblock %}
372
+
373
+ {% block table_header %}{% endblock %}
374
+
375
+ {% block table_header_end %}
376
+ <th style="min-width: 70px; width: 70px;">Actions</th>
377
+ {% endblock %}
378
+ {% endblock %}
379
+ </tr>
380
+ </thead>
381
+
382
+ <tbody
383
+ x-ref="table_body"
384
+ x-show="!is_refreshing"
385
+ :data-table-id="table_id"
386
+ >
387
+ {% block refreshing_skeleton %}
388
+ {% include 'django_spire/table/element/refreshing_skeleton.html' %}
389
+ {% endblock %}
390
+
391
+ {% block table_body %}
392
+ {% endblock %}
393
+
394
+ {% block loading_skeleton %}
395
+ {% include 'django_spire/table/element/loading_skeleton.html' %}
396
+ {% endblock %}
397
+ </tbody>
398
+ </table>
399
+
400
+ {% block trigger %}
401
+ {% include 'django_spire/table/element/trigger.html' %}
402
+ {% endblock %}
403
+ </div>
404
+ {% endblock %}
405
+
406
+ {% block table_footer %}
407
+ {% include 'django_spire/table/element/footer.html' %}
408
+ {% endblock %}
409
+ </div>
@@ -0,0 +1,59 @@
1
+ <div
2
+ class="{{ wrapper_class|default:'' }}"
3
+ x-data="{
4
+ is_loading: false,
5
+ is_open: false,
6
+ has_loaded: false,
7
+ row_id: null,
8
+ endpoint: '{{ child_endpoint }}',
9
+ table_id: null,
10
+
11
+ init() {
12
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
13
+
14
+ let parent = this.$el.closest('[data-row-id]');
15
+ this.row_id = parent ? parent.dataset.rowId : null;
16
+
17
+ this.$nextTick(() => {
18
+ if (this.row_id) {
19
+ this.$dispatch('child-registered', { row_id: this.row_id, table_id: this.table_id });
20
+ }
21
+ });
22
+ },
23
+
24
+ async load_children() {
25
+ if (this.has_loaded || this.is_loading) return;
26
+
27
+ this.is_loading = true;
28
+
29
+ let view = new ViewGlue(this.endpoint, {});
30
+ await view.render_inner(this.$refs.child_container);
31
+
32
+ this.has_loaded = true;
33
+ this.is_loading = false;
34
+ },
35
+
36
+ handle_toggle_row_state(event) {
37
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
38
+ this.is_open = event.detail.is_open;
39
+
40
+ if (this.is_open) {
41
+ this.load_children();
42
+ }
43
+ }
44
+ }
45
+ }"
46
+ @toggle-row-state.window="handle_toggle_row_state($event)"
47
+ x-show="is_open"
48
+ x-cloak
49
+ >
50
+ <div x-show="is_loading" class="p-3">
51
+ <div class="mb-2">
52
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
53
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
54
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
55
+ </div>
56
+ </div>
57
+
58
+ <div x-show="!is_loading" x-ref="child_container"></div>
59
+ </div>
@@ -0,0 +1,45 @@
1
+ <div
2
+ class="{% block row_class %}{% endblock %}"
3
+ x-data="{
4
+ row_id: null,
5
+ table_id: null,
6
+ is_open: false,
7
+ has_children: false,
8
+
9
+ init() {
10
+ this.row_id = this.$id('expandable-row');
11
+ this.$el.setAttribute('data-row-id', this.row_id);
12
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
13
+ },
14
+
15
+ handle_child_registered(event) {
16
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
17
+ this.has_children = true;
18
+ }
19
+ },
20
+
21
+ handle_toggle_row_state(event) {
22
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
23
+ this.is_open = event.detail.is_open;
24
+ }
25
+ }
26
+ }"
27
+ @child-registered.window="handle_child_registered($event)"
28
+ @toggle-row-state.window="handle_toggle_row_state($event)"
29
+ >
30
+ <div class="d-flex align-items-center gap-2">
31
+ <template x-if="has_children">
32
+ <i
33
+ :class="is_open ? 'bi-chevron-down' : 'bi-chevron-right'"
34
+ @click="$dispatch('toggle-row', { row_id: row_id, table_id: table_id })"
35
+ class="bi cursor-pointer text-primary"
36
+ ></i>
37
+ </template>
38
+
39
+ {% block row_header %}{% endblock %}
40
+ </div>
41
+
42
+ {% block row_content %}{% endblock %}
43
+
44
+ {% block child_row %}{% endblock %}
45
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="row mt-3">
2
+ <div class="col-6 text-start">
3
+ <span class="fs-7 text-app-secondary">
4
+ Showing <span x-text="loaded_count"></span> of <span x-text="total_count"></span> rows
5
+ </span>
6
+ </div>
7
+
8
+ <div class="col-md-6 text-end" style="min-height: 2rem;">
9
+ {% block toolbar_action %}
10
+ <span class="fs-7 text-app-secondary" x-cloak x-show="selected_rows.size > 0">
11
+ <span x-text="selected_rows.size"></span> selected
12
+ </span>
13
+
14
+ {% block table_toolbar_button %}{% endblock %}
15
+ {% endblock %}
16
+ </div>
17
+ </div>
@@ -0,0 +1,9 @@
1
+ <th
2
+ data-breakpoint="{{ breakpoint|default:'' }}"
3
+ {% if sort_key %}@click="sort_by('{{ sort_key }}')"{% endif %}
4
+ class="{% if sort_key %}cursor-pointer{% endif %} {% if breakpoint %}d-none d-{{ breakpoint }}-table-cell{% endif %} user-select-none {{ extra_class|default:'' }}"
5
+ style="{{ style|default:'' }}"
6
+ x-init="let index = Array.from($el.parentElement.children).indexOf($el); $dispatch('header-registered', { index: index, breakpoint: '{{ breakpoint|default:'' }}' || null });"
7
+ >
8
+ {{ label }} {% if sort_key %}<i :class="get_sort_icon('{{ sort_key }}')" class="bi"></i>{% endif %}
9
+ </th>
@@ -0,0 +1,13 @@
1
+ <tbody x-cloak x-show="is_loading && !is_refreshing">
2
+ <template x-if="column_count > 0">
3
+ <template x-for="i in skeleton_count" :key="i">
4
+ <tr class="skeleton-row" :style="average_row_height > 0 ? `height: ${average_row_height}px;` : ''">
5
+ <template x-for="j in column_count" :key="j">
6
+ <td class="align-middle">
7
+ <div class="skeleton-box" style="height: 25px; width: 90%;"></div>
8
+ </td>
9
+ </template>
10
+ </tr>
11
+ </template>
12
+ </template>
13
+ </tbody>
@@ -0,0 +1,13 @@
1
+ <tbody x-cloak x-show="is_refreshing">
2
+ <template x-if="column_count > 0">
3
+ <template x-for="i in skeleton_count" :key="i">
4
+ <tr class="skeleton-row" :style="average_row_height > 0 ? `height: ${average_row_height}px;` : ''">
5
+ <template x-for="j in column_count" :key="j">
6
+ <td class="align-middle">
7
+ <div class="skeleton-box" style="height: 25px; width: 90%;"></div>
8
+ </td>
9
+ </template>
10
+ </tr>
11
+ </template>
12
+ </template>
13
+ </tbody>
@@ -0,0 +1,94 @@
1
+ {% if forloop.first %}
2
+ <div
3
+ x-data="{
4
+ init() {
5
+ let table_id = this.$el.closest('[data-table-id]').dataset.tableId;
6
+ this.$dispatch('total-count-updated', { total_count: {{ total_count }}, batch_size: {{ batch_size|default:25 }}, table_id: table_id });
7
+ }
8
+ }"
9
+ style="display: none;"
10
+ ></div>
11
+ {% endif %}
12
+
13
+ <tr
14
+ class="align-middle {% block unified_row_background_colour %}{% endblock %}"
15
+ data-row-id="{{ row.pk }}"
16
+ x-data="{
17
+ row_id: '{{ row.pk }}',
18
+ table_id: null,
19
+ is_checked: false,
20
+ is_open: false,
21
+ has_children: false,
22
+
23
+ init() {
24
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
25
+ this.$dispatch('row-mounted', { row_id: this.row_id, row_element: this.$el, table_id: this.table_id });
26
+ },
27
+
28
+ handle_checkbox_change() {
29
+ this.is_checked = !this.is_checked;
30
+
31
+ if (this.is_checked) {
32
+ this.$dispatch('row-selected', { row_id: this.row_id, table_id: this.table_id });
33
+ } else {
34
+ this.$dispatch('row-deselected', { row_id: this.row_id, table_id: this.table_id });
35
+ }
36
+ },
37
+
38
+ handle_child_registered(event) {
39
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
40
+ this.has_children = true;
41
+ }
42
+ },
43
+
44
+ handle_deselect_all_rows(event) {
45
+ if (!event.detail || event.detail.table_id === this.table_id) {
46
+ this.is_checked = false;
47
+ this.$dispatch('row-deselected', { row_id: this.row_id, table_id: this.table_id });
48
+ }
49
+ },
50
+
51
+ handle_select_all_rows(event) {
52
+ if (!event.detail || event.detail.table_id === this.table_id) {
53
+ this.is_checked = true;
54
+ this.$dispatch('row-selected', { row_id: this.row_id, table_id: this.table_id });
55
+ }
56
+ },
57
+
58
+ handle_toggle_row_state(event) {
59
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
60
+ this.is_open = event.detail.is_open;
61
+ }
62
+ },
63
+ }"
64
+ @child-registered.window="handle_child_registered($event)"
65
+ @deselect-all-rows.window="handle_deselect_all_rows($event)"
66
+ @select-all-rows.window="handle_select_all_rows($event)"
67
+ @toggle-row-state.window="handle_toggle_row_state($event)"
68
+ >
69
+ <td>
70
+ <input
71
+ :checked="is_checked"
72
+ @change="handle_checkbox_change()"
73
+ type="checkbox"
74
+ >
75
+ </td>
76
+
77
+ <td>
78
+ <template x-if="has_children">
79
+ <i
80
+ :class="is_open ? 'bi-chevron-down' : 'bi-chevron-right'"
81
+ @click="$dispatch('toggle-row', { row_id: row_id, table_id: table_id })"
82
+ class="bi cursor-pointer"
83
+ ></i>
84
+ </template>
85
+ </td>
86
+
87
+ {% block table_cells %}{% endblock %}
88
+ </tr>
89
+
90
+ <tr class="align-middle" data-row-id="{{ row.pk }}">
91
+ <td class="p-0" x-effect="$el.setAttribute('colspan', column_count)">
92
+ {% block child_row %}{% endblock %}
93
+ </td>
94
+ </tr>
@@ -0,0 +1 @@
1
+ <div style="height: 10px;" x-ref="infinite_scroll_trigger"></div>
@@ -0,0 +1,11 @@
1
+ <tbody x-show="!is_loading && !is_refreshing && loaded_count === 0 && total_count === 0" x-transition style="display: none;">
2
+ <tr>
3
+ <td class="text-center text-app-secondary py-4" x-effect="$el.setAttribute('colspan', column_count)">
4
+ <span class="fs-6">
5
+ {% block table_no_data %}
6
+ No data available
7
+ {% endblock %}
8
+ </span>
9
+ </td>
10
+ </tr>
11
+ </tbody>
@@ -2,7 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  # These imports are for the migrations to work as a single app "django_spire_knowledge"
4
4
 
5
- from django_spire.knowledge.entry import models
6
- from django_spire.knowledge.entry.version import models
7
- from django_spire.knowledge.entry.version.block import models
8
- from django_spire.knowledge.collection import models
5
+ from django_spire.knowledge.entry import models as entry_models
6
+ from django_spire.knowledge.entry.version import models as entry_version_models
7
+ from django_spire.knowledge.entry.version.block import models as \
8
+ entry_version_block_models
9
+ from django_spire.knowledge.collection import models as collection_models
10
+
11
+ # Must assert to stop unused import removals.
12
+
13
+ assert entry_models
14
+ assert entry_version_models
15
+ assert entry_version_block_models
16
+ assert collection_models
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div class="row">
7
7
  <div class="col">
8
- {{ entry_intel.relevant_text }}
8
+ {{ entry_intel.relevant_text|linebreaksbr }}
9
9
  </div>
10
10
  </div>
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.22.0
3
+ Version: 0.22.1
4
4
  Summary: A project for Django Spire
5
5
  Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
6
6
  License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
@@ -1,6 +1,6 @@
1
1
  django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  django_spire/conf.py,sha256=c5Hs-7lk9T15254tOasiQ2ZTFLQIVJof9_QJDfm1PAI,933
3
- django_spire/consts.py,sha256=TSGO2fhA5NvrhZi9txx5DGQlgsO1tZDXIoDfg7qdtqY,171
3
+ django_spire/consts.py,sha256=GkvgHyxBBoMVG-DwMxZMs702frH-HZ0L5xU4_r338PQ,171
4
4
  django_spire/exceptions.py,sha256=L5ndRO5ftMmh0pHkO2z_NG3LSGZviJ-dDHNT73SzTNw,48
5
5
  django_spire/settings.py,sha256=B4GPqBGt_dmkt0Ay0j-IP-SZ6mY44m2Ap5kVSON5YLA,1005
6
6
  django_spire/urls.py,sha256=mKeZszb5U4iIGqddMb5Tt5fRC72U2wABEOi6mvOfEBU,656
@@ -319,7 +319,7 @@ django_spire/contrib/gamification/static/django_spire/css/contrib/gamification/g
319
319
  django_spire/contrib/gamification/templates/django_spire/contrib/gamification/effect/kapow.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
320
320
  django_spire/contrib/generic_views/__init__.py,sha256=fJU5nY61oqFCK-osl1Hnr7W2FKc_ZWeFuEkADWqCKzA,245
321
321
  django_spire/contrib/generic_views/modal_views.py,sha256=wXPAvinnQWV0p5yrlJk-vl1-u1uSTS3GquEIlEsQXDc,2052
322
- django_spire/contrib/generic_views/portal_views.py,sha256=3XnLJfMeoYkeyDAH9D2oLAKBACVQsA6Rj8l50UgXWlQ,7715
322
+ django_spire/contrib/generic_views/portal_views.py,sha256=gDIr1qPrAerKqiqw9hns6BTEzdzhVxgNG1pKlNNHvPQ,8698
323
323
  django_spire/contrib/help/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
324
324
  django_spire/contrib/help/apps.py,sha256=UM1IoIMFNaVaQdUfACTMfUkm541TqNhOMqlp9X47zBc,193
325
325
  django_spire/contrib/help/templates/django_spire/contrib/help/help_button.html,sha256=ikym1hVCy1OuBJnU0CWhi-aCPCrLsfP3stCs0OV3PFk,162
@@ -420,6 +420,7 @@ django_spire/core/context_processors.py,sha256=l1eH7-rsUX74cy_MWYx6gnNZi3hPsYZNU
420
420
  django_spire/core/decorators.py,sha256=-zRi8lSRqbGMTxBEJ3Ek7sLmowgpLDMhC0at80Fu8vo,674
421
421
  django_spire/core/maps.py,sha256=5q48KuTT7NB-oLFTKpL7A3QuCQHAjdEZvp_JPQdlRy0,1508
422
422
  django_spire/core/middleware.py,sha256=fN5rfTj_a-lfhoibD1MY-czCYjLYcUfgeERtiOqQkCs,948
423
+ django_spire/core/models.py,sha256=-4p4u2zWd6huWzfWMoTdHxOEtNzWd8VQWQGzO5M1eYQ,223
423
424
  django_spire/core/shortcuts.py,sha256=bmurxweUlqkmDXp4vYNlcF52UKw4JBZyM7pP_jzDxF4,2787
424
425
  django_spire/core/urls.py,sha256=A0ylc3k9QuPJrWzNEyvqj7aTxaCNKB0StsZ4jTB76dc,509
425
426
  django_spire/core/utils.py,sha256=IrLFwuvhQzsa11wMDNumnHfnkdLBxkkRri6mtjGDXuQ,1003
@@ -520,8 +521,8 @@ django_spire/core/static/django_spire/css/app-side-panel.css,sha256=tZUwmC_yK9ZN
520
521
  django_spire/core/static/django_spire/css/app-template.css,sha256=D5ORspwfokNqRW0HwyCbzjBp2Kf_2XtWKa6FSQuSXOg,485
521
522
  django_spire/core/static/django_spire/css/app-text.css,sha256=YQYhTsXux7vVuZhzsyHV5TuPCpKam8F14GiOnMcGOyk,10141
522
523
  django_spire/core/static/django_spire/css/app-theme.css,sha256=6vxu-tNDS4i1wWwLxdkCuiucN_ZKzGqwZmqZ8Vcwzk0,421
523
- django_spire/core/static/django_spire/css/bootstrap-extension.css,sha256=yvfy6YYKOmdzfnJhaFonUQkSbwS0YhRdeTHlEGKSXX4,12894
524
- django_spire/core/static/django_spire/css/bootstrap-override.css,sha256=3hOf1TwwWEsFIR8JQ9TXnI7M5U3dRHLL8F1maqVTNt4,9620
524
+ django_spire/core/static/django_spire/css/bootstrap-extension.css,sha256=KJAAz64fGLQn_3TP-PxTrD7-M8JDebC96HMmi2bivAs,13654
525
+ django_spire/core/static/django_spire/css/bootstrap-override.css,sha256=BJYXU2Cz9NCGAlNunAOLi8TCIwDb4NqMdp_wonh_6E8,10471
525
526
  django_spire/core/static/django_spire/css/bootstrap.css,sha256=GEgESnyGv91X48AEz35ovTv1fF8JPaB_VZB-JXJjdb0,155840
526
527
  django_spire/core/static/django_spire/css/flatpickr.min.css,sha256=RXPAyxHVyMLxb0TYCM2OW5R4GWkcDe02jdYgyZp41OU,16081
527
528
  django_spire/core/static/django_spire/css/themes/input.css,sha256=dHY08qEODw-L1xbelx2q-5zu7g9hznVw3Ii3INKlQKU,738
@@ -583,7 +584,7 @@ django_spire/core/static/django_spire/js/theme.js,sha256=Aq3KOJarjCr_Qjn0HyJlEFG
583
584
  django_spire/core/static/django_spire/js/ui.js,sha256=Xrnw6cMmddCiUKC7iAJn1U7lk3nMJYxFJJO-J5zCqSw,673
584
585
  django_spire/core/tag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
585
586
  django_spire/core/tag/mixins.py,sha256=jbBlg-ycTUE7SX1j08iRE2cu8_9j6dp5eI2uzeoYC7Y,707
586
- django_spire/core/tag/models.py,sha256=50z8sVEollHQoedHxDyjcfy6PiJ3C5A6CKZEum7Qhfw,818
587
+ django_spire/core/tag/models.py,sha256=XBxDvfwl_k0dWxm9eClD1jxzVu--gRLQu5FMwmzY8Xs,817
587
588
  django_spire/core/tag/querysets.py,sha256=vLdAV9KRTn-B9G9vhB4tQ3j-iNyz6OVZR5E9nVvAZY8,263
588
589
  django_spire/core/tag/tools.py,sha256=SyYRrYhjwu-TFnUeHRq82J21n5aVe8Wwy-iO5GGvvd8,2142
589
590
  django_spire/core/tag/intelligence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -640,6 +641,7 @@ django_spire/core/templates/django_spire/container/form_container.html,sha256=FZ
640
641
  django_spire/core/templates/django_spire/dropdown/dropdown.html,sha256=O3gUp2YZm_2g0Qd-odFOnxfBkRc4c4af4zTbyGibSU0,528
641
642
  django_spire/core/templates/django_spire/dropdown/ellipsis_dropdown.html,sha256=6DrFtcvfCnegs_gLfDZDkEGYb6ZpJ85clKQWckiTH00,1431
642
643
  django_spire/core/templates/django_spire/dropdown/ellipsis_modal_dropdown.html,sha256=kUNcg-dSqr1wHjU_NijBAyCsa_7Z6_spVE5jytqYaME,898
644
+ django_spire/core/templates/django_spire/dropdown/ellipsis_table_dropdown.html,sha256=_Rf9hKmOchDdE00NU5Aq8pvVb4Q4vPxlIWf1NWOUGg8,935
643
645
  django_spire/core/templates/django_spire/dropdown/element/dropdown_link_element.html,sha256=KB4KRolkw5zOImRkXcVXxIH_bxsN648gQRnRDijgvB4,1005
644
646
  django_spire/core/templates/django_spire/dropdown/element/ellipsis_dropdown_modal_link_element.html,sha256=WC2ruVWh5c_5hYZmrKFYrfJXGW6r5z7KGu8_X-vPvhs,438
645
647
  django_spire/core/templates/django_spire/element/attribute_element.html,sha256=2SOodzlW7aX3oIHic7g9oyFpRgUKu7dtZX6IEybSbrU,1487
@@ -650,7 +652,7 @@ django_spire/core/templates/django_spire/element/no_data_element.html,sha256=-WS
650
652
  django_spire/core/templates/django_spire/element/page_loading_element.html,sha256=wdwYIVGoaUaapYQM_ekv2-3loXQ8Abchhhs8ipm5r-U,242
651
653
  django_spire/core/templates/django_spire/element/pagination_element.html,sha256=jEyTNxaBxwJmuvDtgSP9NEb574Hn2F5taYh44KBSQWw,1702
652
654
  django_spire/core/templates/django_spire/error/error.html,sha256=ZsNCwdEax8IhaMPE1jQ7mwWkQ63KHoZOuRzb_IjYoNc,390
653
- django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html,sha256=03NozlVmt31zkaGc-NrlFJz6-HtjYxI1HnCg1h5rMNM,867
655
+ django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html,sha256=xlXI_D9u7OtkGsmgG5NGVFbx1L7H_pCw-wIkkyLfmq8,860
654
656
  django_spire/core/templates/django_spire/filtering/form/filter_form.html,sha256=MJz2R7lA2BxuLi2n-MPPZ0rrE88q93gTEzOwQJMjwdY,264
655
657
  django_spire/core/templates/django_spire/filtering/form/search_form.html,sha256=XAmbFYJU6pZOcwHe0KWoVPjMP58ozUlzDUZudnksJS4,264
656
658
  django_spire/core/templates/django_spire/footer/footer.html,sha256=QqBoB9g5BWb9BWmGkdfmBBqihycW6bgx7ys1Fz-3Mww,321
@@ -668,7 +670,7 @@ django_spire/core/templates/django_spire/modal/center_modal.html,sha256=1ceBcqTh
668
670
  django_spire/core/templates/django_spire/modal/dispatch_modal.html,sha256=_EOQQC6dmzN1Se1PyX5Brk1X9Hw2mYYuOqVqyK3OHEM,259
669
671
  django_spire/core/templates/django_spire/modal/modal.html,sha256=jEnUweB81u-EYtQdalNMzZrCs38Ml8gU6-zQRwtRi2s,2290
670
672
  django_spire/core/templates/django_spire/modal/title_modal.html,sha256=avPAPBnv4f7XF3O13mJ_osF3-s47iJJXDT2OqN58OjM,664
671
- django_spire/core/templates/django_spire/modal/content/dispatch_modal_delete_confirmation_content.html,sha256=VQYs24LC5RL3rAARooGfDohCKJfBmJ5lEN5pLncCqFs,972
673
+ django_spire/core/templates/django_spire/modal/content/dispatch_modal_delete_confirmation_content.html,sha256=nwVUqOfY1glF93amomA5VEL2evot2ilMOubGyQ-xQVY,1002
672
674
  django_spire/core/templates/django_spire/modal/content/modal_title_content.html,sha256=zsYlbdo6hPbYWKIxfcvl6IU2IBmZ4SH-ii6oY34InLg,725
673
675
  django_spire/core/templates/django_spire/modal/element/model_close_element.html,sha256=yxeesbfVS5PK08UH7IUacBhdtLA7KyIQjK7FrkAOvb8,71
674
676
  django_spire/core/templates/django_spire/navigation/mobile_navigation.html,sha256=btXoeLihu3jd_7XzKrZiwaBgffDiLoDorMSxhYrnhc4,997
@@ -693,6 +695,16 @@ django_spire/core/templates/django_spire/speech/speech_synthesis.html,sha256=25H
693
695
  django_spire/core/templates/django_spire/tab/tab.html,sha256=1zSIX0Xk337VV9Qa4tvoSJlhoT2LDTfEfCwJvESNG-Q,1964
694
696
  django_spire/core/templates/django_spire/tab/element/tab_section_element.html,sha256=tEo0ENKhss74ll8HcFE9_kml35vusAfiMQHpudh3aEc,181
695
697
  django_spire/core/templates/django_spire/tab/element/tab_trigger_element.html,sha256=9VmMhfEGa3ARHerwyXojtQWyIkUj9sOPGhckcQR3h6k,442
698
+ django_spire/core/templates/django_spire/table/base.html,sha256=kghLGWDS3Ka0VFGGcvGq2hyKUra_DXWq4h_uBl8bzIg,14012
699
+ django_spire/core/templates/django_spire/table/element/child_row.html,sha256=B0fIXw3-p7YREdNtpIBwijCom6RS64Lrzj-teRuvvKI,1916
700
+ django_spire/core/templates/django_spire/table/element/expandable_row.html,sha256=sAzzUeGishdYRUqs9t4lduxuxZaOzUYNhMfMytRLq90,1516
701
+ django_spire/core/templates/django_spire/table/element/footer.html,sha256=hOcdcyfZpw3Vc21seilTOBjYxR8ZXiTKQYp2vodFJi4,608
702
+ django_spire/core/templates/django_spire/table/element/header.html,sha256=RVCAV0ji_wP4otjhFZHaLiHdc3yqzJiqqvFdW00GRtM,615
703
+ django_spire/core/templates/django_spire/table/element/loading_skeleton.html,sha256=b3EGdrXoFT1tku4dA_sWv8FAxck5ZZhef_9FumYZYNA,577
704
+ django_spire/core/templates/django_spire/table/element/refreshing_skeleton.html,sha256=nCoDCITzPewTYJ-vPDZiSCv1qhdhT2BqMwygIdF1tYg,562
705
+ django_spire/core/templates/django_spire/table/element/row.html,sha256=qaj4dZZ_Z442813-Nq6YmCS9UueIM2G_sJ_tZ-NT3lo,3258
706
+ django_spire/core/templates/django_spire/table/element/trigger.html,sha256=KixtqIshbs8V1QSupIMxTe0UYJbK2oJIsqD8fZYxAOs,66
707
+ django_spire/core/templates/django_spire/table/item/no_data_item.html,sha256=PxTyI98edGtMxRdRzuEh1P_uOKRY_X5awrWXQsVb1jI,441
696
708
  django_spire/core/templates/django_spire/tag/element/tag.html,sha256=Zx2HyGd-A01SrfohjHhPGjYrJj3h0xH0BUNFDD5BH_4,131
697
709
  django_spire/core/templates/django_spire/wizard/wizard.html,sha256=iPkdo5O9mh3IQnhFijP9ZQXOaJDzhHGLmZo2Y49QnJ4,2266
698
710
  django_spire/core/templates/django_spire/wizard/wizard_page.html,sha256=emW5W2z_mkGERsbw3A9tFJoUOwhxa4Efr8thtZ_gHkI,75
@@ -814,7 +826,7 @@ django_spire/knowledge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
814
826
  django_spire/knowledge/admin.py,sha256=fOF1RT1SQjiqAxQ8_BgrSWATykVkd4r8N6rihEYLWk0,348
815
827
  django_spire/knowledge/apps.py,sha256=jxay3Kf1otlVCaPWcs3sYbatzaJOGK4K4e8jgbmQsuE,750
816
828
  django_spire/knowledge/exceptions.py,sha256=CVe-sXRR8Sw30n-qJuDT12HYekmxD65DIm13-ip4gmk,96
817
- django_spire/knowledge/models.py,sha256=9NtBiv6HD6dcfL9AGMf5n6Z14hKciDkQ7Fu2zzOBWKk,344
829
+ django_spire/knowledge/models.py,sha256=HknEl6GeoDwjyF7Hn9fEsAKR-73p6qDJVRbudvqATXo,596
818
830
  django_spire/knowledge/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
819
831
  django_spire/knowledge/auth/controller.py,sha256=ktTJ7gjtbYmCYESZnK97q7_fPojTAScZXRysg2F_86Y,929
820
832
  django_spire/knowledge/collection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -990,7 +1002,7 @@ django_spire/knowledge/templates/django_spire/knowledge/entry/page/form_page.htm
990
1002
  django_spire/knowledge/templates/django_spire/knowledge/entry/page/import_form_page.html,sha256=TK8Bc7mUZwsmV1Xm9FPyWz7Hws68Ijy4c55orvNoOC4,308
991
1003
  django_spire/knowledge/templates/django_spire/knowledge/entry/version/container/editor_container.html,sha256=NAtXEq8UJOV9_vp3l5HIIdQp-lZhNVW4luF0Nvv8Sl8,3389
992
1004
  django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html,sha256=Gc5uYGjYJR8nwaLa0hm-5GHO3ezI9CLO0H_OLp8EvdE,3092
993
- django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html,sha256=sjZNgvXnOdqg068nhqo79GljR1P1XUJafExERUdBwZc,842
1005
+ django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html,sha256=kOSsRJHkh8hFYp1k1Y0wc-wh75VVjdXldEKErkfANGY,855
994
1006
  django_spire/knowledge/templates/django_spire/knowledge/page/full_page.html,sha256=vz3WQ6SAL43QALRf0haaLfNkUfIQQemR0FAZQ2tznV4,1331
995
1007
  django_spire/knowledge/templates/django_spire/knowledge/page/home_page.html,sha256=LMGSoPuP4N0NMX9QB1r5qJaExDvt6IKlJNp69Dt2EeQ,293
996
1008
  django_spire/knowledge/templates/django_spire/knowledge/sub_navigation/element/collection_sub_navigation_ellipsis_dropdown.html,sha256=DeM7yRcjRN25eRvdOK-3Ae0kbYaMWvk7Ufbc8AZphgk,1023
@@ -1149,8 +1161,8 @@ django_spire/theme/urls/page_urls.py,sha256=S8nkKkgbhG3XHI3uMUL-piOjXIrRkuY2UlM_
1149
1161
  django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1150
1162
  django_spire/theme/views/json_views.py,sha256=W1khC2K_EMbEzAFmMxC_P76_MFnkRH4-eVdodrRfAhw,1904
1151
1163
  django_spire/theme/views/page_views.py,sha256=pHr8iekjtR99xs7w1taj35HEo133Svq1dvDD0y0VL1c,3933
1152
- django_spire-0.22.0.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1153
- django_spire-0.22.0.dist-info/METADATA,sha256=jWPW3aT41-AoEvBFVxoW-pi2ppG7ccH1AxcKV3me4vk,4967
1154
- django_spire-0.22.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1155
- django_spire-0.22.0.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1156
- django_spire-0.22.0.dist-info/RECORD,,
1164
+ django_spire-0.22.1.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1165
+ django_spire-0.22.1.dist-info/METADATA,sha256=NoSKljzmaON1i2bUleBV34oumpw460s_7RyY1lYeHeQ,4967
1166
+ django_spire-0.22.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1167
+ django_spire-0.22.1.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1168
+ django_spire-0.22.1.dist-info/RECORD,,