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 %}
|
||||
<script>
|
||||
const _fm = b => {
|
||||
b.addEventListener('click', () => {
|
||||
const f = _$('form', _id(b.dataset.target), false);
|
||||
f.dataset.next = b.dataset.next;
|
||||
f.dataset.origin = b.dataset.parent
|
||||
|
||||
const d = JSON.parse(b.dataset.json);
|
||||
|
||||
for (const [k, v] of Object.entries(d)) {
|
||||
_$(`[name='${k}']`, f, false).value = v;
|
||||
}
|
||||
});
|
||||
(() => {
|
||||
const initButtons = (f, e) => {
|
||||
_$('.del', e).forEach(remove);
|
||||
initModal(f, e);
|
||||
}
|
||||
|
||||
_$('.modal-button').forEach(_fm);
|
||||
_$('.del').forEach(remove);
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
submitForm(id('form-option'), initButtons);
|
||||
submitForm(id('form-question'), initButtons);
|
||||
})()
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
event.preventDefault();
|
||||
|
||||
if (_$('[name="delete"]', f, false).value == 'oui') {
|
||||
_get(f.action, r => {
|
||||
get(f.action).then(r => {
|
||||
if (r.success && r.action == 'delete') {
|
||||
{% if election.restricted %}
|
||||
const r = _id(f.dataset.target);
|
||||
|
@ -27,21 +27,21 @@
|
|||
i.classList.remove('fa-check');
|
||||
i.classList.add('fa-times');
|
||||
{% else %}
|
||||
_id(f.dataset.target).remove()
|
||||
id(f.dataset.target).remove()
|
||||
{% endif %}
|
||||
|
||||
// On ferme le modal
|
||||
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) {
|
||||
_notif(r.message.content, r.message.class);
|
||||
notify(r.message.content, r.message.class);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
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 class="modal" id="modal-confirm">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-background" data-closes="modal-confirm"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<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>
|
||||
|
||||
<section class="modal-card-body" id="modal-body">
|
||||
</section>
|
||||
<section class="modal-card-body" id="modal-body"></section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-fullwidth is-outlined is-primary is-light" type="submit">
|
||||
|
@ -69,7 +68,7 @@
|
|||
<span>{% trans "Confirmer" %}</span>
|
||||
</button>
|
||||
|
||||
<a class="button is-primary button-close">
|
||||
<a class="button is-primary button-close" data-closes="modal-confirm">
|
||||
<span class="icon">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
// On déplace les options
|
||||
while (rank_zones[next_rank].childElementCount > 1) {
|
||||
const t = rank_zones[next_rank].lastChild;
|
||||
const i = _id(t.dataset.input);
|
||||
const i = id(t.dataset.input);
|
||||
i.value = j.toString();
|
||||
rank_zones[j].append(t);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
|||
_$('.control .input').forEach(i => {
|
||||
// On rajoute la tuile dans le classement ou dans les non classées
|
||||
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) {
|
||||
rank_zones[r].appendChild(t);
|
||||
|
@ -82,8 +82,8 @@
|
|||
const d = event.target.closest('.drop-zone');
|
||||
|
||||
const r = d.dataset.rank;
|
||||
const t = _id(data);
|
||||
const i = _id(t.dataset.input);
|
||||
const t = id(data);
|
||||
const i = id(t.dataset.input);
|
||||
|
||||
// Si on ne change pas de rang, pas besoin de déplacer l'option
|
||||
if (i.value != r) {
|
||||
|
@ -99,7 +99,7 @@
|
|||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 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);
|
||||
|
||||
_$('.control .input').forEach(i => {
|
||||
|
@ -127,7 +127,7 @@
|
|||
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">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -142,10 +142,10 @@
|
|||
});
|
||||
|
||||
// Change le mode de remplissge de formulaire (input vs drag & drop)
|
||||
_id('change-method').addEventListener('click', () => {
|
||||
const h = _id('hide-form');
|
||||
const d = _id('drag-zone');
|
||||
const b = _id('change-method');
|
||||
id('change-method').addEventListener('click', () => {
|
||||
const h = id('hide-form');
|
||||
const d = id('drag-zone');
|
||||
const b = id('change-method');
|
||||
|
||||
// On échange ce qui est visible
|
||||
h.classList.toggle('is-hidden');
|
||||
|
@ -162,10 +162,10 @@
|
|||
});
|
||||
|
||||
// Initialise les éléments pour le formulaire interactif
|
||||
$unranked = _id('unranked');
|
||||
$unranked = id('unranked');
|
||||
|
||||
for (let i = 1; i <= nb_options; i++) {
|
||||
rank_zones[i] = _id(`rank-${i}`);
|
||||
rank_zones[i] = id(`rank-${i}`);
|
||||
}
|
||||
|
||||
_$('.control .input').forEach(i => {
|
||||
|
|
|
@ -2,30 +2,16 @@
|
|||
{% load i18n %}
|
||||
|
||||
|
||||
{% block extra_head %}
|
||||
{% block custom_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
_id('confirm-button').addEventListener('click', () => {
|
||||
id('confirm-button').addEventListener('click', () => {
|
||||
let selected_rows = '';
|
||||
|
||||
_$('.checkbox input').forEach(c => {
|
||||
if (c.checked) {
|
||||
_$('.checkbox input').filter(c => c.checked).forEach(c => {
|
||||
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>
|
||||
|
|
|
@ -10591,6 +10591,59 @@ body {
|
|||
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 {
|
||||
position: fixed;
|
||||
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
|
||||
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
|
||||
position: fixed
|
||||
bottom: 1em
|
||||
|
|
|
@ -4,21 +4,21 @@
|
|||
|
||||
{% block custom_js %}
|
||||
<script>
|
||||
function initSearch(input) {
|
||||
const s = _id(input);
|
||||
const us = _$('a.panel-block', s.closest('div.panel'));
|
||||
function initSearch(i) {
|
||||
const input = id(i);
|
||||
const users = _$('a.panel-block', input.closest('div.panel'));
|
||||
|
||||
s.addEventListener('input', () => {
|
||||
const username = s.value.toLowerCase();
|
||||
input.addEventListener('input', debounce(() => {
|
||||
const search = input.value.toLowerCase();
|
||||
|
||||
us.forEach(u => {
|
||||
if (u.id.includes(username)) {
|
||||
users.forEach(u => {
|
||||
if (u.id.includes(search) || u.dataset.name.includes(search)) {
|
||||
u.classList.remove('is-hidden');
|
||||
} else {
|
||||
u.classList.add('is-hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
initSearch('pwd_search');
|
||||
|
@ -51,7 +51,7 @@
|
|||
|
||||
{# List of 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-left is-flex-shrink-1 pr-3">
|
||||
<span class="panel-icon">
|
||||
|
@ -96,7 +96,7 @@
|
|||
|
||||
{# List of 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-left is-flex-shrink-1 pr-3">
|
||||
<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/solid.min.css' %}">
|
||||
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
<script src="{% static 'js/framework.js' %}"></script>
|
||||
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
|
||||
</head>
|
||||
|
||||
<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 #}
|
||||
<form action="{% url "set_language" %}" method="POST" id="lang-form" class="is-hidden">
|
||||
{% csrf_token %}
|
||||
|
@ -219,39 +212,69 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="notifications"></div>
|
||||
<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="main-content">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-two-thirds-fullhd is-12-desktop is-12-widescreen">
|
||||
<section id="notifications" class="section pt-0">
|
||||
|
||||
{% for message in messages %}
|
||||
<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>
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock layout %}
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<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 %}
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="{% static 'js/kadenios.js' %}"></script>
|
||||
{% 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>
|
||||
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue