Compare commits
1 commit
main
...
thubrecht/
Author | SHA1 | Date | |
---|---|---|---|
cd5ec164dc |
13 changed files with 469 additions and 308 deletions
|
@ -4,66 +4,17 @@
|
||||||
|
|
||||||
{% block custom_js %}
|
{% block custom_js %}
|
||||||
<script>
|
<script>
|
||||||
const _fm = b => {
|
(() => {
|
||||||
b.addEventListener('click', () => {
|
const initButtons = (f, e) => {
|
||||||
const f = _$('form', _id(b.dataset.target), false);
|
_$('.del', e).forEach(remove);
|
||||||
f.dataset.next = b.dataset.next;
|
initModal(f, e);
|
||||||
f.dataset.origin = b.dataset.parent
|
}
|
||||||
|
|
||||||
const d = JSON.parse(b.dataset.json);
|
_$('.del').forEach(remove);
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(d)) {
|
submitForm(id('form-option'), initButtons);
|
||||||
_$(`[name='${k}']`, f, false).value = v;
|
submitForm(id('form-question'), initButtons);
|
||||||
}
|
})()
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_$('.modal-button').forEach(_fm);
|
|
||||||
|
|
||||||
const _del = d => {
|
|
||||||
d.addEventListener('click', () => {
|
|
||||||
_get(d.dataset.url, r => {
|
|
||||||
if (r.success && r.action == 'delete') {
|
|
||||||
_id(d.dataset.target).remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.message) {
|
|
||||||
_notif(r.message.content, r.message.class);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_$('.del').forEach(_del);
|
|
||||||
|
|
||||||
_$('form').forEach(f => {
|
|
||||||
f.addEventListener('submit', event => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
_post(f.action, f, r => {
|
|
||||||
if (r.success) {
|
|
||||||
const e = document.createElement('div');
|
|
||||||
e.innerHTML = r.html;
|
|
||||||
// On initialise les boutons
|
|
||||||
_$('.modal-button', e).forEach(b => {
|
|
||||||
_om(b);
|
|
||||||
_fm(b);
|
|
||||||
});
|
|
||||||
_$('.del', e).forEach(_del);
|
|
||||||
|
|
||||||
if (r.action == 'create') {
|
|
||||||
_id(f.dataset.next).appendChild(e.firstElementChild);
|
|
||||||
} else if (r.action == 'update') {
|
|
||||||
const n = _id(f.dataset.origin);
|
|
||||||
n.parentNode.replaceChild(e.firstElementChild, n);
|
|
||||||
}
|
|
||||||
// On ferme le modal
|
|
||||||
document.documentElement.classList.remove('is-clipped');
|
|
||||||
_id(f.dataset.modal).classList.remove('is-active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (_$('[name="delete"]', f, false).value == 'oui') {
|
if (_$('[name="delete"]', f, false).value == 'oui') {
|
||||||
_get(f.action, r => {
|
get(f.action).then(r => {
|
||||||
if (r.success && r.action == 'delete') {
|
if (r.success && r.action == 'delete') {
|
||||||
{% if election.restricted %}
|
{% if election.restricted %}
|
||||||
const r = _id(f.dataset.target);
|
const r = _id(f.dataset.target);
|
||||||
|
@ -27,21 +27,21 @@
|
||||||
i.classList.remove('fa-check');
|
i.classList.remove('fa-check');
|
||||||
i.classList.add('fa-times');
|
i.classList.add('fa-times');
|
||||||
{% else %}
|
{% else %}
|
||||||
_id(f.dataset.target).remove()
|
id(f.dataset.target).remove()
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
// On ferme le modal
|
// On ferme le modal
|
||||||
document.documentElement.classList.remove('is-clipped');
|
document.documentElement.classList.remove('is-clipped');
|
||||||
_id(f.dataset.modal).classList.remove('is-active');
|
id(f.dataset.modal).classList.remove('is-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
_notif(r.message.content, r.message.class);
|
notify(r.message.content, r.message.class);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('is-clipped');
|
document.documentElement.classList.remove('is-clipped');
|
||||||
_id(f.dataset.modal).classList.remove('is-active');
|
id(f.dataset.modal).classList.remove('is-active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,15 +51,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal" id="modal-confirm">
|
<div class="modal" id="modal-confirm">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background" data-closes="modal-confirm"></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
<p class="modal-card-title">{% trans "Confirmation du vote" %}</p>
|
<p class="modal-card-title">{% trans "Confirmation du vote" %}</p>
|
||||||
<a class="delete" aria-label="close"></a>
|
<a class="delete" aria-label="close" data-closes="modal-confirm"></a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="modal-card-body" id="modal-body">
|
<section class="modal-card-body" id="modal-body"></section>
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer class="modal-card-foot">
|
<footer class="modal-card-foot">
|
||||||
<button class="button is-fullwidth is-outlined is-primary is-light" type="submit">
|
<button class="button is-fullwidth is-outlined is-primary is-light" type="submit">
|
||||||
|
@ -69,7 +68,7 @@
|
||||||
<span>{% trans "Confirmer" %}</span>
|
<span>{% trans "Confirmer" %}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a class="button is-primary button-close">
|
<a class="button is-primary button-close" data-closes="modal-confirm">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
// On déplace les options
|
// On déplace les options
|
||||||
while (rank_zones[next_rank].childElementCount > 1) {
|
while (rank_zones[next_rank].childElementCount > 1) {
|
||||||
const t = rank_zones[next_rank].lastChild;
|
const t = rank_zones[next_rank].lastChild;
|
||||||
const i = _id(t.dataset.input);
|
const i = id(t.dataset.input);
|
||||||
i.value = j.toString();
|
i.value = j.toString();
|
||||||
rank_zones[j].append(t);
|
rank_zones[j].append(t);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
_$('.control .input').forEach(i => {
|
_$('.control .input').forEach(i => {
|
||||||
// On rajoute la tuile dans le classement ou dans les non classées
|
// On rajoute la tuile dans le classement ou dans les non classées
|
||||||
const r = parseInt(i.value);
|
const r = parseInt(i.value);
|
||||||
const t = _id(`tile-${i.id}`);
|
const t = id(`tile-${i.id}`);
|
||||||
|
|
||||||
if (!(typeof r === 'undefined') && r > 0 && r <= nb_options) {
|
if (!(typeof r === 'undefined') && r > 0 && r <= nb_options) {
|
||||||
rank_zones[r].appendChild(t);
|
rank_zones[r].appendChild(t);
|
||||||
|
@ -82,8 +82,8 @@
|
||||||
const d = event.target.closest('.drop-zone');
|
const d = event.target.closest('.drop-zone');
|
||||||
|
|
||||||
const r = d.dataset.rank;
|
const r = d.dataset.rank;
|
||||||
const t = _id(data);
|
const t = id(data);
|
||||||
const i = _id(t.dataset.input);
|
const i = id(t.dataset.input);
|
||||||
|
|
||||||
// Si on ne change pas de rang, pas besoin de déplacer l'option
|
// Si on ne change pas de rang, pas besoin de déplacer l'option
|
||||||
if (i.value != r) {
|
if (i.value != r) {
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Affiche le modal et remplit le récapitulatif
|
// Affiche le modal et remplit le récapitulatif
|
||||||
_id('confirm-button').addEventListener('click', () => {
|
id('confirm-button').addEventListener('click', () => {
|
||||||
const ranks = new Array(nb_options + 1);
|
const ranks = new Array(nb_options + 1);
|
||||||
|
|
||||||
_$('.control .input').forEach(i => {
|
_$('.control .input').forEach(i => {
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
trs += `<tr><th>${j}</th><td><div>${option_list}</div></td></tr>\n`
|
trs += `<tr><th>${j}</th><td><div>${option_list}</div></td></tr>\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
_id('modal-body').innerHTML = `
|
id('modal-body').innerHTML = `
|
||||||
<table class="table is-fullwidth is-striped">
|
<table class="table is-fullwidth is-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -142,10 +142,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change le mode de remplissge de formulaire (input vs drag & drop)
|
// Change le mode de remplissge de formulaire (input vs drag & drop)
|
||||||
_id('change-method').addEventListener('click', () => {
|
id('change-method').addEventListener('click', () => {
|
||||||
const h = _id('hide-form');
|
const h = id('hide-form');
|
||||||
const d = _id('drag-zone');
|
const d = id('drag-zone');
|
||||||
const b = _id('change-method');
|
const b = id('change-method');
|
||||||
|
|
||||||
// On échange ce qui est visible
|
// On échange ce qui est visible
|
||||||
h.classList.toggle('is-hidden');
|
h.classList.toggle('is-hidden');
|
||||||
|
@ -162,10 +162,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialise les éléments pour le formulaire interactif
|
// Initialise les éléments pour le formulaire interactif
|
||||||
$unranked = _id('unranked');
|
$unranked = id('unranked');
|
||||||
|
|
||||||
for (let i = 1; i <= nb_options; i++) {
|
for (let i = 1; i <= nb_options; i++) {
|
||||||
rank_zones[i] = _id(`rank-${i}`);
|
rank_zones[i] = id(`rank-${i}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_$('.control .input').forEach(i => {
|
_$('.control .input').forEach(i => {
|
||||||
|
|
|
@ -2,30 +2,16 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block custom_js %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
id('confirm-button').addEventListener('click', () => {
|
||||||
_id('confirm-button').addEventListener('click', () => {
|
let selected_rows = '';
|
||||||
let selected_rows = '';
|
|
||||||
|
|
||||||
_$('.checkbox input').forEach(c => {
|
_$('.checkbox input').filter(c => c.checked).forEach(c => {
|
||||||
if (c.checked) {
|
selected_rows += `<tr><td>${c.nextSibling.textContent.trim()}</td></tr>\n`;
|
||||||
selected_rows += `<tr><td>${c.nextSibling.textContent.trim()}</td></tr>\n`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_id('modal-body').innerHTML = `
|
|
||||||
<table class="table is-fullwidth">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Option(s) selectionnée(s)" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
${selected_rows}
|
|
||||||
</tbody>
|
|
||||||
</table>`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
id('modal-body').innerHTML = `<table class="table is-fullwidth"><thead><tr><th>{% trans "Option(s) selectionnée(s)" %}</th></tr></thead><tbody>${selected_rows}</tbody></table>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10591,6 +10591,59 @@ body {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade-out {
|
||||||
|
transition: opacity 1s;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.is-loading {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
form.is-loading::after {
|
||||||
|
opacity: 1;
|
||||||
|
animation: spin 750ms infinite linear;
|
||||||
|
border: 5px solid #242424;
|
||||||
|
border-radius: 150px;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-top-color: transparent;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 4em;
|
||||||
|
position: absolute !important;
|
||||||
|
width: 4em;
|
||||||
|
left: calc(50% - 2em);
|
||||||
|
top: calc(50% - 2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#notifications {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 1.75em;
|
||||||
|
}
|
||||||
|
#notifications .notification {
|
||||||
|
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 2px rgba(10, 10, 10, 0.02);
|
||||||
|
margin-left: 22.5%;
|
||||||
|
margin-right: 22.5%;
|
||||||
|
font-size: 1.5em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1152px) {
|
||||||
|
#notifications .notification {
|
||||||
|
margin-left: 12.5%;
|
||||||
|
margin-right: 12.5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
#notifications .notification {
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#scroll-button {
|
#scroll-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
|
|
File diff suppressed because one or more lines are too long
219
shared/static/js/framework.js
Normal file
219
shared/static/js/framework.js
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
const _ = undefined
|
||||||
|
|
||||||
|
// Select elements with the given selector
|
||||||
|
const _$ = (s, e = document, a = true) => {
|
||||||
|
const r = Array.from(e.querySelectorAll(s));
|
||||||
|
return a ? r : r[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selects an element with the given id
|
||||||
|
const id = s => document.getElementById(s);
|
||||||
|
|
||||||
|
// Debounce utility
|
||||||
|
const debounce = (f, t = 200) => {
|
||||||
|
let timer;
|
||||||
|
return (...args) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(f, t, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the data received after a GET request
|
||||||
|
const get = u => fetch(u).then(r => r.json()).catch(e => notify(e, 'danger'))
|
||||||
|
|
||||||
|
// Returns the data received after a POST request
|
||||||
|
const post = (u, d) => fetch(u, {
|
||||||
|
method: 'POST',
|
||||||
|
body: new FormData(d)
|
||||||
|
}).then(r => r.json()).catch(e => notify(e, 'danger'))
|
||||||
|
|
||||||
|
// Creates a new element
|
||||||
|
const element = (t = 'template', c = [], h = '') => {
|
||||||
|
const e = document.createElement(t);
|
||||||
|
e.classList.add(...c);
|
||||||
|
e.innerHTML = h;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a delete button to the given element
|
||||||
|
const addDelete = e => {
|
||||||
|
const b = element('button', ["delete"]);
|
||||||
|
b.addEventListener('click', () => e.remove());
|
||||||
|
|
||||||
|
e.appendChild(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a notification
|
||||||
|
const notify = (m, c) => {
|
||||||
|
const n = element('div', ['notification'], `<b>${m}</b>`);
|
||||||
|
c ? n.classList.add(`is-${c}`) : _;
|
||||||
|
|
||||||
|
id('notifications').insertBefore(n, id('notifications').firstChild)
|
||||||
|
|
||||||
|
addDelete(n);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
n.classList.add('fade-out');
|
||||||
|
setTimeout(() => n.remove(), 1000)
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a listener to remove the target
|
||||||
|
const remove = d => d.addEventListener('click', () => {
|
||||||
|
get(d.dataset.url).then(r => {
|
||||||
|
if (r.success && r.action === 'delete') {
|
||||||
|
id(d.dataset.target).remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
r.message ? notify(r.message.content, r.message.class) : _;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
const openModal = b => b.addEventListener('click', () => {
|
||||||
|
const m = id(b.dataset.target);
|
||||||
|
if ('post_url' in b.dataset) {
|
||||||
|
_$('form', m, false).action = b.dataset.post_url;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('title' in b.dataset) {
|
||||||
|
_$('.modal-card-title', m, false).innerHTML = b.dataset.title;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.documentElement.classList.add('is-clipped');
|
||||||
|
m.classList.add('is-active');
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeModal = b => b.addEventListener('click', () => {
|
||||||
|
document.documentElement.classList.remove('is-clipped');
|
||||||
|
id(b.dataset.closes).classList.remove('is-active')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add error to input
|
||||||
|
const addError = (i, e) => {
|
||||||
|
const s = element('span', ['help', 'is-danger'], e);
|
||||||
|
|
||||||
|
i.classList.add('is-danger');
|
||||||
|
i.insertAdjacentElement('afterend', s);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove error from input
|
||||||
|
const removeError = i => {
|
||||||
|
i.classList.remove('is-danger');
|
||||||
|
_$('span.help.is-danger', i.parentNode).forEach(e => e.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form autofill
|
||||||
|
const autoFill = (d, f) => {
|
||||||
|
for (const [k, v] of Object.entries(d)) {
|
||||||
|
const fd = _$(`[name='${k}']`, f, false);
|
||||||
|
if (typeof(v) === 'boolean') {
|
||||||
|
fd.checked = v;
|
||||||
|
} else {
|
||||||
|
if (fd.value !== undefined) {
|
||||||
|
fd.value = v;
|
||||||
|
} else {
|
||||||
|
fd.innerHTML = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initForm = b => b.addEventListener('click', () => {
|
||||||
|
const f = _$('form', id(b.dataset.target), false);
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.dataset.next = b.dataset.next;
|
||||||
|
f.dataset.origin = b.dataset.parent
|
||||||
|
f.dataset.modal = b.dataset.target;
|
||||||
|
|
||||||
|
_$('input,select', f).forEach(removeError);
|
||||||
|
|
||||||
|
b.dataset.json ? autoFill(JSON.parse(b.dataset.json), f) : _;
|
||||||
|
|
||||||
|
if (b.dataset.json_url) {
|
||||||
|
f.classList.add('is-loading');
|
||||||
|
get(b.dataset.json_url).then(r => {
|
||||||
|
if (r.success) {
|
||||||
|
autoFill(r.data, f);
|
||||||
|
f.classList.remove('is-loading');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Form submission
|
||||||
|
const submitForm = (f, i, s) => f.addEventListener('submit', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.submitter.classList.add('is-loading');
|
||||||
|
|
||||||
|
post(f.action, f).then(r => {
|
||||||
|
event.submitter.classList.remove('is-loading');
|
||||||
|
|
||||||
|
// On enlève les erreurs
|
||||||
|
_$('input,select', f).forEach(removeError);
|
||||||
|
|
||||||
|
if (r.success) {
|
||||||
|
// On crée le résultat
|
||||||
|
const e = element('template', [], r.html).content;
|
||||||
|
|
||||||
|
i(f, e);
|
||||||
|
|
||||||
|
switch (r.action) {
|
||||||
|
case 'create':
|
||||||
|
id(f.dataset.next).appendChild(e);
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
const n = id(f.dataset.origin);
|
||||||
|
n.parentNode.replaceChild(e, n);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
id(f.dataset.origin).remove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On ferme le modal
|
||||||
|
if (f.dataset.modal) {
|
||||||
|
document.documentElement.classList.remove('is-clipped');
|
||||||
|
id(f.dataset.modal).classList.remove('is-active');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const [n, e] of Object.entries(r.errors)) {
|
||||||
|
if (n === '__all__') {
|
||||||
|
e.forEach(m => notify(m, 'danger'));
|
||||||
|
} else {
|
||||||
|
addError(_$(`[name='${n}']`, f, false), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s ? s(r) : _;
|
||||||
|
|
||||||
|
// On affiche un message si besoin
|
||||||
|
r.message.content ? notify(r.message.content, r.message.class) : _;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
// Modal initialisation
|
||||||
|
const initModal = (f, e) => {
|
||||||
|
_$('.modal-button', e).forEach(openModal);
|
||||||
|
_$('.modal-button[json],.modal-button[json_url]', e).forEach(initForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element deletion
|
||||||
|
const _dt = (d, f) => d.addEventListener('click', () => {
|
||||||
|
get(d.dataset.url).then(r => {
|
||||||
|
if (r.success && r.action == 'delete') {
|
||||||
|
id(d.dataset.target).remove();
|
||||||
|
|
||||||
|
f ? f() : _;
|
||||||
|
}
|
||||||
|
|
||||||
|
r.message ? notify(r.message.content, r.message.class) : _;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pluralization
|
||||||
|
const pluralize = (s, n) => n == 1 ? s : `${s}s`
|
52
shared/static/js/kadenios.js
Normal file
52
shared/static/js/kadenios.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
const _smc = '.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button-close';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Interact with dropdowns
|
||||||
|
const ds = _$('.dropdown:not(.is-hoverable)');
|
||||||
|
|
||||||
|
ds.forEach(d => {
|
||||||
|
d.addEventListener('click', event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
d.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
ds.forEach(d => {
|
||||||
|
d.classList.remove('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Interact with modals
|
||||||
|
const ms = _$('.modal');
|
||||||
|
const mcs = _$(_smc);
|
||||||
|
|
||||||
|
_$('.modal-button').forEach(openModal);
|
||||||
|
_$('.modal-button[data-json],.modal-button[data-json_url]').forEach(initForm);
|
||||||
|
|
||||||
|
mcs.forEach(closeModal);
|
||||||
|
|
||||||
|
document.addEventListener('keydown', ev => {
|
||||||
|
const e = ev || window.event;
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
ds.forEach(d => {
|
||||||
|
d.classList.remove('is-active');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.documentElement.classList.remove('is-clipped');
|
||||||
|
|
||||||
|
ms.forEach(m => {
|
||||||
|
m.classList.remove('is-active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Language selection
|
||||||
|
_$('.dropdown-item.lang-selector').forEach(l => {
|
||||||
|
l.addEventListener('click', () => {
|
||||||
|
_id('lang-input').value = l.dataset.lang;
|
||||||
|
_id('lang-form').submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -1,167 +0,0 @@
|
||||||
const _$ = (s, e = document, a = true) => {
|
|
||||||
const r = e.querySelectorAll(s) || [];
|
|
||||||
if (!a) {
|
|
||||||
return r.item(0);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _id = s => document.getElementById(s);
|
|
||||||
|
|
||||||
const _get = (u, f) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.responseType = 'json';
|
|
||||||
xhr.addEventListener('load', () => {
|
|
||||||
f(xhr.response);
|
|
||||||
});
|
|
||||||
xhr.open('GET', u);
|
|
||||||
xhr.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
const _post = (u, d, f) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
const fd = new FormData(d);
|
|
||||||
|
|
||||||
xhr.responseType = 'json';
|
|
||||||
xhr.addEventListener('load', () => {
|
|
||||||
f(xhr.response);
|
|
||||||
});
|
|
||||||
xhr.open('POST', u);
|
|
||||||
xhr.send(fd);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _notif = (m, c) => {
|
|
||||||
const n = document.createElement('div');
|
|
||||||
n.classList.add('notification', 'is-light');
|
|
||||||
if (c !== undefined) {
|
|
||||||
n.classList.add(`is-${c}`);
|
|
||||||
}
|
|
||||||
n.innerHTML = `${m}<button class="delete"></button>`;
|
|
||||||
|
|
||||||
_id('notifications').insertBefore(n, _id('content'))
|
|
||||||
|
|
||||||
_$('.delete', n, false).addEventListener('click', () => {
|
|
||||||
n.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _om = b => {
|
|
||||||
b.addEventListener('click', () => {
|
|
||||||
const m = _id(b.dataset.target);
|
|
||||||
if ('post_url' in b.dataset) {
|
|
||||||
_$('form', m, false).action = b.dataset.post_url;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ('title' in b.dataset) {
|
|
||||||
_$('.modal-card-title', m, false).innerHTML = b.dataset.title;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.documentElement.classList.add('is-clipped');
|
|
||||||
m.classList.add('is-active');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _cm = b => {
|
|
||||||
b.addEventListener('click', () => {
|
|
||||||
document.documentElement.classList.remove('is-clipped');
|
|
||||||
_id(b.dataset.closes).classList.remove('is-active')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _sm = '.modal';
|
|
||||||
const _smb = '.modal-button';
|
|
||||||
const _smc = '.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button-close';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Delete notifications
|
|
||||||
_$('.notification .delete').forEach(d => {
|
|
||||||
const n = d.parentNode;
|
|
||||||
|
|
||||||
d.addEventListener('click', () => {
|
|
||||||
n.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Interact with dropdowns
|
|
||||||
const ds = _$('.dropdown:not(.is-hoverable)');
|
|
||||||
|
|
||||||
ds.forEach(d => {
|
|
||||||
d.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
d.classList.toggle('is-active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
|
||||||
ds.forEach(d => {
|
|
||||||
d.classList.remove('is-active');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Interact with modals
|
|
||||||
const ms = _$(_sm);
|
|
||||||
const mbs = _$(_smb);
|
|
||||||
const mcs = _$(_smc);
|
|
||||||
|
|
||||||
mbs.forEach(_om);
|
|
||||||
|
|
||||||
mcs.forEach(_cm);
|
|
||||||
|
|
||||||
document.addEventListener('keydown', ev => {
|
|
||||||
const e = ev || window.event;
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
ds.forEach(d => {
|
|
||||||
d.classList.remove('is-active');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.documentElement.classList.remove('is-clipped');
|
|
||||||
|
|
||||||
ms.forEach(m => {
|
|
||||||
m.classList.remove('is-active');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Language selection
|
|
||||||
_$('.dropdown-item.lang-selector').forEach(l => {
|
|
||||||
l.addEventListener('click', () => {
|
|
||||||
_id('lang-input').value = l.dataset.lang;
|
|
||||||
_id('lang-form').submit();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disable button after form submission
|
|
||||||
_$('form').forEach(f => {
|
|
||||||
f.addEventListener('submit', () => {
|
|
||||||
_$('button[type=submit]', f).forEach(b => {
|
|
||||||
b.classList.add('is-loading');
|
|
||||||
setTimeout(() => {
|
|
||||||
b.classList.remove('is-loading');
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scroll to top button
|
|
||||||
const up = _id('scroll-button');
|
|
||||||
if (document.documentElement.scrollTop >= 100) {
|
|
||||||
up.classList.remove('is-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onscroll = () => {
|
|
||||||
if (document.documentElement.scrollTop >= 100) {
|
|
||||||
up.classList.remove('is-hidden');
|
|
||||||
} else {
|
|
||||||
up.classList.add('is-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
up.addEventListener('click', () => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -51,6 +51,51 @@ body
|
||||||
.is-grabable
|
.is-grabable
|
||||||
cursor: move
|
cursor: move
|
||||||
|
|
||||||
|
.fade-out
|
||||||
|
transition: opacity 1s
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
form.is-loading
|
||||||
|
pointer-events: none
|
||||||
|
opacity: 0.5
|
||||||
|
&::after
|
||||||
|
opacity: 1
|
||||||
|
animation: spin 750ms infinite linear
|
||||||
|
border: 5px solid $black-ter
|
||||||
|
border-radius: 150px
|
||||||
|
border-right-color: transparent
|
||||||
|
border-top-color: transparent
|
||||||
|
content: ""
|
||||||
|
display: block
|
||||||
|
height: 4em
|
||||||
|
position: absolute !important
|
||||||
|
width: 4em
|
||||||
|
left: calc(50% - 2em)
|
||||||
|
top: calc(50% - 2em)
|
||||||
|
|
||||||
|
#notifications
|
||||||
|
position: fixed
|
||||||
|
z-index: 100
|
||||||
|
width: 100%
|
||||||
|
padding-top: 1.75em
|
||||||
|
|
||||||
|
.notification
|
||||||
|
box-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 2px rgba($scheme-invert, 0.02)
|
||||||
|
margin-left: 22.5%
|
||||||
|
margin-right: 22.5%
|
||||||
|
font-size: 1.5em
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
@media screen and (max-width: 1152px)
|
||||||
|
margin-left: 12.5%
|
||||||
|
margin-right: 12.5%
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px)
|
||||||
|
margin-left: 5%
|
||||||
|
margin-right: 5%
|
||||||
|
|
||||||
#scroll-button
|
#scroll-button
|
||||||
position: fixed
|
position: fixed
|
||||||
bottom: 1em
|
bottom: 1em
|
||||||
|
|
|
@ -4,21 +4,21 @@
|
||||||
|
|
||||||
{% block custom_js %}
|
{% block custom_js %}
|
||||||
<script>
|
<script>
|
||||||
function initSearch(input) {
|
function initSearch(i) {
|
||||||
const s = _id(input);
|
const input = id(i);
|
||||||
const us = _$('a.panel-block', s.closest('div.panel'));
|
const users = _$('a.panel-block', input.closest('div.panel'));
|
||||||
|
|
||||||
s.addEventListener('input', () => {
|
input.addEventListener('input', debounce(() => {
|
||||||
const username = s.value.toLowerCase();
|
const search = input.value.toLowerCase();
|
||||||
|
|
||||||
us.forEach(u => {
|
users.forEach(u => {
|
||||||
if (u.id.includes(username)) {
|
if (u.id.includes(search) || u.dataset.name.includes(search)) {
|
||||||
u.classList.remove('is-hidden');
|
u.classList.remove('is-hidden');
|
||||||
} else {
|
} else {
|
||||||
u.classList.add('is-hidden');
|
u.classList.add('is-hidden');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
initSearch('pwd_search');
|
initSearch('pwd_search');
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
|
|
||||||
{# List of users #}
|
{# List of users #}
|
||||||
{% for u in pwd_users %}
|
{% for u in pwd_users %}
|
||||||
<a class="panel-block" href="{% url 'auth.permissions' %}?user={{ u.username }}" id={{ u.base_username|lower }}>
|
<a class="panel-block" href="{% url 'auth.permissions' %}?user={{ u.username }}" id={{ u.base_username|lower }} data-name="{{ u.full_name|lower }}">
|
||||||
<div class="level is-mobile is-flex-grow-1">
|
<div class="level is-mobile is-flex-grow-1">
|
||||||
<div class="level-left is-flex-shrink-1 pr-3">
|
<div class="level-left is-flex-shrink-1 pr-3">
|
||||||
<span class="panel-icon">
|
<span class="panel-icon">
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
|
|
||||||
{# List of users #}
|
{# List of users #}
|
||||||
{% for u in cas_users %}
|
{% for u in cas_users %}
|
||||||
<a class="panel-block" href="{% url 'auth.permissions' %}?user={{ u.username }}" id={{ u.base_username|lower }}>
|
<a class="panel-block" href="{% url 'auth.permissions' %}?user={{ u.username }}" id={{ u.base_username|lower }} data-name="{{ u.full_name|lower }}">
|
||||||
<div class="level is-mobile is-flex-grow-1">
|
<div class="level is-mobile is-flex-grow-1">
|
||||||
<div class="level-left is-flex-shrink-1 pr-3">
|
<div class="level-left is-flex-shrink-1 pr-3">
|
||||||
<span class="panel-icon">
|
<span class="panel-icon">
|
||||||
|
|
|
@ -21,20 +21,13 @@
|
||||||
<link rel="stylesheet" href="{% static 'vendor/font-awesome/css/font-awesome.min.css' %}">
|
<link rel="stylesheet" href="{% static 'vendor/font-awesome/css/font-awesome.min.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'vendor/font-awesome/css/solid.min.css' %}">
|
<link rel="stylesheet" href="{% static 'vendor/font-awesome/css/solid.min.css' %}">
|
||||||
|
|
||||||
<script src="{% static 'js/main.js' %}"></script>
|
<script src="{% static 'js/framework.js' %}"></script>
|
||||||
|
|
||||||
{% block extra_head %}{% endblock extra_head %}
|
{% block extra_head %}{% endblock extra_head %}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{# Scrool to top #}
|
|
||||||
<button id="scroll-button" class="button is-rounded is-large is-hidden has-tooltip" data-tooltip="{% trans "Revenir en haut de la page" %}">
|
|
||||||
<span class="icon is-large has-text-primary">
|
|
||||||
<i class="fas fa-2x fa-chevron-circle-up"></i>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{# Sélection de la langue #}
|
{# Sélection de la langue #}
|
||||||
<form action="{% url "set_language" %}" method="POST" id="lang-form" class="is-hidden">
|
<form action="{% url "set_language" %}" method="POST" id="lang-form" class="is-hidden">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -219,39 +212,69 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{% block layout %}
|
<div id="notifications"></div>
|
||||||
<div class="main-content">
|
<script>
|
||||||
|
(() => {
|
||||||
|
const messages = [{% for message in messages %}['{{ message }}', '{{ message.level_tag|bulma_message_tag }}'] {% endfor %}];
|
||||||
|
for (const [m, c] of messages) {
|
||||||
|
notify(m, c);
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="main-content mb-5">
|
||||||
|
{% block layout %}
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
<div class="column is-two-thirds-fullhd is-12-desktop is-12-widescreen">
|
<div class="column is-two-thirds-fullhd is-12-desktop is-12-widescreen">
|
||||||
<section id="notifications" class="section pt-0">
|
<div id="content" class="box">
|
||||||
|
{% block content %}{% endblock content %}
|
||||||
{% for message in messages %}
|
</div>
|
||||||
<div class="notification is-{{ message.level_tag|bulma_message_tag }} is-light">
|
|
||||||
{% if 'safe' in message.tags %}
|
|
||||||
{{ message|safe }}
|
|
||||||
{% else %}
|
|
||||||
{{ message }}
|
|
||||||
{% endif %}
|
|
||||||
<button class="delete"></button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div id="content" class="box">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock layout %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock layout %}
|
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p class="has-text-centered">
|
<p class="has-text-centered">
|
||||||
{% blocktrans %}Développé par <a class="tag is-light is-danger" href="https://www.eleves.ens.fr/kde">KDEns</a>. En cas de pépin, contacter <span class="tag is-info is-light">klub-dev [at] ens [dot] fr</span>.{% endblocktrans %}
|
{% blocktrans %}Développé par <a class="tag is-light is-danger" href="https://www.eleves.ens.fr/kde">KDEns</a>. En cas de pépin, contacter <span class="tag is-info is-light">klub-dev [at] ens [dot] fr</span>.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script src="{% static 'js/kadenios.js' %}"></script>
|
||||||
{% block custom_js %}{% endblock %}
|
{% block custom_js %}{% endblock %}
|
||||||
|
|
||||||
|
{# Scrool to top #}
|
||||||
|
<button id="scroll-button" class="button is-rounded is-large is-hidden has-tooltip" data-tooltip="{% trans "Revenir en haut de la page" %}">
|
||||||
|
<span class="icon is-large has-text-primary">
|
||||||
|
<i class="fas fa-2x fa-chevron-circle-up"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const up = id('scroll-button');
|
||||||
|
if (document.documentElement.scrollTop >= 100) {
|
||||||
|
up.classList.remove('is-hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onscroll = () => {
|
||||||
|
if (document.documentElement.scrollTop >= 100) {
|
||||||
|
up.classList.remove('is-hidden');
|
||||||
|
} else {
|
||||||
|
up.classList.add('is-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
up.addEventListener('click', () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue