mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 07:11:18 +02:00
refactor: combined duration (pages + minutes), has_annexes checkbox, remove Mo
- Remove Mo option from duration, keep only pages and minutes - Redesign duration fieldset: separate Pages input + Durée h:m inputs, both can be set together - Fix minutes input visibility: wider inputs (6ch), proper CSS layout - Add has_annexes checkbox to fichiers fragment + DB column + controllers - Display duration on admin backoffice recap page - Display duration on public partage recap page - Update public TFE page for new combined duration format - Migration 041: add duration_pages, duration_minutes, has_annexes columns; migrate data; recreate views
This commit is contained in:
@@ -2160,18 +2160,36 @@ th.admin-ap-col {
|
||||
100% { transform: scaleX(0); transform-origin: right; }
|
||||
}
|
||||
|
||||
/* ── Sidebar TOC (matches public .page-content alignment pattern) ────────── */
|
||||
/* ── Sidebar TOC (matches public .page-content grid layout) ────────────── */
|
||||
|
||||
.admin-main--toc {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
gap: var(--space-2xl);
|
||||
align-items: start;
|
||||
padding: var(--space-xl) var(--space-m) var(--space-2xl);
|
||||
}
|
||||
|
||||
.admin-main--toc > article {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
/* Desktop: article in second column */
|
||||
@media (min-width: 768px) {
|
||||
.admin-main--toc > article {
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: single column */
|
||||
@media (max-width: 767px) {
|
||||
.admin-main--toc {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-l);
|
||||
padding: var(--space-m) var(--space-s) var(--space-xl);
|
||||
}
|
||||
|
||||
.admin-main--toc > article {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Align first child of article with TOC heading (same as public) */
|
||||
@@ -2188,11 +2206,6 @@ th.admin-ap-col {
|
||||
}
|
||||
|
||||
/* Admin TOC: same <details class="toc"> as public pages, positioned sticky */
|
||||
#admin-toc {
|
||||
width: 180px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#admin-toc .toc-list a {
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
@@ -352,7 +352,11 @@
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.duration-time-inputs {
|
||||
.duration-field {
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.duration-time-field {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-xs);
|
||||
@@ -364,6 +368,11 @@
|
||||
gap: var(--space-3xs);
|
||||
}
|
||||
|
||||
.duration-time-fields input[type="number"] {
|
||||
width: 6ch;
|
||||
min-width: 6ch;
|
||||
}
|
||||
|
||||
.duration-time-fields span {
|
||||
font-size: var(--step--1);
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -45,21 +45,63 @@
|
||||
items.push({ section: sec, link: a });
|
||||
});
|
||||
|
||||
// Generate a spread of thresholds for smooth IntersectionObserver firing.
|
||||
function buildThresholds() {
|
||||
var t = [];
|
||||
for (var i = 0; i <= 20; i++) t.push(i / 20);
|
||||
return t;
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
var best = null,
|
||||
bestRatio = 0;
|
||||
entries.forEach((e) => {
|
||||
if (e.intersectionRatio > bestRatio) {
|
||||
bestRatio = e.intersectionRatio;
|
||||
best = e.target;
|
||||
() => {
|
||||
// Active section = whose top is closest to the midpoint of the
|
||||
// viewport from above. At page bottom, the last section wins
|
||||
// even if its heading can't reach 50%.
|
||||
var line = window.innerHeight * 0.5;
|
||||
var best = null;
|
||||
var bestDist = Infinity;
|
||||
|
||||
// Pass 1: sections whose top is above/at the line
|
||||
items.forEach((item) => {
|
||||
var top = item.section.getBoundingClientRect().top;
|
||||
if (top <= line && (line - top) < bestDist) {
|
||||
bestDist = line - top;
|
||||
best = item;
|
||||
}
|
||||
});
|
||||
|
||||
// Pass 2: no section above the line — pick closest from below
|
||||
if (!best) {
|
||||
items.forEach((item) => {
|
||||
var top = item.section.getBoundingClientRect().top;
|
||||
var dist = top - line;
|
||||
if (dist >= 0 && dist < bestDist) {
|
||||
bestDist = dist;
|
||||
best = item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Pass 3: at page bottom, if the last section is visible but
|
||||
// its heading never crossed 50%, promote it anyway.
|
||||
if (best) {
|
||||
var lastItem = items[items.length - 1];
|
||||
if (lastItem !== best) {
|
||||
var lastTop = lastItem.section.getBoundingClientRect().top;
|
||||
var atBottom = (window.innerHeight + window.scrollY + 5)
|
||||
>= document.documentElement.scrollHeight;
|
||||
if (atBottom && lastTop > line && lastTop < window.innerHeight) {
|
||||
best = lastItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!best && items.length > 0) best = items[0];
|
||||
items.forEach((item) => {
|
||||
item.link.classList.toggle('toc-active', item.section === best);
|
||||
item.link.classList.toggle('toc-active', item === best);
|
||||
});
|
||||
},
|
||||
{ rootMargin: '-10% 0px -70% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] }
|
||||
{ threshold: buildThresholds() }
|
||||
);
|
||||
|
||||
items.forEach((item) => {
|
||||
|
||||
@@ -1,51 +1,26 @@
|
||||
/**
|
||||
* form-duration-toggle.js — Duration unit toggle on TFE form.
|
||||
* form-duration-toggle.js — Combined duration helper for TFE form.
|
||||
*
|
||||
* Switches between integer input (pages/mo) and h/m/s time inputs (durée).
|
||||
* Updates hidden #duration_value on change.
|
||||
* Computes duration_minutes from h + m fields on form submit so the server
|
||||
* receives a single integer (total minutes).
|
||||
*/
|
||||
(() => {
|
||||
var unit = document.getElementById('duration_unit');
|
||||
var hidden = document.getElementById('duration_value');
|
||||
var intWrap = document.getElementById('duration-value-integer');
|
||||
var intInput = document.getElementById('duration_value_int');
|
||||
var intLabel = document.getElementById('duration-value-label');
|
||||
var timeWrap = document.getElementById('duration-value-time');
|
||||
var hInput = document.getElementById('duration_h');
|
||||
var mInput = document.getElementById('duration_m');
|
||||
var sInput = document.getElementById('duration_s');
|
||||
var form = document.querySelector('form.admin-form');
|
||||
if (!form) return;
|
||||
|
||||
var LABELS = { pages: 'Nombre :', mo: 'Taille :', durée: 'Durée :' };
|
||||
if (!unit || !hidden) return;
|
||||
form.addEventListener('submit', function() {
|
||||
var h = parseInt(document.getElementById('duration_h')?.value, 10) || 0;
|
||||
var m = parseInt(document.getElementById('duration_m')?.value, 10) || 0;
|
||||
var total = h * 60 + m;
|
||||
|
||||
function updateHidden() {
|
||||
if (unit.value === 'durée') {
|
||||
var h = parseInt(hInput.value, 10) || 0;
|
||||
var m = parseInt(mInput.value, 10) || 0;
|
||||
var s = parseInt(sInput.value, 10) || 0;
|
||||
var total = h + m / 60 + s / 3600;
|
||||
hidden.value = total > 0 ? total.toFixed(6) : '';
|
||||
} else {
|
||||
hidden.value = intInput.value;
|
||||
var hidden = document.getElementById('duration_minutes_hidden');
|
||||
if (!hidden) {
|
||||
hidden = document.createElement('input');
|
||||
hidden.type = 'hidden';
|
||||
hidden.name = 'duration_minutes';
|
||||
hidden.id = 'duration_minutes_hidden';
|
||||
form.appendChild(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFields() {
|
||||
if (unit.value === 'durée') {
|
||||
intWrap.style.display = 'none';
|
||||
timeWrap.style.display = '';
|
||||
} else {
|
||||
timeWrap.style.display = 'none';
|
||||
intWrap.style.display = '';
|
||||
if (intLabel) intLabel.textContent = LABELS[unit.value] || 'Valeur :';
|
||||
}
|
||||
updateHidden();
|
||||
}
|
||||
|
||||
unit.addEventListener('change', toggleFields);
|
||||
if (intInput) intInput.addEventListener('input', updateHidden);
|
||||
if (hInput) hInput.addEventListener('input', updateHidden);
|
||||
if (mInput) mInput.addEventListener('input', updateHidden);
|
||||
if (sInput) sInput.addEventListener('input', updateHidden);
|
||||
toggleFields();
|
||||
hidden.value = total > 0 ? String(total) : '';
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user