@rmdes/indiekit-endpoint-homepage 1.0.9 → 1.0.10
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.
- package/index.js +73 -1
- package/lib/controllers/api.js +2 -0
- package/lib/controllers/dashboard.js +9 -1
- package/lib/storage/config.js +6 -0
- package/locales/en.json +12 -0
- package/package.json +1 -1
- package/views/homepage-dashboard.njk +147 -1
package/index.js
CHANGED
|
@@ -262,6 +262,70 @@ export default class HomepageEndpoint {
|
|
|
262
262
|
];
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Built-in blog post sidebar widget types (post-specific + universal)
|
|
267
|
+
*/
|
|
268
|
+
get blogPostWidgets() {
|
|
269
|
+
return [
|
|
270
|
+
{
|
|
271
|
+
id: "author-card-compact",
|
|
272
|
+
label: "Author Card (Compact)",
|
|
273
|
+
description: "Compact h-card with avatar and name",
|
|
274
|
+
icon: "user",
|
|
275
|
+
defaultConfig: {},
|
|
276
|
+
configSchema: {},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "post-navigation",
|
|
280
|
+
label: "Post Navigation",
|
|
281
|
+
description: "Previous/next post links",
|
|
282
|
+
icon: "arrow-left-right",
|
|
283
|
+
defaultConfig: {},
|
|
284
|
+
configSchema: {},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "toc",
|
|
288
|
+
label: "Table of Contents",
|
|
289
|
+
description: "Auto-generated from headings",
|
|
290
|
+
icon: "list",
|
|
291
|
+
defaultConfig: {},
|
|
292
|
+
configSchema: {},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "post-categories",
|
|
296
|
+
label: "Post Categories",
|
|
297
|
+
description: "Categories for the current post",
|
|
298
|
+
icon: "tag",
|
|
299
|
+
defaultConfig: {},
|
|
300
|
+
configSchema: {},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "webmentions",
|
|
304
|
+
label: "Webmentions",
|
|
305
|
+
description: "Likes, reposts, and replies",
|
|
306
|
+
icon: "message-circle",
|
|
307
|
+
defaultConfig: {},
|
|
308
|
+
configSchema: {},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "share",
|
|
312
|
+
label: "Share",
|
|
313
|
+
description: "Share on Bluesky and Mastodon",
|
|
314
|
+
icon: "share",
|
|
315
|
+
defaultConfig: {},
|
|
316
|
+
configSchema: {},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "subscribe",
|
|
320
|
+
label: "Subscribe",
|
|
321
|
+
description: "RSS and JSON feed links",
|
|
322
|
+
icon: "rss",
|
|
323
|
+
defaultConfig: {},
|
|
324
|
+
configSchema: {},
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
|
|
265
329
|
/**
|
|
266
330
|
* Protected routes (require authentication)
|
|
267
331
|
*/
|
|
@@ -335,6 +399,9 @@ export default class HomepageEndpoint {
|
|
|
335
399
|
const discoveredSections = [...this.homepageSections];
|
|
336
400
|
const discoveredWidgets = [...this.homepageWidgets];
|
|
337
401
|
|
|
402
|
+
// Blog post widgets = post-specific widgets + universal widgets
|
|
403
|
+
const discoveredBlogPostWidgets = [...this.blogPostWidgets];
|
|
404
|
+
|
|
338
405
|
// Scan all endpoints for homepageSections
|
|
339
406
|
for (const endpoint of Indiekit.endpoints || []) {
|
|
340
407
|
if (endpoint === this) continue; // Skip self
|
|
@@ -362,9 +429,14 @@ export default class HomepageEndpoint {
|
|
|
362
429
|
// Store discovered sections/widgets for API access
|
|
363
430
|
Indiekit.config.application.discoveredSections = discoveredSections;
|
|
364
431
|
Indiekit.config.application.discoveredWidgets = discoveredWidgets;
|
|
432
|
+
// Blog post widgets: post-specific + all universal widgets
|
|
433
|
+
Indiekit.config.application.discoveredBlogPostWidgets = [
|
|
434
|
+
...discoveredBlogPostWidgets,
|
|
435
|
+
...discoveredWidgets,
|
|
436
|
+
];
|
|
365
437
|
|
|
366
438
|
console.log(
|
|
367
|
-
`[Homepage] Discovered ${discoveredSections.length} sections, ${discoveredWidgets.length} widgets`
|
|
439
|
+
`[Homepage] Discovered ${discoveredSections.length} sections, ${discoveredWidgets.length} widgets, ${discoveredBlogPostWidgets.length} blog post widgets`
|
|
368
440
|
);
|
|
369
441
|
}
|
|
370
442
|
}
|
package/lib/controllers/api.js
CHANGED
|
@@ -90,6 +90,8 @@ export const apiController = {
|
|
|
90
90
|
hero: config.hero,
|
|
91
91
|
sections: config.sections,
|
|
92
92
|
sidebar: config.sidebar,
|
|
93
|
+
blogListingSidebar: config.blogListingSidebar,
|
|
94
|
+
blogPostSidebar: config.blogPostSidebar,
|
|
93
95
|
footer: config.footer,
|
|
94
96
|
identity: config.identity,
|
|
95
97
|
updatedAt: config.updatedAt,
|
|
@@ -42,6 +42,7 @@ export const dashboardController = {
|
|
|
42
42
|
// Get discovered sections and widgets
|
|
43
43
|
const sections = application.discoveredSections || [];
|
|
44
44
|
const widgets = application.discoveredWidgets || [];
|
|
45
|
+
const blogPostWidgets = application.discoveredBlogPostWidgets || [];
|
|
45
46
|
const presets = application.layoutPresets || [];
|
|
46
47
|
|
|
47
48
|
// Group sections by source plugin
|
|
@@ -62,6 +63,7 @@ export const dashboardController = {
|
|
|
62
63
|
config,
|
|
63
64
|
sections,
|
|
64
65
|
widgets,
|
|
66
|
+
blogPostWidgets,
|
|
65
67
|
presets,
|
|
66
68
|
activePresetId,
|
|
67
69
|
sectionsByPlugin,
|
|
@@ -89,7 +91,11 @@ export const dashboardController = {
|
|
|
89
91
|
const { application } = request.app.locals;
|
|
90
92
|
|
|
91
93
|
try {
|
|
92
|
-
const {
|
|
94
|
+
const {
|
|
95
|
+
layout, hero, sections, sidebar,
|
|
96
|
+
blogListingSidebar, blogPostSidebar,
|
|
97
|
+
footer, identity,
|
|
98
|
+
} = request.body;
|
|
93
99
|
|
|
94
100
|
// Parse JSON strings if needed
|
|
95
101
|
const config = {
|
|
@@ -97,6 +103,8 @@ export const dashboardController = {
|
|
|
97
103
|
hero: typeof hero === "string" ? JSON.parse(hero) : hero,
|
|
98
104
|
sections: typeof sections === "string" ? JSON.parse(sections) : sections,
|
|
99
105
|
sidebar: typeof sidebar === "string" ? JSON.parse(sidebar) : sidebar,
|
|
106
|
+
blogListingSidebar: typeof blogListingSidebar === "string" ? JSON.parse(blogListingSidebar) : blogListingSidebar,
|
|
107
|
+
blogPostSidebar: typeof blogPostSidebar === "string" ? JSON.parse(blogPostSidebar) : blogPostSidebar,
|
|
100
108
|
footer: typeof footer === "string" ? JSON.parse(footer) : footer,
|
|
101
109
|
identity: typeof identity === "string" ? JSON.parse(identity) : identity,
|
|
102
110
|
};
|
package/lib/storage/config.js
CHANGED
|
@@ -42,6 +42,8 @@ export async function saveConfig(application, config) {
|
|
|
42
42
|
hero: config.hero || { enabled: true, showSocial: true },
|
|
43
43
|
sections: config.sections || [],
|
|
44
44
|
sidebar: config.sidebar || [],
|
|
45
|
+
blogListingSidebar: config.blogListingSidebar || [],
|
|
46
|
+
blogPostSidebar: config.blogPostSidebar || [],
|
|
45
47
|
footer: config.footer || [],
|
|
46
48
|
identity: config.identity || null,
|
|
47
49
|
updatedAt: now,
|
|
@@ -79,6 +81,8 @@ async function writeConfigFile(application, config) {
|
|
|
79
81
|
hero: config.hero,
|
|
80
82
|
sections: config.sections,
|
|
81
83
|
sidebar: config.sidebar,
|
|
84
|
+
blogListingSidebar: config.blogListingSidebar,
|
|
85
|
+
blogPostSidebar: config.blogPostSidebar,
|
|
82
86
|
footer: config.footer,
|
|
83
87
|
identity: config.identity,
|
|
84
88
|
updatedAt: config.updatedAt,
|
|
@@ -113,6 +117,8 @@ export function getDefaultConfig() {
|
|
|
113
117
|
{ type: "recent-posts", config: { maxItems: 5 } },
|
|
114
118
|
{ type: "categories", config: {} },
|
|
115
119
|
],
|
|
120
|
+
blogListingSidebar: [],
|
|
121
|
+
blogPostSidebar: [],
|
|
116
122
|
footer: [],
|
|
117
123
|
identity: null,
|
|
118
124
|
};
|
package/locales/en.json
CHANGED
|
@@ -35,6 +35,18 @@
|
|
|
35
35
|
"add": "Add Widget",
|
|
36
36
|
"empty": "No widgets configured. Add widgets from the picker below."
|
|
37
37
|
},
|
|
38
|
+
"blogListingSidebar": {
|
|
39
|
+
"title": "Blog Listing Sidebar",
|
|
40
|
+
"description": "Configure widgets that appear in the sidebar on blog listing pages (/blog/, /notes/, /articles/...). Leave empty to use default widgets.",
|
|
41
|
+
"add": "Add Widget",
|
|
42
|
+
"empty": "No widgets configured — using default sidebar."
|
|
43
|
+
},
|
|
44
|
+
"blogPostSidebar": {
|
|
45
|
+
"title": "Blog Post Sidebar",
|
|
46
|
+
"description": "Configure widgets that appear in the sidebar on individual post pages. Leave empty to use default widgets.",
|
|
47
|
+
"add": "Add Widget",
|
|
48
|
+
"empty": "No widgets configured — using default sidebar."
|
|
49
|
+
},
|
|
38
50
|
"footer": {
|
|
39
51
|
"title": "Footer (3-column)",
|
|
40
52
|
"description": "A responsive 3-column area below the main content — add up to 3 blocks (one per column). Ideal for webrings, links, or custom content.",
|
package/package.json
CHANGED
|
@@ -443,6 +443,59 @@
|
|
|
443
443
|
<input type="hidden" name="sidebar" id="sidebar-json" value='{{ config.sidebar | dump }}'>
|
|
444
444
|
</section>
|
|
445
445
|
|
|
446
|
+
{# Blog Listing Sidebar #}
|
|
447
|
+
<section class="hp-section">
|
|
448
|
+
<h2>{{ __("homepage.blogListingSidebar.title") }}</h2>
|
|
449
|
+
<p class="hp-section__desc">{{ __("homepage.blogListingSidebar.description") }}</p>
|
|
450
|
+
|
|
451
|
+
<ul class="hp-sections-list" id="blog-listing-sidebar-list"></ul>
|
|
452
|
+
|
|
453
|
+
<div class="hp-section-picker">
|
|
454
|
+
<h3>{{ __("homepage.blogListingSidebar.add") }}</h3>
|
|
455
|
+
<div class="hp-section-picker__grid">
|
|
456
|
+
{% for widget in widgets %}
|
|
457
|
+
<div class="hp-section-picker__item" data-add-blog-listing-widget="{{ widget.id }}">
|
|
458
|
+
{{ widget.label }}
|
|
459
|
+
</div>
|
|
460
|
+
{% endfor %}
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<input type="hidden" name="blogListingSidebar" id="blog-listing-sidebar-json" value='{{ config.blogListingSidebar | dump }}'>
|
|
465
|
+
</section>
|
|
466
|
+
|
|
467
|
+
{# Blog Post Sidebar #}
|
|
468
|
+
<section class="hp-section">
|
|
469
|
+
<h2>{{ __("homepage.blogPostSidebar.title") }}</h2>
|
|
470
|
+
<p class="hp-section__desc">{{ __("homepage.blogPostSidebar.description") }}</p>
|
|
471
|
+
|
|
472
|
+
<ul class="hp-sections-list" id="blog-post-sidebar-list"></ul>
|
|
473
|
+
|
|
474
|
+
<div class="hp-section-picker">
|
|
475
|
+
<h3>{{ __("homepage.blogPostSidebar.add") }}</h3>
|
|
476
|
+
<div class="hp-section-picker__grid">
|
|
477
|
+
<div class="hp-section-picker__heading">Post Widgets</div>
|
|
478
|
+
{% for widget in blogPostWidgets %}
|
|
479
|
+
{% if widget.id in ["author-card-compact", "post-navigation", "toc", "post-categories", "webmentions", "share", "subscribe"] %}
|
|
480
|
+
<div class="hp-section-picker__item" data-add-blog-post-widget="{{ widget.id }}">
|
|
481
|
+
{{ widget.label }}
|
|
482
|
+
</div>
|
|
483
|
+
{% endif %}
|
|
484
|
+
{% endfor %}
|
|
485
|
+
<div class="hp-section-picker__heading">Universal Widgets</div>
|
|
486
|
+
{% for widget in blogPostWidgets %}
|
|
487
|
+
{% if widget.id not in ["author-card-compact", "post-navigation", "toc", "post-categories", "webmentions", "share", "subscribe"] %}
|
|
488
|
+
<div class="hp-section-picker__item" data-add-blog-post-widget="{{ widget.id }}">
|
|
489
|
+
{{ widget.label }}
|
|
490
|
+
</div>
|
|
491
|
+
{% endif %}
|
|
492
|
+
{% endfor %}
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<input type="hidden" name="blogPostSidebar" id="blog-post-sidebar-json" value='{{ config.blogPostSidebar | dump }}'>
|
|
497
|
+
</section>
|
|
498
|
+
|
|
446
499
|
{# Footer #}
|
|
447
500
|
<section class="hp-section">
|
|
448
501
|
<h2>{{ __("homepage.footer.title") }}</h2>
|
|
@@ -483,8 +536,11 @@
|
|
|
483
536
|
var widgetLabels = {
|
|
484
537
|
{% for widget in widgets %}'{{ widget.id }}': '{{ widget.label }}'{% if not loop.last %}, {% endif %}{% endfor %}
|
|
485
538
|
};
|
|
539
|
+
var blogPostWidgetLabels = {
|
|
540
|
+
{% for widget in blogPostWidgets %}'{{ widget.id }}': '{{ widget.label }}'{% if not loop.last %}, {% endif %}{% endfor %}
|
|
541
|
+
};
|
|
486
542
|
// Merge so all label maps cover all types
|
|
487
|
-
var allLabels = Object.assign({}, sectionLabels, widgetLabels);
|
|
543
|
+
var allLabels = Object.assign({}, sectionLabels, widgetLabels, blogPostWidgetLabels);
|
|
488
544
|
|
|
489
545
|
// Unique key counter for tracking items
|
|
490
546
|
var nextKey = 0;
|
|
@@ -492,10 +548,14 @@
|
|
|
492
548
|
// Parse current data from hidden inputs and assign keys
|
|
493
549
|
var sections = JSON.parse(document.getElementById('sections-json').value || '[]');
|
|
494
550
|
var sidebar = JSON.parse(document.getElementById('sidebar-json').value || '[]');
|
|
551
|
+
var blogListingSidebar = JSON.parse(document.getElementById('blog-listing-sidebar-json').value || '[]');
|
|
552
|
+
var blogPostSidebar = JSON.parse(document.getElementById('blog-post-sidebar-json').value || '[]');
|
|
495
553
|
var footer = JSON.parse(document.getElementById('footer-json').value || '[]');
|
|
496
554
|
|
|
497
555
|
sections.forEach(function(s) { s._key = nextKey++; });
|
|
498
556
|
sidebar.forEach(function(s) { s._key = nextKey++; });
|
|
557
|
+
blogListingSidebar.forEach(function(s) { s._key = nextKey++; });
|
|
558
|
+
blogPostSidebar.forEach(function(s) { s._key = nextKey++; });
|
|
499
559
|
footer.forEach(function(s) { s._key = nextKey++; });
|
|
500
560
|
|
|
501
561
|
// Strip _key before form submission
|
|
@@ -739,6 +799,72 @@
|
|
|
739
799
|
initSortable();
|
|
740
800
|
}
|
|
741
801
|
|
|
802
|
+
// --- Blog Listing Sidebar ---
|
|
803
|
+
function addBlogListingWidget(id) {
|
|
804
|
+
var item = { type: id, config: {}, _key: nextKey++ };
|
|
805
|
+
blogListingSidebar.push(item);
|
|
806
|
+
updateBlogListingSidebar();
|
|
807
|
+
if (id === 'custom-html') {
|
|
808
|
+
var list = document.getElementById('blog-listing-sidebar-list');
|
|
809
|
+
var lastPanel = list.querySelector('details.hp-edit-panel:last-of-type');
|
|
810
|
+
if (lastPanel) lastPanel.open = true;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function removeBlogListingWidget(key) {
|
|
815
|
+
blogListingSidebar = blogListingSidebar.filter(function(w) { return w._key !== key; });
|
|
816
|
+
updateBlogListingSidebar();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function editBlogListingWidget(key, config) {
|
|
820
|
+
var item = blogListingSidebar.find(function(s) { return s._key === key; });
|
|
821
|
+
if (item) item.config = config;
|
|
822
|
+
updateBlogListingSidebar();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function updateBlogListingSidebar() {
|
|
826
|
+
document.getElementById('blog-listing-sidebar-json').value = JSON.stringify(stripKeys(blogListingSidebar));
|
|
827
|
+
renderList(
|
|
828
|
+
document.getElementById('blog-listing-sidebar-list'),
|
|
829
|
+
blogListingSidebar, allLabels, removeBlogListingWidget, editBlogListingWidget,
|
|
830
|
+
'{{ __("homepage.blogListingSidebar.empty") }}'
|
|
831
|
+
);
|
|
832
|
+
initSortable();
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// --- Blog Post Sidebar ---
|
|
836
|
+
function addBlogPostWidget(id) {
|
|
837
|
+
var item = { type: id, config: {}, _key: nextKey++ };
|
|
838
|
+
blogPostSidebar.push(item);
|
|
839
|
+
updateBlogPostSidebar();
|
|
840
|
+
if (id === 'custom-html') {
|
|
841
|
+
var list = document.getElementById('blog-post-sidebar-list');
|
|
842
|
+
var lastPanel = list.querySelector('details.hp-edit-panel:last-of-type');
|
|
843
|
+
if (lastPanel) lastPanel.open = true;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function removeBlogPostWidget(key) {
|
|
848
|
+
blogPostSidebar = blogPostSidebar.filter(function(w) { return w._key !== key; });
|
|
849
|
+
updateBlogPostSidebar();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function editBlogPostWidget(key, config) {
|
|
853
|
+
var item = blogPostSidebar.find(function(s) { return s._key === key; });
|
|
854
|
+
if (item) item.config = config;
|
|
855
|
+
updateBlogPostSidebar();
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function updateBlogPostSidebar() {
|
|
859
|
+
document.getElementById('blog-post-sidebar-json').value = JSON.stringify(stripKeys(blogPostSidebar));
|
|
860
|
+
renderList(
|
|
861
|
+
document.getElementById('blog-post-sidebar-list'),
|
|
862
|
+
blogPostSidebar, allLabels, removeBlogPostWidget, editBlogPostWidget,
|
|
863
|
+
'{{ __("homepage.blogPostSidebar.empty") }}'
|
|
864
|
+
);
|
|
865
|
+
initSortable();
|
|
866
|
+
}
|
|
867
|
+
|
|
742
868
|
// --- Footer (max 3 columns) ---
|
|
743
869
|
var FOOTER_MAX = 3;
|
|
744
870
|
|
|
@@ -802,6 +928,16 @@
|
|
|
802
928
|
document.getElementById('sidebar-json').value = JSON.stringify(stripKeys(sidebar));
|
|
803
929
|
}
|
|
804
930
|
|
|
931
|
+
function syncBlogListingSidebarFromDom() {
|
|
932
|
+
blogListingSidebar = syncFromDom(document.getElementById('blog-listing-sidebar-list'), blogListingSidebar);
|
|
933
|
+
document.getElementById('blog-listing-sidebar-json').value = JSON.stringify(stripKeys(blogListingSidebar));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function syncBlogPostSidebarFromDom() {
|
|
937
|
+
blogPostSidebar = syncFromDom(document.getElementById('blog-post-sidebar-list'), blogPostSidebar);
|
|
938
|
+
document.getElementById('blog-post-sidebar-json').value = JSON.stringify(stripKeys(blogPostSidebar));
|
|
939
|
+
}
|
|
940
|
+
|
|
805
941
|
function syncFooterFromDom() {
|
|
806
942
|
footer = syncFromDom(document.getElementById('footer-list'), footer);
|
|
807
943
|
document.getElementById('footer-json').value = JSON.stringify(stripKeys(footer));
|
|
@@ -822,6 +958,12 @@
|
|
|
822
958
|
document.querySelectorAll('[data-add-widget]').forEach(function(el) {
|
|
823
959
|
el.addEventListener('click', function() { addWidget(el.dataset.addWidget); });
|
|
824
960
|
});
|
|
961
|
+
document.querySelectorAll('[data-add-blog-listing-widget]').forEach(function(el) {
|
|
962
|
+
el.addEventListener('click', function() { addBlogListingWidget(el.dataset.addBlogListingWidget); });
|
|
963
|
+
});
|
|
964
|
+
document.querySelectorAll('[data-add-blog-post-widget]').forEach(function(el) {
|
|
965
|
+
el.addEventListener('click', function() { addBlogPostWidget(el.dataset.addBlogPostWidget); });
|
|
966
|
+
});
|
|
825
967
|
document.querySelectorAll('[data-add-footer]').forEach(function(el) {
|
|
826
968
|
el.addEventListener('click', function() { addFooter(el.dataset.addFooter); });
|
|
827
969
|
});
|
|
@@ -847,6 +989,8 @@
|
|
|
847
989
|
var lists = [
|
|
848
990
|
{ el: 'sections-list', sync: syncSectionsFromDom },
|
|
849
991
|
{ el: 'widgets-list', sync: syncSidebarFromDom },
|
|
992
|
+
{ el: 'blog-listing-sidebar-list', sync: syncBlogListingSidebarFromDom },
|
|
993
|
+
{ el: 'blog-post-sidebar-list', sync: syncBlogPostSidebarFromDom },
|
|
850
994
|
{ el: 'footer-list', sync: syncFooterFromDom }
|
|
851
995
|
];
|
|
852
996
|
|
|
@@ -877,6 +1021,8 @@
|
|
|
877
1021
|
// --- Initial render ---
|
|
878
1022
|
updateSections();
|
|
879
1023
|
updateWidgets();
|
|
1024
|
+
updateBlogListingSidebar();
|
|
1025
|
+
updateBlogPostSidebar();
|
|
880
1026
|
updateFooter();
|
|
881
1027
|
</script>
|
|
882
1028
|
{% endblock %}
|