mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
À propos: contacts flexibles, liens sidebar éditables, grille contacts admin, et bouton supprimer
- Contacts: on peut laisser vide le nom OU le rôle (plus besoin des deux) - Sidebar: les liens « site de l'erg » et « code source » sont éditables depuis /admin/contenus-edit.php?slug=about - Admin: les champs Nom/Email/Lien des contacts s'affichent en grille 3 colonnes - Admin: icône corbeille (admin-icon-btn--delete) pour supprimer un contact, avec réindexation automatique - Database::getAproposContent() gère maintenant les valeurs string (URLs) en plus des arrays - Database::saveAproposContent() accepte array|string
This commit is contained in:
4
TODO.md
4
TODO.md
@@ -5,3 +5,7 @@
|
|||||||
- [x] Admin nav-logo: use grid layout with SVG + text horizontally aligned and vertically centered
|
- [x] Admin nav-logo: use grid layout with SVG + text horizontally aligned and vertically centered
|
||||||
- [x] repertoire.css: .rep-entry → step-1, years col → step-3, col h2 → step-1
|
- [x] repertoire.css: .rep-entry → step-1, years col → step-3, col h2 → step-1
|
||||||
- [x] Rework tfe.php layout: row1 author above title, row2 meta+synopsis 2-col grid, row3 flex files
|
- [x] Rework tfe.php layout: row1 author above title, row2 meta+synopsis 2-col grid, row3 flex files
|
||||||
|
- [x] Contacts: allow empty name OR empty role (not both) when saving a contact
|
||||||
|
- [x] Sidebar links: make "site de l'erg" and "code source" editable via admin panel
|
||||||
|
- [x] Admin contact blocks: grid layout (3 columns: Nom, Email, Lien)
|
||||||
|
- [x] Admin contacts: bouton supprimer un contact avec réindexation automatique
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
|||||||
die("Erreur de sécurité : token invalide.");
|
die("Erreur de sécurité : token invalide.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowedKeys = ['contacts'];
|
$urlKeys = ['erg_site_url', 'source_code_url'];
|
||||||
|
$groupKeys = ['contacts'];
|
||||||
|
$allowedKeys = array_merge($urlKeys, $groupKeys);
|
||||||
$aproposKey = $_POST['apropos_key'] ?? '';
|
$aproposKey = $_POST['apropos_key'] ?? '';
|
||||||
if (!in_array($aproposKey, $allowedKeys)) {
|
if (!in_array($aproposKey, $allowedKeys)) {
|
||||||
die("Clé invalide.");
|
die("Clé invalide.");
|
||||||
@@ -26,33 +28,45 @@ require_once __DIR__ . '/../../../src/ErrorHandler.php';
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$db = new Database();
|
$db = new Database();
|
||||||
$groups = $_POST['groups'] ?? [];
|
|
||||||
$cleaned = [];
|
|
||||||
|
|
||||||
foreach ($groups as $group) {
|
if (in_array($aproposKey, $urlKeys)) {
|
||||||
$role = trim($group['role'] ?? '');
|
// ── URL-based keys (sidebar links) ──
|
||||||
if ($role === '') continue;
|
$url = trim($_POST['url'] ?? '');
|
||||||
$entries = [];
|
if ($url !== '' && !filter_var($url, FILTER_VALIDATE_URL)) {
|
||||||
foreach ($group['entries'] ?? [] as $entry) {
|
die("URL invalide.");
|
||||||
$text = trim($entry['text'] ?? '');
|
|
||||||
if ($text === '') continue;
|
|
||||||
$e = [
|
|
||||||
'text' => $text,
|
|
||||||
'email' => trim($entry['email'] ?? ''),
|
|
||||||
];
|
|
||||||
$url = trim($entry['url'] ?? '');
|
|
||||||
if ($url !== '') $e['url'] = $url;
|
|
||||||
$entries[] = $e;
|
|
||||||
}
|
}
|
||||||
if (empty($entries)) continue;
|
$db->saveAproposContent($aproposKey, $url);
|
||||||
$cleaned[] = ['role' => $role, 'entries' => $entries];
|
} else {
|
||||||
|
// ── Group-based keys (contacts) ──
|
||||||
|
$groups = $_POST['groups'] ?? [];
|
||||||
|
$cleaned = [];
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$role = trim($group['role'] ?? '');
|
||||||
|
$entries = [];
|
||||||
|
foreach ($group['entries'] ?? [] as $entry) {
|
||||||
|
$text = trim($entry['text'] ?? '');
|
||||||
|
if ($text === '') continue;
|
||||||
|
$e = [
|
||||||
|
'text' => $text,
|
||||||
|
'email' => trim($entry['email'] ?? ''),
|
||||||
|
];
|
||||||
|
$urlEntry = trim($entry['url'] ?? '');
|
||||||
|
if ($urlEntry !== '') $e['url'] = $urlEntry;
|
||||||
|
$entries[] = $e;
|
||||||
|
}
|
||||||
|
// Keep group if it has a role OR at least one entry
|
||||||
|
if ($role === '' && empty($entries)) continue;
|
||||||
|
$cleaned[] = ['role' => $role, 'entries' => $entries];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($cleaned)) {
|
||||||
|
die("Au moins un groupe avec des entrées est requis.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->saveAproposContent($aproposKey, $cleaned);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($cleaned)) {
|
|
||||||
die("Au moins un groupe avec des entrées est requis.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$db->saveAproposContent($aproposKey, $cleaned);
|
|
||||||
AdminLogger::make()->logAproposEdit($aproposKey);
|
AdminLogger::make()->logAproposEdit($aproposKey);
|
||||||
App::flash('success', "Contenu « $aproposKey » mis à jour avec succès.");
|
App::flash('success', "Contenu « $aproposKey » mis à jour avec succès.");
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ if (empty($_SESSION["csrf_token"])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$allowedPageSlugs = ["about", "licenses"];
|
$allowedPageSlugs = ["about", "licenses"];
|
||||||
$allowedApropos = ["contacts"];
|
$allowedApropos = ["contacts", "erg_site_url", "source_code_url"];
|
||||||
|
|
||||||
$pageSlug = $_GET["slug"] ?? "";
|
$pageSlug = $_GET["slug"] ?? "";
|
||||||
$aproposKey = $_GET["apropos"] ?? "";
|
$aproposKey = $_GET["apropos"] ?? "";
|
||||||
@@ -44,6 +44,8 @@ try {
|
|||||||
$editType = 'about_page';
|
$editType = 'about_page';
|
||||||
$aboutContacts = $db->getAproposContent('contacts');
|
$aboutContacts = $db->getAproposContent('contacts');
|
||||||
$aboutContacts = is_array($aboutContacts) ? $aboutContacts : [];
|
$aboutContacts = is_array($aboutContacts) ? $aboutContacts : [];
|
||||||
|
$ergSiteUrl = $db->getAproposContent('erg_site_url');
|
||||||
|
$sourceCodeUrl = $db->getAproposContent('source_code_url');
|
||||||
} else {
|
} else {
|
||||||
$editType = "page";
|
$editType = "page";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2116,3 +2116,27 @@ th.admin-ap-col {
|
|||||||
.recap-dl dd:last-of-type {
|
.recap-dl dd:last-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Apropos contacts grid ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.apropos-entry {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--space-s);
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apropos-entry > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apropos-entry label {
|
||||||
|
font-size: var(--step--1);
|
||||||
|
margin-bottom: var(--space-3xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apropos-entry input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ class AboutController
|
|||||||
}
|
}
|
||||||
$contacts = $db->getAproposContent('contacts');
|
$contacts = $db->getAproposContent('contacts');
|
||||||
$contacts = is_array($contacts) && !empty($contacts) ? $contacts : null;
|
$contacts = is_array($contacts) && !empty($contacts) ? $contacts : null;
|
||||||
|
$ergSiteUrl = $db->getAproposContent('erg_site_url');
|
||||||
|
$sourceCodeUrl = $db->getAproposContent('source_code_url');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
ErrorHandler::log('about_page', $e);
|
ErrorHandler::log('about_page', $e);
|
||||||
$rawContent = $this->defaultContent;
|
$rawContent = $this->defaultContent;
|
||||||
$contacts = null;
|
$contacts = null;
|
||||||
|
$ergSiteUrl = null;
|
||||||
|
$sourceCodeUrl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$converter = new CommonMarkConverter(['html_input' => 'strip']);
|
$converter = new CommonMarkConverter(['html_input' => 'strip']);
|
||||||
@@ -38,6 +42,8 @@ class AboutController
|
|||||||
'currentNav' => 'apropos',
|
'currentNav' => 'apropos',
|
||||||
'aboutHtml' => EmailObfuscator::obfuscateHtml($converter->convert($rawContent)->getContent()),
|
'aboutHtml' => EmailObfuscator::obfuscateHtml($converter->convert($rawContent)->getContent()),
|
||||||
'contacts' => $contacts,
|
'contacts' => $contacts,
|
||||||
|
'ergSiteUrl' => $ergSiteUrl,
|
||||||
|
'sourceCodeUrl' => $sourceCodeUrl,
|
||||||
'pageTitle' => 'À Propos – XAMXAM',
|
'pageTitle' => 'À Propos – XAMXAM',
|
||||||
'metaDescription' => "À propos de XAMXAM, le répertoire des mémoires de fin d'études de l'erg – École de Recherches Graphiques de Bruxelles.",
|
'metaDescription' => "À propos de XAMXAM, le répertoire des mémoires de fin d'études de l'erg – École de Recherches Graphiques de Bruxelles.",
|
||||||
'extraCss' => ['/assets/css/apropos.css'],
|
'extraCss' => ['/assets/css/apropos.css'],
|
||||||
|
|||||||
@@ -2732,8 +2732,8 @@ class Database
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an apropos content value by key.
|
* Get an apropos content value by key.
|
||||||
* @param string $key 'contacts', 'credits', or 'erg_url'
|
* @param string $key 'contacts', 'credits', 'erg_site_url', 'source_code_url'
|
||||||
* @return array|string|null JSON-decoded array for contacts/credits, string for erg_url
|
* @return array|string|null JSON-decoded value (array for contacts, string for URLs)
|
||||||
*/
|
*/
|
||||||
public function getAproposContent(string $key)
|
public function getAproposContent(string $key)
|
||||||
{
|
{
|
||||||
@@ -2745,13 +2745,28 @@ class Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
$decoded = json_decode($row['value'], true);
|
$decoded = json_decode($row['value'], true);
|
||||||
return is_array($decoded) ? $decoded : null;
|
if (is_array($decoded)) {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
if (is_string($decoded)) {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
// Legacy: raw URL strings stored before JSON encoding was enforced
|
||||||
|
if (is_string($row['value']) && $row['value'] !== '') {
|
||||||
|
$trimmed = trim($row['value']);
|
||||||
|
if (filter_var($trimmed, FILTER_VALIDATE_URL)) {
|
||||||
|
return $trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save an apropos content value by key (contacts JSON).
|
* Save an apropos content value by key.
|
||||||
|
* @param string $key
|
||||||
|
* @param array|string $value Array for structured data (contacts), string for URLs
|
||||||
*/
|
*/
|
||||||
public function saveAproposContent(string $key, array $value): void
|
public function saveAproposContent(string $key, $value): void
|
||||||
{
|
{
|
||||||
$storedValue = json_encode($value, JSON_UNESCAPED_UNICODE);
|
$storedValue = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||||
$stmt = $this->pdo->prepare(
|
$stmt = $this->pdo->prepare(
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
{"timestamp":"2026-06-08T09:24:44+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"settings","action":"formulaire_update","status":"success","context":{"values":{"restricted_files_enabled":"0"}}}
|
{"timestamp":"2026-06-08T09:24:44+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"settings","action":"formulaire_update","status":"success","context":{"values":{"restricted_files_enabled":"0"}}}
|
||||||
{"timestamp":"2026-06-08T10:04:25+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"thesis","action":"edit","status":"success","context":{"thesis_id":26,"title":"DepNum"}}
|
{"timestamp":"2026-06-08T10:04:25+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"thesis","action":"edit","status":"success","context":{"thesis_id":26,"title":"DepNum"}}
|
||||||
{"timestamp":"2026-06-08T10:04:50+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"thesis","action":"publish","status":"success","context":{"count":1,"ids":[26]}}
|
{"timestamp":"2026-06-08T10:04:50+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"thesis","action":"publish","status":"success","context":{"count":1,"ids":[26]}}
|
||||||
|
{"timestamp":"2026-06-08T15:09:14+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"apropos","action":"edit","status":"success","context":{"key":"contacts"}}
|
||||||
|
{"timestamp":"2026-06-08T15:09:46+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0","resource":"apropos","action":"edit","status":"success","context":{"key":"contacts"}}
|
||||||
|
|||||||
@@ -21,24 +21,34 @@
|
|||||||
<?php $entries = is_array($group['entries'] ?? null) ? $group['entries'] : []; ?>
|
<?php $entries = is_array($group['entries'] ?? null) ? $group['entries'] : []; ?>
|
||||||
<?php foreach ($entries as $ei => $entry): ?>
|
<?php foreach ($entries as $ei => $entry): ?>
|
||||||
<div class="apropos-entry">
|
<div class="apropos-entry">
|
||||||
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text">Nom :</label>
|
<div>
|
||||||
<input type="text" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text"
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text">Nom :</label>
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][text]"
|
<input type="text" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_text"
|
||||||
value="<?= htmlspecialchars($entry['text'] ?? '') ?>">
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][text]"
|
||||||
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email">Email :</label>
|
value="<?= htmlspecialchars($entry['text'] ?? '') ?>">
|
||||||
<input type="email" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email"
|
</div>
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][email]"
|
<div>
|
||||||
value="<?= htmlspecialchars($entry['email'] ?? '') ?>">
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email">Email :</label>
|
||||||
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url">Lien (optionnel) :</label>
|
<input type="email" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_email"
|
||||||
<input type="url" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url"
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][email]"
|
||||||
name="groups[<?= $gi ?>][entries][<?= $ei ?>][url]"
|
value="<?= htmlspecialchars($entry['email'] ?? '') ?>">
|
||||||
value="<?= htmlspecialchars($entry['url'] ?? '') ?>">
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url">Lien (optionnel) :</label>
|
||||||
|
<input type="url" id="entry_f_<?= $aproposKey ?>_<?= $gi ?>_<?= $ei ?>_url"
|
||||||
|
name="groups[<?= $gi ?>][entries][<?= $ei ?>][url]"
|
||||||
|
value="<?= htmlspecialchars($entry['url'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
||||||
data-group="<?= $gi ?>"
|
data-group="<?= $gi ?>"
|
||||||
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
||||||
|
<button type="button" class="admin-icon-btn admin-icon-btn--delete delete-group-btn-f"
|
||||||
|
data-key="<?= $aproposKey ?>" title="Supprimer ce contact">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>
|
||||||
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
@@ -52,12 +62,18 @@
|
|||||||
|
|
||||||
<template id="entry-template-f-<?= $aproposKey ?>">
|
<template id="entry-template-f-<?= $aproposKey ?>">
|
||||||
<div class="apropos-entry">
|
<div class="apropos-entry">
|
||||||
<label>Entrée :</label>
|
<div>
|
||||||
<input type="text" name="groups[{{gi}}][entries][{{ei}}][text]">
|
<label>Nom :</label>
|
||||||
<label>Email :</label>
|
<input type="text" name="groups[{{gi}}][entries][{{ei}}][text]">
|
||||||
<input type="email" name="groups[{{gi}}][entries][{{ei}}][email]">
|
</div>
|
||||||
<label>Lien (optionnel) :</label>
|
<div>
|
||||||
<input type="url" name="groups[{{gi}}][entries][{{ei}}][url]">
|
<label>Email :</label>
|
||||||
|
<input type="email" name="groups[{{gi}}][entries][{{ei}}][email]">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Lien (optionnel) :</label>
|
||||||
|
<input type="url" name="groups[{{gi}}][entries][{{ei}}][url]">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -69,6 +85,10 @@
|
|||||||
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
<button type="button" class="btn btn--primary btn--sm add-entry-btn-f"
|
||||||
data-group="{{gi}}"
|
data-group="{{gi}}"
|
||||||
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
data-key="<?= $aproposKey ?>">+ Ajouter une entrée</button>
|
||||||
|
<button type="button" class="admin-icon-btn admin-icon-btn--delete delete-group-btn-f"
|
||||||
|
data-key="<?= $aproposKey ?>" title="Supprimer ce contact">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>
|
||||||
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</template>
|
</template>
|
||||||
</form>
|
</form>
|
||||||
@@ -81,7 +101,36 @@
|
|||||||
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML;
|
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML;
|
||||||
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML;
|
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML;
|
||||||
|
|
||||||
form.querySelectorAll('.add-entry-btn-f').forEach(function(btn) {
|
function reindexGroups() {
|
||||||
|
var fieldsets = form.querySelectorAll('fieldset.apropos-group');
|
||||||
|
groupCount = fieldsets.length;
|
||||||
|
fieldsets.forEach(function(fs, i) {
|
||||||
|
var newIdx = i;
|
||||||
|
var legend = fs.querySelector('legend');
|
||||||
|
if (legend) legend.textContent = 'Contact ' + (newIdx + 1);
|
||||||
|
|
||||||
|
// Update name attributes on all inputs
|
||||||
|
fs.querySelectorAll('input').forEach(function(inp) {
|
||||||
|
if (inp.name) {
|
||||||
|
inp.name = inp.name.replace(/groups\[\d+\]/, 'groups[' + newIdx + ']');
|
||||||
|
}
|
||||||
|
if (inp.id) {
|
||||||
|
inp.id = inp.id.replace(/(group_f_contacts_|entry_f_contacts_)\d+/, '$1' + newIdx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update for attributes on labels
|
||||||
|
fs.querySelectorAll('label[for]').forEach(function(lbl) {
|
||||||
|
lbl.setAttribute('for', lbl.getAttribute('for').replace(/(group_f_contacts_|entry_f_contacts_)\d+/, '$1' + newIdx));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update data-group on add-entry button
|
||||||
|
var addBtn = fs.querySelector('.add-entry-btn-f');
|
||||||
|
if (addBtn) addBtn.dataset.group = newIdx;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindAddEntry(btn) {
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
var gi = parseInt(this.dataset.group);
|
var gi = parseInt(this.dataset.group);
|
||||||
var fieldset = this.closest('fieldset');
|
var fieldset = this.closest('fieldset');
|
||||||
@@ -89,7 +138,21 @@
|
|||||||
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
||||||
this.insertAdjacentHTML('beforebegin', html);
|
this.insertAdjacentHTML('beforebegin', html);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function bindDeleteGroup(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var fieldset = this.closest('fieldset');
|
||||||
|
if (fieldset) {
|
||||||
|
fieldset.remove();
|
||||||
|
reindexGroups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind existing buttons
|
||||||
|
form.querySelectorAll('.add-entry-btn-f').forEach(bindAddEntry);
|
||||||
|
form.querySelectorAll('.delete-group-btn-f').forEach(bindDeleteGroup);
|
||||||
|
|
||||||
form.querySelector('.add-group-btn-f').addEventListener('click', function() {
|
form.querySelector('.add-group-btn-f').addEventListener('click', function() {
|
||||||
groupCount++;
|
groupCount++;
|
||||||
@@ -98,17 +161,13 @@
|
|||||||
|
|
||||||
var newGroup = this.previousElementSibling;
|
var newGroup = this.previousElementSibling;
|
||||||
if (newGroup && newGroup.classList.contains('apropos-group')) {
|
if (newGroup && newGroup.classList.contains('apropos-group')) {
|
||||||
var btn = newGroup.querySelector('.add-entry-btn-f');
|
var addBtn = newGroup.querySelector('.add-entry-btn-f');
|
||||||
if (btn) {
|
if (addBtn) {
|
||||||
btn.dataset.group = groupCount;
|
addBtn.dataset.group = groupCount;
|
||||||
btn.addEventListener('click', function() {
|
bindAddEntry(addBtn);
|
||||||
var gi = parseInt(this.dataset.group);
|
|
||||||
var fieldset = this.closest('fieldset');
|
|
||||||
var entryCount = fieldset.querySelectorAll('.apropos-entry').length;
|
|
||||||
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
|
|
||||||
this.insertAdjacentHTML('beforebegin', html);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
var delBtn = newGroup.querySelector('.delete-group-btn-f');
|
||||||
|
if (delBtn) bindDeleteGroup(delBtn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -28,6 +28,32 @@
|
|||||||
include APP_ROOT . '/templates/admin/apropos-groups-form.php';
|
include APP_ROOT . '/templates/admin/apropos-groups-form.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<!-- ── Sidebar links ─────────────────────────────────────────────────── -->
|
||||||
|
<h2 style="margin-top:3rem;">Liens de la barre latérale</h2>
|
||||||
|
<form action="/admin/actions/apropos.php" method="post" class="admin-form">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||||
|
<input type="hidden" name="apropos_key" value="erg_site_url">
|
||||||
|
<label for="apropos-erg-site-url">Site de l'erg :</label>
|
||||||
|
<input type="url" id="apropos-erg-site-url" name="url"
|
||||||
|
value="<?= htmlspecialchars($ergSiteUrl ?? '') ?>"
|
||||||
|
placeholder="https://erg.be">
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="btn btn--primary">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form action="/admin/actions/apropos.php" method="post" class="admin-form" style="margin-top:var(--space-m)">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||||
|
<input type="hidden" name="apropos_key" value="source_code_url">
|
||||||
|
<label for="apropos-source-code-url">Code source :</label>
|
||||||
|
<input type="url" id="apropos-source-code-url" name="url"
|
||||||
|
value="<?= htmlspecialchars($sourceCodeUrl ?? '') ?>"
|
||||||
|
placeholder="https://git.erg.school/PostERG/xamxam">
|
||||||
|
<div class="admin-form-footer">
|
||||||
|
<button type="submit" class="btn btn--primary">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
|
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
|
||||||
<form action="/admin/actions/page.php" method="post" class="admin-form">
|
<form action="/admin/actions/page.php" method="post" class="admin-form">
|
||||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ function renderEntries(array $entries): string
|
|||||||
<li><a href="#apropos-credits">Crédits</a></li>
|
<li><a href="#apropos-credits">Crédits</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="apropos-toc-erg">
|
<div class="apropos-toc-erg">
|
||||||
<a href="https://erg.be" target="_blank" rel="noopener">
|
<a href="<?= htmlspecialchars($ergSiteUrl ?: 'https://erg.be') ?>" target="_blank" rel="noopener">
|
||||||
Site de l'erg ↗
|
Site de l'erg ↗
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="apropos-toc-source">
|
<div class="apropos-toc-source">
|
||||||
<a href="https://git.erg.school/PostERG/xamxam" target="_blank" rel="noopener">
|
<a href="<?= htmlspecialchars($sourceCodeUrl ?: 'https://git.erg.school/PostERG/xamxam') ?>" target="_blank" rel="noopener">
|
||||||
Code source ↗
|
Code source ↗
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user