Commit Graph
350 Commits
Author SHA1 Message Date
Pontoporeia a5f658ce0c retirer l'orientation des cartes de résultats de recherche 2026-07-03 16:58:29 +02:00
Pontoporeia 880d169e72 search: pagination and scroll, add range counter, mobile version 2026-07-03 16:53:16 +02:00
Pontoporeia e607833b2e style: remove border-top from admin-form grid rows 2026-07-03 16:01:54 +02:00
Pontoporeia 4fa57d592a 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
2026-07-03 15:58:10 +02:00
Pontoporeia 42c4e78df6 toc: add independent scrollbar for desktop TOC sidebar
- Added max-height, overflow-y: auto, overscroll-behavior: contain,
  and scrollbar-width: thin to .toc in the desktop media query (toc.css)
- This gives the TOC its own scrollbar when its content exceeds the
  viewport height, scrolling independently from the main content
- Affects both public pages (about, charte, licence via .page-content)
  and admin pages (acces, parametres, contenus via .admin-main--toc)
2026-07-03 15:29:05 +02:00
Pontoporeia e6a989bb36 fix: add missing bootstrap require in markdown-cheatsheet-fragment.php, add bottom padding to dialog 2026-07-03 15:24:07 +02:00
Pontoporeia a790ddc02a Fix admin login heading: prevent 'Administration' from hyphenating or overflowing box 2026-06-24 14:57:28 +02:00
Pontoporeia eb706214ce feat: scroll-position memory on repertoire HTMX swaps + swap transition polish
- Add repertoire-scroll-restore.js: snapshots scrollTop of each column <ul>
  before htmx:beforeSwap, restores after htmx:afterSwap (keyed by data-col)
- Add subtle opacity transition on #repertoire-index during htmx-swapping
- Tighten rep-indicator opacity transition to 0.1s for snappier feedback
- Import new module in public-entry.js
2026-06-24 14:45:55 +02:00
Pontoporeia 0062b29678 Fix security issues from audit: gate partage fragments on share_active session, add CSRF to retry-email POST, remove dead App::verifyCsrf() 2026-06-24 14:17:24 +02:00
Pontoporeia 84869ad968 Fix open redirect in tag.php + language.php: reject protocol-relative URLs (//evil.com) by also checking for // prefix 2026-06-24 14:15:25 +02:00
Pontoporeia 6ecd3d4540 Fix biome lint errors: remove duplicate CSS properties, apply safe auto-fixes
CSS:
- Remove duplicate 'background' fallbacks in base.css, header.css, search.css
  (solid color declared before gradient — gradient always wins)
- Remove duplicate 'padding' in admin.css .admin-import-log

JS (biome --write safe fixes applied):
- function() → arrow functions in all IIFEs and callbacks
- forEach/callback → arrow functions
- evaluePtrn → parseInt(x, 10) in admin-contacts-form.js
- Cleaned label text in build.mjs lint step

Remaining warnings are intentional: !important overrides, descending
specificity (admin.css cascade), noUnusedVariables (functions exported
to window/onclick), useTemplate style preference.
2026-06-24 13:57:00 +02:00
Pontoporeia 82d3dcb084 Fix repertoire column scrolling + admin TOC duplication
- repertoire.css: add min-height: 0 to column <ul> scroll containers so
  grid 1fr row shrinks below content and overflow-y: auto activates
- admin-toc.js: add __adminTocBuilt guard + nav.children check to prevent
  double population when loaded both via admin.min.js and direct <script>
- admin-toc.php: remove duplicate <script src="admin-toc.js"> tag —
  JS is already bundled in admin.min.js
2026-06-24 13:18:19 +02:00
Pontoporeia 20fe4b6c8c Add biome + rolldown + lightningcss build pipeline for JS/CSS bundling & minification
- package.json with biome, rolldown, lightningcss devDependencies  
- biome.json: add CSS formatter support
- scripts/build-css.mjs: lightningcss resolves @import chain, bundles/minifies CSS  
- scripts/build-js.mjs: rolldown per-entry JS bundling (no code splitting)
- scripts/build.mjs: orchestrator for both CSS + JS
- scripts/check-build.mjs: staleness checker for CI/deploy guard
- justfile: add build, build-css, build-js, build-install, build-check recipes
- justfile: deploy recipe now runs build before deploy-code
- head.php + form-page.php: use dist/base.min.css instead of style.css
- All controllers + FormBootstrap: reference dist/*.min.{css,js}
- admin footer: load admin.min.js for all admin pages
- repertoire: use public.min.js instead of individual app JS files
- Fix stray '}' syntax error in admin.css line 305
- .gitignore: add app/public/assets/dist/
2026-06-24 13:09:50 +02:00
Pontoporeia e74f9210c5 #gzip #extract-inline-js enable gzip in nginx + move ~730 lines of inline JS to 15 external files 2026-06-24 12:56:15 +02:00
Pontoporeia 3c786b037a Sort all filter columns: matched entries first, then unmatched, both alphabetical 2026-06-24 11:32:13 +02:00
Pontoporeia 982d91345d Sort keywords column: matched entries first, then unmatched, both alphabetical
When filters are active, viable (matched) keywords now appear at the top
of the Mots-clés column so users don't have to scroll past faded entries
to find clickable tags. Both groups maintain alphabetical order internally.
2026-06-24 11:28:26 +02:00
Pontoporeia 654c884fed Remove mobile filter chip bar from repertoire page
The sticky chip bar added noise: users already see their active
filters highlighted in the accordion columns (with rep-entry--selected
styling and the active-count badge on each toggle). Removing chips
shifts focus back to the accordions, consistent with the expectation
that users return to the dropdowns to adjust filters.
2026-06-24 11:20:47 +02:00
Pontoporeia bb3bb80567 Fix accordion re-init after HTMX outerHTML swap on repertoire page
The htmx:afterSwap handler was calling initAccordions(e.detail.target)
but after an outerHTML swap, e.detail.target references the old detached
DOM element, not the new live element. This meant accordion click
handlers were never attached to the new filter column toggles after
applying or removing a filter — breaking all subsequent interaction.

Fix: query the live DOM with document.querySelector() instead.
2026-06-24 11:13:08 +02:00
Pontoporeia ecb90ba5dd Add accordion + active-filter chip bar for mobile repertoire
- repertoire-index.php: wrap each filter column in rep-accordion with toggle
  button, chevron, badge (active filter count); add rep-chip-bar with
  removable active-filter chips above the columns
- repertoire.css: mobile (≤640px) accordion mode — columns collapse to
  single-open accordion sections with 48px touch targets; chip bar becomes
  sticky; desktop/tablet layout unchanged via display:none on toggle elements
- repertoire.php: JS for single-accordion-open behavior on mobile, HTMX
  re-init after swap, resize-breakpoint cleanup
- docs/repertoire-mobile-propositions.md: analysis + 4 architecture proposals
2026-06-22 16:32:26 +02:00
Pontoporeia cca3d08f05 Replace apropos/licence/charte sticky nav with responsive details/summary TOC
- Convert .apropos-toc <nav> to <details class="toc" open> in all three templates
- Add caret-down icon to summary (visible only on mobile via media queries)
- Desktop (≥768px): sticky sidebar via CSS grid, force-open via pointer-events:none, hide caret
- Mobile (≤767px): single column, collapsible TOC with rotating caret, margin-top below search bar
- Rename .apropos-toc-link to .toc-sidebar-link
- Merge 900px and 600px breakpoints into single 600px mobile media query
- Rename apropos.css → content-page.css; update 3 controller references
2026-06-22 15:33:47 +02:00
Pontoporeia 0a2b4781d1 Fix search icon wrong color 2026-06-21 18:12:47 +02:00
Pontoporeia 6aee5ec131 Clean up cleanup dialog
- Remove duplicate 'Téléversements abandonnés' heading already communicated by the summary
- Replace #peertube-orphans-wrapper with display:contents wrapper so PeerTube details sit as direct grid siblings
- Add hx-confirm to all delete buttons (filepond, trash, PeerTube orphans)
2026-06-21 18:09:22 +02:00
Pontoporeia dfde88eaa5 Migrate all &lt;img&gt;-based icons to inline SVG via PHP helper
Replace every &lt;img src="/assets/icons/..."&gt; with &lt;?= icon('name') ?&gt;
across 26 template files. The PHP helper inlines the SVG markup into the
DOM so CSS color cascades naturally through fill="currentColor".

- Add src/icon.php helper: reads SVG file, sets width/height to 1em,
  injects aria-hidden, supports optional CSS class
- Fix 12 icon SVGs that had hardcoded fill="#000000" or missing fill attr
- Replace search.svg with Phosphor fill-based magnifying glass
- Add explicit SVG sizes for admin header nav icons (16px/20px)
- Scope public search icon CSS to form[role=search]:not(.header-search-form)
  to avoid breaking admin header layout; change stroke to fill
- Remove &lt;img&gt; filter: brightness(0) invert(1) hacks from admin.css
2026-06-21 17:52:27 +02:00
Pontoporeia b1774e6e97 Replace all inline SVGs with icon files, ensure currentColor fill for proper color inheritance on buttons 2026-06-21 17:11:45 +02:00
Pontoporeia 71a92d682b Fix nettoyage modal: SVG icon files, padding/margin consistency, BBBDMSans font, fix HTMX trigger, nested details cleanup 2026-06-21 17:04:37 +02:00
Pontoporeia 03c9c3566f Add SQLite indexes for contenus page language/tag queries + WIP: Peertube orphans, dialogs, contact decoupling, context note, finality types 2026-06-21 16:36:34 +02:00
Pontoporeia 0d5e9dac19 fix: make sticky TOC work for full scroll height and fix heading anchor links 2026-06-20 14:08:16 +02:00
Pontoporeia 19bf9f101a Refactor apropos/charte/licence pages: shared layout, TOC anchors, and UI polish
Unify the three public pages (à propos, charte, licence) onto a single
grid layout (.page-content) with sticky TOC sidebar, replacing the old
separate  /  /  markup.

- Merge about.php, charte.php, licence.php templates into shared
  .page-content / .content-section structure
- Add CommonMark HeadingPermalinkExtension for stable heading anchors
- Use SlugNormalizer for TOC links so they match rendered heading IDs
- Standardize link styling across content blocks: bold black, accent on
  hover (consistent with global link style)
- Fix code block wrapping: use pre-wrap instead of pre, constrain grid
  columns with min-width:0, auto scrollbar
- Fix apropos page grid placement: force content-section into column 2
  so contacts and credits stay in the content area, not the sidebar

Also includes accumulated WIP changes:
- Header gradient: hardcoded purple-to-green (replaces CSS variables)
- Search placeholder font
- Duration field: replace minutes/sec/heures with h:m:s time inputs
- TFE file optional for formats 1,4,6 with client-side JS toggle
- Licence form: em-dash to hyphen, details/summary classes
- Pill search: block Enter key form submission when no results
- Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads)
- Language pill: clear hints for excluded main languages
- Search results: gradient placeholder cards for items without covers
- TFE display: format durée values as XhYm instead of decimal
2026-06-19 19:40:05 +02:00
Pontoporeia c8af3bf869 fix: remove leftover debug console.log that crashed HTMX with new FormData(fieldset)
The htmx:beforeSend listener in admin/footer.php was a debugging
leftover that called new FormData(e.target.closest('fieldset')).
FormData only accepts HTMLFormElement or nothing — passing a
<fieldset> throws 'Argument 1 does not implement interface
HTMLFormElement'.  Removed the serialization call; kept the
minimal debug log.
2026-06-15 16:27:35 +02:00
Pontoporeia 2633cb13b5 fix: increase PHP upload limits to support large video files
Raised upload_max_filesize from 512M to 8192M (8G) and post_max_size
from 520M to 8704M to match the JS-side per-extension size caps that
allow up to 8GB for video files (mp4, webm, mov, etc.).  Also raised
memory_limit to 512M and max_input_time to 600s.
2026-06-15 16:26:48 +02:00
Pontoporeia a5db81a73d fix: change adminOld return type from string to string|array
The closure returned arrays when formData values were arrays (e.g.
jury_promoteur), but the PHP return type annotation was :string.
PHP 8.x enforces this strictly, causing a fatal TypeError in
jury-fieldset.php on add mode.
2026-06-15 16:17:29 +02:00
Pontoporeia d588ae004d Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
2026-06-15 15:56:52 +02:00
Pontoporeia 00fed5f0e3 Add periodic cleanup of orphaned drafts: cleanup job, just command, deploy cron 2026-06-11 13:05:34 +02:00
Pontoporeia a19e9e1454 Extract FormBootstrap helper to eliminate bootstrap duplication across add/edit form pages 2026-06-11 12:23:55 +02:00
Pontoporeia f4a3e26901 Add thesis status column for two-phase commit lifecycle tracking 2026-06-11 12:09:43 +02:00
Pontoporeia 11a6f6a9f2 Preserve FilePond temp files across partage validation redirects 2026-06-11 11:42:33 +02:00
Pontoporeia b744271cf6 Extract partage page chrome to templates/partage/form-page.php 2026-06-11 11:41:11 +02:00
Pontoporeia cbd369bc72 Split form.css into form-base.css and form-admin.css, drop dead upload-progress code
Also introduces $extraCssAdmin support in head.php for admin-only
stylesheets (form-admin.css, filepond CSS, system.css). Admin pages
now use $extraCssAdmin for admin-only assets and $extraCss for
shared stylesheets like form-base.css.
2026-06-11 11:28:58 +02:00
Pontoporeia 99125cc8e3 Add autosave draft system for partage form with HTMX-based session persistence
- New fragment endpoint POST/GET /partage/fragments/draft.php:
  saves all form fields to PHP session, excludes file/csrf/slug fields
  GET returns JSON for JS hydration on page load
  rotates both global CSRF and share CSRF tokens in sync

- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
  allows injecting HTMX attributes and 'Brouillon enregistré' indicator

- renderShareLinkForm adds hx-post with change/input debounce trigger,
  loads autosave-handler.js, hydrate fields from draft on page load

- Draft cleared on successful form submission in handleShareLinkSubmission

- autosave-handler.js now also updates share_link_token hidden input
  when rotating CSRF token (partage form uses both csrf_token and share_link_token)

- Added .autosave-status CSS to form.css (was admin.css-only)

- Updated fragment routing to accept GET requests (needed for draft hydration)
2026-06-11 11:04:49 +02:00
Pontoporeia 4b37a05be3 Guard no-JS file uploads: disabled filepond_mode by default, server-side fallback
The partage/admin form had a hardcoded filepond_mode=1 hidden input,
so without JavaScript the server always entered the FilePond async
path — which found no hex IDs and silently dropped all files.

Three-layer fix:
1. HTML: filepond_mode input starts disabled with value=0; JS enables
   it and sets value=1 on DOMContentLoaded (and after HTMX swaps).
   Disabled inputs aren't submitted → server gets no filepond_mode
   → naturally falls to legacy  path.
2. JS: enableFilepondMode() called on page load and hx:afterSwap so
   FilePond-enhanced forms always send filepond_mode=1.
3. Server (defense-in-depth): ThesisFileHandler::hasFilePondQueueData()
   scans POST['queue_file'] for 32-char hex IDs; ThesisCreateController
   and ThesisEditController use it alongside filepond_mode, so even if
   the flag somehow arrives without async upload IDs, the  path
   takes over.
2026-06-11 10:32:50 +02:00
Pontoporeia 63e65d9856 Add mobile-responsive form layout with WCAG 2.5.5 touch targets
Add @media (max-width: 600px) rule to form.css:
- Stack form row labels above inputs (1fr grid, single column)
- Ensure 44×44px minimum touch targets on checkboxes, radios,
  selects, textareas, text inputs, and .btn/.btn--sm
- Stack thesis-add-header and recap-dl grids to single column
- Stack form footer buttons vertically with full width
- Unstick sticky formats fieldset on mobile
- Tighten fieldset margins for narrow viewports
2026-06-11 10:27:17 +02:00
Pontoporeia e17246c850 Add field-level aria-errormessage, aria-invalid, and aria-describedby across the TFE form
WCAG 3.3.1 (Error Identification): failing fields now get
aria-errormessage pointing to the flash-error container and
aria-invalid="true". WCAG 3.3.3 (Error Suggestion): <small>
hint text on inputs, selects, and file fields is now linked via
aria-describedby (always, not just on error).

Changes:
- text-field.php, select-field.php, checkbox-list.php: accept
  $errorFieldName; add aria-errormessage/aria-invalid on match;
  add id to <small> and aria-describedby on the control
- fieldset-tfe-info.php: aria-invalid on synopsis textarea
- fichiers-fragment.php: aria-describedby on cover, note
  d'intention, TFE, annexes, and website inputs; aria-invalid
  on format checkboxes when error matches 'formats'
- form.php: id="flash-error" + tabindex="-1" on flash-error
  div; accept $errorFieldName from callers
- admin/add.php: set $errorFieldName, wire $withAutofocusFn
  (was identity default)
- admin/edit.php: set $errorFieldName
- partage/index.php: consume autofocus field, wire autofocus
  function, add App::flashAutofocus() in submit catch block

Also fixes WCAG standards issue: removed invalid 'required'
HTML attribute from <fieldset> elements in checkbox-list.php
and fichiers-fragment.php (only aria-required stays). Added
role="group" for explicit ARIA semantics.
2026-06-11 10:23:47 +02:00
Pontoporeia 2c6b55777f Fix logs being captured 2026-06-10 00:21:56 +02:00
Pontoporeia fb752f5ba2 cleanup: remove _write guard — FilePond external API doesn't expose _write
ro=['fire','_read','_write'] is an exclusion list in Ee(), not an inclusion
list. The external pond object has none of these. The only safe interception
point is inside the closure (vendor patch), but the root-cause fix
(fileValidateSizeFilter .filename → .name) already prevents the crash.
2026-06-10 00:18:49 +02:00
Pontoporeia 2829d13a16 filepond: fix crash 'can't access property main, n.status is undefined'
Fixes three root causes of FilePond errors on TFE upload forms:

1. server.process.onerror accessed .status on a string (XHR response
   text body) — now extracts the body safely.

2. server.load was a bare URL string with no error handling — converted
   to object with onload/onerror to prevent FilePond internal _write
   crash when load.php returns HTTP errors.

3. destroyFilePondsIn now aborts in-flight processing before pond.destroy()
   to prevent stale XHR callbacks firing on a torn-down FilePond instance.

Server-side: FilepondHandler now emits Content-Type: text/plain on all
responses (PHP defaults to text/html on die(), confusing FilePond's
response parser).
2026-06-10 00:18:49 +02:00
Pontoporeia 38ef550397 feat: render actual elements in markdown cheatsheet instead of labels
Replace text labels (h1, bold, italic) with rendered HTML in the Rendu column:
headings, strong, em, del, code, links, blockquote, lists, hr, sup, small
2026-06-10 00:18:49 +02:00
Pontoporeia 4a2b000fca Add Charte static page (public + admin editing) 2026-06-10 00:18:49 +02:00
Pontoporeia 317547ac93 Fix #4 v2: decouple contact_interne from contact_visible in ThesisCreateController
validateAndSanitise() no longer cross-contaminates:
- contact_interne overwrote mail, which then copied to contact_visible
- Fixed: contactInterne from contact_interne (admin) or confirmation_email (student)
- Fixed: contactVisible from contact_visible (admin) or mail (student)
- Fixed: submit() uses contactInterne as author email, not mail
2026-06-10 00:18:49 +02:00
Pontoporeia 1490c99268 Fix FilePond: maxFileSize as bytes + temp files survive page reload
1. maxFileSize bug: FileValidateSize plugin overrides core's maxFileSize
   setter. Core uses toBytes('1GB') = 1073741824, but plugin registers
   maxFileSize as [null, Type.INT] which calls toInt('1GB') = 1.
   Fix: all maxFileSize and perExtensionMaxSize values as raw bytes.
   Also fix option name: fileValidateSizeFilterItem → fileValidateSizeFilter.

2. Temp file persistence: files uploaded via FilePond went to
   tmp/filepond/ and vanished from the UI on page reload because
   data-existing-files only included DB-persisted files.
   Fix: session-track temp file_ids in handleProcess, inject via
   getSessionTempFiles() into data-existing-files, teach handleLoad
   to stream temp files from disk, and route JS remove → revert for hex IDs.
2026-06-10 00:18:49 +02:00
Pontoporeia c4a550f9d1 Rework contenus-edit: auto-save, OverType toolbar, dynamic sidebar links
- Auto-save: new autosave.js with 1.5s debounce, watches all forms with
  data-autosave, POSTs to form action with Accept: application/json, shows
  saving/saved/error status indicator
- All action handlers (page.php, apropos.php, form-help.php) now detect
  JSON Accept header and return {success, csrf_token} or {error} responses
- OverType toolbar enabled (toolbar:true) on all three markdown editors
  (page, about_page, form_help)
- Sidebar links: replaced fixed erg_site_url / source_code_url rows with
  dynamic sidebar_links array of {label, url} objects. Add/remove via JS.
  Fallback migration reads legacy keys if sidebar_links is empty.
- Updated AboutController and about.php template to render dynamic links
- Updated apropos.css: unified .apropos-toc-link replacing .apropos-toc-erg
  and .apropos-toc-source
- New CSS: autosave-status states, sidebar-link-row layout
- Removed all Enregistrer + Annuler buttons — auto-save and h1 back-arrow
  make them redundant
2026-06-10 00:18:40 +02:00