style: normalize headers, overtype editor rounded corners, remove duplicate cover preview, thesis-add-header grid layout, subtitle below header with top gradient

This commit is contained in:
Pontoporeia
2026-05-08 19:24:24 +02:00
parent 7ccadbb224
commit 21c2b55bfb
23 changed files with 1855 additions and 1531 deletions

View File

@@ -1,47 +1,11 @@
<?php
/**
* HTMX handler: persist the new drag-and-drop sort order for form help blocks.
*
* Expects POST fields:
* csrf_token — standard admin CSRF token
* block[] — ordered list of block keys (one hidden input per block, submitted by
* Sortable+htmx via the form's `end` event trigger)
*
* Returns a 204 No Content on success (htmx will not swap anything).
* On error, returns a 400 with a plain-text message.
* Legacy endpoint — no longer used (blocks are now static, non-sortable).
* Returns 204 No Content for backwards compatibility.
*/
require_once __DIR__ . '/../../../bootstrap.php';
require_once __DIR__ . '/../../../src/AdminAuth.php';
AdminAuth::requireLogin();
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
echo 'Token invalide.';
exit;
}
$keys = $_POST['block'] ?? [];
if (!is_array($keys) || empty($keys)) {
http_response_code(400);
echo 'Paramètre block[] manquant.';
exit;
}
// Sanitise: keep only scalar strings, deduplicate.
$keys = array_values(array_unique(array_filter($keys, 'is_string')));
require_once APP_ROOT . '/src/Database.php';
$db = new Database();
try {
$db->reorderFormHelpBlocks($keys);
} catch (Exception $e) {
error_log('form-help-reorder error: ' . $e->getMessage());
http_response_code(500);
echo 'Erreur lors de la sauvegarde.';
exit;
}
http_response_code(204);
exit;

View File

@@ -27,7 +27,7 @@ $autofocusField = App::consumeAutofocus();
$siteSettings = Database::getInstance()->getAllSettings();
// Form help blocks
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
function withAutofocus(string $fieldName, array $attrs = []): array {
global $autofocusField;

View File

@@ -50,7 +50,7 @@ try {
} elseif ($formHelpKey) {
$editType = "form_help";
$formHelpContent = $db->getFormHelpBlock($formHelpKey);
$editTitle = Database::FORM_HELP_LABELS[$formHelpKey] ?? $formHelpKey;
$editTitle = $formHelpKey;
} else {
$editType = "apropos";
$value = $db->getAproposContent($aproposKey);

View File

@@ -19,7 +19,7 @@ $autofocusField = App::consumeAutofocus();
// Form help blocks for editable généralités
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
function old($key, $default = "") {
global $formData;

View File

@@ -0,0 +1,196 @@
<?php
/**
* HTMX fragment: expand/collapse/editor for a single form help block.
*
* GET ?key=xxx → render collapsed chip: 3/4 left (name + MD preview), 1/4 right (edit + toggle dot)
* GET ?key=xxx&edit=1 → render inline OverType editor + name field
* POST → save content + name, return collapsed view
* POST _action=toggle → toggle enabled, return collapsed view
*/
require_once __DIR__ . '/../../bootstrap.php';
require_once __DIR__ . '/../../src/AdminAuth.php';
AdminAuth::requireLogin();
require_once __DIR__ . '/../../src/Database.php';
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$key = $_GET['key'] ?? '';
if (!in_array($key, Database::FORM_HELP_KEYS, true)) {
http_response_code(400);
echo 'Clé invalide.';
exit;
}
// ── POST ─────────────────────────────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['_action'] ?? 'save';
$db = new Database();
// Toggle doesn't need CSRF — low-risk action behind admin auth
if ($action === 'toggle') {
$db->toggleFormHelpBlock($key);
renderCollapsed($db, $key);
exit;
}
// Save requires CSRF
if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
http_response_code(403);
echo 'Token invalide.';
exit;
}
// save
$content = $_POST['content'] ?? '';
$name = trim($_POST['name'] ?? '');
try {
$db->setFormHelpBlock($key, $content);
if ($name !== '') {
$db->setFormHelpBlockName($key, $name);
}
require_once __DIR__ . '/../../src/AdminLogger.php';
AdminLogger::make()->logFormStructureEdit($key);
} catch (Exception $e) {
error_log('form-help-inline save error: ' . $e->getMessage());
http_response_code(500);
echo 'Erreur lors de la sauvegarde.';
exit;
}
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
renderCollapsed($db, $key);
exit;
}
// ── GET ──────────────────────────────────────────────────────────────────────
$db = new Database();
$editMode = ($_GET['edit'] ?? '') === '1';
if ($editMode) {
renderEditor($db, $key);
} else {
renderCollapsed($db, $key);
}
// ═══════════════════════════════════════════════════════════════════════════════
// RENDER HELPERS
// ═══════════════════════════════════════════════════════════════════════════════
function renderCollapsed(Database $db, string $key): void
{
$blocks = $db->getAllFormHelpBlocks();
$b = $blocks[$key] ?? ['content' => '', 'name' => '', 'enabled' => 0];
$name = $b['name'] ?: $key;
$content = $b['content'] ?? '';
$enabled = (int)($b['enabled'] ?? 1);
$hasContent = trim($content) !== '';
$mdHtml = '';
if ($hasContent) {
require_once APP_ROOT . '/src/Parsedown.php';
$pd = new Parsedown();
$pd->setSafeMode(true);
$mdHtml = $pd->text($content);
}
?>
<div class="fhb-inline <?= $enabled ? '' : 'fhb-inline--disabled' ?>" data-key="<?= htmlspecialchars($key) ?>">
<!-- Left side: name + content (content hidden when disabled) -->
<div class="fhb-inline-body">
<div class="fhb-inline-name"><?= htmlspecialchars($name) ?></div>
<?php if ($enabled && $hasContent): ?>
<div class="fhb-md-preview"><?= $mdHtml ?></div>
<?php elseif ($enabled): ?>
<div class="fhb-inline-empty">— vide —</div>
<?php endif; ?>
</div>
<!-- Right side: dot above edit button (edit hidden when disabled) -->
<div class="fhb-inline-actions">
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
hx-target="closest .fhb-inline"
hx-swap="outerHTML"
class="fhb-toggle-form">
<input type="hidden" name="_action" value="toggle">
<button type="submit"
class="fhb-dot <?= $enabled ? 'fhb-dot--on' : 'fhb-dot--off' ?>"
title="<?= $enabled ? 'Désactiver ce bloc' : 'Activer ce bloc' ?>"
aria-label="<?= $enabled ? 'Désactiver ce bloc' : 'Activer ce bloc' ?>"></button>
</form>
<?php if ($enabled): ?>
<button type="button" class="btn btn--primary btn--sm"
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>&edit=1"
hx-target="closest .fhb-inline"
hx-swap="outerHTML">Éditer</button>
<?php endif; ?>
</div>
</div>
<?php
}
function renderEditor(Database $db, string $key): void
{
$blocks = $db->getAllFormHelpBlocks();
$b = $blocks[$key] ?? ['content' => '', 'name' => ''];
$name = $b['name'] ?: $key;
$content = $b['content'] ?? '';
?>
<div class="fhb-inline fhb-inline--editing" data-key="<?= htmlspecialchars($key) ?>">
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
hx-swap="outerHTML"
hx-target="closest .fhb-inline"
class="fhb-inline-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<div class="fhb-edit-name-row">
<label for="fhb-name-<?= htmlspecialchars($key) ?>" class="fhb-edit-label">Nom du bloc :</label>
<input type="text" id="fhb-name-<?= htmlspecialchars($key) ?>" name="name"
value="<?= htmlspecialchars($name) ?>"
class="fhb-name-input"
placeholder="Nom du bloc d'aide">
</div>
<label for="fhb-ed-<?= htmlspecialchars($key) ?>" class="fhb-edit-label">Contenu (Markdown) :</label>
<input type="hidden" id="fhb-content-<?= htmlspecialchars($key) ?>" name="content"
value="<?= htmlspecialchars($content) ?>">
<div id="fhb-editor-<?= htmlspecialchars($key) ?>" class="fhb-overtype-editor"></div>
<div class="fhb-edit-buttons">
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
<button type="button" class="btn btn--secondary btn--sm"
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
hx-target="closest .fhb-inline"
hx-swap="outerHTML">Annuler</button>
</div>
</form>
<script>
(function(){
var hidden = document.getElementById('fhb-content-<?= htmlspecialchars($key) ?>');
var ed = document.getElementById('fhb-editor-<?= htmlspecialchars($key) ?>');
if (hidden && ed && typeof OverType !== 'undefined') {
var OT = OverType.default || OverType;
new OT(ed, {
value: hidden.value,
minHeight: '400px',
spellcheck: false,
onChange: function(v) { hidden.value = v; }
});
} else {
ed.innerHTML = '<textarea name="content" style="width:100%;min-height:400px;font-family:monospace">'
+ hidden.value.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</textarea>';
var ta = ed.querySelector('textarea');
ta.addEventListener('input', function() {
hidden.value = this.value;
});
}
})();
</script>
</div>
<?php
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* HTMX fragment (admin): renders the licence section with conditional required states.
*
* POST: access_type_id, license_id, license_custom, cc2r, admin_mode
*
* Admin mode: never required.
*/
require_once __DIR__ . '/../../bootstrap.php';
require_once __DIR__ . '/../../src/AdminAuth.php';
AdminAuth::requireLogin();
$accessTypeId = $_POST['access_type_id'] ?? '2';
$licenseId = $_POST['license_id'] ?? '';
$licenseCustom = $_POST['license_custom'] ?? '';
$cc2r = !empty($_POST['cc2r']);
$adminMode = true;
require_once APP_ROOT . '/src/Database.php';
$db = Database::getInstance();
$licenseTypes = $db->getAllLicenseTypes();
?>
<div class="licence-license-choice">
<?php
$name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes;
$selected = $licenseId; $placeholder = '— Sélectionner —'; $required = false;
include APP_ROOT . '/templates/partials/form/select-field.php';
?>
<?php
$name = 'license_custom'; $label = 'Ou précisez une autre licence :';
$value = htmlspecialchars($licenseCustom);
$hint = 'Ex: CC BY-NC 4.0, Tous droits réservés...';
include APP_ROOT . '/templates/partials/form/text-field.php';
?>
<div class="admin-form-group">
<label class="admin-checkbox-label">
<input type="checkbox" name="cc2r" value="1"
<?= $cc2r ? 'checked' : '' ?>>
J'accepte les Conditions Collectives de Réutilisation (CC2r)
</label>
<small><a href="https://cc2r.net/" target="_blank" rel="noopener">En savoir plus sur la CC2r ↗</a></small>
</div>
</div>

View File

@@ -16,7 +16,6 @@
white-space: nowrap;
scrollbar-width: thin;
}
.admin-body header nav .nav-left,
.admin-body header nav .nav-right-links {
flex-shrink: 0;
}
@@ -1480,7 +1479,7 @@
}
/* ═══════════════════════════════════════════════════════════════════════════
Form Help Blocks — drag-and-drop builder (contenus.php)
Form Help Blocks — static structure view (contenus.php)
═══════════════════════════════════════════════════════════════════════════ */
.fhb-hint {
@@ -1489,225 +1488,250 @@
margin-bottom: var(--space-m);
}
.fhb-layout {
/* ── Structure container ───────────────────────────────────────────────────── */
.fhb-structure {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-m);
align-items: start;
grid-template-columns: 1fr;
gap: var(--space-xs);
max-width: 100%;
margin-top: var(--space-m);
}
@media (max-width: 800px) {
.fhb-layout {
grid-template-columns: 1fr;
}
}
/* ── Fieldset cards (static reference) ─────────────────────────────────────── */
/* ── Panels ─────────────────────────────────────────────────────────────── */
.fhb-sortable-panel,
.fhb-form-preview-panel {
.fhb-fieldset-card {
border: 1px solid var(--border-primary);
border-radius: var(--radius);
padding: var(--space-s);
padding: var(--space-xs) var(--space-s);
background: var(--bg-secondary);
}
.fhb-panel-title {
.fhb-fieldset-card-legend {
font-size: var(--step-0);
font-weight: 600;
margin: 0 0 var(--space-3xs) 0;
letter-spacing: 0.03em;
}
.fhb-panel-desc {
font-size: var(--step--2);
color: var(--text-secondary);
margin: 0 0 var(--space-xs) 0;
}
/* ── Saving indicator ─────────────────────────────────────────────────────── */
.fhb-saving {
display: none;
align-items: center;
gap: var(--space-2xs);
font-size: var(--step--1);
color: var(--accent-primary);
padding: var(--space-2xs) 0;
}
.fhb-saving.htmx-request {
display: flex;
}
/* ── Draggable block cards ─────────────────────────────────────────────────── */
.fhb-sortable {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
padding: 0;
margin: 0;
}
.fhb-block-card {
display: flex;
align-items: center;
gap: var(--space-xs);
background: var(--bg-primary);
border: 1px solid var(--border-primary);
border-left: 4px solid var(--accent-primary);
border-radius: var(--radius);
padding: var(--space-2xs) var(--space-xs);
cursor: default;
transition: box-shadow 0.15s, border-color 0.15s;
}
.fhb-block-card:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border-color: var(--accent-primary);
}
.fhb-drag-handle {
font-size: 1.2em;
color: var(--text-tertiary);
cursor: grab;
flex-shrink: 0;
line-height: 1;
user-select: none;
padding: 2px 4px;
}
.fhb-drag-handle:active {
cursor: grabbing;
}
.fhb-block-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.fhb-block-label {
font-size: var(--step--1);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fhb-block-preview {
font-size: var(--step--2);
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.fhb-block-empty {
font-size: var(--step--2);
color: var(--text-tertiary);
font-style: italic;
}
.fhb-edit-btn {
flex-shrink: 0;
font-size: var(--step--2) !important;
padding: 2px var(--space-xs) !important;
}
/* ── SortableJS state classes ─────────────────────────────────────────────── */
.fhb-ghost {
opacity: 0.35;
background: var(--accent-muted);
border-color: var(--accent-primary);
}
.fhb-chosen {
box-shadow: 0 4px 16px rgba(149, 87, 181, 0.25);
border-color: var(--accent-primary);
}
.fhb-dragging {
opacity: 0.9;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
/* ── Form structure preview (right panel) ─────────────────────────────────── */
.fhb-form-preview {
display: flex;
flex-direction: column;
gap: var(--space-2xs);
}
.fhb-fieldset-preview {
border: 1px solid var(--border-secondary);
border-radius: var(--radius);
padding: var(--space-xs);
background: var(--bg-primary);
}
.fhb-fieldset-legend {
font-size: var(--step--1);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-3xs);
padding-bottom: var(--space-3xs);
border-bottom: 1px solid var(--border-primary);
}
.fhb-fieldset-inputs {
.fhb-fieldset-card-inputs {
margin: 0;
padding: 0 0 0 var(--space-s);
list-style: disc;
list-style: none;
display: flex;
flex-wrap: wrap;
gap: var(--space-3xs) var(--space-m);
}
.fhb-fieldset-inputs li {
.fhb-fieldset-card-inputs li {
font-size: var(--step--2);
color: var(--text-secondary);
line-height: 1.6;
}
.fhb-anchor {
display: flex;
align-items: center;
gap: var(--space-2xs);
border-radius: var(--radius);
padding: var(--space-3xs) var(--space-xs);
font-size: var(--step--2);
border: 1px dashed var(--border-primary);
background: transparent;
}
.fhb-anchor--filled {
border-color: var(--accent-primary);
background: var(--accent-muted);
color: var(--accent-secondary);
}
.fhb-anchor--empty {
.fhb-fieldset-card-inputs li::before {
content: '· ';
color: var(--text-tertiary);
}
.fhb-anchor-icon {
flex-shrink: 0;
font-style: normal;
/* ── Help block wrapper ────────────────────────────────────────────────────── */
.fhb-block-wrapper {
/* container for the fhb-inline, one per help block */
}
.fhb-anchor-label {
flex: 1;
}
/* ── Inline help block (collapsed state) ───────────────────────────────────── */
.fhb-anchor-pos {
font-size: var(--step--2);
font-weight: 600;
color: var(--accent-primary);
background: var(--accent-muted);
.fhb-inline {
display: grid;
grid-template-columns: 1fr auto;
gap: var(--space-s);
align-items: center;
border: 1px solid var(--border-primary);
border-left: 4px solid var(--accent-primary);
border-radius: var(--radius);
padding: 0 4px;
padding: var(--space-xs) var(--space-s);
background: var(--bg-primary);
transition: opacity 0.15s;
}
.fhb-inline:hover {
opacity: 0.85;
}
/* Disabled state — one line only, content hidden */
.fhb-inline--disabled {
border-left-color: var(--text-tertiary);
opacity: 0.55;
}
.fhb-inline--disabled:hover {
opacity: 0.75;
}
.fhb-inline--disabled .fhb-md-preview,
.fhb-inline--disabled .fhb-inline-empty {
display: none;
}
/* Editing state */
.fhb-inline--editing {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
border-color: var(--accent-primary);
box-shadow: 0 4px 16px rgba(149, 87, 181, 0.15);
padding: var(--space-s);
min-height: 50vh;
}
.fhb-inline--editing .fhb-inline-form {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr auto;
height: 100%;
}
/* ── Left side: name + content ─────────────────────────────────────────────── */
.fhb-inline-body {
min-width: 0;
}
.fhb-inline-name {
font-size: var(--step--1);
font-weight: 600;
color: var(--accent-secondary);
margin-bottom: var(--space-3xs);
}
/* ── Small rendered Markdown preview ──────────────────────────────────────── */
.fhb-md-preview {
font-size: var(--step--2);
color: var(--text-secondary);
line-height: 1.45;
max-height: 6em;
overflow: hidden;
}
.fhb-md-preview p {
margin: 0;
}
.fhb-md-preview p + p {
margin-top: var(--space-3xs);
}
.fhb-md-preview ul,
.fhb-md-preview ol {
margin: var(--space-3xs) 0;
padding-left: var(--space-s);
}
.fhb-md-preview li {
margin-bottom: 0;
}
.fhb-md-preview strong { font-weight: 600; }
.fhb-md-preview em { font-style: italic; }
.fhb-md-preview code {
font-size: 0.9em;
background: var(--bg-secondary);
padding: 0 var(--space-4xs);
border-radius: 3px;
}
.fhb-inline-empty {
font-size: var(--step--2);
color: var(--text-tertiary);
font-style: italic;
}
/* ── Right side: edit button + live dot ───────────────────────────────────── */
.fhb-inline-actions {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--space-2xs);
flex-shrink: 0;
}
.fhb-toggle-form {
margin: 0;
line-height: 0;
}
/* Live dot — green when on, red when off */
.fhb-dot {
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
border: none;
cursor: pointer;
padding: 0;
transition: opacity 0.15s;
}
.fhb-dot:hover {
opacity: 0.7;
}
.fhb-dot--on {
background: #2d6a4f;
}
.fhb-dot--off {
background: #c0392b;
}
/* ── Editor form ──────────────────────────────────────────────────────────── */
.fhb-edit-name-row {
margin-bottom: var(--space-xs);
}
.fhb-edit-label {
display: block;
font-size: var(--step--2);
font-weight: 500;
color: var(--text-primary);
margin-bottom: var(--space-3xs);
}
.fhb-name-input {
width: 100%;
max-width: 400px;
padding: var(--space-2xs) var(--space-xs);
border: 1px solid var(--border-primary);
border-radius: var(--radius);
font-size: var(--step--1);
font-family: var(--font-body);
}
.fhb-name-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px var(--accent-muted);
}
.fhb-overtype-editor .--type-container {
border-radius: var(--radius);
}
.fhb-edit-buttons {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: var(--space-xs);
margin-top: var(--space-xs);
}
.fhb-inline-form {
margin-top: var(--space-xs);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -50,6 +50,7 @@ $selectedFormats = isset($_POST['formats']) && is_array($_POST['formats'])
: [];
$adminMode = ($_POST['admin_mode'] ?? '0') === '1';
$editMode = ($_POST['edit_mode'] ?? '0') === '1';
$hasSiteWeb = $siteWebId && in_array($siteWebId, $selectedFormats, true);
$hasVideo = $videoId && in_array($videoId, $selectedFormats, true);
@@ -70,6 +71,10 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
?>
<div id="format-fichiers-block">
<input type="hidden" name="admin_mode" value="<?= $adminMode ? '1' : '0' ?>">
<input type="hidden" name="edit_mode" value="<?= $editMode ? '1' : '0' ?>">
<?php if ($editMode && ($_POST['_cover'] ?? null)): ?>
<input type="hidden" name="_cover" value="<?= htmlspecialchars($_POST['_cover']) ?>">
<?php endif; ?>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
@@ -81,7 +86,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
hx-post="<?= htmlspecialchars($hxPost) ?>"
hx-target="#format-fichiers-block"
hx-trigger="change"
hx-include="this, [name='website_url'], [name='website_label'], [name='admin_mode']"
hx-include="this, [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
hx-swap="outerHTML">
<legend class="sr-only">Format(s) du TFE</legend>
<ul>
@@ -108,13 +113,26 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
<!-- ── 1. Couverture (always) ── -->
<div>
<?php
$_cover = $_POST['_cover'] ?? null;
if ($editMode && $_cover): ?>
<div class="admin-banner-preview">
<img src="/media?path=<?= urlencode($_cover) ?>"
alt="Couverture actuelle" style="max-height:180px;">
<label class="admin-checkbox-label">
<input type="checkbox" name="remove_cover" value="1"> Supprimer la couverture
</label>
</div>
<?php endif;
$name = 'couverture';
$label = 'Image de couverture (optionnel) :';
$accept = 'image/jpeg,image/png,image/webp';
$hint = 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
$hint = ($editMode && $_cover)
? 'Laisser vide pour conserver la couverture actuelle. JPG, PNG ou WEBP. Max 20 MB.'
: 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
$required = false;
$id = 'couverture';
include APP_ROOT . '/templates/partials/form/file-field.php';
unset($_cover);
?>
</div>
@@ -162,7 +180,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm
hx-post="<?= htmlspecialchars($hxPost) ?>"
hx-target="#format-fichiers-block"
hx-trigger="change"
hx-include="[name='formats[]'], [name='website_url'], [name='website_label'], [name='admin_mode'], [name='has_annexes']"
hx-include="[name='formats[]'], [name='website_url'], [name='website_label'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
hx-swap="outerHTML">
Ce TFE comporte des annexes
</label>

View File

@@ -42,6 +42,13 @@ if ($slug === 'fichiers-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
exit;
}
// Special route: /partage/licence-fragment (HTMX fragment — licence section with conditional required)
if ($slug === 'licence-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
App::boot();
require_once __DIR__ . '/licence-fragment.php';
exit;
}
// Special route: /partage/recapitulatif?id=N
if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') {
App::boot();
@@ -267,7 +274,7 @@ function renderShareLinkForm(string $slug, array $link): void
// Load all form help blocks in one query.
$helpBlocks = Database::getInstance()->getAllFormHelpBlocks();
$helpFn = fn(string $key) => $helpBlocks[$key]['content'] ?? '';
$helpFn = fn(string $key) => empty($helpBlocks[$key]['enabled']) ? '' : ($helpBlocks[$key]['content'] ?? '');
// ── Shared form variables ──────────────────────────────────────────────
$mode = 'partage';
@@ -356,6 +363,7 @@ function renderShareLinkForm(string $slug, array $link): void
<?php if ($isVerified): ?>
<span class="share-badge">🔓 Accès partagé</span>
<?php endif; ?>
<p class="thesis-add-subtitle">Formulaire pour <a href="/">XAMXAM</a></p>
</div>
<?php include APP_ROOT . '/templates/partials/form/form.php'; ?>

View File

@@ -0,0 +1,47 @@
<?php
/**
* HTMX fragment: renders the licence section with conditional required states.
*
* POST: access_type_id, license_id, license_custom, cc2r, admin_mode
*
* When access_type_id=1 (Libre): licence fields are required.
* When access_type_id=2|3 (Interne/Interdit): licence fields are optional.
*/
require_once __DIR__ . '/../../bootstrap.php';
$accessTypeId = $_POST['access_type_id'] ?? '2';
$licenseId = $_POST['license_id'] ?? '';
$licenseCustom = $_POST['license_custom'] ?? '';
$cc2r = !empty($_POST['cc2r']);
$adminMode = ($_POST['admin_mode'] ?? '0') === '1';
$required = !$adminMode && $accessTypeId === '1';
require_once APP_ROOT . '/src/Database.php';
$db = Database::getInstance();
$licenseTypes = $db->getAllLicenseTypes();
?>
<div class="licence-license-choice">
<?php
$name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes;
$selected = $licenseId; $placeholder = '— Sélectionner —';
include APP_ROOT . '/templates/partials/form/select-field.php';
?>
<?php
$name = 'license_custom'; $label = 'Ou précisez une autre licence :';
$value = htmlspecialchars($licenseCustom);
$hint = 'Ex: CC BY-NC 4.0, Tous droits réservés...';
include APP_ROOT . '/templates/partials/form/text-field.php';
?>
<div class="admin-form-group">
<label class="admin-checkbox-label">
<input type="checkbox" name="cc2r" value="1"
<?= $cc2r ? 'checked' : '' ?>>
J'accepte les Conditions Collectives de Réutilisation (CC2r)
</label>
<small><a href="https://cc2r.net/" target="_blank" rel="noopener">En savoir plus sur la CC2r ↗</a></small>
</div>
</div>