Initial commit
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# --- Dépendances Composer ---
|
||||
vendor/
|
||||
|
||||
# --- Artefacts SASS ---
|
||||
.sass-cache/
|
||||
*.css.map
|
||||
|
||||
# --- Node ---
|
||||
node_modules/
|
||||
|
||||
# --- OS / éditeur ---
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
3
404.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
Timber::render('404.twig', $context);
|
||||
86
README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# thalim-theme
|
||||
|
||||
Thème WordPress personnalisé du laboratoire **THALIM** (UMR 7172). Basé sur [Timber 2.x](https://timber.github.io/docs/) (templating Twig pour WordPress), avec un système maison de bilinguisme FR/EN, une admin très customisée, et une logique de tri par date d'événement pour les annonces.
|
||||
|
||||
- **Version :** 1.0.0
|
||||
- **Requires WordPress :** 6.0+
|
||||
- **Requires PHP :** 7.4+
|
||||
|
||||
## Installation
|
||||
|
||||
Le thème est conçu pour être cloné dans le `wp-content/themes/` d'une installation WordPress. Dans le cadre du projet THALIM, le clonage est automatisé par le script `bootstrap.sh` du repo [`thalim-stack`](https://figureslibres.io/valentin_le_moign/thalim-stack).
|
||||
|
||||
Manuellement :
|
||||
|
||||
```bash
|
||||
cd wp-content/themes
|
||||
git clone gitea@figureslibres.io:valentin_le_moign/thalim-theme.git thalim
|
||||
cd thalim
|
||||
composer install
|
||||
```
|
||||
|
||||
Puis activer le thème depuis l'admin WordPress.
|
||||
|
||||
## Plugins requis
|
||||
|
||||
- [Pods](https://wordpress.org/plugins/pods/) — types de contenu et champs personnalisés (toute la structure éditoriale du site est en Pods)
|
||||
- [Members](https://wordpress.org/plugins/members/) — gestion fine des rôles et capacités
|
||||
- [Simple Local Avatars](https://wordpress.org/plugins/simple-local-avatars/) — avatars uploadés (utilisés en priorité, Gravatar en fallback)
|
||||
- [Relevanssi](https://wordpress.org/plugins/relevanssi/) — moteur de recherche
|
||||
|
||||
## Fonctionnalités notables
|
||||
|
||||
### Bilinguisme FR/EN maison
|
||||
- Détection de la langue via le préfixe d'URL `/en/` (`thalim_current_language()`)
|
||||
- Convention `"FR // EN"` dans un même champ pour les textes courts (titres, lieux, sous-titres, options Pods pick…) — split par le filtre Twig `|bilingual`
|
||||
- Champs Pods séparés `*_en` pour les textes longs (`body_en`, `biographie_en`, `presentation_en`…)
|
||||
- Auto-préfixage des URLs internes en mode EN via les filtres `term_link`, `post_link`, `page_link`, `post_type_link`, `author_link`
|
||||
- Sélecteur de langue : `thalim_language_switcher()`
|
||||
- Menus : `Navigation` / `Navigation-en` et `Footer` / `Footer-en`
|
||||
|
||||
### Tri par date d'événement
|
||||
Les annonces avec `date_de_debut` / `datetime` (champs Pods) sont triées sur cette date plutôt que `post_date`, via `'thalim_event_date_order' => true` sur les args de `WP_Query`. Filtre de plage : `'thalim_event_date_filter' => ['from' => …, 'to' => …]`.
|
||||
|
||||
### Admin très customisée
|
||||
- Onglets FR/EN sur l'éditeur de corps de post (`body` natif WP + `body_en` Pods)
|
||||
- Renommage « Article » → « Annonce » dans toute l'UI
|
||||
- Visibilité conditionnelle des metaboxes Pods + groupement des axes thématiques par période
|
||||
- Restauration automatique des champs en cas d'erreur de validation Pods (transient + JS)
|
||||
- Restrictions des contributeurs (édition limitée aux posts où ils figurent comme membre)
|
||||
- Dashboard et menu Outils masqués pour les non-admins
|
||||
- Voir `CLAUDE.md` côté stack pour le détail
|
||||
|
||||
### AJAX et infinite scroll
|
||||
Les pages d'archives utilisent un système d'infinite scroll AJAX (`wp_ajax_load_more_posts`) avec recherche Relevanssi, filtres par axe / date / taxonomie / catégorie, et override de langue côté serveur.
|
||||
|
||||
## Compilation des styles
|
||||
|
||||
Les fichiers SCSS dans `scss/` sont compilés **manuellement** vers `css/`.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── functions.php # setup, i18n, contexte Twig, AJAX, filtres de requête, mods admin (≈1400 lignes)
|
||||
├── index.php, single.php … # templates PHP qui chargent les Twig correspondants
|
||||
├── templates/ # templates Twig (base.twig = layout, autres l'étendent)
|
||||
│ └── partials/ # header, footer, post-card, agenda-card, search-panel…
|
||||
├── scss/ → css/ # sources SASS → CSS compilé (commité)
|
||||
├── js/ # scripts frontend + adminDashboardMods.js
|
||||
├── inc/ # helpers PHP par contexte :
|
||||
│ ├── single-helpers.php # résolution champs Pods d'un post
|
||||
│ ├── author-helpers.php # profil membre + posts liés
|
||||
│ ├── membres-helpers.php # page /membres (groupes par rôle)
|
||||
│ ├── post-card-helpers.php # données pour les cards
|
||||
│ ├── pods-conditional-required.php # patch validation Pods
|
||||
│ ├── pods-save-error-handler.php # restauration des champs en cas d'erreur
|
||||
│ ├── post-title-required.php # titre obligatoire
|
||||
│ └── admin-users-filter.php # filtre Statut sur /wp-admin/users.php
|
||||
├── assets/ # fonts, images, logo-shapes (SVG)
|
||||
├── composer.json # dépendance : timber/timber ^2.3
|
||||
└── vendor/ # Composer (gitignoré, à reconstruire après clone)
|
||||
```
|
||||
|
||||
## Architecture détaillée
|
||||
|
||||
Voir le fichier `CLAUDE.md` à la racine du repo [`thalim-stack`](https://figureslibres.io/valentin_le_moign/thalim-stack) pour la documentation exhaustive (conventions Pods, customisations admin, restrictions de contenu, helpers, rewrite rules, etc.).
|
||||
BIN
assets/fonts/Gelasio-Regular.woff2
Normal file
BIN
assets/fonts/NewsCycle-Regular.woff2
Normal file
BIN
assets/images/cnrs.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/images/ens.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/sorbonne.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/images/thalim-logo.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
87
assets/images/thalim-logo.svg
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="170.98988mm"
|
||||
height="129.68549mm"
|
||||
viewBox="0 0 170.98988 129.68549"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
sodipodi:docname="logo-thalim-inkscape.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.30295176"
|
||||
inkscape:cx="-523.18561"
|
||||
inkscape:cy="2751.2631"
|
||||
inkscape:window-width="1728"
|
||||
inkscape:window-height="1108"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"><inkscape:page
|
||||
x="0"
|
||||
y="-1.517571e-14"
|
||||
width="170.98988"
|
||||
height="129.68549"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" /></sodipodi:namedview><defs
|
||||
id="defs1"><linearGradient
|
||||
id="swatch1"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" /></linearGradient></defs><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="Layer 4"
|
||||
transform="translate(476.37288,-54.015086)"><path
|
||||
d="m -351.91056,165.85995 h -52.50622 V 67.507728 c 0,0 8.8076,-12.737588 26.73354,-12.960949 16.61076,-0.206875 25.06867,12.315648 25.06867,12.315648 l 0.70401,98.997523"
|
||||
fill="url(#fill-0-render-5)"
|
||||
id="path9"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:export-filename="/home/val/code/test_thalim_hero_header/logos/thalim-logo-bolder-bolder.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" /><path
|
||||
d="m -426.30884,183.17725 -8.34311,-120.774255 63.88462,7.799753 9.05832,103.284002 -64.59983,9.6905"
|
||||
fill="url(#fill-0-render-3)"
|
||||
id="path10"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1" /><path
|
||||
d="m -317.73563,85.200571 -4.02352,77.834909 -154.0598,-8.0928 3.78594,-78.549023 154.29738,8.806914"
|
||||
fill="url(#fill-0-render-2)"
|
||||
id="path11"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1" /><ellipse
|
||||
cx="-406.18973"
|
||||
cy="132.90717"
|
||||
rx="71.197098"
|
||||
ry="8.8586254"
|
||||
transform="rotate(-3.0000013)"
|
||||
fill="url(#fill-0-render-1)"
|
||||
id="ellipse11"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1" /><path
|
||||
d="M -460.51897,146.56124 V 86.474302 l 154.60354,-19.54642 -0.475,86.631298 -154.12854,-6.99794"
|
||||
fill="url(#fill-0-render-4)"
|
||||
id="path12"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1" /><path
|
||||
id="path13"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -321.37051,153.07232 3.49911,-67.684754 -91.43175,-5.218878"
|
||||
sodipodi:nodetypes="ccc" /><path
|
||||
style="font-size:172px;font-family:'News Cycle';-inkscape-font-specification:'News Cycle';white-space:pre;stroke-width:2"
|
||||
d="m 804.12695,-143.04834 v -9.40625 h 14.94922 v -32.33399 h 11.08594 v 32.33399 h 20.32422 v 9.40625 h -20.32422 v 56.185543 q 0,8.482422 2.60351,11.589844 2.60352,3.107422 8.56641,3.107422 2.43555,0 5.79492,-0.419922 3.35938,-0.419922 3.35938,-0.503906 v 8.314453 q -5.54297,2.015625 -10.83399,2.015625 -5.20703,0 -7.13867,-0.251953 -1.93164,-0.251953 -4.70312,-1.595703 -2.77149,-1.34375 -4.53516,-3.779297 -4.19922,-5.878906 -4.19922,-18.476563 v -56.185543 z m 58.70508,79.197262 V -191.08741 h 11.08594 v 53.16211 q 12.42969,-11.84179 20.4082,-14.27734 4.11524,-1.17578 8.56641,-1.17578 9.6582,0 15.87304,5.8789 6.21485,5.87891 6.21485,17.63672 v 66.011722 H 913.89453 V -129.8628 q 0,-6.29882 -4.53516,-10.16211 -4.45117,-3.94726 -9.6582,-3.94726 -12.42969,0 -25.7832,14.5293 v 65.591792 z m 77.43359,-21.583984 q 0,-6.046875 1.51172,-10.498047 1.51172,-4.535161 4.70313,-7.978511 3.27539,-3.52735 7.39062,-6.21485 4.11524,-2.6875 10.2461,-4.87109 11.16992,-3.94727 27.29492,-7.13867 0,-13.26954 -5.12305,-17.72071 -4.53515,-3.94726 -13.18554,-3.94726 -10.58204,0 -18.22461,8.0625 -3.27539,3.27539 -4.53516,5.8789 l -7.97852,-5.29101 q 9.40625,-18.05664 31.99805,-18.05664 20.24024,0 25.86722,15.53711 2.2675,6.29882 2.2675,15.28515 v 46.107425 q 0,2.435547 0.9239,6.634765 0.9238,4.199219 1.6797,5.794922 h -11.16996 q -2.51953,-1.259765 -2.51953,-14.109375 -11.8418,15.201172 -26.11914,15.201172 -15.78906,0 -21.91992,-11.085937 -3.10743,-5.626954 -3.10743,-11.589844 z m 13.18555,-9.658203 q -2.09961,3.863281 -2.09961,10.078125 0,6.214843 4.36719,9.574218 4.36719,3.275391 9.6582,3.275391 5.375,0 9.40625,-1.595703 4.11524,-1.595703 7.47461,-4.115234 6.13086,-4.619141 9.1543,-8.230469 v -26.623043 q -15.95703,4.11523 -18.89649,5.03906 -14.78125,4.78711 -19.06445,12.597655 z m 69.53903,31.410156 V -191.08741 h 11.086 v 127.404301 z m 34.2657,-99.941411 v -12.59765 h 12.5976 v 12.59765 z m 0.7558,99.773442 v -88.603512 h 11.086 v 88.603512 z m 34.2656,0 v -88.603512 h 11.086 v 14.61328 q 11.5898,-11.58985 20.4082,-14.27735 4.1152,-1.25976 10.3301,-1.25976 6.2148,0 11.9257,4.03125 5.795,4.03125 7.6426,12.17773 12.1778,-12.17773 20.9121,-14.86523 4.1153,-1.34375 8.7344,-1.34375 9.6582,0 15.8731,5.8789 6.2148,5.87891 6.2148,17.63672 v 66.011722 h -11.0859 V -129.8628 q 0,-6.29882 -4.5352,-10.16211 -4.4512,-3.94726 -9.6582,-3.94726 -12.3457,0 -25.6992,14.5293 v 65.591792 h -11.086 V -129.8628 q 0,-6.29882 -4.5351,-10.16211 -4.4512,-3.94726 -9.6582,-3.94726 -12.4297,0 -25.7832,14.5293 v 65.591792 z"
|
||||
id="text14"
|
||||
transform="matrix(0.22608231,0,0,0.22608231,-618.79063,142.94722)"
|
||||
aria-label="thalim" /></g></svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
6
assets/logo-shapes/fillshape.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="142.78297mm" height="72.903015mm" viewBox="0 0 142.78297 72.903015" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(421.12105,-208.44674)">
|
||||
<path d="m 3501.8495,-1061.6392 -33.7838,4.271 v 39.53 l 91.6323,4.1605 2.3019,-44.5285 z" transform="matrix(1.520032,0,0,1.520032,-5692.6919,1822.1723)" fill="#ffffffee" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 424 B |
8
assets/logo-shapes/shape1.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="53.564522mm" height="112.37409mm" viewBox="0 0 53.564522 112.37409" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(448.53132,-700.6753)">
|
||||
<g transform="matrix(1.0501243,0,0,1.0501243,-4112.9365,1981.3086)">
|
||||
<path d="m 3540.0005,-1113.0001 h -50 v -93.6577 c 0,0 8.3872,-12.1296 25.4575,-12.3423 15.8179,-0.197 23.8721,11.7278 23.8721,11.7278 l 0.6704,94.2722" fill="none" stroke="#000000" stroke-width="1.428"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 525 B |
8
assets/logo-shapes/shape2.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="143.26076mm" height="20.26207mm" viewBox="0 0 143.26076 20.26207" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(377.25504,-763.4427)">
|
||||
<g transform="matrix(0.65619448,0,0,0.65619448,-376.82181,764.71516)">
|
||||
<ellipse cx="108.5" cy="13.5" rx="108.5" ry="13.5" transform="rotate(-3.0000011,108.50006,13.499027)" fill="none" stroke="#000000" stroke-width="2.286"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
8
assets/logo-shapes/shape3.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="159.16571mm" height="87.756729mm" viewBox="0 0 159.16571 87.756729" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(220.90156,-725.07326)">
|
||||
<g transform="matrix(1.520032,0,0,1.520032,-5476.6183,2342.946)">
|
||||
<path d="M 3562,-1058.2061 3559.353,-1007 3458,-1012.3241 3460.4907,-1064 3562,-1058.2061" fill="none" stroke="#000000" stroke-width="0.987"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 461 B |
8
assets/logo-shapes/shape4.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="74.08564mm" height="121.90051mm" viewBox="0 0 74.08564 121.90051" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(27.64622,-702.0151)">
|
||||
<g transform="matrix(1.1957859,0,0,1.1957859,-4339.0775,1997.654)">
|
||||
<path d="m 3612.9766,-982 -6.9771,-100.9999 53.4248,6.5227 7.5752,86.37332 -54.0229,8.10388" fill="none" stroke="#000000" stroke-width="1.254"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 461 B |
8
assets/logo-shapes/shape5.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="155.66518mm" height="87.785599mm" viewBox="0 0 155.66518 87.785599" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-78.011869,-718.14361)">
|
||||
<g transform="matrix(1.3327892,0,0,1.3327892,-4692.8443,2295.4339)">
|
||||
<path d="m 3580,-1123.2506 v -45.0836 l 116,-14.6658 -0.3564,65 -115.6436,-5.2506" fill="none" stroke="#000000" stroke-width="1.125"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
16
author.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
|
||||
$user = get_queried_object();
|
||||
if (!($user instanceof WP_User)) {
|
||||
global $wp_query;
|
||||
$wp_query->set_404();
|
||||
status_header(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$context['author'] = thalim_get_author_data($user->ID);
|
||||
$context['author_posts'] = thalim_get_author_posts_by_category($user->ID);
|
||||
$context['author_edit_link'] = current_user_can('edit_user', $user->ID) ? get_edit_user_link($user->ID) : '';
|
||||
|
||||
Timber::render('author.twig', $context);
|
||||
272
category.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$category = get_queried_object();
|
||||
$context['category'] = Timber::get_term($category);
|
||||
$context['cards'] = [];
|
||||
|
||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
||||
|
||||
// Parent category slug for color theming
|
||||
if ($category->parent) {
|
||||
$parent_cat = get_category($category->parent);
|
||||
$context['parent_slug'] = $parent_cat->slug;
|
||||
$context['active_rubrique'] = $parent_cat->term_id;
|
||||
} else {
|
||||
$context['parent_slug'] = $category->slug;
|
||||
$context['active_rubrique'] = $category->term_id;
|
||||
}
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
$context['active_date_to'] = $active_date_to;
|
||||
|
||||
// Build query param string to preserve across filter links
|
||||
$filter_query = http_build_query(array_filter([
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]));
|
||||
$context['filter_query'] = $filter_query;
|
||||
|
||||
// Build extra query args for axe/date filtering
|
||||
$extra_query_args = [];
|
||||
if ($active_axe) {
|
||||
$extra_query_args['meta_query'] = [[
|
||||
'key' => 'axes_thematiques',
|
||||
'value' => $active_axe,
|
||||
'type' => 'NUMERIC',
|
||||
]];
|
||||
}
|
||||
if ($active_date_from || $active_date_to) {
|
||||
$extra_query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to];
|
||||
}
|
||||
|
||||
// Build parent categories for filter bar (with links)
|
||||
$all_cats = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'exclude' => $excluded_ids,
|
||||
]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$link = get_category_link($cat->term_id);
|
||||
if ($filter_query) $link .= '?' . $filter_query;
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
|
||||
// Children of active rubrique for catégorie filter (with links)
|
||||
$active_rubrique_id = $context['active_rubrique'];
|
||||
$is_direct = (bool) get_query_var('thalim_direct_posts');
|
||||
$lang = thalim_current_language();
|
||||
|
||||
$filter_categories = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$link = get_category_link($cat->term_id);
|
||||
if ($filter_query) $link .= '?' . $filter_query;
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add "Autres" entry if the active rubrique has posts directly assigned to it
|
||||
if ($is_direct) {
|
||||
$has_direct_posts = true;
|
||||
} else {
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
]);
|
||||
$has_direct_posts = $direct_check->have_posts();
|
||||
}
|
||||
if ($has_direct_posts && !empty($filter_categories)) {
|
||||
$autres_link = trailingslashit(get_category_link($active_rubrique_id)) . 'autres/';
|
||||
if ($filter_query) $autres_link .= '?' . $filter_query;
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => $autres_link,
|
||||
];
|
||||
}
|
||||
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
$context['active_category_id'] = $is_direct ? 'autres' : $category->term_id;
|
||||
|
||||
// Axes thématiques for filter dropdown
|
||||
$axes_groups = thalim_get_axes_filter_groups();
|
||||
$current_axes = $axes_groups[0]['terms'] ?? [];
|
||||
$context['filter_axes'] = $current_axes;
|
||||
$context['axe_stay_on_page'] = true;
|
||||
|
||||
// Fetch posts for initial display
|
||||
$children = get_categories([
|
||||
'parent' => $category->term_id,
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => true,
|
||||
'exclude' => $excluded_ids,
|
||||
]);
|
||||
|
||||
// Ordre personnalisé des sous-catégories (term_id => position).
|
||||
// Les termes absents du tableau sont placés en dernier (position 999).
|
||||
$subcategory_order = [
|
||||
// Publications et productions (parent: 4)
|
||||
15 => 0, // Ouvrages
|
||||
16 => 1, // Articles
|
||||
65 => 2, // Revues et collections
|
||||
17 => 3, // Multimédia
|
||||
// Activités (parent: 3)
|
||||
11 => 0, // Séminaires
|
||||
10 => 1, // Colloques et journées d'études
|
||||
13 => 2, // Communications
|
||||
14 => 3, // Soutenances
|
||||
];
|
||||
usort($children, function($a, $b) use ($subcategory_order) {
|
||||
$pos_a = $subcategory_order[$a->term_id] ?? 999;
|
||||
$pos_b = $subcategory_order[$b->term_id] ?? 999;
|
||||
return $pos_a - $pos_b;
|
||||
});
|
||||
|
||||
$context['category_id'] = $category->term_id;
|
||||
$context['agenda_include_children'] = ( ! $is_direct && ! empty( $children ) ) ? 1 : 0;
|
||||
|
||||
// Helper: move pinned posts to the front (same logic as homepage diaporamas)
|
||||
$sort_with_pinned = function ( $posts ) {
|
||||
$today = date( 'Y-m-d' );
|
||||
$pinned = [];
|
||||
$normal = [];
|
||||
foreach ( $posts as $post ) {
|
||||
$epingle = get_post_meta( $post->ID, 'epingler_dans_la_categorie', true );
|
||||
$fin = get_post_meta( $post->ID, 'date_de_fin_depinglage', true );
|
||||
$active = $epingle == '1' && ( empty( $fin ) || $fin === '0000-00-00' || $fin >= $today );
|
||||
if ( $active ) { $pinned[] = $post; } else { $normal[] = $post; }
|
||||
}
|
||||
return array_merge( $pinned, $normal );
|
||||
};
|
||||
|
||||
if (!$is_direct && !empty($children)) {
|
||||
$context['is_parent'] = true;
|
||||
$context['subcategories'] = [];
|
||||
|
||||
foreach ($children as $child) {
|
||||
$query_args = array_merge([
|
||||
'post_type' => 'post',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$child->term_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
'posts_per_page' => 3,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args);
|
||||
$posts = $sort_with_pinned( Timber::get_posts($query_args) );
|
||||
$context['cards'] += thalim_get_cards_data($posts);
|
||||
$context['subcategories'][] = [
|
||||
'term' => Timber::get_term($child),
|
||||
'posts' => $posts,
|
||||
];
|
||||
}
|
||||
|
||||
// Fetch posts directly in the parent category (no child category assigned)
|
||||
if ($has_direct_posts) {
|
||||
$direct_query_args = array_merge([
|
||||
'post_type' => 'post',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$category->term_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
'posts_per_page' => 3,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args);
|
||||
$direct_posts = $sort_with_pinned( Timber::get_posts($direct_query_args) );
|
||||
if (!empty($direct_posts)) {
|
||||
$context['cards'] += thalim_get_cards_data($direct_posts);
|
||||
$context['direct_posts'] = $direct_posts;
|
||||
$autres_link = trailingslashit(get_category_link($category->term_id)) . 'autres/';
|
||||
if ($filter_query) $autres_link .= '?' . $filter_query;
|
||||
$context['autres_link'] = $autres_link;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$context['is_parent'] = false;
|
||||
$context['is_direct'] = $is_direct;
|
||||
$query_args = array_merge([
|
||||
'post_type' => 'post',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$category->term_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args);
|
||||
$posts = $sort_with_pinned( Timber::get_posts($query_args) );
|
||||
$context['cards'] = thalim_get_cards_data($posts);
|
||||
$context['posts'] = $posts;
|
||||
}
|
||||
|
||||
// View mode toggle (?view=agenda)
|
||||
$view_mode = ( isset( $_GET['view'] ) && $_GET['view'] === 'agenda' ) ? 'agenda' : 'grid';
|
||||
$context['view_mode'] = $view_mode;
|
||||
|
||||
// Toggle URL (used as href fallback on the button)
|
||||
$toggle_base = get_category_link( $category->term_id );
|
||||
$toggle_params = array_filter([
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]);
|
||||
if ( $view_mode === 'grid' ) {
|
||||
$toggle_params['view'] = 'agenda';
|
||||
}
|
||||
// When toggling back to grid we omit ?view entirely
|
||||
$context['agenda_toggle_url'] = add_query_arg( $toggle_params, $toggle_base );
|
||||
|
||||
// Custom Pods presentation fields
|
||||
$cat_lang = thalim_current_language();
|
||||
$pres_fr = get_term_meta( $category->term_id, 'presentation', true ) ?: '';
|
||||
$pres_en = get_term_meta( $category->term_id, 'presentation_en', true ) ?: '';
|
||||
$context['term_presentation'] = wpautop( ( $cat_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr );
|
||||
|
||||
Timber::render('category.twig', $context);
|
||||
5
composer.json
Executable file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"timber/timber": "^2.3"
|
||||
}
|
||||
}
|
||||
434
composer.lock
generated
Executable file
@@ -0,0 +1,434 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "977b0c3760f52f4b6ad4694d7ac704ba",
|
||||
"packages": [
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/contracts",
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:21:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-23T08:48:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "timber/timber",
|
||||
"version": "v2.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/timber/timber.git",
|
||||
"reference": "7a87ac27c0b9deedffe419388b63a0c95d8798ca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/timber/timber/zipball/7a87ac27c0b9deedffe419388b63a0c95d8798ca",
|
||||
"reference": "7a87ac27c0b9deedffe419388b63a0c95d8798ca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"twig/twig": "^3.19"
|
||||
},
|
||||
"require-dev": {
|
||||
"ergebnis/composer-normalize": "^2.28",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"php-stubs/wp-cli-stubs": "^2.0",
|
||||
"phpro/grumphp": "^2.0",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"rector/rector": "^2.0",
|
||||
"squizlabs/php_codesniffer": "^3.0",
|
||||
"symplify/easy-coding-standard": "^12",
|
||||
"szepeviktor/phpstan-wordpress": "^2",
|
||||
"twig/cache-extra": "^3.17",
|
||||
"wpackagist-plugin/advanced-custom-fields": "^6.0",
|
||||
"wpackagist-plugin/co-authors-plus": "^3.6",
|
||||
"yoast/wp-test-utils": "^1.2"
|
||||
},
|
||||
"suggest": {
|
||||
"php-coveralls/php-coveralls": "^2.0 for code coverage",
|
||||
"twig/cache-extra": "For using the cache tag in Twig"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Timber\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Erik van der Bas",
|
||||
"email": "erik@basedonline.nl",
|
||||
"homepage": "https://basedonline.nl"
|
||||
},
|
||||
{
|
||||
"name": "Lukas Gächter",
|
||||
"email": "lukas.gaechter@mind.ch",
|
||||
"homepage": "https://www.mind.ch"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Lemoine",
|
||||
"email": "nico@n5s.dev",
|
||||
"homepage": "https://n5s.dev"
|
||||
},
|
||||
{
|
||||
"name": "Jared Novack",
|
||||
"email": "jared@upstatement.com",
|
||||
"homepage": "https://upstatement.com"
|
||||
},
|
||||
{
|
||||
"name": "Timber Community",
|
||||
"homepage": "https://github.com/timber/timber"
|
||||
}
|
||||
],
|
||||
"description": "Create WordPress themes with beautiful OOP code and the Twig Template Engine",
|
||||
"homepage": "https://timber.upstatement.com",
|
||||
"keywords": [
|
||||
"templating",
|
||||
"themes",
|
||||
"timber",
|
||||
"twig",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://timber.github.io/docs/",
|
||||
"issues": "https://github.com/timber/timber/issues",
|
||||
"source": "https://github.com/timber/timber"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/timber",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://opencollective.com/timber",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-24T14:07:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.22.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "4509984193026de413baf4ba80f68590a7f2c51d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d",
|
||||
"reference": "4509984193026de413baf4ba80f68590a7f2c51d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1.0",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-mbstring": "^1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Resources/core.php",
|
||||
"src/Resources/debug.php",
|
||||
"src/Resources/escaper.php",
|
||||
"src/Resources/string_loader.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Twig\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com",
|
||||
"homepage": "http://fabien.potencier.org",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Twig Team",
|
||||
"role": "Contributors"
|
||||
},
|
||||
{
|
||||
"name": "Armin Ronacher",
|
||||
"email": "armin.ronacher@active-4.com",
|
||||
"role": "Project Founder"
|
||||
}
|
||||
],
|
||||
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||
"homepage": "https://twig.symfony.com",
|
||||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.22.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-29T15:56:47+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
923
css/admin.css
Normal file
@@ -0,0 +1,923 @@
|
||||
/* Fade in transition for post edit and profile pages */
|
||||
body.post-php #wpbody,
|
||||
body.post-new-php #wpbody,
|
||||
body.profile-php #wpbody,
|
||||
body.user-edit-php #wpbody,
|
||||
body.user-new-php #wpbody {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
body.post-php.admin-mods-ready #wpbody,
|
||||
body.post-new-php.admin-mods-ready #wpbody,
|
||||
body.profile-php.admin-mods-ready #wpbody,
|
||||
body.user-edit-php.admin-mods-ready #wpbody,
|
||||
body.user-new-php.admin-mods-ready #wpbody {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Post edit pages: hide elements */
|
||||
body.post-php #advanced-sortables,
|
||||
body.post-new-php #advanced-sortables,
|
||||
body.post-php #preview-action,
|
||||
body.post-new-php #preview-action,
|
||||
body.post-php #visibility,
|
||||
body.post-new-php #visibility,
|
||||
body.post-php #add_pod_button,
|
||||
body.post-new-php #add_pod_button,
|
||||
body.post-php #wp-content-editor-tools > .wp-editor-tabs,
|
||||
body.post-new-php #wp-content-editor-tools > .wp-editor-tabs,
|
||||
body.post-php #mceu_0,
|
||||
body.post-new-php #mceu_0,
|
||||
body.post-php #mceu_6,
|
||||
body.post-new-php #mceu_6,
|
||||
body.post-php #mceu_7,
|
||||
body.post-new-php #mceu_7,
|
||||
body.post-php #mceu_8,
|
||||
body.post-new-php #mceu_8,
|
||||
body.post-php #mceu_10,
|
||||
body.post-new-php #mceu_10,
|
||||
body.post-php #mceu_11,
|
||||
body.post-new-php #mceu_11,
|
||||
body.post-php #postexcerpt,
|
||||
body.post-new-php #postexcerpt,
|
||||
body.post-php #postcustom,
|
||||
body.post-new-php #postcustom,
|
||||
body.post-php #slugdiv,
|
||||
body.post-new-php #slugdiv,
|
||||
body.post-php #authordiv,
|
||||
body.post-new-php #authordiv,
|
||||
body.post-php #screen-meta-links,
|
||||
body.post-new-php #screen-meta-links,
|
||||
body.post-php .handle-order-higher,
|
||||
body.post-new-php .handle-order-higher,
|
||||
body.post-php .handle-order-lower,
|
||||
body.post-new-php .handle-order-lower,
|
||||
body.post-php #pods-meta-seances-seminaire .pods-field-wrapper > div:first-of-type,
|
||||
body.post-new-php #pods-meta-seances-seminaire .pods-field-wrapper > div:first-of-type,
|
||||
body.post-php #tagsdiv-post_tag,
|
||||
body.post-new-php #tagsdiv-post_tag,
|
||||
body.post-php #wp-content-media-buttons,
|
||||
body.post-new-php #wp-content-media-buttons,
|
||||
body.post-php #members-cp,
|
||||
body.post-new-php #members-cp {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.post-php .postbox-header .hndle,
|
||||
body.post-new-php .postbox-header .hndle {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
overflow: visible;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
body.post-php #pods-form-ui-pods-meta-categorie option[value="31"],
|
||||
body.post-new-php #pods-form-ui-pods-meta-categorie option[value="31"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.post-php #pods-form-ui-pods-meta-categorie option:disabled,
|
||||
body.post-new-php #pods-form-ui-pods-meta-categorie option:disabled {
|
||||
color: #a7aaad;
|
||||
}
|
||||
|
||||
/* Documents joints: margin-top to separate it from the body editor section */
|
||||
body.post-php #pods-meta-documents-joints,
|
||||
body.post-new-php #pods-meta-documents-joints {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Pods iframe modal: hide elements */
|
||||
body.pods-modal-window #ml_box,
|
||||
body.pods-modal-window #pods-meta-liens-externes,
|
||||
body.pods-modal-window #pods-meta-documents-joints {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Axes thématiques checkbox group headers */
|
||||
body.post-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label,
|
||||
body.post-new-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #646970;
|
||||
background-color: #f0f0f1;
|
||||
padding: 0.4rem 0.5rem;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
border-top: 1px solid #dcdcde;
|
||||
margin-top: 0.4rem;
|
||||
width: 100%;
|
||||
}
|
||||
body.post-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label:first-child,
|
||||
body.post-new-php .pods-form-ui-row-name-axes-thematiques li.axes-group-label:first-child {
|
||||
border-top: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Remove WP's JS-injected padding-top on the editor wrap (compensation for sticky toolbar) */
|
||||
body.post-php #wp-content-editor-tools,
|
||||
body.post-new-php #wp-content-editor-tools {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
body.post-php .pods-dfv-container-wysiwyg,
|
||||
body.post-new-php .pods-dfv-container-wysiwyg {
|
||||
max-width: unset !important;
|
||||
}
|
||||
|
||||
body.post-php .pods-tinymce-reinit,
|
||||
body.post-new-php .pods-tinymce-reinit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Keep statusbar in layout so TinyMCE's reposition() can read its getBoundingClientRect().
|
||||
display:none would return top=0, making mceStatusbarTop=windowHeight → editorHeight<0 → toolbar always hidden. */
|
||||
body.post-php .pods-dfv-container-wysiwyg .mce-statusbar,
|
||||
body.post-new-php .pods-dfv-container-wysiwyg .mce-statusbar {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body.post-php #pods-form-ui-pods-meta-body-en_ifr,
|
||||
body.post-new-php #pods-form-ui-pods-meta-body-en_ifr {
|
||||
min-height: 334px !important;
|
||||
}
|
||||
|
||||
body.post-php #content_ifr,
|
||||
body.post-new-php #content_ifr,
|
||||
body.post-php #pods-form-ui-pods-meta-body-en_ifr,
|
||||
body.post-new-php #pods-form-ui-pods-meta-body-en_ifr {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Pods body-en: hide Visuel/Code tabs and same toolbar buttons removed from native editor
|
||||
(paragraph format, alignment ×3, "Lire la suite", toggle-advanced-toolbar).
|
||||
Uses aria-label selectors — more stable than MCE numeric IDs used for the native editor. */
|
||||
body.post-php #pods-meta-body-en .wp-editor-tabs,
|
||||
body.post-new-php #pods-meta-body-en .wp-editor-tabs,
|
||||
body.post-php #pods-meta-body-en .mce-listbox.mce-fixed-width,
|
||||
body.post-new-php #pods-meta-body-en .mce-listbox.mce-fixed-width,
|
||||
body.post-php #pods-meta-body-en [aria-label*="Aligner"],
|
||||
body.post-new-php #pods-meta-body-en [aria-label*="Aligner"],
|
||||
body.post-php #pods-meta-body-en [aria-label*="Centrer"],
|
||||
body.post-new-php #pods-meta-body-en [aria-label*="Centrer"],
|
||||
body.post-php #pods-meta-body-en [aria-label*="Lire la suite"],
|
||||
body.post-new-php #pods-meta-body-en [aria-label*="Lire la suite"],
|
||||
body.post-php #pods-meta-body-en [aria-label*="Permuter la barre"],
|
||||
body.post-new-php #pods-meta-body-en [aria-label*="Permuter la barre"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Référence bibliographique: hide tabs, media buttons, status bar,
|
||||
and all toolbar buttons except Italic (only formatting allowed). */
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .wp-editor-tabs,
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .wp-media-buttons,
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-statusbar {
|
||||
display: none !important;
|
||||
}
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn {
|
||||
display: none !important;
|
||||
}
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn:has(.mce-i-italic),
|
||||
#wp-pods-form-ui-pods-meta-reference-bibliographique-wrap .mce-toolbar .mce-btn:has(.mce-i-bold) {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* Profile / user-edit: fixed submit button */
|
||||
body.profile-php #your-profile p.submit::before,
|
||||
body.user-edit-php #your-profile p.submit::before {
|
||||
content: "Pensez à sauvegarder les modifications";
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #646970;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
body.profile-php #your-profile p.submit,
|
||||
body.user-edit-php #your-profile p.submit {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
background: #f0f0f1;
|
||||
border-top: 1px solid #c3c4c7;
|
||||
border-left: 1px solid #c3c4c7;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Profile / user-edit / user-new / taxonomy term pages: TinyMCE editor mods (mirrors post-edit treatment) */
|
||||
body.profile-php .wp-editor-tabs,
|
||||
body.user-edit-php .wp-editor-tabs,
|
||||
body.user-new-php .wp-editor-tabs,
|
||||
body.edit-tags-php .wp-editor-tabs,
|
||||
body.term-php .wp-editor-tabs,
|
||||
body.profile-php .mce-listbox.mce-fixed-width,
|
||||
body.user-edit-php .mce-listbox.mce-fixed-width,
|
||||
body.user-new-php .mce-listbox.mce-fixed-width,
|
||||
body.edit-tags-php .mce-listbox.mce-fixed-width,
|
||||
body.term-php .mce-listbox.mce-fixed-width,
|
||||
body.profile-php [aria-label*="Aligner"],
|
||||
body.user-edit-php [aria-label*="Aligner"],
|
||||
body.user-new-php [aria-label*="Aligner"],
|
||||
body.edit-tags-php [aria-label*="Aligner"],
|
||||
body.term-php [aria-label*="Aligner"],
|
||||
body.profile-php [aria-label*="Centrer"],
|
||||
body.user-edit-php [aria-label*="Centrer"],
|
||||
body.user-new-php [aria-label*="Centrer"],
|
||||
body.edit-tags-php [aria-label*="Centrer"],
|
||||
body.term-php [aria-label*="Centrer"],
|
||||
body.profile-php [aria-label*="Lire la suite"],
|
||||
body.user-edit-php [aria-label*="Lire la suite"],
|
||||
body.user-new-php [aria-label*="Lire la suite"],
|
||||
body.edit-tags-php [aria-label*="Lire la suite"],
|
||||
body.term-php [aria-label*="Lire la suite"],
|
||||
body.profile-php [aria-label*="Permuter la barre"],
|
||||
body.user-edit-php [aria-label*="Permuter la barre"],
|
||||
body.user-new-php [aria-label*="Permuter la barre"],
|
||||
body.edit-tags-php [aria-label*="Permuter la barre"],
|
||||
body.term-php [aria-label*="Permuter la barre"],
|
||||
body.profile-php .wp-media-buttons,
|
||||
body.user-edit-php .wp-media-buttons,
|
||||
body.user-new-php .wp-media-buttons,
|
||||
body.edit-tags-php .wp-media-buttons,
|
||||
body.term-php .wp-media-buttons,
|
||||
body.profile-php .pods-tinymce-reinit,
|
||||
body.user-edit-php .pods-tinymce-reinit,
|
||||
body.user-new-php .pods-tinymce-reinit,
|
||||
body.edit-tags-php .pods-tinymce-reinit,
|
||||
body.term-php .pods-tinymce-reinit {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.profile-php .wp-editor-tools,
|
||||
body.user-edit-php .wp-editor-tools,
|
||||
body.user-new-php .wp-editor-tools,
|
||||
body.edit-tags-php .wp-editor-tools,
|
||||
body.term-php .wp-editor-tools {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
body.profile-php .pods-dfv-container-wysiwyg,
|
||||
body.user-edit-php .pods-dfv-container-wysiwyg,
|
||||
body.user-new-php .pods-dfv-container-wysiwyg,
|
||||
body.edit-tags-php .pods-dfv-container-wysiwyg,
|
||||
body.term-php .pods-dfv-container-wysiwyg {
|
||||
max-width: unset !important;
|
||||
}
|
||||
|
||||
body.profile-php .wp-editor-container iframe,
|
||||
body.user-edit-php .wp-editor-container iframe,
|
||||
body.user-new-php .wp-editor-container iframe,
|
||||
body.edit-tags-php .wp-editor-container iframe,
|
||||
body.term-php .wp-editor-container iframe {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Fade-in transition for tab panels (opacity is set to 0 by JS before reveal, then cleared) */
|
||||
body.post-php #postdivrich,
|
||||
body.post-new-php #postdivrich,
|
||||
body.post-php #pods-meta-body-en,
|
||||
body.post-new-php #pods-meta-body-en {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
/* Body FR/EN language tabs */
|
||||
body.post-php .body-lang-tabs,
|
||||
body.post-new-php .body-lang-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
body.post-php .body-lang-tab,
|
||||
body.post-new-php .body-lang-tab {
|
||||
padding: 5px 14px;
|
||||
background: #f0f0f1;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-bottom-color: #c3c4c7;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #50575e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
body.post-php .body-lang-tab.is-active,
|
||||
body.post-new-php .body-lang-tab.is-active {
|
||||
background: #fff;
|
||||
border-bottom-color: #fff;
|
||||
color: #1d2327;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Hide postbox headers for both body editors — tabs replace them */
|
||||
body.post-php #postdivrich .postbox-header,
|
||||
body.post-new-php #postdivrich .postbox-header,
|
||||
body.post-php #pods-meta-body-en .postbox-header,
|
||||
body.post-new-php #pods-meta-body-en .postbox-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Strip Pods table layout from body-en — make it full width like native editor */
|
||||
body.post-php #pods-meta-body-en .inside,
|
||||
body.post-new-php #pods-meta-body-en .inside {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-body-en .form-table,
|
||||
body.post-new-php #pods-meta-body-en .form-table,
|
||||
body.post-php #pods-meta-body-en .form-table tbody,
|
||||
body.post-new-php #pods-meta-body-en .form-table tbody,
|
||||
body.post-php #pods-meta-body-en .form-table tr,
|
||||
body.post-new-php #pods-meta-body-en .form-table tr,
|
||||
body.post-php #pods-meta-body-en .form-table td,
|
||||
body.post-new-php #pods-meta-body-en .form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-body-en .form-table th,
|
||||
body.post-new-php #pods-meta-body-en .form-table th {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-body-en .pods-submittable-fields,
|
||||
body.post-new-php #pods-meta-body-en .pods-submittable-fields,
|
||||
body.post-php #pods-meta-body-en .pods-dfv-container,
|
||||
body.post-new-php #pods-meta-body-en .pods-dfv-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Allow TinyMCE floating panels (link popover, inline toolbar) to escape the Pods
|
||||
container chain — any overflow:hidden in Pods DFV elements would clip them */
|
||||
body.post-php #pods-meta-body-en .inside,
|
||||
body.post-new-php #pods-meta-body-en .inside,
|
||||
body.post-php #pods-meta-body-en .pods-submittable-fields,
|
||||
body.post-new-php #pods-meta-body-en .pods-submittable-fields,
|
||||
body.post-php #pods-meta-body-en .pods-dfv-field,
|
||||
body.post-new-php #pods-meta-body-en .pods-dfv-field,
|
||||
body.post-php #pods-meta-body-en .pods-field-option,
|
||||
body.post-new-php #pods-meta-body-en .pods-field-option,
|
||||
body.post-php #pods-meta-body-en .pods-field-option__field,
|
||||
body.post-new-php #pods-meta-body-en .pods-field-option__field,
|
||||
body.post-php #pods-meta-body-en .pods-dfv-container-wysiwyg,
|
||||
body.post-new-php #pods-meta-body-en .pods-dfv-container-wysiwyg,
|
||||
body.post-php #pods-meta-body-en .pods-field-wrapper,
|
||||
body.post-new-php #pods-meta-body-en .pods-field-wrapper,
|
||||
body.post-php #pods-meta-body-en .wp-editor-wrap,
|
||||
body.post-new-php #pods-meta-body-en .wp-editor-wrap,
|
||||
body.post-php #pods-meta-body-en .wp-editor-container,
|
||||
body.post-new-php #pods-meta-body-en .wp-editor-container {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Info popovers */
|
||||
.thalim-info-wrapper {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.thalim-info-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
color: #646970;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.thalim-info-btn:hover,
|
||||
.thalim-info-btn:focus {
|
||||
color: #2271b1;
|
||||
background: #f0f6fc;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thalim-info-popover {
|
||||
display: none;
|
||||
position: fixed;
|
||||
transform: translateX(-50%);
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
padding: 10px 12px;
|
||||
min-width: 220px;
|
||||
max-width: 320px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
color: #3c434a;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.thalim-info-popover.is-open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thalim-info-popover p {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.thalim-info-popover p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Translate popovers */
|
||||
.thalim-translate-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
color: #2a7a4f;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.thalim-translate-btn:hover,
|
||||
.thalim-translate-btn:focus {
|
||||
color: #1d5c3a;
|
||||
background: #edfaf3;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.thalim-translate-popover {
|
||||
border-color: #b2dfcc;
|
||||
}
|
||||
|
||||
/* Required field indicator */
|
||||
abbr.required::after {
|
||||
content: " Champ nécessaire";
|
||||
font-size: 0.72rem;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
color: #646970;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Profile pages: hide sections */
|
||||
body.user-new-php #simple-local-avatar-section,
|
||||
body.profile-php .user-admin-color-wrap,
|
||||
body.user-edit-php .user-admin-color-wrap,
|
||||
body.user-new-php .user-admin-color-wrap,
|
||||
body.profile-php .user-admin-bar-front-wrap,
|
||||
body.user-edit-php .user-admin-bar-front-wrap,
|
||||
body.user-new-php .user-admin-bar-front-wrap,
|
||||
body.profile-php .user-nickname-wrap,
|
||||
body.user-edit-php .user-nickname-wrap,
|
||||
body.user-new-php .user-nickname-wrap,
|
||||
body.profile-php .user-display-name-wrap,
|
||||
body.user-edit-php .user-display-name-wrap,
|
||||
body.user-new-php .user-display-name-wrap,
|
||||
body.profile-php .user-profile-picture,
|
||||
body.user-edit-php .user-profile-picture,
|
||||
body.user-new-php .user-profile-picture,
|
||||
body.profile-php .ratings-row,
|
||||
body.user-edit-php .ratings-row,
|
||||
body.user-new-php .ratings-row,
|
||||
body.profile-php .application-passwords,
|
||||
body.user-edit-php .application-passwords,
|
||||
body.user-new-php .application-passwords,
|
||||
body.profile-php .user-comment-shortcuts-wrap,
|
||||
body.user-edit-php .user-comment-shortcuts-wrap,
|
||||
body.user-new-php .user-comment-shortcuts-wrap,
|
||||
body.profile-php .form-table:has(.user-description-wrap),
|
||||
body.user-edit-php .form-table:has(.user-description-wrap),
|
||||
body.user-new-php .form-table:has(.user-description-wrap) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.profile-php .form-table,
|
||||
body.user-edit-php .form-table,
|
||||
body.user-new-php .form-table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody,
|
||||
body.user-edit-php .form-table tbody,
|
||||
body.user-new-php .form-table tbody {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody tr,
|
||||
body.user-edit-php .form-table tbody tr,
|
||||
body.user-new-php .form-table tbody tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody tr th,
|
||||
body.user-edit-php .form-table tbody tr th,
|
||||
body.user-new-php .form-table tbody tr th,
|
||||
body.profile-php .form-table tbody tr td,
|
||||
body.user-edit-php .form-table tbody tr td,
|
||||
body.user-new-php .form-table tbody tr td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 0 4px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* Side-by-side section pairs */
|
||||
body.profile-php .profile-section-row,
|
||||
body.user-edit-php .profile-section-row,
|
||||
body.user-new-php .profile-section-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
body.profile-php .profile-section-col,
|
||||
body.user-edit-php .profile-section-col,
|
||||
body.user-new-php .profile-section-col {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Tables inside a col are already at 50% width — keep fields single-column */
|
||||
body.profile-php .profile-section-col .form-table tbody,
|
||||
body.user-edit-php .profile-section-col .form-table tbody,
|
||||
body.user-new-php .profile-section-col .form-table tbody {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
body.profile-php .form-table tbody,
|
||||
body.user-edit-php .form-table tbody,
|
||||
body.user-new-php .form-table tbody {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0 15px;
|
||||
&:has(.pods-form-ui-row-name-role-1) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody .user-user-login-wrap,
|
||||
body.user-edit-php .form-table tbody .user-user-login-wrap,
|
||||
body.user-new-php .form-table tbody .user-user-login-wrap,
|
||||
body.profile-php .form-table tbody .user-first-name-wrap,
|
||||
body.user-edit-php .form-table tbody .user-first-name-wrap,
|
||||
body.user-new-php .form-table tbody .user-first-name-wrap,
|
||||
body.profile-php .form-table tbody .user-email-wrap,
|
||||
body.user-edit-php .form-table tbody .user-email-wrap,
|
||||
body.user-new-php .form-table tbody .user-email-wrap,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-1,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-1,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-1,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-2,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-2,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-2,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-lien-externe-3,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-lien-externe-3,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-lien-externe-3,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-role-1,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-1,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-role-1,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-role-2,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-2,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-role-2,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-role-3,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-role-3,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-role-3,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-affiliation,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-affiliation,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-affiliation,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-titre-de-these,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-de-these,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-de-these,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-directeur-de-these-thalim,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-biographie,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-biographie,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-biographie {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody .user-role-wrap,
|
||||
body.user-edit-php .form-table tbody .user-role-wrap,
|
||||
body.user-new-php .form-table tbody .user-role-wrap,
|
||||
body.profile-php .form-table tbody .user-last-name-wrap,
|
||||
body.user-edit-php .form-table tbody .user-last-name-wrap,
|
||||
body.user-new-php .form-table tbody .user-last-name-wrap,
|
||||
body.profile-php .form-table tbody .user-url-wrap,
|
||||
body.user-edit-php .form-table tbody .user-url-wrap,
|
||||
body.user-new-php .form-table tbody .user-url-wrap,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-1,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-2,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-titre-du-lien-3,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-1,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-2,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-complement-de-role-3,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-affiliation-autre,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-affiliation-autre,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-affiliation-autre,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-autre-directeur-de-these,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-date-de-soutenance,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-recherches-en-cours-en,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-autres-domaines-de-recherches-en,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-resume-de-la-these-en,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-biographie-en,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-biographie-en,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-biographie-en {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-1,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-2,
|
||||
body.profile-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3,
|
||||
body.user-edit-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3,
|
||||
body.user-new-php .form-table tbody .pods-form-ui-row-name-affichage-du-statut-3 {
|
||||
grid-column: 3;
|
||||
}
|
||||
|
||||
body.profile-php .user-user-login-wrap span.description,
|
||||
body.user-edit-php .user-user-login-wrap span.description,
|
||||
body.user-new-php .user-user-login-wrap span.description {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Profile FR/EN language overlay tabs */
|
||||
body.profile-php .profile-lang-tabs,
|
||||
body.user-edit-php .profile-lang-tabs,
|
||||
body.user-new-php .profile-lang-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
body.profile-php .profile-lang-tab,
|
||||
body.user-edit-php .profile-lang-tab,
|
||||
body.user-new-php .profile-lang-tab {
|
||||
padding: 5px 14px;
|
||||
background: #f0f0f1;
|
||||
border: 1px solid #c3c4c7;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #50575e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
body.profile-php .profile-lang-tab.is-active,
|
||||
body.user-edit-php .profile-lang-tab.is-active,
|
||||
body.user-new-php .profile-lang-tab.is-active {
|
||||
background: #fff;
|
||||
border-bottom-color: #fff;
|
||||
color: #1d2327;
|
||||
font-weight: 600;
|
||||
}
|
||||
body.profile-php .form-table tbody .profile-lang-tabs-row td,
|
||||
body.user-edit-php .form-table tbody .profile-lang-tabs-row td,
|
||||
body.user-new-php .form-table tbody .profile-lang-tabs-row td {
|
||||
padding: 0;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
|
||||
/* Membres: 2-column explicit grid
|
||||
Row 1: Fonction (col 1 only)
|
||||
Row 2: Membres | Autrepersonnes
|
||||
Row 3: full-width separator (injected <tr class="membres-grid-separator">)
|
||||
Row 4: Autre fonction (col 1 only)
|
||||
Row 5: Autre membres | Autre autrepersonnes
|
||||
*/
|
||||
body.post-php #pods-meta-membres .form-table,
|
||||
body.post-new-php #pods-meta-membres .form-table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .form-table tbody,
|
||||
body.post-new-php #pods-meta-membres .form-table tbody {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .form-table tr,
|
||||
body.post-new-php #pods-meta-membres .form-table tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .form-table th,
|
||||
body.post-new-php #pods-meta-membres .form-table th,
|
||||
body.post-php #pods-meta-membres .form-table td,
|
||||
body.post-new-php #pods-meta-membres .form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 0 4px;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .form-table input[type="text"],
|
||||
body.post-new-php #pods-meta-membres .form-table input[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Row 1: Fonction — left column only */
|
||||
body.post-php #pods-meta-membres [class*="pods-form-ui-row-name-fonction-"],
|
||||
body.post-new-php #pods-meta-membres [class*="pods-form-ui-row-name-fonction-"] {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
/* Row 2: Membres (left) | Autrepersonnes (right) */
|
||||
body.post-php #pods-meta-membres .pods-form-ui-row-name-membres,
|
||||
body.post-new-php #pods-meta-membres .pods-form-ui-row-name-membres {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .pods-form-ui-row-name-autrepersonnes,
|
||||
body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autrepersonnes {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
/* Row 3: full-width separator */
|
||||
body.post-php #pods-meta-membres .membres-grid-separator,
|
||||
body.post-new-php #pods-meta-membres .membres-grid-separator {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
border-top: 1px solid #dcdcde;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Row 4: Autre fonction — left column only */
|
||||
body.post-php #pods-meta-membres [class*="pods-form-ui-row-name-autre-fonction-"],
|
||||
body.post-new-php #pods-meta-membres [class*="pods-form-ui-row-name-autre-fonction-"] {
|
||||
grid-column: 1;
|
||||
grid-row: 4;
|
||||
}
|
||||
|
||||
/* Row 5: Autre membres (left) | Autre autrepersonnes (right) */
|
||||
body.post-php #pods-meta-membres .pods-form-ui-row-name-autre-membres,
|
||||
body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autre-membres {
|
||||
grid-column: 1;
|
||||
grid-row: 5;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-membres .pods-form-ui-row-name-autre-autrepersonnes,
|
||||
body.post-new-php #pods-meta-membres .pods-form-ui-row-name-autre-autrepersonnes {
|
||||
grid-column: 2;
|
||||
grid-row: 5;
|
||||
}
|
||||
|
||||
/* Liens externes: two-column layout — URL field and title field side by side per link */
|
||||
body.post-php #pods-meta-liens-externes .form-table,
|
||||
body.post-new-php #pods-meta-liens-externes .form-table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-liens-externes .form-table tbody,
|
||||
body.post-new-php #pods-meta-liens-externes .form-table tbody {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-liens-externes .form-table tr,
|
||||
body.post-new-php #pods-meta-liens-externes .form-table tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-liens-externes .form-table th,
|
||||
body.post-new-php #pods-meta-liens-externes .form-table th,
|
||||
body.post-php #pods-meta-liens-externes .form-table td,
|
||||
body.post-new-php #pods-meta-liens-externes .form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 0 4px;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-liens-externes .form-table input[type="text"],
|
||||
body.post-new-php #pods-meta-liens-externes .form-table input[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Visual separator between link pairs 2 and 3 */
|
||||
body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-2,
|
||||
body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-2,
|
||||
body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-2,
|
||||
body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-2,
|
||||
body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-3,
|
||||
body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-lien-externe-3,
|
||||
body.post-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-3,
|
||||
body.post-new-php #pods-meta-liens-externes .pods-form-ui-row-name-titre-du-lien-externe-3 {
|
||||
border-top: 1px solid #dcdcde;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* Dates: date de début and date de fin side by side */
|
||||
body.post-php #pods-meta-dates .form-table,
|
||||
body.post-new-php #pods-meta-dates .form-table {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-dates .form-table tbody,
|
||||
body.post-new-php #pods-meta-dates .form-table tbody {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 16px;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-dates .form-table tr,
|
||||
body.post-new-php #pods-meta-dates .form-table tr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
body.post-php #pods-meta-dates .form-table th,
|
||||
body.post-new-php #pods-meta-dates .form-table th,
|
||||
body.post-php #pods-meta-dates .form-table td,
|
||||
body.post-new-php #pods-meta-dates .form-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 0 4px;
|
||||
}
|
||||
|
||||
/* Taxonomy pages — hide unused description field */
|
||||
.edit-tags-php .term-description-wrap,
|
||||
.term-php .term-description-wrap {
|
||||
display: none;
|
||||
}
|
||||
2901
css/style.css
Executable file
1375
functions.php
Executable file
48
inc/admin-users-filter.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Add a "Statut" filter dropdown to /wp-admin/users.php.
|
||||
* Filters by the custom "role" taxonomy stored in user meta keys role_1/role_2/role_3.
|
||||
*/
|
||||
|
||||
add_action( 'restrict_manage_users', function() {
|
||||
$terms = get_terms( [ 'taxonomy' => 'role', 'hide_empty' => true, 'orderby' => 'name' ] );
|
||||
if ( is_wp_error( $terms ) || empty( $terms ) ) return;
|
||||
|
||||
$selected = isset( $_GET['thalim_statut'] ) ? intval( $_GET['thalim_statut'] ) : 0;
|
||||
?>
|
||||
<select id="thalim_statut_filter">
|
||||
<option value=""><?php esc_html_e( 'Tous les statuts', 'thalim' ); ?></option>
|
||||
<?php foreach ( $terms as $term ) : ?>
|
||||
<option value="<?php echo esc_attr( $term->term_id ); ?>"<?php selected( $selected, $term->term_id ); ?>>
|
||||
<?php echo esc_html( $term->name ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="button" class="button" id="thalim_statut_go"><?php esc_html_e( 'Filtrer', 'thalim' ); ?></button>
|
||||
<script>
|
||||
document.getElementById('thalim_statut_go').addEventListener('click', function() {
|
||||
var v = document.getElementById('thalim_statut_filter').value;
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.delete('paged');
|
||||
if (v) url.searchParams.set('thalim_statut', v);
|
||||
else url.searchParams.delete('thalim_statut');
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
} );
|
||||
|
||||
add_action( 'pre_get_users', function( WP_User_Query $query ) {
|
||||
if ( ! is_admin() ) return;
|
||||
|
||||
$term_id = isset( $_GET['thalim_statut'] ) ? intval( $_GET['thalim_statut'] ) : 0;
|
||||
if ( ! $term_id ) return;
|
||||
|
||||
$query->set( 'meta_query', [
|
||||
'relation' => 'OR',
|
||||
[ 'key' => 'role_1', 'value' => $term_id, 'compare' => '=' ],
|
||||
[ 'key' => 'role_2', 'value' => $term_id, 'compare' => '=' ],
|
||||
[ 'key' => 'role_3', 'value' => $term_id, 'compare' => '=' ],
|
||||
] );
|
||||
} );
|
||||
250
inc/author-helpers.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Resolve all profile data for a member/author into a display-ready array.
|
||||
*/
|
||||
function thalim_get_author_data($user_id) {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) return [];
|
||||
|
||||
$lang = thalim_current_language();
|
||||
|
||||
// --- Avatar (Simple Local Avatar with Gravatar fallback) ---
|
||||
$avatar_url = thalim_get_user_avatar_url( $user_id );
|
||||
|
||||
// --- Role (taxonomy 'role') ---
|
||||
$role_id = get_user_meta($user_id, 'role_1', true);
|
||||
$role_label = '';
|
||||
if ($role_id) {
|
||||
$role_term = get_term(intval($role_id), 'role');
|
||||
if ($role_term && !is_wp_error($role_term)) {
|
||||
$override = thalim_bilingual(get_user_meta($user_id, 'affichage_du_statut_1', true) ?: '', $lang);
|
||||
$role_label = $override ?: $role_term->name;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Direction title (read from "Le laboratoire" page) ---
|
||||
$labo_page = get_page_by_path('le-laboratoire');
|
||||
$directeur_id = $labo_page ? intval(get_post_meta($labo_page->ID, 'directeur', true)) : 0;
|
||||
$adjoint_id = $labo_page ? intval(get_post_meta($labo_page->ID, 'directeur_adjoint', true)) : 0;
|
||||
if ($user_id === $directeur_id) {
|
||||
$role_label = 'Directeur' . ($role_label ? ', ' . $role_label : '');
|
||||
} elseif ($user_id === $adjoint_id) {
|
||||
$role_label = 'Directeur adjoint' . ($role_label ? ', ' . $role_label : '');
|
||||
}
|
||||
|
||||
// --- Domaines de recherches (multiple usermeta rows with post_tag IDs) ---
|
||||
$domaine_ids = get_user_meta($user_id, 'domaines_de_recherches', false);
|
||||
$domaines_tags = [];
|
||||
foreach ($domaine_ids as $tag_id) {
|
||||
if (!$tag_id) continue;
|
||||
$term = get_term(intval($tag_id), 'post_tag');
|
||||
if ($term && !is_wp_error($term)) {
|
||||
$link = get_term_link($term);
|
||||
if (!is_wp_error($link)) {
|
||||
$domaines_tags[] = ['name' => thalim_bilingual($term->name, $lang), 'url' => $link];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Axes thématiques (multiple usermeta rows) ---
|
||||
$axe_ids = get_user_meta($user_id, 'axes_thematiques', false);
|
||||
$axes = [];
|
||||
foreach ($axe_ids as $axe_id) {
|
||||
$term = get_term(intval($axe_id), 'axe_thematique');
|
||||
if ($term && !is_wp_error($term)) {
|
||||
$axes[] = [
|
||||
'name' => thalim_bilingual($term->name, $lang),
|
||||
'url' => get_term_link($term),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- External links (up to 4) ---
|
||||
$liens_externes = [];
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$url = get_user_meta($user_id, 'lien_externe_' . $i, true);
|
||||
if ($url) {
|
||||
$titre = thalim_bilingual(get_user_meta($user_id, 'titre_du_lien_' . $i, true) ?: '', $lang);
|
||||
if (!$titre) {
|
||||
$host = parse_url($url, PHP_URL_HOST) ?: $url;
|
||||
$parts = explode('.', $host);
|
||||
$titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host;
|
||||
}
|
||||
$liens_externes[] = ['url' => $url, 'titre' => $titre];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Documents (multiple usermeta rows with attachment IDs) ---
|
||||
$doc_ids = get_user_meta($user_id, 'documents', false);
|
||||
$documents = [];
|
||||
foreach ($doc_ids as $doc_id) {
|
||||
$url = wp_get_attachment_url(intval($doc_id));
|
||||
if ($url) {
|
||||
$documents[] = [
|
||||
'url' => $url,
|
||||
'title' => get_the_title($doc_id) ?: basename(get_attached_file($doc_id)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Thesis director (THALIM member — stored as user ID) ---
|
||||
$directeur_id = get_user_meta($user_id, 'directeur_de_these_thalim', true);
|
||||
$directeur_thalim = null;
|
||||
if ($directeur_id) {
|
||||
$dir_user = get_userdata(intval($directeur_id));
|
||||
if ($dir_user) {
|
||||
$directeur_thalim = [
|
||||
'name' => $dir_user->display_name,
|
||||
'url' => get_author_posts_url(intval($directeur_id)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Email visibility ---
|
||||
$is_ancien = isset($role_term) && $role_term && $role_term->slug === 'anciens-membres';
|
||||
$show_email = !$is_ancien && get_user_meta($user_id, 'afficher_ladresse_mail_sur_le_profil', true);
|
||||
|
||||
return [
|
||||
'display_name' => $user->display_name,
|
||||
'avatar_url' => $avatar_url,
|
||||
'role_label' => $role_label,
|
||||
'role_complement' => thalim_bilingual(get_user_meta($user_id, 'complement_de_role_1', true) ?: '', $lang),
|
||||
'affiliation' => (function() use ($user_id, $lang) {
|
||||
$v = get_user_meta($user_id, 'affiliation', true) ?: '';
|
||||
return strtolower($v) === 'autre'
|
||||
? thalim_bilingual(get_user_meta($user_id, 'affiliation_autre', true) ?: '', $lang)
|
||||
: $v;
|
||||
})(),
|
||||
'bio' => wpautop( make_clickable( get_user_meta($user_id, 'biographie', true) ?: '' ) ),
|
||||
'bio_en' => wpautop( make_clickable( get_user_meta($user_id, 'biographie_en', true) ?: '' ) ),
|
||||
'domaines_tags' => $domaines_tags,
|
||||
'domaines' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches', true) ?: '' ) ),
|
||||
'domaines_en' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches_en', true) ?: '' ) ),
|
||||
'recherches' => wpautop( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ),
|
||||
'recherches_en' => wpautop( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ),
|
||||
'axes' => $axes,
|
||||
'titre_these' => thalim_bilingual(get_user_meta($user_id, 'titre_de_these', true) ?: '', $lang),
|
||||
'date_soutenance' => get_user_meta($user_id, 'date_de_soutenance', true) ?: '',
|
||||
'directeur_thalim'=> $directeur_thalim,
|
||||
'autre_directeur' => get_user_meta($user_id, 'autre_directeur_de_these', true) ?: '',
|
||||
'resume_these' => wpautop( get_user_meta($user_id, 'resume_de_la_these', true) ?: '' ),
|
||||
'resume_these_en' => wpautop( get_user_meta($user_id, 'resume_de_la_these_en', true) ?: '' ),
|
||||
'email' => $show_email ? $user->user_email : '',
|
||||
'liens_externes' => $liens_externes,
|
||||
'documents' => $documents,
|
||||
'hal_publications_url' => (function() use ($user_id) {
|
||||
$hal_id = get_user_meta($user_id, 'identifiant_hal', true) ?: '';
|
||||
return $hal_id
|
||||
? 'https://hal.science/search/index/?qa[authIdHal_s][]=' . rawurlencode($hal_id) . '&sort=publicationDate_tdate+desc'
|
||||
: '';
|
||||
})(),
|
||||
'user_since' => date_i18n('d/m/Y', strtotime($user->user_registered)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all posts linked to a member and group them by primary category.
|
||||
* Returns an array sorted by post count (descending).
|
||||
*/
|
||||
function thalim_get_author_posts_by_category($user_id) {
|
||||
$excluded_cats = [12, 31]; // séances de séminaire, etc.
|
||||
$lang = thalim_current_language();
|
||||
|
||||
$posts = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => 'membres',
|
||||
'value' => $user_id,
|
||||
],
|
||||
[
|
||||
'key' => 'autre_membres',
|
||||
'value' => $user_id,
|
||||
],
|
||||
],
|
||||
'thalim_event_date_order' => true,
|
||||
'lang' => '',
|
||||
]);
|
||||
|
||||
$groups = [];
|
||||
|
||||
foreach ($posts as $post) {
|
||||
$categories = wp_get_post_categories($post->ID, ['fields' => 'all']);
|
||||
$primary_cat = null;
|
||||
|
||||
foreach ($categories as $cat) {
|
||||
if (in_array($cat->term_id, $excluded_cats)) continue;
|
||||
$primary_cat = $cat;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$primary_cat) continue;
|
||||
|
||||
$cat_id = $primary_cat->term_id;
|
||||
if (!isset($groups[$cat_id])) {
|
||||
// A top-level category with subcategories → these posts are "Autres"
|
||||
$is_autres = false;
|
||||
if ($primary_cat->parent == 0) {
|
||||
$subcats = get_categories(['parent' => $cat_id, 'hide_empty' => true, 'exclude' => $excluded_cats]);
|
||||
$is_autres = !empty($subcats);
|
||||
}
|
||||
$groups[$cat_id] = [
|
||||
'cat_id' => $cat_id,
|
||||
'cat_name' => $is_autres
|
||||
? ($lang === 'en' ? 'Other ' : 'Autres ') . thalim_cat_name($primary_cat, $lang)
|
||||
: thalim_cat_name($primary_cat, $lang),
|
||||
'cat_url' => $is_autres
|
||||
? trailingslashit(get_category_link($cat_id)) . 'autres/'
|
||||
: get_category_link($cat_id),
|
||||
'posts' => [],
|
||||
];
|
||||
}
|
||||
$groups[$cat_id]['posts'][] = $post;
|
||||
}
|
||||
|
||||
// Séances de séminaire — dedicated group. Posts in cat 12 where the member
|
||||
// is listed in `membres`/`autre_membres`. Cards use the parent séminaire
|
||||
// permalink with a #seance-{ID} hash (see thalim_get_card_data).
|
||||
$seances = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => -1,
|
||||
'category__in' => [12],
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[ 'key' => 'membres', 'value' => $user_id ],
|
||||
[ 'key' => 'autre_membres', 'value' => $user_id ],
|
||||
],
|
||||
'thalim_event_date_order' => true,
|
||||
'lang' => '',
|
||||
]);
|
||||
if (count($seances) > 0) {
|
||||
$seance_cat = get_term(12, 'category');
|
||||
$groups[12] = [
|
||||
'cat_id' => 12,
|
||||
'cat_name' => $seance_cat && !is_wp_error($seance_cat)
|
||||
? thalim_cat_name($seance_cat, $lang)
|
||||
: ($lang === 'en' ? 'Seminar sessions' : 'Séances de séminaire'),
|
||||
'cat_url' => get_category_link(12),
|
||||
'posts' => $seances,
|
||||
];
|
||||
}
|
||||
|
||||
// Resolve card data and sort by count descending
|
||||
foreach ($groups as &$group) {
|
||||
$group['cards'] = thalim_get_cards_data($group['posts']);
|
||||
}
|
||||
unset($group);
|
||||
|
||||
uasort($groups, function($a, $b) {
|
||||
$oa = (int) get_term_meta($a['cat_id'], 'ordre_profil', true) ?: 999;
|
||||
$ob = (int) get_term_meta($b['cat_id'], 'ordre_profil', true) ?: 999;
|
||||
return $oa !== $ob
|
||||
? $oa <=> $ob
|
||||
: count($b['posts']) <=> count($a['posts']);
|
||||
});
|
||||
|
||||
return array_values($groups);
|
||||
}
|
||||
244
inc/membres-helpers.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Normalise a free-text research-domain field for use in a data-* attribute:
|
||||
* converts <br> variants to \n and strips any remaining HTML tags.
|
||||
*/
|
||||
function thalim_sanitize_domaines( $raw ) {
|
||||
// Normalise all <br> variants (including \r before them) to a newline
|
||||
$text = preg_replace( '/\r?<br\s*\/?>/i', "\n", $raw );
|
||||
// Strip any remaining HTML tags
|
||||
$text = strip_tags( $text );
|
||||
// Clean up excess blank lines / whitespace
|
||||
$text = preg_replace( "/\n{3,}/", "\n\n", trim( $text ) );
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the display data array for a single user.
|
||||
*/
|
||||
function thalim_build_membre_data( $user ) {
|
||||
$lang = thalim_current_language();
|
||||
$status_parts = [];
|
||||
$role_names = [];
|
||||
for ( $n = 1; $n <= 3; $n++ ) {
|
||||
$role_id = get_user_meta( $user->ID, 'role_' . $n, true );
|
||||
if ( ! $role_id ) continue;
|
||||
$term = get_term( intval( $role_id ), 'role' );
|
||||
if ( ! $term || is_wp_error( $term ) ) continue;
|
||||
$role_names[] = $term->name;
|
||||
$override = thalim_bilingual( get_user_meta( $user->ID, 'affichage_du_statut_' . $n, true ) ?: '', $lang );
|
||||
if ( $override ) {
|
||||
$status_parts[] = $override;
|
||||
} else {
|
||||
$entry = $term->name;
|
||||
$complement = thalim_bilingual( get_user_meta( $user->ID, 'complement_de_role_' . $n, true ) ?: '', $lang );
|
||||
if ( $complement ) $entry .= ' ' . $complement;
|
||||
$status_parts[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar (Simple Local Avatar with Gravatar fallback)
|
||||
$avatar_url = thalim_get_user_avatar_url( $user->ID );
|
||||
|
||||
// Domaines de recherches: multiple usermeta rows, each is a post_tag term ID
|
||||
$domaine_ids = get_user_meta( $user->ID, 'domaines_de_recherches', false );
|
||||
$domaines = [];
|
||||
foreach ( $domaine_ids as $term_id ) {
|
||||
$term = get_term( intval( $term_id ), 'post_tag' );
|
||||
if ( $term && ! is_wp_error( $term ) ) {
|
||||
$domaines[] = $term->name;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'display_name' => $user->display_name,
|
||||
'sort_key' => thalim_get_sort_key( $user->ID, $user->display_name ),
|
||||
'url' => get_author_posts_url( $user->ID ),
|
||||
'status' => implode( ', ', $status_parts ),
|
||||
'affiliation' => (function() use ($user) {
|
||||
$v = get_user_meta( $user->ID, 'affiliation', true ) ?: '';
|
||||
return strtolower( $v ) === 'autre'
|
||||
? ( get_user_meta( $user->ID, 'affiliation_autre', true ) ?: '' )
|
||||
: $v;
|
||||
})(),
|
||||
'role_names' => $role_names,
|
||||
'avatar_url' => $avatar_url,
|
||||
'domaines' => $domaines,
|
||||
'autres_domaines' => thalim_sanitize_domaines( get_user_meta( $user->ID, 'autres_domaines_de_recherches', true ) ?: '' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all role taxonomy terms that are in use, sorted by name.
|
||||
*/
|
||||
function thalim_get_role_terms() {
|
||||
$terms = get_terms( [ 'taxonomy' => 'role', 'hide_empty' => true, 'orderby' => 'name' ] );
|
||||
if ( is_wp_error( $terms ) ) return [];
|
||||
return array_values( array_map(
|
||||
fn( $t ) => [ 'id' => $t->term_id, 'name' => $t->name ],
|
||||
array_filter( $terms, fn( $t ) => ! in_array( mb_strtolower( $t->name, 'UTF-8' ), [ 'archive', 'à ranger' ], true ) )
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all role term_ids set for a user (role_1, role_2, role_3).
|
||||
*/
|
||||
function thalim_get_user_role_ids( $user_id ) {
|
||||
$ids = [];
|
||||
for ( $n = 1; $n <= 3; $n++ ) {
|
||||
$role_id = get_user_meta( $user_id, 'role_' . $n, true );
|
||||
if ( $role_id ) $ids[] = intval( $role_id );
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort key: first word of last_name user meta (handles compound last names like
|
||||
* "Duclaux de l'Estoile" → "Duclaux"). Falls back to last word of display_name.
|
||||
*/
|
||||
function thalim_get_sort_key( $user_id, $display_name ) {
|
||||
$last = get_user_meta( $user_id, 'last_name', true );
|
||||
if ( $last ) {
|
||||
$parts = explode( ' ', trim( $last ) );
|
||||
return $parts[0];
|
||||
}
|
||||
$parts = explode( ' ', trim( $display_name ) );
|
||||
return end( $parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all member groups for the /membres page.
|
||||
* Each group: ['title' => string, 'members' => array of member data arrays].
|
||||
* Empty groups are omitted.
|
||||
*/
|
||||
function thalim_get_membres_groups() {
|
||||
// Fetch all users that have role_1 set
|
||||
$users = get_users( [
|
||||
'meta_key' => 'role_1',
|
||||
'number' => -1,
|
||||
] );
|
||||
|
||||
// Direction: read directeur and directeur_adjoint from "Le laboratoire" page
|
||||
$labo_page = get_page_by_path( 'le-laboratoire' );
|
||||
$directeur_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur', true ) ) : 0;
|
||||
$adjoint_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur_adjoint', true ) ) : 0;
|
||||
|
||||
$direction_users = [];
|
||||
foreach ( [ $directeur_id, $adjoint_id ] as $uid ) {
|
||||
if ( $uid ) {
|
||||
$u = get_userdata( $uid );
|
||||
if ( $u ) $direction_users[] = $u;
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-build member data for all relevant users (cache by ID)
|
||||
$member_cache = [];
|
||||
$all_users = array_merge( $users, $direction_users );
|
||||
foreach ( $all_users as $user ) {
|
||||
if ( ! isset( $member_cache[ $user->ID ] ) ) {
|
||||
$member_cache[ $user->ID ] = thalim_build_membre_data( $user );
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend direction title to status for director / deputy director
|
||||
if ( $directeur_id && isset( $member_cache[ $directeur_id ] ) ) {
|
||||
$existing = $member_cache[ $directeur_id ]['status'];
|
||||
$member_cache[ $directeur_id ]['status'] = 'Directeur' . ( $existing ? ', ' . $existing : '' );
|
||||
}
|
||||
if ( $adjoint_id && isset( $member_cache[ $adjoint_id ] ) ) {
|
||||
$existing = $member_cache[ $adjoint_id ]['status'];
|
||||
$member_cache[ $adjoint_id ]['status'] = 'Directeur adjoint' . ( $existing ? ', ' . $existing : '' );
|
||||
}
|
||||
|
||||
// Build a slug→ID map for the 'role' taxonomy so group definitions survive
|
||||
// database migrations where auto-incremented term IDs change.
|
||||
$slug_to_id = [];
|
||||
foreach ( get_terms( [ 'taxonomy' => 'role', 'hide_empty' => false ] ) as $term ) {
|
||||
$slug_to_id[ $term->slug ] = $term->term_id;
|
||||
}
|
||||
$by_slug = fn( ...$slugs ) => array_values(
|
||||
array_filter( array_map( fn( $s ) => $slug_to_id[ $s ] ?? null, $slugs ) )
|
||||
);
|
||||
|
||||
// Group definitions (title => role slugs that qualify a user for membership)
|
||||
$group_definitions = [
|
||||
'Chercheuses et chercheurs CNRS' => $by_slug( 'directeur-de-recherche', 'charge-de-recherche' ),
|
||||
'Enseignantes-chercheuses et enseignants-chercheurs' => $by_slug( 'professeur', 'maitre-de-conferences' ),
|
||||
'Doctorantes et doctorants' => $by_slug( 'doctorant' ),
|
||||
'Docteures et docteurs' => $by_slug( 'docteur' ),
|
||||
'Postdoctorantes et postdoctorants' => $by_slug( 'postdoctorant' ),
|
||||
'Personnel contractuel' => $by_slug( 'personnel-contractuel' ),
|
||||
"Personnel d'accompagnement à la recherche" => $by_slug( 'personnel-technique' ),
|
||||
'Membres associées et membres associés' => $by_slug( 'membre-associe' ),
|
||||
'Anciennes et anciens membres' => $by_slug( 'anciens-membres' ),
|
||||
];
|
||||
|
||||
$groups = [];
|
||||
|
||||
// Direction group first: directeur before directeur adjoint
|
||||
$direction_members = [];
|
||||
if ( $directeur_id && isset( $member_cache[ $directeur_id ] ) ) {
|
||||
$direction_members[] = $member_cache[ $directeur_id ];
|
||||
}
|
||||
if ( $adjoint_id && isset( $member_cache[ $adjoint_id ] ) ) {
|
||||
$direction_members[] = $member_cache[ $adjoint_id ];
|
||||
}
|
||||
if ( ! empty( $direction_members ) ) {
|
||||
$groups[] = [
|
||||
'title' => 'Direction',
|
||||
'members' => $direction_members,
|
||||
'fixed_order' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// Role-based groups (a user appears in every group that matches any of their roles)
|
||||
foreach ( $group_definitions as $title => $term_ids ) {
|
||||
$group_members = [];
|
||||
foreach ( $users as $user ) {
|
||||
$user_role_ids = thalim_get_user_role_ids( $user->ID );
|
||||
if ( array_intersect( $term_ids, $user_role_ids ) ) {
|
||||
$group_members[] = $member_cache[ $user->ID ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $group_members ) ) continue;
|
||||
|
||||
// Sort alphabetically by last name, accent- and case-insensitive (fr locale)
|
||||
static $collator = null;
|
||||
if ( $collator === null ) {
|
||||
$collator = class_exists( 'Collator' ) ? new Collator( 'fr_FR' ) : false;
|
||||
if ( $collator ) $collator->setStrength( Collator::PRIMARY );
|
||||
}
|
||||
usort( $group_members, function( $a, $b ) use ( $collator ) {
|
||||
$la = $a['sort_key'];
|
||||
$lb = $b['sort_key'];
|
||||
if ( $collator ) return $collator->compare( $la, $lb );
|
||||
return strcmp( mb_strtolower( $la, 'UTF-8' ), mb_strtolower( $lb, 'UTF-8' ) );
|
||||
} );
|
||||
|
||||
// In "Personnel d'accompagnement", place "Gestion et pilotage" first
|
||||
$fixed = false;
|
||||
if ( $title === "Personnel d'accompagnement à la recherche" ) {
|
||||
$priority = [];
|
||||
$rest = [];
|
||||
foreach ( $group_members as $m ) {
|
||||
if ( stripos( $m['status'], 'Gestion et pilotage' ) !== false ) {
|
||||
$priority[] = $m;
|
||||
} else {
|
||||
$rest[] = $m;
|
||||
}
|
||||
}
|
||||
$group_members = array_merge( $priority, $rest );
|
||||
$fixed = true;
|
||||
}
|
||||
|
||||
$groups[] = [
|
||||
'title' => $title,
|
||||
'members' => $group_members,
|
||||
'fixed_order' => $fixed,
|
||||
];
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
94
inc/pods-conditional-required.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Désactiver le required des champs masqués par la logique conditionnelle de Pods.
|
||||
*
|
||||
* Pods ne prend pas en compte sa propre logique conditionnelle lors de la
|
||||
* validation serveur des champs requis. Ce filtre corrige ce comportement
|
||||
* pour le pod "post".
|
||||
*/
|
||||
add_filter( 'pods_api_pre_save_pod_item_post', 'thalim_skip_required_for_hidden_fields', 10, 3 );
|
||||
|
||||
function thalim_skip_required_for_hidden_fields( $pieces, $is_new_item, $id ) {
|
||||
if ( empty( $pieces['fields'] ) ) {
|
||||
return $pieces;
|
||||
}
|
||||
|
||||
// Récupérer les valeurs actuelles des champs pour évaluer la logique conditionnelle
|
||||
$field_values = [];
|
||||
foreach ( $pieces['fields'] as $name => $field ) {
|
||||
$field_values[ $name ] = isset( $field['value'] ) ? $field['value'] : '';
|
||||
}
|
||||
// Pour un post existant, si un champ n'a pas été soumis explicitement via
|
||||
// pods_meta_* (ex. Pods DFV React en éditeur classique), remplir sa valeur
|
||||
// depuis la BDD. Cela corrige à la fois :
|
||||
// - la validation du champ lui-même (pieces['fields']['value'])
|
||||
// - l'évaluation de la logique conditionnelle des autres champs ($field_values)
|
||||
if ( ! $is_new_item && $id ) {
|
||||
foreach ( $pieces['fields'] as $name => $field ) {
|
||||
// Skip pick/file/avatar fields: their value format in $pieces is complex
|
||||
// and get_post_meta returns a raw value that corrupts Pods' pick processing.
|
||||
// These fields are always submitted via POST by Pods DFV React.
|
||||
$field_type = pods_v( 'type', $field, '' );
|
||||
if ( in_array( $field_type, [ 'pick', 'file', 'avatar' ], true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$current_val = isset( $field['value'] ) ? $field['value'] : '';
|
||||
if ( ( '' === $current_val || null === $current_val ) && ! isset( $_POST[ 'pods_meta_' . $name ] ) ) {
|
||||
$db_val = get_post_meta( (int) $id, $name, true );
|
||||
if ( '' !== $db_val && null !== $db_val ) {
|
||||
$pieces['fields'][ $name ]['value'] = $db_val;
|
||||
$field_values[ $name ] = $db_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $pieces['fields'] as $field_name => $field_data ) {
|
||||
// Ne traiter que les champs required
|
||||
$required = is_object( $field_data ) && method_exists( $field_data, 'get_field_object' )
|
||||
? (int) $field_data->get_field_object()->get_arg( 'required', 0 )
|
||||
: (int) pods_v( 'required', $field_data, 0 );
|
||||
|
||||
if ( 1 !== $required ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Récupérer la logique conditionnelle
|
||||
$conditional_logic = null;
|
||||
|
||||
if ( is_object( $field_data ) && method_exists( $field_data, 'get_field_object' ) ) {
|
||||
$conditional_logic = $field_data->get_field_object()->get_conditional_logic();
|
||||
}
|
||||
|
||||
// Fallback : charger le champ via l'API Pods
|
||||
if ( ! $conditional_logic ) {
|
||||
$field_obj = pods_api()->load_field( [
|
||||
'name' => $field_name,
|
||||
'pod' => 'post',
|
||||
] );
|
||||
|
||||
if ( $field_obj && method_exists( $field_obj, 'get_conditional_logic' ) ) {
|
||||
$conditional_logic = $field_obj->get_conditional_logic();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $conditional_logic ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Évaluer si le champ est visible avec les valeurs actuelles
|
||||
if ( ! $conditional_logic->is_visible( $field_values ) ) {
|
||||
// Le champ est masqué → désactiver le required
|
||||
if ( is_object( $field_data ) && method_exists( $field_data, 'get_field_object' ) ) {
|
||||
$field_data->get_field_object()->set_arg( 'required', 0 );
|
||||
}
|
||||
$pieces['fields'][ $field_name ]['required'] = 0;
|
||||
if ( isset( $pieces['fields'][ $field_name ]['options'] ) ) {
|
||||
$pieces['fields'][ $field_name ]['options']['required'] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $pieces;
|
||||
}
|
||||
195
inc/pods-save-error-handler.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Intercepte les erreurs de validation Pods lors du save d'un post admin :
|
||||
* - Empêche wp_die() (qui publiait quand même le post sans les champs Pods)
|
||||
* - Annule le changement de statut si le post n'était pas encore publié
|
||||
* - Redirige vers la page d'édition avec les champs restaurés via transient
|
||||
*/
|
||||
|
||||
// Capture l'ID du post en cours de sauvegarde (avant Pods, qui peut le créer pour les nouveaux posts)
|
||||
add_action( 'save_post', function ( $post_id ) {
|
||||
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
$GLOBALS['thalim_saving_post_id'] = $post_id;
|
||||
}, 1 );
|
||||
|
||||
// Intercepte wp_die() de Pods pendant un save admin
|
||||
add_filter( 'pods_error_die', function ( $die, $error ) {
|
||||
if ( ! is_admin() || ! isset( $_REQUEST['action'] ) || $_REQUEST['action'] !== 'editpost' ) {
|
||||
return $die;
|
||||
}
|
||||
|
||||
$post_id = $GLOBALS['thalim_saving_post_id'] ?? intval( $_POST['post_ID'] ?? 0 );
|
||||
if ( ! $post_id ) {
|
||||
return $die;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Collecter les valeurs Pods soumises
|
||||
$restore = [];
|
||||
foreach ( $_POST as $key => $val ) {
|
||||
if ( str_starts_with( $key, 'pods_meta_' ) ) {
|
||||
$restore[ $key ] = is_array( $val ) ? array_map( 'wp_unslash', $val ) : wp_unslash( $val );
|
||||
}
|
||||
}
|
||||
$error_text = is_wp_error( $error ) ? $error->get_error_message() : (string) $error;
|
||||
$restore['_msg'] = wp_strip_all_tags( $error_text );
|
||||
$restore['_title'] = sanitize_text_field( wp_unslash( $_POST['post_title'] ?? '' ) );
|
||||
|
||||
set_transient( 'thalim_pods_restore_' . $post_id . '_' . $user_id, $restore, 10 * MINUTE_IN_SECONDS );
|
||||
|
||||
$GLOBALS['thalim_pods_error_post_id'] = $post_id;
|
||||
|
||||
return false; // empêche wp_die()
|
||||
}, 10, 2 );
|
||||
|
||||
// Après le save : rediriger vers la page d'édition + annuler le statut si besoin
|
||||
add_filter( 'redirect_post_location', function ( $location ) {
|
||||
$post_id = $GLOBALS['thalim_pods_error_post_id'] ?? 0;
|
||||
if ( ! $post_id ) {
|
||||
return $location;
|
||||
}
|
||||
|
||||
// Annuler le changement de statut vers publish si le post n'était pas encore publié
|
||||
$original = isset( $_POST['original_post_status'] ) ? sanitize_key( $_POST['original_post_status'] ) : '';
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if (
|
||||
$post &&
|
||||
in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) &&
|
||||
! in_array( $original, [ 'publish', 'future', 'pending' ], true )
|
||||
) {
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
$wpdb->posts,
|
||||
[ 'post_status' => $original ?: 'draft' ],
|
||||
[ 'ID' => $post_id ],
|
||||
[ '%s' ],
|
||||
[ '%d' ]
|
||||
);
|
||||
clean_post_cache( $post_id );
|
||||
}
|
||||
|
||||
return admin_url( 'post.php?post=' . $post_id . '&action=edit' );
|
||||
}, 10 );
|
||||
|
||||
// Sur la page d'édition (GET) : lire le transient une seule fois → global → supprimer
|
||||
add_action( 'current_screen', function ( $screen ) {
|
||||
if ( $screen->base !== 'post' ) {
|
||||
return;
|
||||
}
|
||||
$post_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0;
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$key = 'thalim_pods_restore_' . $post_id . '_' . $user_id;
|
||||
$data = get_transient( $key );
|
||||
if ( ! $data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$GLOBALS['thalim_pods_restore'] = [
|
||||
'post_id' => $post_id,
|
||||
'data' => $data,
|
||||
];
|
||||
delete_transient( $key );
|
||||
} );
|
||||
|
||||
// Injecter les valeurs dans get_post_meta → Pods DFV les embarque dans son JSON React
|
||||
add_filter( 'get_post_metadata', function ( $value, $object_id, $meta_key, $single ) {
|
||||
$restore = $GLOBALS['thalim_pods_restore'] ?? null;
|
||||
if ( ! $restore || $restore['post_id'] !== (int) $object_id ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$pods_key = 'pods_meta_' . $meta_key;
|
||||
if ( ! isset( $restore['data'][ $pods_key ] ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$val = $restore['data'][ $pods_key ];
|
||||
return $single ? $val : [ $val ];
|
||||
}, 10, 4 );
|
||||
|
||||
// Restauration JS : titre + champs Pods select/pick via PodsDFV (même pattern que les modales)
|
||||
add_action( 'admin_footer', function () {
|
||||
$restore = $GLOBALS['thalim_pods_restore'] ?? null;
|
||||
if ( ! $restore ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen || $screen->base !== 'post' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = intval( $restore['post_id'] );
|
||||
$title = $restore['data']['_title'] ?? '';
|
||||
|
||||
// Construire le map fieldName => value pour les champs Pods
|
||||
$fields = [];
|
||||
foreach ( $restore['data'] as $key => $val ) {
|
||||
if ( str_starts_with( $key, 'pods_meta_' ) ) {
|
||||
$fields[ substr( $key, 10 ) ] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<script>
|
||||
(function ($) {
|
||||
var postId = <?php echo $post_id; ?>;
|
||||
var fields = <?php echo wp_json_encode( $fields ); ?>;
|
||||
var title = <?php echo wp_json_encode( $title ); ?>;
|
||||
|
||||
function doRestore() {
|
||||
// Titre WordPress (non géré par get_post_metadata)
|
||||
var $titleInput = $('#title');
|
||||
if ($titleInput.length && !$titleInput.val() && title) {
|
||||
$titleInput.val(title).trigger('input');
|
||||
}
|
||||
|
||||
// Champs Pods : DOM direct + PodsDFV pour les selects/picks
|
||||
Object.keys(fields).forEach(function (fieldName) {
|
||||
var value = fields[fieldName];
|
||||
var $el = $('[name="pods_meta_' + fieldName + '"]');
|
||||
if ($el.length) {
|
||||
$el.val(value).trigger('change');
|
||||
}
|
||||
if (window.PodsDFV && postId) {
|
||||
try {
|
||||
window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0);
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on('load', function () {
|
||||
setTimeout(doRestore, 300);
|
||||
});
|
||||
}(jQuery));
|
||||
</script>
|
||||
<?php
|
||||
} );
|
||||
|
||||
// Afficher le message d'erreur en admin notice
|
||||
add_action( 'admin_notices', function () {
|
||||
$restore = $GLOBALS['thalim_pods_restore'] ?? null;
|
||||
if ( ! $restore ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen || $screen->base !== 'post' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$msg = esc_html( $restore['data']['_msg'] ?? '' );
|
||||
if ( $msg ) {
|
||||
echo '<div class="notice notice-error is-dismissible"><p>' . $msg . '</p></div>';
|
||||
}
|
||||
echo '<div class="notice notice-info is-dismissible"><p>Votre contenu a été restauré. Vérifiez les champs obligatoires avant de republier.</p></div>';
|
||||
} );
|
||||
172
inc/post-card-helpers.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Build card display data for a single post.
|
||||
* Resolves Pods relationship fields (stored as multiple meta rows) into
|
||||
* ready-to-display values so Twig templates don't need to call PHP functions.
|
||||
*
|
||||
* Returns an associative array with resolved card fields.
|
||||
*/
|
||||
function thalim_get_card_data($post_id) {
|
||||
$data = [
|
||||
'card_image' => null,
|
||||
'card_membres' => [],
|
||||
'card_axes' => [],
|
||||
'card_etiquettes' => [],
|
||||
'parent_slug' => '',
|
||||
'card_category_name' => '',
|
||||
'card_category_url' => '',
|
||||
'card_type' => '',
|
||||
'card_event_date' => '',
|
||||
'card_event_date_iso' => '',
|
||||
'card_link' => '',
|
||||
];
|
||||
|
||||
// Event date — date_de_debut (events), fallback to datetime (communications)
|
||||
// Used for display instead of post_date when set
|
||||
foreach (['date_de_debut', 'datetime'] as $date_key) {
|
||||
$event_raw = get_post_meta($post_id, $date_key, true) ?: '';
|
||||
if ($event_raw && !str_starts_with($event_raw, '0000-00-00')) {
|
||||
$ts = strtotime($event_raw);
|
||||
if ($ts) {
|
||||
$data['card_event_date'] = date_i18n('d/m/Y', $ts);
|
||||
$data['card_event_date_iso'] = date('Y-m-d', $ts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve top-level parent category slug for color theming and direct category name for display
|
||||
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
||||
$excluded_ids = [12, 31];
|
||||
$is_seance = false;
|
||||
foreach ($categories as $cat) {
|
||||
if ($cat->term_id === 12) { $is_seance = true; }
|
||||
}
|
||||
foreach ($categories as $cat) {
|
||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||
if (!empty($ancestor_ids)) {
|
||||
$root = get_category(end($ancestor_ids));
|
||||
} else {
|
||||
$root = $cat;
|
||||
}
|
||||
$data['parent_slug'] = $root->slug;
|
||||
$data['card_category_name'] = thalim_cat_name($cat);
|
||||
$data['card_category_url'] = get_category_link($cat->term_id);
|
||||
break;
|
||||
}
|
||||
|
||||
// Séances de séminaire: link to parent séminaire with hash, derive color from parent's categories
|
||||
if ($is_seance) {
|
||||
// Always show the category label for séances even though cat 12 is excluded from color resolution
|
||||
if (!$data['card_category_name']) {
|
||||
$seance_cat = get_category(12);
|
||||
if ($seance_cat) {
|
||||
$data['card_category_name'] = thalim_cat_name($seance_cat);
|
||||
$data['card_category_url'] = get_category_link(12);
|
||||
}
|
||||
}
|
||||
global $wpdb;
|
||||
$parent_id = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
||||
AND p.post_status = 'publish'
|
||||
LIMIT 1",
|
||||
(string) $post_id
|
||||
));
|
||||
if ($parent_id) {
|
||||
$data['card_link'] = get_permalink((int) $parent_id) . '#seance-' . $post_id;
|
||||
// Derive color from parent séminaire's categories if not already set
|
||||
if (!$data['parent_slug']) {
|
||||
foreach (wp_get_post_categories((int) $parent_id, ['fields' => 'all']) as $cat) {
|
||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||
$root = !empty($ancestor_ids) ? get_category(end($ancestor_ids)) : $cat;
|
||||
$data['parent_slug'] = $root->slug;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Type label (first non-empty type_* field)
|
||||
$type_fields = [
|
||||
'type_colloque_journee_d_etude',
|
||||
'type_soutenance',
|
||||
'type_evenement_culturel',
|
||||
'type_media',
|
||||
'type_captation',
|
||||
'type_revue_collection',
|
||||
'type_autre',
|
||||
];
|
||||
foreach ($type_fields as $field) {
|
||||
$val = get_post_meta($post_id, $field, true);
|
||||
if ($val) {
|
||||
$data['card_type'] = $val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// First image from documents_joints
|
||||
$doc_ids = get_post_meta($post_id, 'documents_joints', false);
|
||||
foreach ($doc_ids as $doc_id) {
|
||||
$mime = get_post_mime_type($doc_id);
|
||||
if ($mime && str_starts_with($mime, 'image/')) {
|
||||
$src = wp_get_attachment_image_url($doc_id, 'medium');
|
||||
if ($src) {
|
||||
$data['card_image'] = $src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Members (user IDs → display names + profile URLs)
|
||||
// Falls back to autre_membres if membres is empty
|
||||
$membre_ids = get_post_meta($post_id, 'membres', false);
|
||||
if (empty($membre_ids)) {
|
||||
$membre_ids = get_post_meta($post_id, 'autre_membres', false);
|
||||
}
|
||||
foreach ($membre_ids as $uid) {
|
||||
$user = get_userdata($uid);
|
||||
if ($user) {
|
||||
$data['card_membres'][] = [
|
||||
'name' => $user->display_name,
|
||||
'url' => get_author_posts_url($uid),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Axes thématiques (post IDs → titles)
|
||||
$axe_ids = get_post_meta($post_id, 'axes_thematiques', false);
|
||||
foreach ($axe_ids as $axe_id) {
|
||||
$axe = get_post($axe_id);
|
||||
if ($axe) {
|
||||
$data['card_axes'][] = $axe->post_title;
|
||||
}
|
||||
}
|
||||
|
||||
// Etiquettes (post IDs → titles)
|
||||
$tag_ids = get_post_meta($post_id, 'etiquettes', false);
|
||||
foreach ($tag_ids as $tag_id) {
|
||||
$tag_post = get_post($tag_id);
|
||||
if ($tag_post) {
|
||||
$data['card_etiquettes'][] = $tag_post->post_title;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build card data map for a collection of posts.
|
||||
* Returns an array keyed by post ID.
|
||||
*/
|
||||
function thalim_get_cards_data($posts) {
|
||||
$cards = [];
|
||||
foreach ($posts as $post) {
|
||||
$cards[$post->ID] = thalim_get_card_data($post->ID);
|
||||
}
|
||||
return $cards;
|
||||
}
|
||||
57
inc/post-title-required.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Require a non-empty title when saving a post from the admin.
|
||||
*
|
||||
* Uses the same transient / redirect / restore mechanism as pods-save-error-handler.php
|
||||
* so content is never lost: the post saves (with empty title), the status is reverted
|
||||
* to draft if needed, the editor reopens with all fields restored and an error notice.
|
||||
*/
|
||||
add_action( 'save_post', 'thalim_check_post_title_required', 5 );
|
||||
|
||||
function thalim_check_post_title_required( $post_id ) {
|
||||
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
if ( ! is_admin() ) {
|
||||
return;
|
||||
}
|
||||
if ( ( $_POST['action'] ?? '' ) !== 'editpost' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post || ! post_type_supports( $post->post_type, 'title' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Title was provided — nothing to do.
|
||||
if ( trim( wp_unslash( $_POST['post_title'] ?? '' ) ) !== '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Title is empty: store restore transient and signal the redirect handler
|
||||
// (same keys as pods-save-error-handler.php so everything is shared).
|
||||
$user_id = get_current_user_id();
|
||||
$restore = [];
|
||||
|
||||
foreach ( $_POST as $key => $val ) {
|
||||
if ( str_starts_with( $key, 'pods_meta_' ) ) {
|
||||
$restore[ $key ] = is_array( $val )
|
||||
? array_map( 'wp_unslash', $val )
|
||||
: wp_unslash( $val );
|
||||
}
|
||||
}
|
||||
|
||||
$restore['_msg'] = __( 'Le champ Titre est obligatoire.', 'thalim' );
|
||||
$restore['_title'] = ''; // intentionally empty — user must fill it
|
||||
|
||||
set_transient(
|
||||
'thalim_pods_restore_' . $post_id . '_' . $user_id,
|
||||
$restore,
|
||||
10 * MINUTE_IN_SECONDS
|
||||
);
|
||||
|
||||
// Signal redirect_post_location (defined in pods-save-error-handler.php):
|
||||
// it will revert the post status if needed and redirect to the edit screen.
|
||||
$GLOBALS['thalim_pods_error_post_id'] = $post_id;
|
||||
}
|
||||
447
inc/single-helpers.php
Normal file
@@ -0,0 +1,447 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Format a Pods datetime string (e.g. "2026-01-17 00:00:00") into natural French/English.
|
||||
* Shows time only if not midnight.
|
||||
*/
|
||||
function thalim_format_date($raw, $lang = 'fr') {
|
||||
if (!$raw || str_starts_with($raw, '0000-00-00')) return '';
|
||||
$ts = strtotime($raw);
|
||||
if ($ts === false || $ts < 0) return '';
|
||||
return date_i18n('j F Y', $ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all Pods custom fields for a single post into a display-ready array.
|
||||
*/
|
||||
function thalim_get_single_data($post_id) {
|
||||
$lang = thalim_current_language();
|
||||
|
||||
$data = [
|
||||
// Text fields
|
||||
'sous_titre' => thalim_bilingual( get_post_meta($post_id, 'sous-titre', true) ?: '', $lang ),
|
||||
'reference_bibliographique' => get_post_meta($post_id, 'reference_bibliographique', true) ?: '',
|
||||
'editeur' => get_post_meta($post_id, 'editeur', true) ?: '',
|
||||
'journal' => get_post_meta($post_id, 'journal', true) ?: '',
|
||||
'lieu' => thalim_bilingual( get_post_meta($post_id, 'lieu', true) ?: '', $lang ),
|
||||
'adresse' => get_post_meta($post_id, 'adresse', true) ?: '',
|
||||
'autrepersonnes' => get_post_meta($post_id, 'autrepersonnes', true) ?: '',
|
||||
'autre_autrepersonnes' => get_post_meta($post_id, 'autre_autrepersonnes', true) ?: '',
|
||||
'body_en' => apply_filters( 'the_content', get_post_meta($post_id, 'body_en', true) ?: '' ),
|
||||
|
||||
// Dates (formatted for display)
|
||||
'datetime' => thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang),
|
||||
'date_de_debut' => '',
|
||||
'date_de_fin' => '',
|
||||
'date_debut_ymd' => '',
|
||||
'date_fin_ymd' => '',
|
||||
'heure_de_debut' => substr( get_post_meta($post_id, 'heure_de_debut', true) ?: '', 0, 5 ),
|
||||
'heure_de_fin' => substr( get_post_meta($post_id, 'heure_de_fin', true) ?: '', 0, 5 ),
|
||||
|
||||
// URLs
|
||||
'hal_url' => get_post_meta($post_id, 'hal_url', true) ?: '',
|
||||
'hal_file' => get_post_meta($post_id, 'hal_file', true) ?: '',
|
||||
'canal_u' => array_values( array_filter( array_map( function( $url ) {
|
||||
if ( preg_match( '/(\d+)\/?$/', trim( $url ), $m ) ) {
|
||||
return 'https://www.canal-u.tv/embed/' . $m[1] . '?t=0';
|
||||
}
|
||||
return '';
|
||||
}, get_post_meta( $post_id, 'lien_canal_u', false ) ) ) ),
|
||||
'youtube' => array_values( array_filter( array_map( function( $url ) {
|
||||
$url = trim( $url );
|
||||
// youtu.be/ID or youtube.com/embed/ID or youtube.com/watch?v=ID
|
||||
if ( preg_match( '/(?:youtu\.be\/|youtube\.com\/(?:embed\/|watch\?.*v=|shorts\/))([A-Za-z0-9_-]{11})/', $url, $m ) ) {
|
||||
return 'https://www.youtube-nocookie.com/embed/' . $m[1];
|
||||
}
|
||||
return '';
|
||||
}, get_post_meta( $post_id, 'lien_youtube', false ) ) ) ),
|
||||
|
||||
// Resolved below
|
||||
'liens_externes' => [],
|
||||
'membres' => [],
|
||||
'autre_membres' => [],
|
||||
'autre_fonction_label' => '',
|
||||
'axes' => [],
|
||||
'etiquettes' => [],
|
||||
'programmes' => [],
|
||||
'annonces_liees' => [],
|
||||
'seances_a_venir' => [],
|
||||
'seances_passees' => [],
|
||||
'show_image_titles' => (bool) get_post_meta($post_id, 'afficher_le_titre_des_images_en_legende', true),
|
||||
'images' => [],
|
||||
'documents' => [],
|
||||
'type_label' => '',
|
||||
'fonction_label' => '',
|
||||
'parent_slug' => '',
|
||||
'parent_name' => '',
|
||||
'parent_link' => '',
|
||||
'category_name' => '',
|
||||
'category_link' => '',
|
||||
];
|
||||
|
||||
// --- Dates ---
|
||||
$raw_debut = get_post_meta($post_id, 'date_de_debut', true);
|
||||
$raw_fin = get_post_meta($post_id, 'date_de_fin', true);
|
||||
$ts_debut = ($raw_debut && !str_starts_with($raw_debut, '0000-00-00')) ? strtotime($raw_debut) : 0;
|
||||
$ts_fin = ($raw_fin && !str_starts_with($raw_fin, '0000-00-00')) ? strtotime($raw_fin) : 0;
|
||||
|
||||
$data['date_de_debut'] = thalim_format_date($raw_debut, $lang);
|
||||
$data['date_de_fin'] = thalim_format_date($raw_fin, $lang);
|
||||
if ($ts_debut) $data['date_debut_ymd'] = date('Y-m-d', $ts_debut);
|
||||
if ($ts_fin) $data['date_fin_ymd'] = date('Y-m-d', $ts_fin);
|
||||
|
||||
// --- External links (up to 3) ---
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$url = get_post_meta($post_id, 'lien_externe_' . $i, true);
|
||||
if ($url) {
|
||||
$titre = thalim_bilingual( get_post_meta($post_id, 'titre_du_lien_externe_' . $i, true) ?: '', $lang );
|
||||
if (!$titre) {
|
||||
$host = parse_url($url, PHP_URL_HOST) ?: $url;
|
||||
$parts = explode('.', $host);
|
||||
$titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host;
|
||||
}
|
||||
$data['liens_externes'][] = [
|
||||
'url' => $url,
|
||||
'titre' => $titre,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Category hierarchy for breadcrumb and color ---
|
||||
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
||||
$excluded_ids = [12, 31];
|
||||
foreach ($categories as $cat) {
|
||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||
if (!empty($ancestor_ids)) {
|
||||
$root = get_category(end($ancestor_ids));
|
||||
$data['parent_slug'] = $root->slug;
|
||||
$data['parent_name'] = $root->name;
|
||||
$data['parent_link'] = get_category_link($root->term_id);
|
||||
$data['category_name'] = $cat->name;
|
||||
} else {
|
||||
$data['parent_slug'] = $cat->slug;
|
||||
$data['parent_name'] = $cat->name;
|
||||
$data['parent_link'] = get_category_link($cat->term_id);
|
||||
$data['category_name'] = $lang === 'en' ? 'Other' : 'Autre';
|
||||
}
|
||||
// category_link: for direct posts (no ancestors), point to the /autres index
|
||||
$data['category_link'] = empty($ancestor_ids)
|
||||
? trailingslashit(get_category_link($cat->term_id)) . 'autres/'
|
||||
: get_category_link($cat->term_id);
|
||||
break;
|
||||
}
|
||||
|
||||
// --- Documents joints: split images vs files ---
|
||||
$doc_ids = get_post_meta($post_id, 'documents_joints', false);
|
||||
foreach ($doc_ids as $doc_id) {
|
||||
$mime = get_post_mime_type($doc_id);
|
||||
if (!$mime) continue;
|
||||
if (str_starts_with($mime, 'image/')) {
|
||||
$src = wp_get_attachment_image_url($doc_id, 'large');
|
||||
if ($src) {
|
||||
$meta = wp_get_attachment_metadata($doc_id);
|
||||
$w = isset($meta['width']) ? $meta['width'] : 0;
|
||||
$h = isset($meta['height']) ? $meta['height'] : 0;
|
||||
$data['images'][] = [
|
||||
'url' => $src,
|
||||
'alt' => get_post_meta($doc_id, '_wp_attachment_image_alt', true) ?: '',
|
||||
'caption' => thalim_bilingual(wp_get_attachment_caption($doc_id) ?: '', $lang),
|
||||
'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang),
|
||||
'portrait' => ($h > $w),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$data['documents'][] = [
|
||||
'url' => wp_get_attachment_url($doc_id),
|
||||
'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang) ?: basename(get_attached_file($doc_id)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Members (user IDs → name + profile URL) ---
|
||||
foreach (get_post_meta($post_id, 'membres', false) as $uid) {
|
||||
$user = get_userdata($uid);
|
||||
if ($user) {
|
||||
$data['membres'][] = [
|
||||
'name' => $user->display_name,
|
||||
'url' => get_author_posts_url($uid),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Autre membres (user IDs → name + profile URL) ---
|
||||
foreach (get_post_meta($post_id, 'autre_membres', false) as $uid) {
|
||||
$user = get_userdata($uid);
|
||||
if ($user) {
|
||||
$data['autre_membres'][] = [
|
||||
'name' => $user->display_name,
|
||||
'url' => get_author_posts_url($uid),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Axes thématiques (taxonomy term IDs) ---
|
||||
$axe_ids = get_post_meta($post_id, 'axes_thematiques', false);
|
||||
foreach ($axe_ids as $axe_id) {
|
||||
$term = get_term(intval($axe_id), 'axe_thematique');
|
||||
if ($term && !is_wp_error($term)) {
|
||||
$data['axes'][] = [
|
||||
'id' => $term->term_id,
|
||||
'name' => thalim_bilingual($term->name, $lang),
|
||||
'url' => get_term_link($term),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Étiquettes (taxonomy term IDs) ---
|
||||
$tag_ids = get_post_meta($post_id, 'etiquettes', false);
|
||||
foreach ($tag_ids as $tag_id) {
|
||||
$term = get_term(intval($tag_id), 'post_tag');
|
||||
if ($term && !is_wp_error($term)) {
|
||||
$data['etiquettes'][] = [
|
||||
'id' => $term->term_id,
|
||||
'name' => thalim_bilingual($term->name, $lang),
|
||||
'url' => get_term_link($term),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Programmes de recherche (taxonomy term IDs) ---
|
||||
$prog_ids = get_post_meta($post_id, 'programmes_de_recherche', false);
|
||||
foreach ($prog_ids as $prog_id) {
|
||||
$term = get_term(intval($prog_id), 'programme_de_recherche');
|
||||
if ($term && !is_wp_error($term)) {
|
||||
$data['programmes'][] = [
|
||||
'id' => $term->term_id,
|
||||
'name' => thalim_bilingual($term->name, $lang),
|
||||
'url' => get_term_link($term),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// --- Annonces liées (related posts) ---
|
||||
$related_ids = get_post_meta($post_id, 'annonces_liees', false);
|
||||
if (!empty($related_ids)) {
|
||||
$data['annonces_liees'] = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'post__in' => array_map('intval', $related_ids),
|
||||
'posts_per_page' => -1,
|
||||
'lang' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
// --- Séances (session posts) — split into upcoming / past ---
|
||||
$seance_ids = get_post_meta($post_id, 'seances', false);
|
||||
$data['seances_a_venir'] = [];
|
||||
$data['seances_passees'] = [];
|
||||
if (!empty($seance_ids)) {
|
||||
$seance_posts = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'post__in' => array_map('intval', $seance_ids),
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'meta_value',
|
||||
'meta_key' => 'date_de_debut',
|
||||
'order' => 'ASC',
|
||||
'lang' => '',
|
||||
'post_status' => ['publish', 'future'],
|
||||
]);
|
||||
$now = time();
|
||||
$current_year = date('Y');
|
||||
$months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
|
||||
$months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
|
||||
|
||||
foreach ($seance_posts as $seance) {
|
||||
$raw_date = get_post_meta($seance->ID, 'date_de_debut', true);
|
||||
$ts = $raw_date ? strtotime($raw_date) : strtotime($seance->post_date);
|
||||
|
||||
// Only expose date_fin when it's a different day than date_de_debut
|
||||
$raw_fin = get_post_meta($seance->ID, 'date_de_fin', true);
|
||||
$ts_fin = $raw_fin && !str_starts_with($raw_fin, '0000-00-00') ? strtotime($raw_fin) : false;
|
||||
$date_fin_display = ($ts_fin && date('Y-m-d', $ts_fin) !== date('Y-m-d', $ts))
|
||||
? thalim_format_date($raw_fin, $lang)
|
||||
: '';
|
||||
|
||||
$month_idx = intval(date('n', $ts)) - 1;
|
||||
$seance_data = [
|
||||
'post' => $seance,
|
||||
'day' => date('d', $ts),
|
||||
'month' => ($lang === 'en') ? $months_en[$month_idx] : $months_fr[$month_idx],
|
||||
'year' => (date('Y', $ts) !== $current_year) ? date('Y', $ts) : '',
|
||||
'date_full' => thalim_format_date($raw_date, $lang),
|
||||
'date_fin' => $date_fin_display,
|
||||
'heure_de_debut' => substr( get_post_meta($seance->ID, 'heure_de_debut', true) ?: '', 0, 5 ),
|
||||
'heure_de_fin' => substr( get_post_meta($seance->ID, 'heure_de_fin', true) ?: '', 0, 5 ),
|
||||
'lieu' => thalim_bilingual( get_post_meta($seance->ID, 'lieu', true) ?: '', $lang ),
|
||||
'adresse' => get_post_meta($seance->ID, 'adresse', true) ?: '',
|
||||
'body_en' => apply_filters( 'the_content', get_post_meta($seance->ID, 'body_en', true) ?: '' ),
|
||||
'intervenants' => [],
|
||||
'images' => [],
|
||||
'documents' => [],
|
||||
'liens_externes' => [],
|
||||
'annonces_liees' => [],
|
||||
];
|
||||
|
||||
// Resolve intervenants (membres or autrepersonnes)
|
||||
$m_ids = get_post_meta($seance->ID, 'membres', false);
|
||||
if (empty($m_ids)) {
|
||||
$m_ids = get_post_meta($seance->ID, 'autre_membres', false);
|
||||
}
|
||||
foreach ($m_ids as $uid) {
|
||||
$user = get_userdata($uid);
|
||||
if ($user) {
|
||||
$seance_data['intervenants'][] = [
|
||||
'name' => $user->display_name,
|
||||
'url' => get_author_posts_url($uid),
|
||||
];
|
||||
}
|
||||
}
|
||||
$seance_data['autrepersonnes'] = get_post_meta($seance->ID, 'autrepersonnes', true) ?: '';
|
||||
$seance_data['show_image_titles'] = (bool) get_post_meta($seance->ID, 'afficher_le_titre_des_images_en_legende', true);
|
||||
|
||||
// Documents joints: images and files
|
||||
$s_doc_ids = get_post_meta($seance->ID, 'documents_joints', false);
|
||||
foreach ($s_doc_ids as $doc_id) {
|
||||
$mime = get_post_mime_type($doc_id);
|
||||
if (!$mime) continue;
|
||||
if (str_starts_with($mime, 'image/')) {
|
||||
$src = wp_get_attachment_image_url($doc_id, 'large');
|
||||
if ($src) {
|
||||
$seance_data['images'][] = [
|
||||
'url' => $src,
|
||||
'alt' => get_post_meta($doc_id, '_wp_attachment_image_alt', true) ?: '',
|
||||
'caption' => thalim_bilingual(wp_get_attachment_caption($doc_id) ?: '', $lang),
|
||||
'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$seance_data['documents'][] = [
|
||||
'url' => wp_get_attachment_url($doc_id),
|
||||
'title' => thalim_bilingual(get_the_title($doc_id) ?: '', $lang) ?: basename(get_attached_file($doc_id)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// External links (up to 3)
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$url = get_post_meta($seance->ID, 'lien_externe_' . $i, true);
|
||||
if ($url) {
|
||||
$titre = thalim_bilingual( get_post_meta($seance->ID, 'titre_du_lien_externe_' . $i, true) ?: '', $lang );
|
||||
if (!$titre) {
|
||||
$host = parse_url($url, PHP_URL_HOST) ?: $url;
|
||||
$parts = explode('.', $host);
|
||||
$titre = count($parts) >= 2 ? implode('.', array_slice($parts, -2)) : $host;
|
||||
}
|
||||
$seance_data['liens_externes'][] = ['url' => $url, 'titre' => $titre];
|
||||
}
|
||||
}
|
||||
|
||||
// Annonces liées
|
||||
$s_related_ids = get_post_meta($seance->ID, 'annonces_liees', false);
|
||||
if (!empty($s_related_ids)) {
|
||||
$seance_data['annonces_liees'] = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'post__in' => array_map('intval', $s_related_ids),
|
||||
'posts_per_page' => -1,
|
||||
'lang' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($ts >= $now) {
|
||||
$data['seances_a_venir'][] = $seance_data;
|
||||
} else {
|
||||
$data['seances_passees'][] = $seance_data;
|
||||
}
|
||||
}
|
||||
// Past séances: most recent first
|
||||
$data['seances_passees'] = array_reverse($data['seances_passees']);
|
||||
}
|
||||
|
||||
// --- Type label (category-conditional type fields) ---
|
||||
$type_fields = [
|
||||
'type_colloque_journee_d_etude',
|
||||
'type_soutenance',
|
||||
'type_evenement_culturel',
|
||||
'type_media',
|
||||
'type_captation',
|
||||
'type_revue_collection',
|
||||
'type_autre',
|
||||
];
|
||||
foreach ($type_fields as $field) {
|
||||
$val = get_post_meta($post_id, $field, true);
|
||||
if ($val) {
|
||||
$data['type_label'] = thalim_bilingual( $val, $lang );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fonction label (first non-empty fonction_* field) ---
|
||||
$fonction_fields = [
|
||||
'fonction_auteur',
|
||||
'fonction_organisation',
|
||||
'fonction_intervention',
|
||||
'fonction_redaction',
|
||||
'fonction_realisation',
|
||||
'fonction_dirige',
|
||||
'fonction_responsable',
|
||||
'fonction_candidat',
|
||||
];
|
||||
foreach ($fonction_fields as $field) {
|
||||
$val = get_post_meta($post_id, $field, true);
|
||||
if ($val) {
|
||||
$data['fonction_label'] = thalim_bilingual( $val, $lang );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Autre fonction label (first non-empty autre_fonction_* field) ---
|
||||
$autre_fonction_fields = [
|
||||
'autre_fonction_autre',
|
||||
'autre_fonction_concerne',
|
||||
'autre_fonction_directeur',
|
||||
'autre_fonction_direction_d_ouvrage',
|
||||
'autre_fonction_intervenant',
|
||||
'autre_fonction_participants',
|
||||
];
|
||||
foreach ($autre_fonction_fields as $field) {
|
||||
$val = get_post_meta($post_id, $field, true);
|
||||
if ($val) {
|
||||
$data['autre_fonction_label'] = thalim_bilingual( $val, $lang );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback: derive labels from Pods categorie ID for older posts ---
|
||||
if (!$data['fonction_label'] || !$data['autre_fonction_label']) {
|
||||
$pods_cat = get_post_meta($post_id, '_pods_categorie', true);
|
||||
$cat_id = (is_array($pods_cat) && !empty($pods_cat)) ? intval($pods_cat[0]) : 0;
|
||||
|
||||
// Pods categorie ID → fonction label (main membres)
|
||||
$cat_to_fonction = [
|
||||
3 => 'Organisation', 4 => 'Auteur', 6 => 'Responsable',
|
||||
8 => 'Organisation', 9 => 'Responsable', 10 => 'Organisation',
|
||||
11 => 'Organisation', 12 => 'Intervention', 13 => 'Intervention',
|
||||
14 => 'Candidat', 15 => 'Auteur', 16 => 'Auteur',
|
||||
17 => 'Responsable', 18 => 'Organisation', 19 => 'Intervention',
|
||||
21 => 'Rédaction', 22 => 'Réalisation', 23 => 'Intervention',
|
||||
24 => 'Responsable', 25 => 'Responsable', 65 => 'Dirigé par',
|
||||
];
|
||||
|
||||
// Pods categorie ID → autre_fonction label (autre membres)
|
||||
$cat_to_autre_fonction = [
|
||||
3 => 'Participants', 4 => "Direction d'ouvrage",
|
||||
10 => 'Participants', 14 => 'Directeur de thèse',
|
||||
15 => "Direction d'ouvrage", 16 => "Direction d'ouvrage",
|
||||
19 => 'Membre concerné', 22 => 'Intervenant',
|
||||
];
|
||||
|
||||
if (!$data['fonction_label'] && isset($cat_to_fonction[$cat_id])) {
|
||||
$data['fonction_label'] = $cat_to_fonction[$cat_id];
|
||||
}
|
||||
if (!$data['autre_fonction_label'] && isset($cat_to_autre_fonction[$cat_id])) {
|
||||
$data['autre_fonction_label'] = $cat_to_autre_fonction[$cat_id];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
263
index.php
Executable file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
|
||||
// Helper: split posts into active-pinned / normal and merge (pinned first, max_normal non-pinned)
|
||||
$sort_with_pinned = function ($posts, string $pin_field, int $max_normal = PHP_INT_MAX) {
|
||||
$today = date('Y-m-d');
|
||||
$pinned = [];
|
||||
$normal = [];
|
||||
foreach ($posts as $post) {
|
||||
$epingle = get_post_meta($post->ID, $pin_field, true);
|
||||
$fin = get_post_meta($post->ID, 'date_de_fin_depinglage', true);
|
||||
$active = $epingle == '1' && (empty($fin) || $fin === '0000-00-00' || $fin >= $today);
|
||||
if ($active) { $pinned[] = $post; } else { $normal[] = $post; }
|
||||
}
|
||||
return array_merge($pinned, array_slice($normal, 0, $max_normal));
|
||||
};
|
||||
|
||||
// --- Nombre d'items des diaporamas (depuis la page Le Laboratoire) ---
|
||||
$labo_page = get_page_by_path('le-laboratoire');
|
||||
$max_swiper = $labo_page ? intval(get_post_meta($labo_page->ID, 'nombres_ditems_des_diaporamas', true)) : 0;
|
||||
if ($max_swiper < 1) $max_swiper = 10;
|
||||
|
||||
// --- Annonces diaporama ---
|
||||
$annonces_raw = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => [[
|
||||
'key' => 'afficher_dans_le_diaporama_dannonces_page_daccueil',
|
||||
'value' => '1',
|
||||
]],
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
]);
|
||||
$context['annonces'] = $sort_with_pinned($annonces_raw, 'epingler_dans_le_diaporama_dannonces', $max_swiper);
|
||||
$context['annonces_cards'] = thalim_get_cards_data($annonces_raw);
|
||||
|
||||
// --- Publications et productions diaporama ---
|
||||
$publications_raw = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 30,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [4],
|
||||
'operator' => 'IN',
|
||||
'include_children' => true,
|
||||
],
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [16],
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
],
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => 'type_autre',
|
||||
'value' => "Chapitre d'ouvrage",
|
||||
'compare' => '!=',
|
||||
],
|
||||
[
|
||||
'key' => 'type_autre',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$context['publications'] = $sort_with_pinned($publications_raw, 'epingler_dans_le_diaporama_des_publications_et_productions', $max_swiper);
|
||||
$context['publications_cards'] = thalim_get_cards_data($publications_raw);
|
||||
$context['publications_link'] = thalim_en_url( get_category_link(4) );
|
||||
$context['annonces_link'] = thalim_en_url( get_permalink(29100) );
|
||||
|
||||
// --- Message du laboratoire ---
|
||||
$messages_labo = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 5,
|
||||
'cat' => 268,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
]);
|
||||
$context['messages_labo'] = $messages_labo ?: [];
|
||||
$context['message_labo_link'] = thalim_en_url( get_category_link(268) );
|
||||
|
||||
// --- Agenda (médiation scientifique + séances de séminaire à venir) ---
|
||||
$agenda_lang = thalim_current_language();
|
||||
$mediation_cat_ids = [5, 18, 19, 20, 21, 22, 23];
|
||||
$months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
|
||||
$months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
|
||||
$agenda_type_fields = [
|
||||
'type_colloque_journee_d_etude', 'type_soutenance', 'type_evenement_culturel',
|
||||
'type_media', 'type_captation', 'type_revue_collection', 'type_autre',
|
||||
];
|
||||
$now_str = date('Y-m-d H:i:s');
|
||||
|
||||
$make_agenda_item = function ($post, $raw_date, $type_label, $lieu, $link) use ($agenda_lang, $months_fr, $months_en) {
|
||||
$ts = strtotime($raw_date);
|
||||
if (!$ts) return null;
|
||||
$month_idx = intval(date('n', $ts)) - 1;
|
||||
return [
|
||||
'post' => $post,
|
||||
'ts' => $ts,
|
||||
'day' => intval(date('j', $ts)),
|
||||
'month' => $agenda_lang === 'en' ? $months_en[$month_idx] : $months_fr[$month_idx],
|
||||
'type_label' => $type_label,
|
||||
'lieu' => $lieu,
|
||||
'link' => $link,
|
||||
];
|
||||
};
|
||||
|
||||
$agenda_items = [];
|
||||
|
||||
// 1. Upcoming médiation scientifique posts
|
||||
$mediation_upcoming = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 8,
|
||||
'category__in' => $mediation_cat_ids,
|
||||
'orderby' => ['date_clause' => 'ASC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => [
|
||||
'key' => 'date_de_debut',
|
||||
'value' => $now_str,
|
||||
'compare' => '>=',
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
],
|
||||
]);
|
||||
foreach ($mediation_upcoming as $mpost) {
|
||||
$raw_date = get_post_meta($mpost->ID, 'date_de_debut', true);
|
||||
if (!$raw_date) continue;
|
||||
$type_label = '';
|
||||
foreach ($agenda_type_fields as $field) {
|
||||
$val = get_post_meta($mpost->ID, $field, true);
|
||||
if ($val) { $type_label = $val; break; }
|
||||
}
|
||||
if (!$type_label) {
|
||||
foreach (get_the_category($mpost->ID) as $cat) {
|
||||
if (in_array($cat->term_id, $mediation_cat_ids)) { $type_label = thalim_cat_name($cat); break; }
|
||||
}
|
||||
}
|
||||
$item = $make_agenda_item($mpost, $raw_date, $type_label, get_post_meta($mpost->ID, 'lieu', true) ?: '', get_permalink($mpost->ID));
|
||||
if ($item) $agenda_items[] = $item;
|
||||
}
|
||||
|
||||
// 2. Upcoming séances de séminaire (cat 12)
|
||||
$seances_upcoming = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 8,
|
||||
'category__in' => [12],
|
||||
'orderby' => ['date_clause' => 'ASC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => [
|
||||
'key' => 'date_de_debut',
|
||||
'value' => $now_str,
|
||||
'compare' => '>=',
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
],
|
||||
]);
|
||||
foreach ($seances_upcoming as $seance) {
|
||||
$raw_date = get_post_meta($seance->ID, 'date_de_debut', true);
|
||||
if (!$raw_date) continue;
|
||||
// Direct DB lookup — bypasses Polylang and other hook filters
|
||||
global $wpdb;
|
||||
$parent_id = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
||||
AND p.post_status = 'publish'
|
||||
LIMIT 1",
|
||||
(string) $seance->ID
|
||||
));
|
||||
$link = $parent_id
|
||||
? get_permalink((int) $parent_id) . '#seance-' . $seance->ID
|
||||
: get_permalink($seance->ID);
|
||||
$label = $agenda_lang === 'en' ? 'Seminar session' : 'Séance de séminaire';
|
||||
$item = $make_agenda_item($seance, $raw_date, $label, get_post_meta($seance->ID, 'lieu', true) ?: '', $link);
|
||||
if ($item) $agenda_items[] = $item;
|
||||
}
|
||||
|
||||
// Sort merged list by date, keep 5 soonest
|
||||
usort($agenda_items, fn($a, $b) => $a['ts'] <=> $b['ts']);
|
||||
$agenda_items = array_slice($agenda_items, 0, 5);
|
||||
|
||||
// Fallback: if no upcoming events, show 5 most recent mediation events
|
||||
if (empty($agenda_items)) {
|
||||
$fallback = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 5,
|
||||
'category__in' => $mediation_cat_ids,
|
||||
'orderby' => ['date_clause' => 'DESC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => ['key' => 'date_de_debut', 'type' => 'DATETIME'],
|
||||
],
|
||||
]);
|
||||
foreach ($fallback as $fpost) {
|
||||
$raw_date = get_post_meta($fpost->ID, 'date_de_debut', true);
|
||||
if (!$raw_date) continue;
|
||||
$type_label = '';
|
||||
foreach ($agenda_type_fields as $field) {
|
||||
$val = get_post_meta($fpost->ID, $field, true);
|
||||
if ($val) { $type_label = $val; break; }
|
||||
}
|
||||
if (!$type_label) {
|
||||
foreach (get_the_category($fpost->ID) as $cat) {
|
||||
if (in_array($cat->term_id, $mediation_cat_ids)) { $type_label = thalim_cat_name($cat); break; }
|
||||
}
|
||||
}
|
||||
$item = $make_agenda_item($fpost, $raw_date, $type_label, get_post_meta($fpost->ID, 'lieu', true) ?: '', get_permalink($fpost->ID));
|
||||
if ($item) $agenda_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$context['agenda_items'] = $agenda_items;
|
||||
$context['manifestations_link'] = thalim_en_url( add_query_arg( 'view', 'agenda', get_category_link(3) ) );
|
||||
|
||||
// --- Quick links ---
|
||||
$newsletter_cat = get_category_by_slug('newsletter');
|
||||
$newsletter_url = '';
|
||||
if ($newsletter_cat) {
|
||||
$nl_posts = get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'category__in' => [ $newsletter_cat->term_id ],
|
||||
'include_children' => false,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'suppress_filters' => true,
|
||||
]);
|
||||
if ( ! empty( $nl_posts ) ) {
|
||||
$newsletter_url = thalim_en_url( get_permalink( $nl_posts[0]->ID ) );
|
||||
}
|
||||
}
|
||||
if ( ! $newsletter_url ) {
|
||||
$newsletter_url = thalim_en_url(
|
||||
$newsletter_cat ? get_category_link( $newsletter_cat->term_id ) : home_url( '/category/le-laboratoire/newsletter/' )
|
||||
);
|
||||
}
|
||||
$context['quick_links'] = [
|
||||
'agenda' => thalim_en_url(add_query_arg('view', 'agenda', get_category_link(3))),
|
||||
'contacts' => thalim_en_url(home_url('/contacts/')),
|
||||
'newsletter' => $newsletter_url,
|
||||
];
|
||||
|
||||
// --- Tags (étiquettes) pour le nuage de mots-clés ---
|
||||
$context['has_tags'] = !empty(get_terms([
|
||||
'taxonomy' => 'post_tag',
|
||||
'hide_empty' => true,
|
||||
'number' => 1,
|
||||
'lang' => '',
|
||||
]));
|
||||
|
||||
Timber::render('index.twig', $context);
|
||||
883
js/adminDashboardMods.js
Normal file
@@ -0,0 +1,883 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
function isPostEditPage() {
|
||||
return window.pagenow === 'post'
|
||||
|| window.pagenow === 'post-new'
|
||||
// On CPTs, pagenow is the post_type slug — also catch them via the
|
||||
// body classes WP sets for any post.php / post-new.php screen.
|
||||
|| document.body.classList.contains('post-php')
|
||||
|| document.body.classList.contains('post-new-php');
|
||||
}
|
||||
|
||||
function isProfileEditPage() {
|
||||
return window.pagenow === 'profile' || window.pagenow === 'user-edit' || window.pagenow === 'user-new';
|
||||
}
|
||||
|
||||
function getProfileForm() {
|
||||
return document.querySelector('#your-profile, #createuser');
|
||||
}
|
||||
|
||||
function isPodsModal() {
|
||||
return new URLSearchParams(window.location.search).has('pods_modal');
|
||||
}
|
||||
|
||||
function renameArticlesToAnnonces() {
|
||||
const replacements = [
|
||||
[/Tous les articles/g, 'Toutes les annonces'],
|
||||
[/Ajouter un article/g, 'Ajouter une annonce'],
|
||||
[/Modifier l.article/g, "Modifier l'annonce"],
|
||||
[/Pr\u00e9visualiser l.article/g, "Pr\u00e9visualiser l'annonce"],
|
||||
[/Afficher l.article/g, "Afficher l'annonce"],
|
||||
[/Voir l.article/g, "Voir l'annonce"],
|
||||
[/Article publi\u00e9/g, 'Annonce publi\u00e9e'],
|
||||
[/Article mis \u00e0 jour/g, 'Annonce mise \u00e0 jour'],
|
||||
[/Article planifi\u00e9/g, 'Annonce planifi\u00e9e'],
|
||||
[/Articles par page/g, 'Annonces par page'],
|
||||
[/Articles/g, 'Annonces'],
|
||||
[/Article/g, 'Annonce'],
|
||||
[/Rechercher des articles/g, 'Rechercher des annonces'],
|
||||
];
|
||||
|
||||
function applyReplacements(text) {
|
||||
return replacements.reduce((t, [s, r]) => t.replace(s, r), text);
|
||||
}
|
||||
|
||||
function replaceInTextNodes(el) {
|
||||
if (!el) return;
|
||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
||||
const nodes = [];
|
||||
while (walker.nextNode()) nodes.push(walker.currentNode);
|
||||
nodes.forEach(function(node) {
|
||||
const replaced = applyReplacements(node.textContent);
|
||||
if (replaced !== node.textContent) node.textContent = replaced;
|
||||
});
|
||||
}
|
||||
|
||||
// Menu latéral
|
||||
replaceInTextNodes(document.querySelector('#menu-posts'));
|
||||
|
||||
// Titre de page (h1) et bouton d'ajout
|
||||
document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes);
|
||||
|
||||
// Notifications après sauvegarde (Article publié, mis à jour…)
|
||||
document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes);
|
||||
|
||||
// Boîte de publication — lien "Voir l'article"
|
||||
replaceInTextNodes(document.querySelector('.submitbox'));
|
||||
|
||||
// Options d'écran — "Articles par page"
|
||||
replaceInTextNodes(document.querySelector('#screen-options-wrap'));
|
||||
|
||||
// Bouton de recherche (attribut value + aria-label)
|
||||
var searchSubmit = document.querySelector('#search-submit');
|
||||
if (searchSubmit) {
|
||||
if (searchSubmit.value) {
|
||||
searchSubmit.value = applyReplacements(searchSubmit.value);
|
||||
}
|
||||
var ariaLabel = searchSubmit.getAttribute('aria-label');
|
||||
if (ariaLabel) {
|
||||
searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel));
|
||||
}
|
||||
}
|
||||
|
||||
// Titre de l'onglet du navigateur
|
||||
document.title = applyReplacements(document.title);
|
||||
}
|
||||
|
||||
function updatePostboxVisibility() {
|
||||
document.querySelectorAll('.postbox').forEach((postBox) => {
|
||||
if (postBox.id.startsWith('pods')) {
|
||||
// body-en is controlled by language tabs — never auto-hide it
|
||||
if (postBox.id === 'pods-meta-body-en') return;
|
||||
const fields = postBox.querySelectorAll('tr');
|
||||
const hasVisibleFields = Array.from(fields).some(field => field.style.display !== 'none');
|
||||
postBox.style.display = hasVisibleFields ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Force Visual (TinyMCE) mode on page load.
|
||||
// WP stores the last-used editor mode in localStorage and restores it at document.ready.
|
||||
// When Code mode is restored, TinyMCE is never initialised — tinymce.get() returns null.
|
||||
// Instead, check the wrapper's CSS class:
|
||||
// tmce-active = Visual mode (fine)
|
||||
// html-active = Code mode (switch to Visual)
|
||||
function ensureVisualMode(editorId, attempt) {
|
||||
attempt = attempt || 0;
|
||||
if (attempt > 15) return;
|
||||
var wrap = document.getElementById('wp-' + editorId + '-wrap');
|
||||
if (!wrap) {
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
return;
|
||||
}
|
||||
if (wrap.classList.contains('html-active')) {
|
||||
var ed = window.tinymce && tinymce.get(editorId);
|
||||
if (!ed || !ed.initialized) {
|
||||
// TinyMCE not ready yet — retry rather than calling switchEditors.go() prematurely
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
return;
|
||||
}
|
||||
if (typeof switchEditors !== 'undefined') {
|
||||
switchEditors.go(editorId, 'tmce');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!wrap.classList.contains('tmce-active')) {
|
||||
// Mode not yet determined — retry
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: insert the tab bar and relocate #pods-meta-body-en.
|
||||
// The DOM move breaks TinyMCE's iframe (browsers reset iframe content on detach),
|
||||
// so we leave the container visible here and let Pods/TinyMCE initialise normally.
|
||||
// The broken iframe is repaired by reinitEditor() on first EN tab open.
|
||||
function setupBodyTabsDom() {
|
||||
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
|
||||
var bodyEnBox = document.getElementById('pods-meta-body-en');
|
||||
if (!nativeEditor || !bodyEnBox) return;
|
||||
|
||||
var tabBar = document.createElement('div');
|
||||
tabBar.className = 'body-lang-tabs';
|
||||
tabBar.innerHTML =
|
||||
'<button type="button" class="body-lang-tab is-active" data-panel="fr">Fran\u00e7ais</button>' +
|
||||
'<button type="button" class="body-lang-tab" data-panel="en">English</button>';
|
||||
nativeEditor.parentNode.insertBefore(tabBar, nativeEditor);
|
||||
|
||||
// Move EN metabox to sit right after the native editor for correct visual layout.
|
||||
// Do NOT hide it yet — Pods must init TinyMCE with the container visible so the
|
||||
// iframe can measure its dimensions. Page is still opacity:0 so no flash.
|
||||
nativeEditor.parentNode.insertBefore(bodyEnBox, nativeEditor.nextSibling);
|
||||
}
|
||||
|
||||
// Rebuild a TinyMCE editor whose iframe is broken (empty/non-interactive).
|
||||
// This happens when TinyMCE is initialised on a hidden (display:none) element:
|
||||
// the iframe can't measure dimensions and its document body stays empty.
|
||||
//
|
||||
// We reinit from tinyMCEPreInit.mceInit — first trying the editor's own config
|
||||
// (registered by Pods server-side), falling back to 'content' (the native WP editor).
|
||||
//
|
||||
// Inline toolbar positioning fix:
|
||||
// TinyMCE's 'wordpress' plugin captures document.getElementById(id+'_ifr') during
|
||||
// 'preinit' — before the iframe is created — so mceIframe is always null.
|
||||
// Fix: intercept getElementById during preinit so the 'wordpress' plugin captures
|
||||
// a proxyIframe instead of null. After init, proxy delegates to the real iframe.
|
||||
function reinitEditor(editorId) {
|
||||
var ed = window.tinymce && tinymce.get(editorId);
|
||||
// Preserve existing content before destroying the instance
|
||||
var savedContent = '';
|
||||
if (ed) {
|
||||
try { savedContent = ed.getContent(); } catch (e) {}
|
||||
ed.remove();
|
||||
}
|
||||
if (!savedContent) {
|
||||
var ta = document.getElementById(editorId);
|
||||
if (ta) savedContent = ta.value || '';
|
||||
}
|
||||
|
||||
if (!window.tinyMCEPreInit || !window.tinymce) return;
|
||||
|
||||
// Use the editor's own server-side config if available, else clone from 'content'
|
||||
var baseInit = (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[editorId])
|
||||
|| (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit['content']);
|
||||
if (!baseInit) return;
|
||||
|
||||
// Proxy iframe: getBoundingClientRect() falls back to the editor wrap
|
||||
var wrapId = 'wp-' + editorId + '-wrap';
|
||||
var proxyIframe = {
|
||||
getBoundingClientRect: function() {
|
||||
var el = document.getElementById(wrapId);
|
||||
return el ? el.getBoundingClientRect()
|
||||
: { top: 0, left: 0, right: window.innerWidth,
|
||||
bottom: window.innerHeight, width: window.innerWidth,
|
||||
height: window.innerHeight };
|
||||
}
|
||||
};
|
||||
|
||||
var savedGetById = document.getElementById;
|
||||
var origSetup = baseInit.setup;
|
||||
var content = savedContent;
|
||||
|
||||
tinymce.init($.extend({}, baseInit, {
|
||||
selector: '#' + editorId,
|
||||
setup: function(editor) {
|
||||
if (typeof origSetup === 'function') origSetup(editor);
|
||||
|
||||
editor.on('focus', function() {
|
||||
window.wpActiveEditor = editorId;
|
||||
});
|
||||
|
||||
editor.on('preinit', function() {
|
||||
document.getElementById = function(id) {
|
||||
if (id === editorId + '_ifr') return proxyIframe;
|
||||
return savedGetById.call(document, id);
|
||||
};
|
||||
setTimeout(function() {
|
||||
document.getElementById = savedGetById;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
editor.on('init', function() {
|
||||
// Point proxy to real iframe
|
||||
var realIframe = savedGetById.call(document, editorId + '_ifr');
|
||||
if (realIframe) {
|
||||
proxyIframe.getBoundingClientRect = function() {
|
||||
return realIframe.getBoundingClientRect();
|
||||
};
|
||||
}
|
||||
// Restore content that was in the textarea
|
||||
if (content) {
|
||||
editor.setContent(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Phase 2: wire tab click handlers — runs at t=100ms after metabox reordering.
|
||||
function initBodyLanguageTabs() {
|
||||
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
|
||||
var bodyEnBox = document.getElementById('pods-meta-body-en');
|
||||
var tabBar = document.querySelector('.body-lang-tabs');
|
||||
if (!nativeEditor || !bodyEnBox || !tabBar) {
|
||||
// body_en not available (e.g. contributor role) — still force visual mode on main editor
|
||||
if (nativeEditor) ensureVisualMode('content');
|
||||
return;
|
||||
}
|
||||
|
||||
var enEditorId = 'pods-form-ui-pods-meta-body-en';
|
||||
var enTmceReady = false;
|
||||
|
||||
// Hide EN panel — page is still opacity:0, user won't see the switch
|
||||
bodyEnBox.style.display = 'none';
|
||||
|
||||
tabBar.querySelectorAll('.body-lang-tab').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
tabBar.querySelectorAll('.body-lang-tab').forEach(function(b) {
|
||||
b.classList.remove('is-active');
|
||||
});
|
||||
btn.classList.add('is-active');
|
||||
|
||||
var revealedPanel;
|
||||
|
||||
if (btn.dataset.panel === 'fr') {
|
||||
bodyEnBox.style.display = 'none';
|
||||
nativeEditor.style.opacity = '0';
|
||||
nativeEditor.style.display = '';
|
||||
revealedPanel = nativeEditor;
|
||||
} else {
|
||||
nativeEditor.style.display = 'none';
|
||||
bodyEnBox.style.opacity = '0';
|
||||
bodyEnBox.style.display = 'block';
|
||||
revealedPanel = bodyEnBox;
|
||||
|
||||
if (!enTmceReady) {
|
||||
enTmceReady = true;
|
||||
// Reinit while container is visible so TinyMCE can measure dimensions
|
||||
reinitEditor(enEditorId);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify TinyMCE to reflow, then fade in once layout is correct
|
||||
setTimeout(function() {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
requestAnimationFrame(function() {
|
||||
requestAnimationFrame(function() {
|
||||
revealedPanel.style.opacity = '';
|
||||
});
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure both editors start in Visual (not Code) mode
|
||||
ensureVisualMode('content');
|
||||
ensureVisualMode(enEditorId);
|
||||
}
|
||||
|
||||
function groupAxesCheckboxes() {
|
||||
if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
|
||||
|
||||
var row = document.querySelector('.pods-form-ui-row-name-axes-thematiques');
|
||||
if (!row) return;
|
||||
|
||||
var list = row.querySelector('ul');
|
||||
if (!list) return;
|
||||
|
||||
// Already grouped — nothing to do
|
||||
if (list.querySelector('.axes-group-label')) return;
|
||||
|
||||
// Map existing <li> by checkbox value; preserve "add new" button
|
||||
var liMap = {};
|
||||
var addNewItem = null;
|
||||
list.querySelectorAll('li').forEach(function(li) {
|
||||
if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; }
|
||||
var cb = li.querySelector('input[type="checkbox"]');
|
||||
if (cb) liMap[cb.value] = li;
|
||||
});
|
||||
|
||||
// Rebuild list in group order
|
||||
list.innerHTML = '';
|
||||
thalimAxesGroups.forEach(function(group) {
|
||||
var labelLi = document.createElement('li');
|
||||
labelLi.className = 'axes-group-label';
|
||||
labelLi.textContent = group.label;
|
||||
list.appendChild(labelLi);
|
||||
group.terms.forEach(function(term) {
|
||||
var li = liMap[String(term.id)];
|
||||
if (li) list.appendChild(li);
|
||||
});
|
||||
});
|
||||
|
||||
if (addNewItem) list.appendChild(addNewItem);
|
||||
}
|
||||
|
||||
var REF_BIB_EDITOR_ID = 'pods-form-ui-pods-meta-reference-bibliographique';
|
||||
var refBibReinited = false;
|
||||
|
||||
// Reinit the référence bibliographique TinyMCE editor.
|
||||
// Called at page load (if the field is already visible) and by the
|
||||
// MutationObserver (when the field becomes visible after a category change).
|
||||
function initRefBibEditor() {
|
||||
if (refBibReinited) return;
|
||||
var row = document.querySelector('.pods-form-ui-row-name-reference-bibliographique');
|
||||
if (!row || row.style.display === 'none') return;
|
||||
refBibReinited = true;
|
||||
reinitEditor(REF_BIB_EDITOR_ID);
|
||||
ensureVisualMode(REF_BIB_EDITOR_ID);
|
||||
}
|
||||
|
||||
function initAxesGroupObserver() {
|
||||
// Pods shows/hides conditional rows by removing inline style="display:none"
|
||||
// Watch the entire Pods meta form for style changes on the axes row
|
||||
var podsForm = document.querySelector('.pods-pick-values, #pods-meta-champs-contextuels, form#post');
|
||||
if (!podsForm) podsForm = document.body;
|
||||
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var target = mutations[i].target;
|
||||
if (target.classList && target.classList.contains('pods-form-ui-row-name-axes-thematiques')) {
|
||||
if (target.style.display !== 'none') {
|
||||
setTimeout(groupAxesCheckboxes, 50);
|
||||
}
|
||||
}
|
||||
// Reinit TinyMCE on the référence bibliographique field when its
|
||||
// row becomes visible — Pods hides it with display:none which breaks
|
||||
// the TinyMCE iframe. Only reinit once per page load.
|
||||
if (!refBibReinited && target.classList &&
|
||||
target.classList.contains('pods-form-ui-row-name-reference-bibliographique')) {
|
||||
if (target.style.display !== 'none') {
|
||||
setTimeout(initRefBibEditor, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
|
||||
}
|
||||
|
||||
function initPostEditPage() {
|
||||
// Disable category options (CSS handles the color)
|
||||
const categorieSelect = document.querySelector('#pods-form-ui-pods-meta-categorie');
|
||||
if (categorieSelect) {
|
||||
const categoriesToDisable = ['1', '12', '5', '20'];
|
||||
categorieSelect.querySelectorAll('option').forEach(option => {
|
||||
if (categoriesToDisable.includes(option.value)) {
|
||||
option.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reorder meta boxes
|
||||
const sideSortables = document.querySelector('#side-sortables');
|
||||
if (sideSortables) {
|
||||
const typeDannonce = document.querySelector('#pods-meta-type-dannonce');
|
||||
const affichageAccueil = document.querySelector('#pods-meta-affichage-sur-laccueil');
|
||||
const thematique = document.querySelector('#pods-meta-thematique');
|
||||
if (typeDannonce) sideSortables.prepend(typeDannonce);
|
||||
if (affichageAccueil) sideSortables.appendChild(affichageAccueil);
|
||||
if (thematique) sideSortables.appendChild(thematique);
|
||||
}
|
||||
|
||||
const submitDiv = document.querySelector('#submitdiv');
|
||||
if (submitDiv && submitDiv.parentNode) {
|
||||
submitDiv.parentNode.appendChild(submitDiv);
|
||||
}
|
||||
|
||||
const champsContextuels = document.querySelector('#pods-meta-champs-contextuels');
|
||||
if (champsContextuels && champsContextuels.parentNode) {
|
||||
champsContextuels.parentNode.prepend(champsContextuels);
|
||||
}
|
||||
|
||||
initBodyLanguageTabs();
|
||||
initRefBibEditor();
|
||||
groupAxesCheckboxes();
|
||||
initAxesGroupObserver();
|
||||
updatePostboxVisibility();
|
||||
initDatePickerPopoverFix();
|
||||
initInfoPopovers();
|
||||
|
||||
// Place #pods-meta-documents-joints in #normal-sortables, right after
|
||||
// #pods-meta-champs-contextuels. This keeps it out of #post-body-content
|
||||
// (the body editor section) regardless of whether champsContextuels is
|
||||
// currently visible. When champsContextuels is hidden it takes no space,
|
||||
// so documentsJoints simply appears first in #normal-sortables.
|
||||
const documentsJoints = document.querySelector('#pods-meta-documents-joints');
|
||||
if (documentsJoints) {
|
||||
if (champsContextuels && champsContextuels.parentNode) {
|
||||
champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling);
|
||||
} else {
|
||||
const normalSortables = document.querySelector('#normal-sortables');
|
||||
if (normalSortables) normalSortables.prepend(documentsJoints);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject separator row for the Membres grid layout
|
||||
var membresTbody = document.querySelector('#pods-meta-membres .form-table tbody');
|
||||
if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) {
|
||||
var sep = document.createElement('tr');
|
||||
sep.className = 'membres-grid-separator';
|
||||
membresTbody.appendChild(sep);
|
||||
}
|
||||
updateMembresGridSeparator();
|
||||
}
|
||||
|
||||
var INFO_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 11v6"/><path d="M12 8v.01" stroke-width="2"/></svg>';
|
||||
var TRANSLATE_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
||||
|
||||
var TRANSLATE_LINES = [
|
||||
'Traduction en anglais apr\u00e8s //',
|
||||
'ex\u00a0: Texte en fran\u00e7ais // English text'
|
||||
];
|
||||
|
||||
// Tips without a `page` key default to 'post'.
|
||||
// type: 'translate' uses the globe icon + green button style.
|
||||
var INFO_TIPS = [
|
||||
// --- post edit page: info ---
|
||||
{
|
||||
selector: '.wp-heading-inline',
|
||||
lines: [
|
||||
'Saisir le titre anglais apr\u00e8s //',
|
||||
'ex\u00a0: Titre de l\u2019annonce // Title of the announcement'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-documents-joints .postbox-header h2',
|
||||
lines: [
|
||||
'Ajouter les images dans les documents.',
|
||||
'Ajouter les l\u00e9gendes comme titre du document.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-membres .postbox-header h2',
|
||||
lines: [
|
||||
'Le champ fonction change le libell\u00e9 de la liste de personnes cit\u00e9es.',
|
||||
'Le champ membre permet de lister les membres de Thalim li\u00e9s \u00e0 l\u2019annonce.',
|
||||
'Le champ autre personnes permet de lister des personnes ext\u00e9rieures \u00e0 Thalim.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-dates .postbox-header h2',
|
||||
lines: [
|
||||
'Pour entrer une date sans l\u2019heure, r\u00e9gler l\u2019heure sur 00\u202f:00.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2',
|
||||
lines: [
|
||||
'\u00c9pingler l\u2019annonce dans le diaporama la fait s\u2019afficher avant les autres.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-medias .postbox-header h2',
|
||||
lines: [
|
||||
'Pour ajouter un m\u00e9dia Canal\u00a0U, copier le lien depuis \u00ab\u00a0Citer cette ressource\u00a0\u00bb.',
|
||||
'ex\u00a0: https://www.canal-u.tv/166564'
|
||||
]
|
||||
},
|
||||
// --- post edit page: translate ---
|
||||
{ type: 'translate', selector: '#pods-meta-documents-joints .postbox-header h2', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-sous-titre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-lieu th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-organisation th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-intervention th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-candidat th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-realisation th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-dirige th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-redaction th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-auteur th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-responsable th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-autre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-concerne th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-directeur th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-direction-d-ouvrage th',lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-intervenant th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-participants th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-type-autre th', lines: TRANSLATE_LINES },
|
||||
// --- contenu_general edit page: translate ---
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-umr th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-thalim th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-siecles th', lines: TRANSLATE_LINES },
|
||||
// --- user/profile edit page: translate ---
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-4 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affiliation-autre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-de-these th', lines: TRANSLATE_LINES },
|
||||
// --- taxonomy edit pages: translate ---
|
||||
{ type: 'translate', page: 'taxonomy', selector: 'label[for="name"]', lines: TRANSLATE_LINES },
|
||||
// --- user/profile edit page: info ---
|
||||
{
|
||||
page: 'user',
|
||||
selector: '.pods-form-ui-label-pods-meta-identifiant-hal',
|
||||
lines: [
|
||||
'Renseigner votre idHAL (en lettres), pas votre PersonId (en chiffres).'
|
||||
]
|
||||
},
|
||||
{
|
||||
page: 'user',
|
||||
selector: '.pods-form-ui-label-pods-meta-affichage-du-statut-1',
|
||||
lines: [
|
||||
'Texte de statut affiché sur le profil publique.'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var _popoverCloseHandlerRegistered = false;
|
||||
|
||||
function initInfoPopovers(currentPage) {
|
||||
currentPage = currentPage || 'post';
|
||||
|
||||
INFO_TIPS.forEach(function(tip) {
|
||||
if ((tip.page || 'post') !== currentPage) return;
|
||||
|
||||
var el = document.querySelector(tip.selector);
|
||||
if (!el) return;
|
||||
|
||||
var isTranslate = tip.type === 'translate';
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = isTranslate ? 'thalim-translate-btn' : 'thalim-info-btn';
|
||||
btn.setAttribute('aria-label', isTranslate ? 'Traduction bilingue' : 'Informations');
|
||||
btn.innerHTML = isTranslate ? TRANSLATE_ICON : INFO_ICON;
|
||||
|
||||
var popover = document.createElement('div');
|
||||
popover.className = 'thalim-info-popover' + (isTranslate ? ' thalim-translate-popover' : '');
|
||||
popover.innerHTML = tip.lines.map(function(line) {
|
||||
return '<p>' + line + '</p>';
|
||||
}).join('');
|
||||
|
||||
var wrapper = document.createElement('span');
|
||||
wrapper.className = 'thalim-info-wrapper';
|
||||
wrapper.appendChild(btn);
|
||||
wrapper.appendChild(popover);
|
||||
|
||||
el.appendChild(wrapper);
|
||||
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = popover.classList.contains('is-open');
|
||||
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
|
||||
p.classList.remove('is-open');
|
||||
});
|
||||
if (!isOpen) {
|
||||
var rect = btn.getBoundingClientRect();
|
||||
popover.style.top = (rect.bottom + 6) + 'px';
|
||||
popover.style.left = (rect.left + rect.width / 2) + 'px';
|
||||
popover.classList.add('is-open');
|
||||
}
|
||||
});
|
||||
|
||||
popover.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
|
||||
if (!_popoverCloseHandlerRegistered) {
|
||||
_popoverCloseHandlerRegistered = true;
|
||||
document.addEventListener('click', function() {
|
||||
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
|
||||
p.classList.remove('is-open');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Only native WP field sections — never touch Pods tables (they may contain TinyMCE editors)
|
||||
var PROFILE_SECTION_KEYS = [
|
||||
'user-language-wrap',
|
||||
'user-first-name-wrap',
|
||||
'user-email-wrap',
|
||||
'user-pass1-wrap',
|
||||
'upload-avatar-row',
|
||||
];
|
||||
|
||||
// Desired order. Groups of 2 are wrapped in a flex row and displayed side by side.
|
||||
var PROFILE_ORDER = [
|
||||
['user-first-name-wrap', 'upload-avatar-row'],
|
||||
['user-email-wrap'],
|
||||
['user-language-wrap', 'user-pass1-wrap'],
|
||||
];
|
||||
|
||||
function reorderProfileSections() {
|
||||
var form = getProfileForm();
|
||||
if (!form) return;
|
||||
|
||||
var pairMap = {};
|
||||
|
||||
form.querySelectorAll('table.form-table').forEach(function(table) {
|
||||
PROFILE_SECTION_KEYS.forEach(function(key) {
|
||||
if (pairMap[key] || !table.querySelector('.' + key)) return;
|
||||
|
||||
// Find the associated heading: first try preceding sibling in same parent,
|
||||
// then look for an h2/h3 inside the same wrapper element.
|
||||
var h2 = null;
|
||||
var el = table.previousElementSibling;
|
||||
while (el) {
|
||||
if (el.tagName === 'H2' || el.tagName === 'H3') { h2 = el; break; }
|
||||
if (el.tagName === 'TABLE') break;
|
||||
el = el.previousElementSibling;
|
||||
}
|
||||
if (!h2 && table.parentElement !== form) {
|
||||
h2 = table.parentElement.querySelector('h2, h3');
|
||||
}
|
||||
|
||||
// The unit to move: if h2 and table share a non-form wrapper, move the wrapper.
|
||||
var wrapper = null;
|
||||
if (h2 && h2.parentElement !== form && h2.parentElement === table.parentElement) {
|
||||
wrapper = h2.parentElement;
|
||||
}
|
||||
|
||||
pairMap[key] = { h2: h2, table: table, wrapper: wrapper };
|
||||
});
|
||||
});
|
||||
|
||||
// Remove all matched units from DOM (dedup by actual element)
|
||||
var removed = new Set();
|
||||
function removeEl(el) {
|
||||
if (el && !removed.has(el)) { removed.add(el); el.remove(); }
|
||||
}
|
||||
Object.values(pairMap).forEach(function(unit) {
|
||||
if (unit.wrapper) { removeEl(unit.wrapper); }
|
||||
else { removeEl(unit.h2); removeEl(unit.table); }
|
||||
});
|
||||
|
||||
// Re-insert in declared order before the submit button
|
||||
var submitAnchor = form.querySelector('p.submit');
|
||||
function append(el) {
|
||||
if (submitAnchor && submitAnchor.parentNode) form.insertBefore(el, submitAnchor);
|
||||
else form.appendChild(el);
|
||||
}
|
||||
function appendUnit(unit) {
|
||||
if (unit.wrapper) { append(unit.wrapper); }
|
||||
else { if (unit.h2) append(unit.h2); append(unit.table); }
|
||||
}
|
||||
|
||||
PROFILE_ORDER.forEach(function(group) {
|
||||
var available = group.filter(function(key) { return !!pairMap[key]; });
|
||||
if (!available.length) return;
|
||||
|
||||
// Dedup: two keys may resolve to the same table/wrapper
|
||||
var seen = new Set();
|
||||
var units = [];
|
||||
available.forEach(function(key) {
|
||||
var unit = pairMap[key];
|
||||
var id = unit.wrapper || unit.table;
|
||||
if (!seen.has(id)) { seen.add(id); units.push(unit); }
|
||||
});
|
||||
|
||||
if (units.length === 1) {
|
||||
appendUnit(units[0]);
|
||||
} else {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'profile-section-row';
|
||||
units.forEach(function(unit) {
|
||||
var col = document.createElement('div');
|
||||
col.className = 'profile-section-col';
|
||||
if (unit.wrapper) { col.appendChild(unit.wrapper); }
|
||||
else { if (unit.h2) col.appendChild(unit.h2); col.appendChild(unit.table); }
|
||||
row.appendChild(col);
|
||||
});
|
||||
append(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initProfileEditors() {
|
||||
reorderProfileSections();
|
||||
initInfoPopovers('user');
|
||||
|
||||
// Hide the "À propos du compte" section heading
|
||||
document.querySelectorAll('#your-profile h2, #adduser h2, #createuser h2').forEach(function(h2) {
|
||||
if (h2.textContent.trim() === '\u00c0 propos du compte') {
|
||||
h2.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Rename "Rôle" label to "Rôle sur le site"
|
||||
var roleLabel = document.querySelector('label[for="role"]');
|
||||
if (roleLabel && roleLabel.textContent.trim() === 'R\u00f4le') {
|
||||
roleLabel.textContent = 'R\u00f4le sur le site';
|
||||
}
|
||||
}
|
||||
|
||||
// Gutenberg's Popover component closes on outside click via focusout detection.
|
||||
// But if focus never enters the popover, focusout never fires and clicking outside
|
||||
// does nothing. Fix: focus the popover container as soon as it appears in the DOM.
|
||||
function initDatePickerPopoverFix() {
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var added = mutations[i].addedNodes;
|
||||
for (var j = 0; j < added.length; j++) {
|
||||
var node = added[j];
|
||||
if (node.nodeType !== 1) continue;
|
||||
var content = node.classList.contains('components-popover__content')
|
||||
? node
|
||||
: node.querySelector && node.querySelector('.components-popover__content');
|
||||
if (content) {
|
||||
var c = content;
|
||||
requestAnimationFrame(function() {
|
||||
if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1');
|
||||
c.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function updateMembresGridSeparator() {
|
||||
var sep = document.querySelector('#pods-meta-membres .membres-grid-separator');
|
||||
if (!sep) return;
|
||||
var autreRows = document.querySelectorAll('#pods-meta-membres [class*="pods-form-ui-row-name-autre-"]');
|
||||
var anyVisible = Array.from(autreRows).some(function(row) {
|
||||
return row.style.display !== 'none';
|
||||
});
|
||||
sep.style.display = anyVisible ? '' : 'none';
|
||||
}
|
||||
|
||||
// Inject a "Type de programme" filter select into the taxonomy search form.
|
||||
// The form already has hidden taxonomy/post_type fields so the select value
|
||||
// is submitted with them and picked up by pre_get_terms server-side.
|
||||
function initProgrammeFilter() {
|
||||
var form = document.querySelector('form.search-form');
|
||||
if (!form) return;
|
||||
|
||||
var types = [
|
||||
'Programme subventionné',
|
||||
'Autre programme',
|
||||
'Ancien programme'
|
||||
];
|
||||
|
||||
// Read current filter value from the URL.
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var current = params.get('type_de_programme') || '';
|
||||
|
||||
var select = document.createElement('select');
|
||||
select.name = 'type_de_programme';
|
||||
select.id = 'filter-type-de-programme';
|
||||
select.style.cssText = 'margin-right:6px;';
|
||||
|
||||
var blank = document.createElement('option');
|
||||
blank.value = '';
|
||||
blank.textContent = 'Tous les types';
|
||||
select.appendChild(blank);
|
||||
|
||||
types.forEach(function(type) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = type;
|
||||
opt.textContent = type;
|
||||
if (type === current) opt.selected = true;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
// Insert before the first <p> (search-box) inside the form.
|
||||
var searchBox = form.querySelector('p.search-box');
|
||||
form.insertBefore(select, searchBox || null);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
renameArticlesToAnnonces();
|
||||
|
||||
if (isPostEditPage()) {
|
||||
setupBodyTabsDom();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (isPostEditPage()) {
|
||||
initPostEditPage();
|
||||
}
|
||||
if (isProfileEditPage()) {
|
||||
initProfileEditors();
|
||||
}
|
||||
var TRANSLATE_TAXONOMIES = ['axe_thematique', 'programme_de_recherche', 'post_tag'];
|
||||
var isTaxonomyListPage = TRANSLATE_TAXONOMIES.some(function(tax) {
|
||||
return window.location.search.indexOf('taxonomy=' + tax) !== -1;
|
||||
});
|
||||
if (isTaxonomyListPage) {
|
||||
initInfoPopovers('taxonomy');
|
||||
}
|
||||
if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) {
|
||||
initProgrammeFilter();
|
||||
}
|
||||
document.body.classList.add('admin-mods-ready');
|
||||
}, 100);
|
||||
|
||||
// Fallback: force reveal after 2s in case the 100ms path failed (e.g. JS error mid-init)
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('admin-mods-ready');
|
||||
}, 2000);
|
||||
|
||||
$('#pods-form-ui-pods-meta-categorie').change(function() {
|
||||
setTimeout(function() {
|
||||
updatePostboxVisibility();
|
||||
updateMembresGridSeparator();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
if (isProfileEditPage() || window.pagenow === 'edit-tags' || window.pagenow === 'term') {
|
||||
$(window).on('load', function() {
|
||||
var scope = getProfileForm() || document;
|
||||
scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
|
||||
if (!ta.id) return;
|
||||
ensureVisualMode(ta.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (isPodsModal()) {
|
||||
$(window).on('load', function() {
|
||||
var itemId = $('#post_ID').val();
|
||||
if (window.PodsDFV && itemId) {
|
||||
window.PodsDFV.setFieldValue('post', itemId, 'categorie', '12', 0);
|
||||
}
|
||||
|
||||
// Lock category select to 12 in iframe — delay to run after Pods React re-render
|
||||
setTimeout(function() {
|
||||
var $select = $('#pods-form-ui-pods-meta-categorie');
|
||||
if ($select.length) {
|
||||
$select.find('option').each(function() {
|
||||
this.disabled = this.value !== '12';
|
||||
});
|
||||
$select.val('12');
|
||||
}
|
||||
updatePostboxVisibility();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
119
js/adminFormRestore.js
Normal file
@@ -0,0 +1,119 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_PREFIX = 'thalim_form_restore_';
|
||||
var MAX_AGE_MS = 10 * 60 * 1000; // 10 min
|
||||
|
||||
function getPostId() {
|
||||
return $('#post_ID').val() || 'new';
|
||||
}
|
||||
|
||||
function getStorageKey() {
|
||||
return STORAGE_PREFIX + getPostId();
|
||||
}
|
||||
|
||||
function saveFormData() {
|
||||
var data = { timestamp: Date.now() };
|
||||
|
||||
var $title = $('#title');
|
||||
if ($title.length) data.title = $title.val();
|
||||
|
||||
var $content = $('#content');
|
||||
if ($content.length) {
|
||||
if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) {
|
||||
data.content = tinyMCE.get('content').getContent();
|
||||
} else {
|
||||
data.content = $content.val();
|
||||
}
|
||||
}
|
||||
|
||||
$('[name^="pods_meta_"]').each(function() {
|
||||
data[this.name] = $(this).val();
|
||||
});
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(getStorageKey(), JSON.stringify(data));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function restoreFormData() {
|
||||
var navEntries = performance.getEntriesByType('navigation');
|
||||
if (!navEntries.length || navEntries[0].type !== 'back_forward') return;
|
||||
|
||||
var key = getStorageKey();
|
||||
var stored, data;
|
||||
try {
|
||||
stored = sessionStorage.getItem(key);
|
||||
if (!stored) return;
|
||||
data = JSON.parse(stored);
|
||||
} catch (e) { return; }
|
||||
|
||||
if (!data.timestamp || Date.now() - data.timestamp > MAX_AGE_MS) {
|
||||
try { sessionStorage.removeItem(key); } catch (e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// Restaurer titre
|
||||
if (data.title !== undefined) $('#title').val(data.title);
|
||||
|
||||
// Restaurer contenu (TinyMCE ou textarea brut)
|
||||
if (data.content !== undefined) {
|
||||
$('#content').val(data.content);
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
var ed = tinyMCE.get('content');
|
||||
if (ed) {
|
||||
ed.setContent(data.content);
|
||||
} else {
|
||||
tinyMCE.on('AddEditor', function(e) {
|
||||
if (e.editor.id === 'content') {
|
||||
e.editor.on('init', function() {
|
||||
e.editor.setContent(data.content);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restaurer champs Pods après rendu React
|
||||
var restorePods = function() {
|
||||
setTimeout(function() {
|
||||
var postId = getPostId();
|
||||
Object.keys(data).forEach(function(name) {
|
||||
if (!name.startsWith('pods_meta_')) return;
|
||||
var fieldName = name.replace(/^pods_meta_/, '');
|
||||
var value = data[name];
|
||||
var $el = $('[name="' + name + '"]');
|
||||
if ($el.length) $el.val(value).trigger('change');
|
||||
if (window.PodsDFV && postId) {
|
||||
try { window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0); } catch (e) {}
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
restorePods();
|
||||
} else {
|
||||
$(window).on('load', restorePods);
|
||||
}
|
||||
|
||||
// Notice informative
|
||||
var $notice = $('<div class="notice notice-info is-dismissible"><p>Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.</p></div>');
|
||||
$('#wpbody-content').find('.wrap').first().find('h1').after($notice);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if (window.pagenow !== 'post' && window.pagenow !== 'post-new') return;
|
||||
|
||||
// Nettoyage si sauvegarde réussie
|
||||
if ($('.notice-success, #message.updated').length) {
|
||||
try { sessionStorage.removeItem(getStorageKey()); } catch (e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
$('#post').on('submit', saveFormData);
|
||||
restoreFormData();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
146
js/agendaView.js
Normal file
@@ -0,0 +1,146 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var toggleBtn = document.querySelector('.agenda-toggle-btn');
|
||||
var gridSections = document.getElementById('grid-sections');
|
||||
var agendaEl = document.getElementById('agenda-view');
|
||||
if (!toggleBtn || !agendaEl) return;
|
||||
|
||||
var swiper = null;
|
||||
var agendaPage = 0;
|
||||
var agendaDone = false;
|
||||
var agendaLoading = false;
|
||||
var swiperWrapper = document.getElementById('agenda-swiper-wrapper');
|
||||
var agendaSpinner = document.getElementById('agenda-spinner');
|
||||
|
||||
var categoryId = agendaEl.dataset.category || '';
|
||||
var includeChildren = agendaEl.dataset.includeChildren || '0';
|
||||
var axe = agendaEl.dataset.axe || '';
|
||||
var dateFrom = agendaEl.dataset.dateFrom || '';
|
||||
var dateTo = agendaEl.dataset.dateTo || '';
|
||||
|
||||
function showAgenda() {
|
||||
if (gridSections) gridSections.style.display = 'none';
|
||||
agendaEl.classList.add('is-active');
|
||||
toggleBtn.innerHTML =
|
||||
'<i class="iconoir-view-grid"></i>' +
|
||||
(agendaViewData.lang === 'en' ? 'Switch to grid view' : 'Passer à la vue grille');
|
||||
if (!swiper) {
|
||||
loadMoreAgenda(function (data) {
|
||||
var todayOffset = Math.max(0, ((data && data.today_offset) || 0) - 1);
|
||||
var anchorPage = Math.ceil((todayOffset + 1) / 12);
|
||||
if (anchorPage > 1) {
|
||||
chainLoadPages(2, anchorPage, function () {
|
||||
initSwiper(todayOffset);
|
||||
});
|
||||
} else {
|
||||
initSwiper(todayOffset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showGrid() {
|
||||
agendaEl.classList.remove('is-active');
|
||||
if (gridSections) gridSections.style.display = '';
|
||||
toggleBtn.innerHTML =
|
||||
'<i class="iconoir-calendar"></i>' +
|
||||
(agendaViewData.lang === 'en' ? 'Switch to agenda view' : 'Passer à la vue agenda');
|
||||
}
|
||||
|
||||
function initSwiper(initialSlide) {
|
||||
swiper = new Swiper(agendaEl.querySelector('.agenda-swiper'), {
|
||||
slidesPerView: 1.2,
|
||||
spaceBetween: 20,
|
||||
initialSlide: initialSlide || 0,
|
||||
navigation: {
|
||||
nextEl: agendaEl.querySelector('.agenda-swiper-prev'),
|
||||
prevEl: agendaEl.querySelector('.agenda-swiper-next'),
|
||||
},
|
||||
breakpoints: {
|
||||
640: { slidesPerView: 2, spaceBetween: 24 },
|
||||
1024: { slidesPerView: 3, spaceBetween: 32 },
|
||||
},
|
||||
on: {
|
||||
reachEnd: function () {
|
||||
if (!agendaDone) loadMoreAgenda();
|
||||
},
|
||||
},
|
||||
});
|
||||
swiper.changeLanguageDirection('rtl');
|
||||
}
|
||||
|
||||
function chainLoadPages(fromPage, toPage, callback) {
|
||||
if (fromPage > toPage) { callback(); return; }
|
||||
loadMoreAgenda(function () {
|
||||
chainLoadPages(fromPage + 1, toPage, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreAgenda(callback) {
|
||||
if (agendaLoading || agendaDone) return;
|
||||
agendaLoading = true;
|
||||
agendaPage++;
|
||||
agendaSpinner.style.display = 'flex';
|
||||
|
||||
var data = new FormData();
|
||||
data.append('action', 'load_more_agenda');
|
||||
data.append('page', agendaPage);
|
||||
data.append('nonce', agendaViewData.nonce);
|
||||
data.append('lang', agendaViewData.lang);
|
||||
if (categoryId) data.append('category', categoryId);
|
||||
if (includeChildren==='1') data.append('include_children', '1');
|
||||
if (axe) data.append('axe', axe);
|
||||
if (dateFrom) data.append('date_from', dateFrom);
|
||||
if (dateTo) data.append('date_to', dateTo);
|
||||
|
||||
fetch(agendaViewData.ajaxUrl, { method: 'POST', body: data })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (result) {
|
||||
agendaSpinner.style.display = 'none';
|
||||
agendaLoading = false;
|
||||
if (result.success && result.data.html) {
|
||||
var tmp = document.createElement('div');
|
||||
tmp.innerHTML = result.data.html;
|
||||
Array.from(tmp.children).forEach(function (slide) {
|
||||
if (swiper) {
|
||||
swiper.appendSlide(slide.outerHTML);
|
||||
} else {
|
||||
swiperWrapper.appendChild(slide);
|
||||
}
|
||||
});
|
||||
if (swiper) swiper.update();
|
||||
if (callback) callback(result.data);
|
||||
} else {
|
||||
agendaDone = true;
|
||||
if (callback) callback(result.data);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
agendaSpinner.style.display = 'none';
|
||||
agendaLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
toggleBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
if (agendaEl.classList.contains('is-active')) {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.delete('view');
|
||||
history.pushState({}, '', url.toString());
|
||||
showGrid();
|
||||
} else {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('view', 'agenda');
|
||||
history.pushState({}, '', url.toString());
|
||||
showAgenda();
|
||||
}
|
||||
});
|
||||
|
||||
// Initial state driven by server-side class
|
||||
if (agendaEl.classList.contains('is-active')) {
|
||||
showAgenda();
|
||||
}
|
||||
});
|
||||
}());
|
||||
577
js/animatedLogo.js
Normal file
@@ -0,0 +1,577 @@
|
||||
const CURSOR_INFLUENCE_INNER = 150;
|
||||
const CURSOR_INFLUENCE_OUTER = 300;
|
||||
const EASING_FACTOR = 0.03;
|
||||
|
||||
const COLORS = [
|
||||
'#e0775d',
|
||||
'#7cc0c6',
|
||||
'#e05680',
|
||||
'#46ae51',
|
||||
'#bb8dd9',
|
||||
'#f7ff29',
|
||||
];
|
||||
|
||||
class FloatingShape {
|
||||
constructor(element, originalX, originalY, width, height) {
|
||||
this.element = element;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.originalX = originalX;
|
||||
this.originalY = originalY;
|
||||
this.posX = originalX;
|
||||
this.posY = originalY;
|
||||
this.targetX = originalX;
|
||||
this.targetY = originalY;
|
||||
}
|
||||
|
||||
update(mouseX, mouseY) {
|
||||
const shapeCenterX = this.originalX + this.width / 2;
|
||||
const shapeCenterY = this.originalY + this.height / 2;
|
||||
|
||||
const dx = mouseX - shapeCenterX;
|
||||
const dy = mouseY - shapeCenterY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
const innerRadius = CURSOR_INFLUENCE_INNER / 2;
|
||||
const outerRadius = CURSOR_INFLUENCE_OUTER / 2;
|
||||
|
||||
if (distance < outerRadius && distance > 0) {
|
||||
const dirX = dx / distance;
|
||||
const dirY = dy / distance;
|
||||
|
||||
let strength, maxDisplacement;
|
||||
|
||||
if (distance < innerRadius) {
|
||||
strength = (innerRadius - distance) / innerRadius;
|
||||
maxDisplacement = innerRadius;
|
||||
} else {
|
||||
const outerZoneProgress = (outerRadius - distance) / (outerRadius - innerRadius);
|
||||
strength = outerZoneProgress * 0.5;
|
||||
maxDisplacement = innerRadius * 0.6;
|
||||
}
|
||||
|
||||
this.targetX = this.originalX - dirX * strength * maxDisplacement;
|
||||
this.targetY = this.originalY - dirY * strength * maxDisplacement;
|
||||
} else {
|
||||
this.targetX = this.originalX;
|
||||
this.targetY = this.originalY;
|
||||
}
|
||||
|
||||
this.posX += (this.targetX - this.posX) * EASING_FACTOR;
|
||||
this.posY += (this.targetY - this.posY) * EASING_FACTOR;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.element.style.transform = `translate3d(${this.posX}px, ${this.posY}px, 0)`;
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingShapesManager {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
if (!this.container) {
|
||||
console.error(`Container #${containerId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.shapes = [];
|
||||
this.strokePaths = []; // Store all stroke paths for fill control
|
||||
this.fillShape = null; // Reference to the filled shape SVG for fade control
|
||||
this.fillShapeReady = false; // Track if fillshape animation has completed
|
||||
this.thalimText = null; // Reference to the THALIM text element
|
||||
this.textReady = false; // Track if text animation has completed
|
||||
this.mouseX = 0;
|
||||
this.mouseY = 0;
|
||||
this.animationId = null;
|
||||
this.isTouching = false; // Track if currently in a touch interaction
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const containerRect = this.container.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width;
|
||||
const containerHeight = containerRect.height;
|
||||
|
||||
const shapeConfigs = [
|
||||
{
|
||||
id: 'shape-1',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape1.svg`,
|
||||
baseWidth: 53.564522,
|
||||
baseHeight: 112.37409,
|
||||
scale: 1.5,
|
||||
posX: this.isMobile() ? 20: 35,
|
||||
posY: -20,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 1.9,
|
||||
gradientStart: COLORS[0], // orange
|
||||
gradientEnd: '#e0b7ad'
|
||||
},
|
||||
{
|
||||
id: 'shape-2',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape4.svg`,
|
||||
baseWidth: 74.08564,
|
||||
baseHeight: 121.90051,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: this.isMobile() ? -8 : -5,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 2.5,
|
||||
gradientStart: '#aec4c6',
|
||||
gradientEnd: COLORS[1] // blue
|
||||
},
|
||||
{
|
||||
id: 'shape-3',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape3.svg`,
|
||||
baseWidth: 159.16571,
|
||||
baseHeight: 87.756729,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: -10,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 3.1,
|
||||
gradientStart: COLORS[2], // pink
|
||||
gradientEnd: '#e0b0be'
|
||||
},
|
||||
{
|
||||
id: 'shape-4',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape2.svg`,
|
||||
baseWidth: 143.26076,
|
||||
baseHeight: 20.26207,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: this.isMobile() ? 20 : 45,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 3.7,
|
||||
gradientStart: '#8bc491',
|
||||
gradientEnd: COLORS[3] // green
|
||||
},
|
||||
{
|
||||
id: 'shape-5',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape5.svg`,
|
||||
baseWidth: 155.66518,
|
||||
baseHeight: 87.785599,
|
||||
scale: 1.5,
|
||||
posX: this.isMobile() ? 13 : 19.5,
|
||||
posY: this.isMobile() ? -18 : -23.5,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 4.3,
|
||||
gradientStart: COLORS[4], // purple
|
||||
gradientEnd: '#c9b0d9'
|
||||
},
|
||||
{
|
||||
id: 'fillshape',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/fillshape.svg`,
|
||||
baseWidth: 142.78297,
|
||||
baseHeight: 72.903015,
|
||||
scale: this.isMobile() ? 0.78 : 1.47,
|
||||
posX: this.isMobile() ? 7.5 : 11,
|
||||
posY: this.isMobile() ? -13 : -14.5,
|
||||
isFilled: true,
|
||||
targetOpacity: 1,
|
||||
animationDuration: 0.9,
|
||||
animationDelay: 3.7 // Start after longest stroke animation
|
||||
}
|
||||
];
|
||||
|
||||
// Create shapes with center-based positioning
|
||||
const centerX = containerWidth / 2;
|
||||
const centerY = containerHeight / 2;
|
||||
|
||||
// Apply mobile scale on smaller viewports
|
||||
const mobileScale = 0.8;
|
||||
shapeConfigs.forEach((config) => {
|
||||
const scale = config.id === 'fillshape' ? config.scale : this.isMobile() ? mobileScale : config.scale;
|
||||
const scaledWidth = config.baseWidth * scale;
|
||||
const scaledHeight = config.baseHeight * scale;
|
||||
|
||||
// Calculate position from center with offset, minus half dimensions to center the shape
|
||||
const x = centerX + (config.posX || 0) - scaledWidth / 2;
|
||||
const y = centerY + (config.posY || 0) - scaledHeight / 2;
|
||||
|
||||
// Create animated stroke element
|
||||
this.createAnimatedStrokeElement(config, scaledWidth, scaledHeight, x, y);
|
||||
});
|
||||
|
||||
// Create THALIM text overlay
|
||||
this.createThalimText(centerX, centerY);
|
||||
|
||||
this.container.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||
this.container.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
|
||||
this.container.addEventListener('touchstart', this.handleTouchStart.bind(this));
|
||||
this.container.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
|
||||
this.container.addEventListener('touchend', this.handleTouchEnd.bind(this));
|
||||
window.addEventListener('resize', this.handleResize.bind(this));
|
||||
|
||||
// Set sketch margin on mobile based on hero-logos height
|
||||
this.setSketchMarginOnMobile();
|
||||
|
||||
this.animate();
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
return window.innerWidth < 768; // tablet breakpoint from scss/_variables.scss
|
||||
}
|
||||
|
||||
setSketchMarginOnMobile() {
|
||||
if (this.isMobile()) {
|
||||
const heroLogos = document.querySelector('.hero-logos');
|
||||
if (heroLogos) {
|
||||
const logoHeight = heroLogos.offsetHeight;
|
||||
this.container.style.marginTop = `${logoHeight}px`;
|
||||
}
|
||||
} else {
|
||||
// Reset margin-top on desktop (handled by CSS)
|
||||
this.container.style.marginTop = '';
|
||||
}
|
||||
}
|
||||
|
||||
async createAnimatedStrokeElement(config, width, height, x, y) {
|
||||
try {
|
||||
const response = await fetch(config.svgPath);
|
||||
const svgText = await response.text();
|
||||
|
||||
const parser = new DOMParser();
|
||||
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
|
||||
const svgElement = svgDoc.querySelector('svg');
|
||||
|
||||
if (!svgElement) {
|
||||
console.error(`Failed to parse SVG for ${config.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create shape div for physics
|
||||
const shapeDiv = document.createElement('div');
|
||||
shapeDiv.className = 'floating-shape';
|
||||
shapeDiv.id = config.id;
|
||||
shapeDiv.style.zIndex = config.isFilled ? '10' : '1'; // Fillshape above strokes
|
||||
|
||||
// Set SVG dimensions
|
||||
svgElement.setAttribute('width', width);
|
||||
svgElement.setAttribute('height', height);
|
||||
|
||||
if (config.isFilled) {
|
||||
// Handle filled shapes (fade-in animation)
|
||||
svgElement.style.opacity = '0';
|
||||
svgElement.style.transition = 'opacity 0.3s ease-out'; // Smooth fade on mouse interaction
|
||||
const duration = config.animationDuration || 1.5;
|
||||
const delay = config.animationDelay || 0;
|
||||
const targetOpacity = config.targetOpacity || 0.5;
|
||||
|
||||
svgElement.style.setProperty('--target-opacity', targetOpacity);
|
||||
svgElement.style.animation = `fadeIn ${duration}s ease-in-out ${delay}s forwards`;
|
||||
|
||||
// Remove animation after it completes so we can control opacity manually
|
||||
setTimeout(() => {
|
||||
svgElement.style.animation = 'none';
|
||||
svgElement.style.opacity = targetOpacity;
|
||||
this.fillShapeReady = true; // Mark fillshape as ready for interaction
|
||||
}, (delay + duration) * 1000);
|
||||
|
||||
// Store reference for mouse interaction
|
||||
this.fillShape = svgElement;
|
||||
this.fillShapeOpacity = targetOpacity;
|
||||
|
||||
// Position the fillshape div (since it's not in the physics system)
|
||||
shapeDiv.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
||||
} else {
|
||||
// Handle stroked shapes (gradient + stroke drawing animation)
|
||||
// Create gradient for stroke
|
||||
const gradientId = `gradient-${config.id}`;
|
||||
|
||||
// Create defs element if it doesn't exist
|
||||
let defs = svgElement.querySelector('defs');
|
||||
if (!defs) {
|
||||
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
svgElement.insertBefore(defs, svgElement.firstChild);
|
||||
}
|
||||
|
||||
// Create linear gradient
|
||||
const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
||||
gradient.setAttribute('id', gradientId);
|
||||
gradient.setAttribute('x1', '0%');
|
||||
gradient.setAttribute('y1', '0%');
|
||||
gradient.setAttribute('x2', '100%');
|
||||
gradient.setAttribute('y2', '100%');
|
||||
|
||||
// Create gradient stops
|
||||
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop1.setAttribute('offset', '0%');
|
||||
stop1.setAttribute('stop-color', config.gradientStart);
|
||||
|
||||
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop2.setAttribute('offset', '100%');
|
||||
stop2.setAttribute('stop-color', config.gradientEnd);
|
||||
|
||||
gradient.appendChild(stop1);
|
||||
gradient.appendChild(stop2);
|
||||
defs.appendChild(gradient);
|
||||
|
||||
// Apply stroke styling and animation to all paths in the SVG
|
||||
const paths = svgElement.querySelectorAll('path, polyline, polygon, line, circle, ellipse, rect');
|
||||
paths.forEach(path => {
|
||||
// Set stroke properties
|
||||
path.style.fill = 'white';
|
||||
path.style.fillOpacity = '0'; // Start invisible
|
||||
path.style.transition = 'fill-opacity 0.5s ease-in-out'; // Smooth fill changes
|
||||
path.style.stroke = `url(#${gradientId})`;
|
||||
// Don't override stroke-width - preserve SVG's compensated values
|
||||
path.style.strokeLinecap = 'round';
|
||||
path.style.strokeLinejoin = 'round';
|
||||
|
||||
// Calculate path length for animation
|
||||
const pathLength = path.getTotalLength ? path.getTotalLength() : 1000;
|
||||
|
||||
// Set CSS variable for path length
|
||||
path.style.setProperty('--path-length', pathLength);
|
||||
|
||||
// Set up stroke dash animation
|
||||
path.style.strokeDasharray = pathLength;
|
||||
path.style.strokeDashoffset = pathLength;
|
||||
|
||||
// Create CSS animation (plays once and stays completed)
|
||||
const duration = config.animationDuration || 3;
|
||||
path.style.animation = `drawStroke ${duration}s ease-in-out forwards`;
|
||||
|
||||
// Store path reference for fill control
|
||||
this.strokePaths.push(path);
|
||||
});
|
||||
|
||||
// Fade in white fill when text starts appearing (at 4.3s)
|
||||
setTimeout(() => {
|
||||
paths.forEach(path => {
|
||||
path.style.fillOpacity = '0.7';
|
||||
});
|
||||
}, 4300);
|
||||
}
|
||||
|
||||
// Append SVG to shape div
|
||||
shapeDiv.appendChild(svgElement);
|
||||
this.container.appendChild(shapeDiv);
|
||||
|
||||
// Create FloatingShape instance (only for shapes with physics, not filled shapes)
|
||||
if (!config.isFilled) {
|
||||
const floatingShape = new FloatingShape(shapeDiv, x, y, width, height);
|
||||
this.shapes.push(floatingShape);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error loading SVG for ${config.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
createThalimText(centerX, centerY) {
|
||||
|
||||
// Create container for the text
|
||||
const textContainer = document.createElement('div');
|
||||
textContainer.className = 'thalim-text';
|
||||
textContainer.style.top = '-20px';
|
||||
textContainer.style.left = '10px';
|
||||
textContainer.style.transform = `translate3d(${centerX}px, ${centerY}px, 0) translate(-50%, -50%)`; // Center the text
|
||||
textContainer.style.color = '#000000cc';
|
||||
|
||||
// Create individual letter spans
|
||||
const letters = 'thalim'.split('');
|
||||
letters.forEach((letter, index) => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = letter;
|
||||
span.style.opacity = '0';
|
||||
span.style.animation = `letterAppear 0.8s ease-in-out ${4.2 + index * 0.1}s forwards`;
|
||||
textContainer.appendChild(span);
|
||||
});
|
||||
|
||||
// Mark text as ready after last letter finishes animating
|
||||
// Last letter: 4.2s + 0.5s (6th letter) + 0.8s (duration) = 5.5s
|
||||
setTimeout(() => {
|
||||
this.textReady = true;
|
||||
}, 5500);
|
||||
|
||||
this.container.appendChild(textContainer);
|
||||
this.thalimText = textContainer;
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
// Ignore mouse events during touch interactions (prevents interference from synthetic mouse events)
|
||||
if (this.isTouching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
this.mouseX = e.clientX - rect.left;
|
||||
this.mouseY = e.clientY - rect.top;
|
||||
|
||||
// Fade out fillshape and text on mouse move (only if they've finished appearing)
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
this.fillShape.style.opacity = '0';
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
this.thalimText.style.opacity = '0';
|
||||
}
|
||||
// Fade out white fills on stroke shapes
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0';
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
// Move mouse far away to trigger return animation
|
||||
this.mouseX = -10000;
|
||||
this.mouseY = -10000;
|
||||
|
||||
// Fade fillshape and text back in after delay (let strokes settle first)
|
||||
// Only if they have completed their initial animations
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
setTimeout(() => {
|
||||
this.fillShape.style.opacity = this.fillShapeOpacity;
|
||||
}, 800); // Delay to allow strokes to return to position
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
setTimeout(() => {
|
||||
this.thalimText.style.opacity = '1';
|
||||
}, 800);
|
||||
}
|
||||
// Fade white fills back in on stroke shapes
|
||||
setTimeout(() => {
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0.7';
|
||||
});
|
||||
}, 800);
|
||||
}
|
||||
|
||||
handleTouchStart() {
|
||||
// Mark that we're in a touch interaction to prevent mouse event interference
|
||||
this.isTouching = true;
|
||||
}
|
||||
|
||||
handleTouchMove(e) {
|
||||
e.preventDefault(); // Prevent scrolling while interacting with sketch
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
const touch = e.touches[0];
|
||||
this.mouseX = touch.clientX - rect.left;
|
||||
this.mouseY = touch.clientY - rect.top;
|
||||
|
||||
// Fade out fillshape and text on touch move (only if they've finished appearing)
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
this.fillShape.style.opacity = '0';
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
this.thalimText.style.opacity = '0';
|
||||
}
|
||||
// Fade out white fills on stroke shapes
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0';
|
||||
});
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
// Reset shapes to original position
|
||||
this.handleMouseLeave();
|
||||
|
||||
// Clear touch flag after a delay to ignore synthetic mouse events
|
||||
setTimeout(() => {
|
||||
this.isTouching = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
// Don't refresh on mobile (prevents refresh during scroll when browser bar appears/disappears)
|
||||
if (this.isMobile()) {
|
||||
// Still update margin-top on mobile if needed
|
||||
this.setSketchMarginOnMobile();
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounced resize: only recreate after user stops resizing for 250ms
|
||||
// This prevents page crashes from rapid resize events
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
// Stop animation loop before destroying
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
|
||||
// Clear shapes and container
|
||||
this.shapes = [];
|
||||
this.container.innerHTML = '';
|
||||
|
||||
// Reinitialize
|
||||
this.init();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
animate() {
|
||||
// Update all shapes
|
||||
this.shapes.forEach(shape => {
|
||||
shape.update(this.mouseX, this.mouseY);
|
||||
shape.render();
|
||||
});
|
||||
|
||||
// Continue loop
|
||||
this.animationId = requestAnimationFrame(this.animate.bind(this));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
this.container.removeEventListener('mousemove', this.handleMouseMove);
|
||||
this.container.removeEventListener('mouseleave', this.handleMouseLeave);
|
||||
this.container.removeEventListener('touchstart', this.handleTouchStart);
|
||||
this.container.removeEventListener('touchmove', this.handleTouchMove);
|
||||
this.container.removeEventListener('touchend', this.handleTouchEnd);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject CSS for stroke and fade animations
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes drawStroke {
|
||||
0% {
|
||||
stroke-dashoffset: var(--path-length);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: var(--target-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes letterAppear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.floatingShapes = new FloatingShapesManager('sketch');
|
||||
});
|
||||
} else {
|
||||
window.floatingShapes = new FloatingShapesManager('sketch');
|
||||
}
|
||||
28
js/annoncesSwiper.js
Normal file
@@ -0,0 +1,28 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-swiper]').forEach(function (section) {
|
||||
new Swiper(section.querySelector('.swiper'), {
|
||||
navigation: {
|
||||
addIcons: false,
|
||||
nextEl: section.querySelector('.swiper-button-next'),
|
||||
prevEl: section.querySelector('.swiper-button-prev'),
|
||||
},
|
||||
autoplay: {
|
||||
delay: 5000,
|
||||
disableOnInteraction: false,
|
||||
pauseOnMouseEnter: true,
|
||||
},
|
||||
slidesPerView: 1,
|
||||
spaceBetween: 30,
|
||||
breakpoints: {
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
spaceBetween: 40,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 3,
|
||||
spaceBetween: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
138
js/categoryFilters.js
Normal file
@@ -0,0 +1,138 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// ── Filters toggle ────────────────────────────────────────
|
||||
var toggleBtn = document.getElementById('category-filters-toggle');
|
||||
var filtersEl = document.getElementById('category-filters');
|
||||
|
||||
if (toggleBtn && filtersEl) {
|
||||
toggleBtn.addEventListener('click', function () {
|
||||
var isOpen = filtersEl.classList.toggle('is-open');
|
||||
toggleBtn.classList.toggle('is-open', isOpen);
|
||||
toggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
var dateBtn = document.getElementById('filter-date-btn');
|
||||
var datePopover = document.getElementById('filter-date-popover');
|
||||
var dateFrom = document.getElementById('filter-date-from');
|
||||
var dateTo = document.getElementById('filter-date-to');
|
||||
var dateApply = document.getElementById('filter-date-apply');
|
||||
var axeBtn = document.getElementById('filter-axe-btn');
|
||||
var axePopover = document.getElementById('filter-axe-popover');
|
||||
|
||||
if (!dateBtn) return;
|
||||
|
||||
// Build URL with updated query params
|
||||
function buildUrl(params) {
|
||||
var url = new URL(window.location.href);
|
||||
Object.keys(params).forEach(function (key) {
|
||||
if (params[key]) {
|
||||
url.searchParams.set(key, params[key]);
|
||||
} else {
|
||||
url.searchParams.delete(key);
|
||||
}
|
||||
});
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
var y = d.getFullYear();
|
||||
var m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
var day = String(d.getDate()).padStart(2, '0');
|
||||
return y + '-' + m + '-' + day;
|
||||
}
|
||||
|
||||
// Dropdown toggle
|
||||
function openDropdown(popover) {
|
||||
popover.style.display = '';
|
||||
popover.closest('.filter-dd').classList.add('is-open');
|
||||
}
|
||||
|
||||
function closeDropdown(popover) {
|
||||
popover.style.display = 'none';
|
||||
popover.closest('.filter-dd').classList.remove('is-open');
|
||||
}
|
||||
|
||||
function toggleDropdown(popover) {
|
||||
if (popover.style.display === 'none') {
|
||||
openDropdown(popover);
|
||||
} else {
|
||||
closeDropdown(popover);
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdowns on outside click
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('#filter-date-dd')) {
|
||||
closeDropdown(datePopover);
|
||||
}
|
||||
if (axePopover && !e.target.closest('#filter-axe-dd')) {
|
||||
closeDropdown(axePopover);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Date dropdown ---
|
||||
dateBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (axePopover) closeDropdown(axePopover);
|
||||
toggleDropdown(datePopover);
|
||||
});
|
||||
|
||||
// Date presets
|
||||
datePopover.addEventListener('click', function (e) {
|
||||
var preset = e.target.dataset.preset;
|
||||
if (!preset) return;
|
||||
|
||||
var now = new Date();
|
||||
var from, to;
|
||||
|
||||
if (preset === 'week') {
|
||||
var day = now.getDay();
|
||||
var diff = day === 0 ? 6 : day - 1;
|
||||
from = new Date(now);
|
||||
from.setDate(now.getDate() - diff);
|
||||
to = new Date(from);
|
||||
to.setDate(from.getDate() + 6);
|
||||
} else if (preset === 'month') {
|
||||
from = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
} else if (preset === 'lastmonth') {
|
||||
from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
to = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
} else if (preset === 'upcoming') {
|
||||
from = now;
|
||||
to = null;
|
||||
}
|
||||
|
||||
window.location.href = buildUrl({
|
||||
date_from: formatDate(from),
|
||||
date_to: to ? formatDate(to) : ''
|
||||
});
|
||||
});
|
||||
|
||||
// Date apply → navigate
|
||||
dateApply.addEventListener('click', function () {
|
||||
window.location.href = buildUrl({
|
||||
date_from: dateFrom.value,
|
||||
date_to: dateTo.value
|
||||
});
|
||||
});
|
||||
|
||||
// --- Axe dropdown ---
|
||||
if (axeBtn && axePopover) {
|
||||
axeBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
closeDropdown(datePopover);
|
||||
toggleDropdown(axePopover);
|
||||
});
|
||||
|
||||
axePopover.addEventListener('click', function (e) {
|
||||
var li = e.target.closest('[data-axe-id]');
|
||||
if (!li) return;
|
||||
if (li.dataset.axeHref) {
|
||||
window.location.href = li.dataset.axeHref;
|
||||
} else {
|
||||
window.location.href = buildUrl({ axe: li.dataset.axeId });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
22
js/coloredWordsHero.js
Normal file
@@ -0,0 +1,22 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.querySelector('body').classList.contains('home')) {
|
||||
const colors = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
|
||||
const timeouts = new Map();
|
||||
|
||||
// Color changing on hover
|
||||
document.querySelectorAll('.color-changer').forEach(element => {
|
||||
element.addEventListener('mouseenter', (e) => {
|
||||
if (timeouts.has(e.target)) {
|
||||
clearTimeout(timeouts.get(e.target));
|
||||
}
|
||||
|
||||
e.target.style.color = colors[Math.floor(Math.random() * colors.length)];
|
||||
const timeoutId = setTimeout(() => {
|
||||
e.target.style.color = 'black';
|
||||
timeouts.delete(e.target);
|
||||
}, 2000);
|
||||
timeouts.set(e.target, timeoutId);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
43
js/fitPostCardTitle.js
Normal file
@@ -0,0 +1,43 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function fitTitles() {
|
||||
var cards = document.querySelectorAll('.post-card');
|
||||
cards.forEach(function (card) {
|
||||
var h2 = card.querySelector('.gradient-container h2');
|
||||
if (!h2) return;
|
||||
|
||||
var container = h2.closest('.gradient-container');
|
||||
var maxHeight = container.clientHeight;
|
||||
var fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
|
||||
var minSize = 12;
|
||||
|
||||
h2.style.fontSize = '';
|
||||
fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
|
||||
|
||||
while (h2.scrollHeight > maxHeight && fontSize > minSize) {
|
||||
fontSize -= 1;
|
||||
h2.style.fontSize = fontSize + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.fitPostCardTitles = fitTitles;
|
||||
|
||||
fitTitles();
|
||||
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(fitTitles, 200);
|
||||
});
|
||||
|
||||
// Re-fit after infinite scroll loads new cards
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
if (m.addedNodes.length) fitTitles();
|
||||
});
|
||||
});
|
||||
var grid = document.getElementById('post-grid');
|
||||
if (grid) {
|
||||
observer.observe(grid, { childList: true });
|
||||
}
|
||||
});
|
||||
37
js/frenchTypography.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* French typography: replaces normal spaces with non-breaking spaces
|
||||
* where French typographic rules require them.
|
||||
*/
|
||||
function applyFrenchTypography(elements) {
|
||||
elements.forEach(function (el) {
|
||||
var html = el.innerHTML;
|
||||
// Collapse multiple spaces into one (outside HTML tags)
|
||||
html = html.replace(/([^<>]) {2,}(?=[^<>])/g, '$1 ');
|
||||
// Space before ? ! : ; »
|
||||
html = html.replace(/ ([?!:;»])/g, '\u00A0$1');
|
||||
// Space after «
|
||||
html = html.replace(/(«) /g, '$1\u00A0');
|
||||
// "p. 42" → non-breaking space after "p."
|
||||
html = html.replace(/\bp\. /g, 'p.\u00A0');
|
||||
// "n° 3" → non-breaking space after "n°"
|
||||
html = html.replace(/\bn° /g, 'n°\u00A0');
|
||||
el.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
applyFrenchTypography(document.querySelectorAll('.post-card h2, .post-card .post-card__title'));
|
||||
|
||||
// Re-apply on any post-card dynamically added anywhere on the page
|
||||
new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
m.addedNodes.forEach(function (node) {
|
||||
if (node.nodeType !== 1) return;
|
||||
var targets = node.classList && node.classList.contains('post-card')
|
||||
? node.querySelectorAll('h2, .post-card__title')
|
||||
: node.querySelectorAll('.post-card h2, .post-card .post-card__title');
|
||||
applyFrenchTypography(targets);
|
||||
});
|
||||
});
|
||||
}).observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
16
js/imageSwiper.js
Normal file
@@ -0,0 +1,16 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-image-swiper]').forEach(function (container) {
|
||||
new Swiper(container.querySelector('.swiper'), {
|
||||
slidesPerView: 1,
|
||||
loop: true,
|
||||
navigation: {
|
||||
nextEl: container.querySelector('.swiper-button-next'),
|
||||
prevEl: container.querySelector('.swiper-button-prev'),
|
||||
},
|
||||
pagination: {
|
||||
el: container.querySelector('.swiper-pagination'),
|
||||
clickable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
71
js/infiniteScroll.js
Normal file
@@ -0,0 +1,71 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var sentinel = document.getElementById('scroll-sentinel');
|
||||
var grid = document.getElementById('post-grid');
|
||||
var spinner = document.getElementById('scroll-spinner');
|
||||
|
||||
if (!sentinel || !grid) return;
|
||||
|
||||
var page = 1;
|
||||
var loading = false;
|
||||
var done = false;
|
||||
|
||||
// Read filter params from sentinel data attributes (set server-side)
|
||||
var categoryId = sentinel.dataset.category || '';
|
||||
var axe = sentinel.dataset.axe || '';
|
||||
var dateFrom = sentinel.dataset.dateFrom || '';
|
||||
var dateTo = sentinel.dataset.dateTo || '';
|
||||
var taxonomy = sentinel.dataset.taxonomy || '';
|
||||
var termId = sentinel.dataset.term || '';
|
||||
var catFilter = sentinel.dataset.filterCat || '';
|
||||
var filterAutres = sentinel.dataset.filterAutres || '';
|
||||
var excludeCats = sentinel.dataset.excludeCats || '';
|
||||
var searchQuery = sentinel.dataset.search || '';
|
||||
|
||||
function fetchPosts() {
|
||||
if (loading || done) return;
|
||||
loading = true;
|
||||
page++;
|
||||
spinner.style.display = 'flex';
|
||||
|
||||
var data = new FormData();
|
||||
data.append('action', 'load_more_posts');
|
||||
data.append('page', page);
|
||||
data.append('nonce', infiniteScrollData.nonce);
|
||||
data.append('lang', infiniteScrollData.lang || 'fr');
|
||||
if (categoryId) data.append('category', categoryId);
|
||||
if (axe) data.append('axe', axe);
|
||||
if (dateFrom) data.append('date_from', dateFrom);
|
||||
if (dateTo) data.append('date_to', dateTo);
|
||||
if (taxonomy) data.append('taxonomy', taxonomy);
|
||||
if (termId) data.append('term', termId);
|
||||
if (catFilter) data.append('filter_cat', catFilter);
|
||||
if (filterAutres) data.append('filter_autres', filterAutres);
|
||||
if (excludeCats) data.append('exclude_cats', excludeCats);
|
||||
if (searchQuery) data.append('search', searchQuery);
|
||||
|
||||
fetch(infiniteScrollData.ajaxUrl, { method: 'POST', body: data })
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (result) {
|
||||
spinner.style.display = 'none';
|
||||
if (result.success && result.data.html) {
|
||||
grid.insertAdjacentHTML('beforeend', result.data.html);
|
||||
loading = false;
|
||||
} else {
|
||||
done = true;
|
||||
observer.disconnect();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
spinner.style.display = 'none';
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
if (entries[0].isIntersecting) {
|
||||
fetchPosts();
|
||||
}
|
||||
}, { rootMargin: '200px' });
|
||||
|
||||
observer.observe(sentinel);
|
||||
});
|
||||
249
js/keywordCloud.js
Normal file
@@ -0,0 +1,249 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var container = document.getElementById('keyword-container');
|
||||
var tags = window.thalimTags;
|
||||
|
||||
if (!container || !tags || !tags.length) return;
|
||||
|
||||
// — init —
|
||||
|
||||
var GAP = 14;
|
||||
var COLORS = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
|
||||
var colorTimeouts = new Map();
|
||||
|
||||
// Mélange aléatoire pour varier la disposition à chaque chargement
|
||||
var shuffled = tags.slice().sort(function () { return Math.random() - 0.5; });
|
||||
|
||||
// Crée les éléments une seule fois (réutilisés à chaque layout)
|
||||
var items = shuffled.map(function (tag) {
|
||||
var el = document.createElement('a');
|
||||
el.className = 'keyword';
|
||||
el.href = tag.url;
|
||||
el.textContent = tag.name;
|
||||
|
||||
el.addEventListener('mouseenter', function () {
|
||||
if (colorTimeouts.has(el)) clearTimeout(colorTimeouts.get(el));
|
||||
el.style.color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
colorTimeouts.set(el, setTimeout(function () {
|
||||
el.style.color = '';
|
||||
colorTimeouts.delete(el);
|
||||
}, 2000));
|
||||
});
|
||||
|
||||
container.appendChild(el);
|
||||
return { el: el, w: 0, h: 0 };
|
||||
});
|
||||
|
||||
var gradOverlay = null;
|
||||
var lastLayoutWidth = 0;
|
||||
|
||||
function layoutCloud() {
|
||||
var cw = container.offsetWidth;
|
||||
if (cw === lastLayoutWidth) return;
|
||||
lastLayoutWidth = cw;
|
||||
|
||||
var cx = cw / 2;
|
||||
var isMobile = cw < 768;
|
||||
var a = cx - GAP; // demi-grand axe horizontal
|
||||
var b = Math.round(cw * (isMobile ? 0.45 : 0.20)); // demi-petit axe vertical (plus haut sur mobile)
|
||||
var R_V = Math.round(b * (isMobile ? 0.45 : 0.70)); // demi-axe vertical de la zone d'exclusion (plus petit sur mobile)
|
||||
var R_H = Math.round(b * (isMobile ? 0.70 : 1.15)); // demi-axe horizontal (plus petit sur mobile)
|
||||
|
||||
// Re-mesurer les éléments (la taille peut changer avec le viewport)
|
||||
// +1 compense les arrondis sub-pixel sur écrans haute densité
|
||||
items.forEach(function (item) {
|
||||
var rect = item.el.getBoundingClientRect();
|
||||
item.w = Math.ceil(rect.width) + 1;
|
||||
item.h = Math.ceil(rect.height) + 1;
|
||||
});
|
||||
|
||||
var placed = [];
|
||||
|
||||
function hasOverlap(x, y, w, h) {
|
||||
for (var i = 0; i < placed.length; i++) {
|
||||
var p = placed[i];
|
||||
if (x < p.x + p.w + GAP &&
|
||||
x + w + GAP > p.x &&
|
||||
y < p.y + p.h + GAP &&
|
||||
y + h + GAP > p.y) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Placement contraint à l'anneau elliptique :
|
||||
// { intérieur ellipse (a, b) } ∩ { extérieur ellipse d'exclusion (R_H, R_V) }
|
||||
// Les candidats sont triés par distance à l'ellipse d'exclusion (croissante) :
|
||||
// les mots s'accumulent au plus près du centre avant de s'étendre.
|
||||
function findPos(w, h) {
|
||||
var candidates = [];
|
||||
|
||||
for (var x = 0; x <= cw - w; x += 8) {
|
||||
var dx = (x + w / 2) - cx;
|
||||
var ratio = dx / a;
|
||||
if (Math.abs(ratio) >= 1) continue;
|
||||
|
||||
// Limite verticale du centre du mot imposée par l'ellipse externe (avec marge h/2)
|
||||
var maxPcy = b * Math.sqrt(1 - ratio * ratio);
|
||||
if (maxPcy < h / 2) continue;
|
||||
var maxAbsY = maxPcy - h / 2;
|
||||
|
||||
// Limite verticale minimale imposée par l'ellipse d'exclusion (R_H, R_V)
|
||||
var minAbsY = (Math.abs(dx) >= R_H) ? 0
|
||||
: R_V * Math.sqrt(Math.max(0, 1 - (dx * dx) / (R_H * R_H)));
|
||||
if (minAbsY > maxAbsY) continue;
|
||||
|
||||
for (var absY = minAbsY; absY <= maxAbsY; absY += 4) {
|
||||
// Distance normalisée à l'ellipse d'exclusion (0 = sur le bord)
|
||||
var nx = dx / R_H, ny = absY / R_V;
|
||||
var dist = Math.sqrt(nx * nx + ny * ny) - 1;
|
||||
var yB = Math.round(absY - h / 2);
|
||||
if (absY > 0) {
|
||||
candidates.push({ x: x, y: Math.round(-absY - h / 2), dist: dist });
|
||||
}
|
||||
candidates.push({ x: x, y: yB, dist: dist });
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par distance à l'ellipse d'exclusion croissante → attraction vers le centre
|
||||
candidates.sort(function (ca, cb) { return ca.dist - cb.dist; });
|
||||
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
var c = candidates[i];
|
||||
if (!hasOverlap(c.x, c.y, w, h)) return { x: c.x, y: c.y };
|
||||
}
|
||||
|
||||
return null; // aucune position dans l'anneau
|
||||
}
|
||||
|
||||
// Place les mots dans l'anneau elliptique, collecte les débordements
|
||||
var overflow = [];
|
||||
items.forEach(function (item) {
|
||||
var pos = findPos(item.w, item.h);
|
||||
if (pos) {
|
||||
item.pos = { x: pos.x, y: pos.y, w: item.w, h: item.h };
|
||||
placed.push(item.pos);
|
||||
item.el.style.left = pos.x + 'px';
|
||||
item.el.style.top = pos.y + 'px';
|
||||
} else {
|
||||
overflow.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Placement des débordements en lignes centrées (style flex-wrap center)
|
||||
if (overflow.length) {
|
||||
var startY = placed.reduce(function (m, p) { return Math.max(m, p.y + p.h); }, 0) + GAP;
|
||||
var rows = [];
|
||||
var currentRow = [];
|
||||
var currentRowW = 0;
|
||||
|
||||
overflow.forEach(function (item) {
|
||||
var needed = currentRowW > 0 ? item.w + GAP : item.w;
|
||||
if (currentRowW + needed > cw && currentRow.length > 0) {
|
||||
rows.push(currentRow);
|
||||
currentRow = [];
|
||||
currentRowW = 0;
|
||||
}
|
||||
currentRow.push(item);
|
||||
currentRowW += (currentRowW > 0 ? GAP : 0) + item.w;
|
||||
});
|
||||
if (currentRow.length) rows.push(currentRow);
|
||||
|
||||
var curY = startY;
|
||||
rows.forEach(function (row) {
|
||||
var rowW = row.reduce(function (s, item) { return s + item.w; }, 0) + (row.length - 1) * GAP;
|
||||
var rowH = row.reduce(function (m, item) { return Math.max(m, item.h); }, 0);
|
||||
var offsetX = Math.round((cw - rowW) / 2);
|
||||
|
||||
row.forEach(function (item) {
|
||||
item.pos = { x: offsetX, y: curY, w: item.w, h: item.h };
|
||||
placed.push(item.pos);
|
||||
item.el.style.left = offsetX + 'px';
|
||||
item.el.style.top = curY + 'px';
|
||||
offsetX += item.w + GAP;
|
||||
});
|
||||
curY += rowH + GAP;
|
||||
});
|
||||
}
|
||||
|
||||
// Normalisation Y : cy=0 → shift px depuis le haut du conteneur
|
||||
var minY = items.reduce(function (m, item) { return Math.min(m, item.pos.y); }, Infinity);
|
||||
var shift = Math.max(0, GAP - minY);
|
||||
|
||||
items.forEach(function (item) {
|
||||
item.pos.y += shift;
|
||||
item.el.style.top = item.pos.y + 'px';
|
||||
});
|
||||
|
||||
// Hauteur basée sur le contenu réel (évite le débordement sur mobile)
|
||||
var maxPlacedY = items.reduce(function (m, item) { return Math.max(m, item.pos.y + item.pos.h); }, 0);
|
||||
container.style.height = (maxPlacedY + GAP) + 'px';
|
||||
|
||||
// Dégradé sur un overlay séparé pour permettre l'animation scale au survol
|
||||
if (gradOverlay) {
|
||||
container.removeChild(gradOverlay);
|
||||
}
|
||||
gradOverlay = document.createElement('div');
|
||||
gradOverlay.style.cssText =
|
||||
'position:absolute;inset:0;pointer-events:none;' +
|
||||
'background:radial-gradient(circle ' + Math.round(R_V * 2) + 'px at ' + cx + 'px ' + shift + 'px,' +
|
||||
'rgba(247,255,41,1) 0%,rgba(247,255,41,0.6) 16%,rgba(247,255,41,0.15) 55%,transparent 70%);' +
|
||||
'transform-origin:' + cx + 'px ' + shift + 'px;' +
|
||||
'transition:transform 0.4s ease;';
|
||||
container.insertBefore(gradOverlay, container.firstChild);
|
||||
}
|
||||
|
||||
// Premier layout
|
||||
layoutCloud();
|
||||
|
||||
// Événements du dégradé (référencent gradOverlay via closure)
|
||||
container.addEventListener('mouseenter', function () {
|
||||
if (!gradOverlay) return;
|
||||
gradOverlay.style.transition = 'transform 0.4s ease';
|
||||
gradOverlay.style.transform = 'scale(0.82)';
|
||||
});
|
||||
container.addEventListener('mousemove', function (e) {
|
||||
if (!gradOverlay) return;
|
||||
var cw = container.offsetWidth;
|
||||
var cx = cw / 2;
|
||||
var rect = container.getBoundingClientRect();
|
||||
var dx = (e.clientX - rect.left) - cx;
|
||||
var dy = (e.clientY - rect.top) - parseFloat(gradOverlay.style.transformOrigin.split(' ')[1]);
|
||||
var tx = (-dx * 0.09).toFixed(2);
|
||||
var ty = (-dy * 0.09).toFixed(2);
|
||||
gradOverlay.style.transition = 'transform 0.15s ease-out';
|
||||
gradOverlay.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(0.82)';
|
||||
});
|
||||
container.addEventListener('mouseleave', function () {
|
||||
if (!gradOverlay) return;
|
||||
gradOverlay.style.transition = 'transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
||||
gradOverlay.style.transform = 'scale(1)';
|
||||
});
|
||||
|
||||
// Resize avec debounce
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function () {
|
||||
lastLayoutWidth = 0; // forcer le recalcul
|
||||
layoutCloud();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
// Animation d'apparition au scroll
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
items.forEach(function (item, i) {
|
||||
item.el.style.animationDelay = (i * 0.03) + 's';
|
||||
item.el.classList.add('keyword--visible');
|
||||
});
|
||||
observer.unobserve(container);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.05 });
|
||||
|
||||
observer.observe(container);
|
||||
});
|
||||
})();
|
||||
229
js/membresFilters.js
Normal file
@@ -0,0 +1,229 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// ── Filters toggle ────────────────────────────────────────
|
||||
var membresToggleBtn = document.getElementById('membres-filters-toggle');
|
||||
var membresFiltersEl = document.getElementById('membres-filters');
|
||||
|
||||
if (membresToggleBtn && membresFiltersEl) {
|
||||
membresToggleBtn.addEventListener('click', function () {
|
||||
var isOpen = membresFiltersEl.classList.toggle('is-open');
|
||||
membresToggleBtn.classList.toggle('is-open', isOpen);
|
||||
membresToggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
if (isOpen) {
|
||||
var searchInput = document.getElementById('membres-search');
|
||||
if (searchInput) searchInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var activeRole = '';
|
||||
var searchQuery = '';
|
||||
|
||||
// ── Dropdown (role filter) ────────────────────────────────
|
||||
var roleBtn = document.getElementById('filter-role-btn');
|
||||
var rolePopover = document.getElementById('filter-role-popover');
|
||||
var roleLabel = document.getElementById('filter-role-label');
|
||||
var roleReset = document.getElementById('role-reset');
|
||||
var roleDd = document.getElementById('filter-role-dd');
|
||||
|
||||
function openRole() { rolePopover.style.display = ''; roleDd.classList.add('is-open'); }
|
||||
function closeRole() { rolePopover.style.display = 'none'; roleDd.classList.remove('is-open'); }
|
||||
|
||||
roleBtn.addEventListener('click', function () {
|
||||
rolePopover.style.display === 'none' ? openRole() : closeRole();
|
||||
});
|
||||
|
||||
rolePopover.querySelectorAll('[data-role]').forEach(function (item) {
|
||||
item.addEventListener('click', function () {
|
||||
activeRole = item.dataset.role;
|
||||
roleLabel.textContent = item.textContent.trim();
|
||||
roleReset.style.display = activeRole ? '' : 'none';
|
||||
roleDd.classList.toggle('is-active', !!activeRole);
|
||||
closeRole();
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
|
||||
roleReset.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
activeRole = '';
|
||||
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
|
||||
roleReset.style.display = 'none';
|
||||
roleDd.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!roleDd.contains(e.target)) closeRole();
|
||||
});
|
||||
|
||||
// ── Search input ──────────────────────────────────────────
|
||||
var searchInput = document.getElementById('membres-search');
|
||||
var searchReset = document.getElementById('search-reset');
|
||||
|
||||
searchInput.addEventListener('input', function () {
|
||||
searchQuery = searchInput.value.trim().toLowerCase();
|
||||
searchReset.style.display = searchQuery ? '' : 'none';
|
||||
searchInput.classList.toggle('is-active', !!searchQuery);
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
searchReset.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
searchQuery = '';
|
||||
searchInput.value = '';
|
||||
searchReset.style.display = 'none';
|
||||
searchInput.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// ── Active chips ──────────────────────────────────────────
|
||||
var chipsContainer = document.getElementById('membres-active-chips');
|
||||
|
||||
function makeChip(label, onRemove) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'filter-chip';
|
||||
btn.innerHTML = label + '<i class="iconoir-xmark"></i>';
|
||||
btn.addEventListener('click', onRemove);
|
||||
return btn;
|
||||
}
|
||||
|
||||
function updateChips() {
|
||||
if (!chipsContainer) return;
|
||||
chipsContainer.innerHTML = '';
|
||||
if (activeRole) {
|
||||
chipsContainer.appendChild(makeChip(activeRole, function () {
|
||||
activeRole = '';
|
||||
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
|
||||
roleReset.style.display = 'none';
|
||||
roleDd.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
}));
|
||||
}
|
||||
if (searchQuery) {
|
||||
chipsContainer.appendChild(makeChip(searchInput.value, function () {
|
||||
searchQuery = '';
|
||||
searchInput.value = '';
|
||||
searchReset.style.display = 'none';
|
||||
searchInput.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Column sort ───────────────────────────────────────────
|
||||
var sortKey = 'nom';
|
||||
var sortDir = 'asc';
|
||||
|
||||
function getSortValue(row, key) {
|
||||
if (key === 'nom') {
|
||||
return (row.dataset.sortName || '').trim().toLowerCase();
|
||||
}
|
||||
if (key === 'statut') return (row.dataset.status || '').toLowerCase();
|
||||
if (key === 'affiliation') return (row.dataset.affiliation || '').toLowerCase();
|
||||
return '';
|
||||
}
|
||||
|
||||
function applySortToTable(tbody) {
|
||||
var rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
rows.sort(function (a, b) {
|
||||
var va = getSortValue(a, sortKey);
|
||||
var vb = getSortValue(b, sortKey);
|
||||
var cmp = va.localeCompare(vb, 'fr', { sensitivity: 'base' });
|
||||
return sortDir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
rows.forEach(function (row) { tbody.appendChild(row); });
|
||||
}
|
||||
|
||||
function applySort() {
|
||||
document.querySelectorAll('.membres-table tbody').forEach(function (tbody) {
|
||||
if (!tbody.closest('table').hasAttribute('data-fixed-order')) {
|
||||
applySortToTable(tbody);
|
||||
}
|
||||
restripe(tbody);
|
||||
});
|
||||
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
|
||||
th.classList.remove('sort-asc', 'sort-desc');
|
||||
if (th.dataset.sort === sortKey) {
|
||||
th.classList.add('sort-' + sortDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
|
||||
th.addEventListener('click', function () {
|
||||
var key = th.dataset.sort;
|
||||
if (sortKey === key) {
|
||||
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortDir = 'asc';
|
||||
}
|
||||
applySort();
|
||||
});
|
||||
});
|
||||
|
||||
applySort();
|
||||
|
||||
// ── Row restriping (fixes alternating colors after filter) ──
|
||||
function restripe(tbody) {
|
||||
var n = 0;
|
||||
tbody.querySelectorAll('tr').forEach(function (row) {
|
||||
if (row.style.display === 'none') return;
|
||||
row.classList.toggle('is-even-row', n % 2 === 1);
|
||||
n++;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Apply both filters ────────────────────────────────────
|
||||
function applyFilters() {
|
||||
var isFiltering = activeRole || searchQuery;
|
||||
|
||||
document.querySelectorAll('.membres-item').forEach(function (item) {
|
||||
// Filter rows by role and/or name
|
||||
var rows = item.querySelectorAll('tbody tr');
|
||||
var visible = 0;
|
||||
rows.forEach(function (row) {
|
||||
var name = (row.dataset.name || '').toLowerCase();
|
||||
var status = (row.dataset.status || '').toLowerCase();
|
||||
var affiliation = (row.dataset.affiliation || '').toLowerCase();
|
||||
var roles = (row.dataset.roles || '').split('|').map(function (r) { return r.trim().toLowerCase(); });
|
||||
|
||||
var matchesRole = !activeRole || roles.includes(activeRole.toLowerCase());
|
||||
var matchesName = !searchQuery || name.includes(searchQuery)
|
||||
|| status.includes(searchQuery)
|
||||
|| affiliation.includes(searchQuery);
|
||||
|
||||
var show = matchesRole && matchesName;
|
||||
row.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
});
|
||||
item.querySelectorAll('tbody').forEach(restripe);
|
||||
|
||||
// Hide group if no rows are visible
|
||||
if (isFiltering && visible === 0) {
|
||||
item.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
item.style.display = '';
|
||||
|
||||
var content = item.querySelector('.membres-content');
|
||||
if (isFiltering) {
|
||||
// Auto-expand when a filter is active
|
||||
content.style.display = '';
|
||||
item.classList.add('is-open');
|
||||
} else {
|
||||
// Collapse back when all filters are cleared
|
||||
content.style.display = 'none';
|
||||
item.classList.remove('is-open');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
81
js/membresPopover.js
Normal file
@@ -0,0 +1,81 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var section = document.querySelector('.membres-section');
|
||||
if (!section) return;
|
||||
|
||||
// Build the popover element once and append to body
|
||||
var popover = document.createElement('div');
|
||||
popover.id = 'membre-popover';
|
||||
popover.innerHTML =
|
||||
'<div class="membre-popover-inner">' +
|
||||
'<img class="membre-popover-pic" src="" alt="">' +
|
||||
'<div class="membre-popover-info">' +
|
||||
'<p class="membre-popover-name"></p>' +
|
||||
'<p class="membre-popover-status"></p>' +
|
||||
'<p class="membre-popover-domaines"></p>' +
|
||||
'<p class="membre-popover-autres"></p>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(popover);
|
||||
|
||||
var pic = popover.querySelector('.membre-popover-pic');
|
||||
var elName = popover.querySelector('.membre-popover-name');
|
||||
var elStat = popover.querySelector('.membre-popover-status');
|
||||
var elDom = popover.querySelector('.membre-popover-domaines');
|
||||
var elAut = popover.querySelector('.membre-popover-autres');
|
||||
|
||||
var visible = false;
|
||||
var currentRow = null;
|
||||
|
||||
// ── Show/hide via event delegation on the section ────────
|
||||
section.addEventListener('mouseover', function (e) {
|
||||
var row = e.target.closest('tbody tr');
|
||||
if (!row || row === currentRow) return;
|
||||
currentRow = row;
|
||||
|
||||
var avatar = row.dataset.avatar || '';
|
||||
if (avatar) {
|
||||
pic.src = avatar;
|
||||
pic.style.display = '';
|
||||
} else {
|
||||
pic.src = '';
|
||||
pic.style.display = 'none';
|
||||
}
|
||||
|
||||
elName.textContent = row.dataset.name || '';
|
||||
elStat.textContent = row.dataset.status || '';
|
||||
|
||||
var domVal = row.dataset.domaines || '';
|
||||
elDom.textContent = domVal;
|
||||
elDom.style.display = domVal ? '' : 'none';
|
||||
|
||||
var autVal = row.dataset.autresDomaines || '';
|
||||
elAut.innerHTML = autVal.replace(/\n/g, '<br>');
|
||||
elAut.style.display = autVal ? '' : 'none';
|
||||
|
||||
popover.classList.add('is-visible');
|
||||
visible = true;
|
||||
});
|
||||
|
||||
section.addEventListener('mouseout', function (e) {
|
||||
var row = e.target.closest('tbody tr');
|
||||
if (!row) return;
|
||||
// only hide when leaving the row entirely (not moving to a child)
|
||||
if (e.relatedTarget && row.contains(e.relatedTarget)) return;
|
||||
currentRow = null;
|
||||
popover.classList.remove('is-visible');
|
||||
visible = false;
|
||||
});
|
||||
|
||||
// ── Follow the cursor ─────────────────────────────────────
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
if (!visible) return;
|
||||
var x = e.clientX + 18;
|
||||
var y = e.clientY + 18;
|
||||
var pw = popover.offsetWidth;
|
||||
var ph = popover.offsetHeight;
|
||||
if (x + pw > window.innerWidth) x = e.clientX - pw - 8;
|
||||
if (y + ph > window.innerHeight) y = e.clientY - ph - 8;
|
||||
popover.style.left = x + 'px';
|
||||
popover.style.top = y + 'px';
|
||||
});
|
||||
});
|
||||
57
js/messageLabo.js
Normal file
@@ -0,0 +1,57 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var messageList = document.querySelector('.messages-list');
|
||||
var agendaContent = document.querySelector('.agenda-content');
|
||||
var sectionTitle = document.querySelector('.message-du-labo .section-title');
|
||||
var buttonMessages = document.querySelector('.button-messages');
|
||||
if (!messageList || !agendaContent) return;
|
||||
|
||||
var items = Array.from(messageList.querySelectorAll('.message-item'));
|
||||
|
||||
function sync() {
|
||||
items.forEach(function (item) {
|
||||
item.style.display = '';
|
||||
var content = item.querySelector('.message-content');
|
||||
if (content) { content.style.maxHeight = ''; content.classList.remove('is-overflowing'); }
|
||||
});
|
||||
|
||||
if (window.innerWidth < 768) {
|
||||
// Mobile : afficher uniquement le premier message
|
||||
items.forEach(function (item, i) { item.style.display = i === 0 ? '' : 'none'; });
|
||||
return;
|
||||
}
|
||||
|
||||
var budget = agendaContent.offsetHeight
|
||||
- (sectionTitle ? sectionTitle.offsetHeight : 0)
|
||||
- (buttonMessages ? buttonMessages.offsetHeight : 0);
|
||||
var used = 0;
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var itemHeight = item.offsetHeight;
|
||||
if (used + itemHeight <= budget) {
|
||||
used += itemHeight;
|
||||
} else {
|
||||
var remaining = budget - used;
|
||||
var content = item.querySelector('.message-content');
|
||||
if (content && remaining > 100) {
|
||||
var contentBudget = remaining - (itemHeight - content.offsetHeight);
|
||||
if (contentBudget > 60) {
|
||||
content.style.maxHeight = contentBudget + 'px';
|
||||
content.classList.add('is-overflowing');
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
for (var j = i + 1; j < items.length; j++) {
|
||||
items[j].style.display = 'none';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sync();
|
||||
window.addEventListener('resize', sync);
|
||||
});
|
||||
32
js/navAxesToggle.js
Normal file
@@ -0,0 +1,32 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var item = document.querySelector('.nav-axes-item');
|
||||
if (!item) return;
|
||||
|
||||
var trigger = item.querySelector('.nav-axes-trigger');
|
||||
var mainMenu = document.querySelector('.main-menu');
|
||||
|
||||
trigger.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = item.classList.toggle('is-open');
|
||||
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
|
||||
// Close when clicking outside
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!item.contains(e.target)) {
|
||||
item.classList.remove('is-open');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// Reset when main menu closes
|
||||
if (mainMenu) {
|
||||
var observer = new MutationObserver(function () {
|
||||
if (!mainMenu.classList.contains('active')) {
|
||||
item.classList.remove('is-open');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
observer.observe(mainMenu, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
});
|
||||
133
js/overlay.js
Normal file
@@ -0,0 +1,133 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const header = document.querySelector('header');
|
||||
const body = document.querySelector('body');
|
||||
const menuToggle = document.querySelector('.menu-toggle');
|
||||
const mainMenu = document.querySelector('.main-menu');
|
||||
const menuOverlay = document.querySelector('.overlay');
|
||||
const menuIcon = document.querySelector('.menu-toggle i');
|
||||
const wpAdminBar = document.querySelector('#wpadminbar');
|
||||
const stickyHeaderMobile = document.querySelector('.header-right');
|
||||
const searchButton = document.querySelector('.search-button');
|
||||
const searchPanel = document.querySelector('.search-panel');
|
||||
const searchIcon = document.querySelector('.search-button i');
|
||||
const searchInput = document.querySelector('.search-panel__input');
|
||||
|
||||
const breakpointTablet = 768;
|
||||
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
|
||||
// Compute the pixel offset at which panels should appear (just below the header)
|
||||
function getHeaderBottom() {
|
||||
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
return stickyHeaderMobile.getBoundingClientRect().bottom + window.scrollY;
|
||||
}
|
||||
return header.getBoundingClientRect().bottom + window.scrollY;
|
||||
}
|
||||
return header.offsetHeight + adminBarHeight;
|
||||
}
|
||||
|
||||
function updateOverlay() {
|
||||
const anyOpen = mainMenu.classList.contains('active') || searchPanel.classList.contains('active');
|
||||
menuOverlay.classList.toggle('active', anyOpen);
|
||||
if (anyOpen) {
|
||||
body.style.overflow = 'hidden';
|
||||
} else {
|
||||
body.style.removeProperty('overflow');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Menu ---
|
||||
function openMenu() {
|
||||
mainMenu.scrollTo(0, 0);
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
mainMenu.style.height = `${window.innerHeight - adminBarHeight - stickyHeaderMobile.offsetHeight}px`;
|
||||
} else {
|
||||
mainMenu.style.height = `${window.innerHeight - header.getBoundingClientRect().bottom}px`;
|
||||
}
|
||||
} else {
|
||||
mainMenu.style.removeProperty('height');
|
||||
}
|
||||
mainMenu.style.top = `${getHeaderBottom()}px`;
|
||||
mainMenu.classList.add('active');
|
||||
menuIcon.classList.remove('iconoir-menu');
|
||||
menuIcon.classList.add('iconoir-xmark');
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
mainMenu.classList.remove('active');
|
||||
menuIcon.classList.remove('iconoir-xmark');
|
||||
menuIcon.classList.add('iconoir-menu');
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
if (mainMenu.classList.contains('active')) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
// --- Search ---
|
||||
function openSearch() {
|
||||
searchPanel.style.top = `${getHeaderBottom()}px`;
|
||||
searchPanel.classList.add('active');
|
||||
searchIcon.classList.remove('iconoir-search');
|
||||
searchIcon.classList.add('iconoir-xmark');
|
||||
setTimeout(function() { if (searchInput) searchInput.focus(); }, 400);
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
searchPanel.classList.remove('active');
|
||||
searchIcon.classList.remove('iconoir-xmark');
|
||||
searchIcon.classList.add('iconoir-search');
|
||||
}
|
||||
|
||||
function toggleSearch() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) {
|
||||
closeSearch();
|
||||
} else {
|
||||
openSearch();
|
||||
}
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
// --- Event listeners ---
|
||||
menuToggle.addEventListener('click', toggleMenu);
|
||||
searchButton.addEventListener('click', toggleSearch);
|
||||
|
||||
menuOverlay.addEventListener('click', function() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
24
js/quickLinks.js
Normal file
@@ -0,0 +1,24 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var quickLinks = document.querySelector('.quick-links');
|
||||
if (!quickLinks) return;
|
||||
|
||||
// Last section: keyword cloud if present, otherwise last swiper section
|
||||
var lastSection = document.querySelector('.keyword-cloud');
|
||||
if (!lastSection) {
|
||||
var swiperSections = document.querySelectorAll('.swiper-section');
|
||||
lastSection = swiperSections[swiperSections.length - 1] || null;
|
||||
}
|
||||
if (!lastSection) return;
|
||||
|
||||
var initialTop = quickLinks.getBoundingClientRect().top + window.scrollY;
|
||||
var quickLinksHeight = quickLinks.offsetHeight;
|
||||
|
||||
window.addEventListener('scroll', function () {
|
||||
var sectionBottom = lastSection.getBoundingClientRect().bottom;
|
||||
if (initialTop - sectionBottom > 0) {
|
||||
quickLinks.style.top = (initialTop - (initialTop - sectionBottom) - quickLinksHeight) + 'px';
|
||||
} else {
|
||||
quickLinks.style.top = initialTop + 'px';
|
||||
}
|
||||
});
|
||||
});
|
||||
39
js/seanceToggle.js
Normal file
@@ -0,0 +1,39 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-seance-toggle]').forEach(function (header) {
|
||||
header.addEventListener('click', function (e) {
|
||||
// Don't toggle when clicking a link
|
||||
if (e.target.closest('a')) return;
|
||||
|
||||
var item = header.closest('.seance-item, .membres-item, .author-posts-item, .labo-dropdown-item');
|
||||
var content = item.querySelector('.seance-content, .membres-content, .author-posts-content, .labo-dropdown-content');
|
||||
var isOpen = item.classList.contains('is-open');
|
||||
|
||||
if (isOpen) {
|
||||
content.style.display = 'none';
|
||||
item.classList.remove('is-open');
|
||||
} else {
|
||||
content.style.display = '';
|
||||
item.classList.add('is-open');
|
||||
if (window.fitPostCardTitles) window.fitPostCardTitles();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-expand and scroll to a séance targeted by URL hash (#seance-{ID})
|
||||
var hash = window.location.hash;
|
||||
if (hash && hash.startsWith('#seance-')) {
|
||||
var target = document.querySelector(hash + '.seance-item');
|
||||
if (target) {
|
||||
var content = target.querySelector('.seance-content');
|
||||
if (content) {
|
||||
content.style.display = '';
|
||||
target.classList.add('is-open');
|
||||
if (window.fitPostCardTitles) window.fitPostCardTitles();
|
||||
setTimeout(function () {
|
||||
var top = target.getBoundingClientRect().top + window.scrollY - 150;
|
||||
window.scrollTo({ top: top, behavior: 'smooth' });
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
91
js/stickyHeader.js
Normal file
@@ -0,0 +1,91 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const header = document.querySelector('header');
|
||||
const wpAdminBar = document.querySelector('#wpadminbar');
|
||||
|
||||
const stickyHeaderMobile = document.querySelector('.header-right');
|
||||
const relativeHeaderMobile = document.querySelector('.header-left');
|
||||
|
||||
const mainLogo = document.querySelector('.main-logo');
|
||||
const description = document.querySelector('.description');
|
||||
const burgerContainer = document.querySelector('.menu-toggle > div');
|
||||
const menuIconContainer = document.querySelector('.menu-toggle > div > div');
|
||||
const menuText = document.querySelector('.menu-toggle > div > p');
|
||||
|
||||
const breakpointTablet = 768;
|
||||
|
||||
function checkMobile() {
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
stickyHeaderMobile.style.top = wpAdminBar ? `${wpAdminBar.offsetHeight}px` : '0px';
|
||||
} else {
|
||||
stickyHeaderMobile.style.top = 'unset';
|
||||
}
|
||||
}
|
||||
|
||||
function resetStyles() {
|
||||
header.style.removeProperty("height");
|
||||
mainLogo.style.removeProperty("padding");
|
||||
description.style.removeProperty("opacity");
|
||||
burgerContainer.style.removeProperty("padding");
|
||||
burgerContainer.style.removeProperty("justify-content");
|
||||
menuIconContainer.style.removeProperty("font-size");
|
||||
menuText.style.removeProperty("display");
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const isScrolledTop = window.scrollY === 0;
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
// mobile
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
// déployer petit logo à gauche
|
||||
if(!stickyHeaderMobile.classList.contains('scrolled')) {
|
||||
stickyHeaderMobile.classList.add('scrolled')
|
||||
}
|
||||
} else {
|
||||
// rétracter petit logo à gauche
|
||||
if(stickyHeaderMobile.classList.contains('scrolled')) {
|
||||
stickyHeaderMobile.classList.remove('scrolled');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// desktop
|
||||
header.style.height = isScrolledTop ? '12vh' : '6vh';
|
||||
header.style.minHeight = isScrolledTop ? '100px' : 'unset';
|
||||
mainLogo.style.padding = isScrolledTop ? '1.5rem 2rem' : '0.2rem 0.4rem';
|
||||
description.style.opacity = isScrolledTop ? '1' : '0';
|
||||
burgerContainer.style.padding = isScrolledTop ? '2rem' : '0.6rem 1rem';
|
||||
burgerContainer.style.justifyContent = isScrolledTop ? 'space-between' : 'center';
|
||||
menuIconContainer.style.fontSize = isScrolledTop ? '2rem' : '1.5rem';
|
||||
menuText.style.display = isScrolledTop ? '' : 'none';
|
||||
|
||||
|
||||
|
||||
if (window.scrollY === 0) {
|
||||
// agrandir le header
|
||||
} else {
|
||||
// diminuer le header
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
let resizeTimeout;
|
||||
let previousWidth = window.innerWidth;
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
let currentWidth = window.innerWidth;
|
||||
|
||||
if (currentWidth !== previousWidth) {
|
||||
window.scrollTo(0, 0);
|
||||
previousWidth = currentWidth;
|
||||
}
|
||||
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
resetStyles();
|
||||
checkMobile();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
checkMobile();
|
||||
});
|
||||
165
page-annonces.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
|
||||
$excluded_cat_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
$context['active_date_to'] = $active_date_to;
|
||||
$context['active_category_id'] = $filter_autres ? 'autres' : $active_cat_id;
|
||||
$context['active_cat_id'] = $active_cat_id;
|
||||
$context['filter_autres'] = $filter_autres;
|
||||
|
||||
// Redirect ?filter_cat=X to the actual category page (preserving axe/date params)
|
||||
if ( $active_cat_id && ! $filter_autres ) {
|
||||
$cat_obj = get_category( $active_cat_id );
|
||||
if ( $cat_obj && ! is_wp_error( $cat_obj ) ) {
|
||||
$redir_params = array_filter([
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]);
|
||||
$redir_url = $redir_params
|
||||
? add_query_arg( $redir_params, get_category_link( $cat_obj->term_id ) )
|
||||
: get_category_link( $cat_obj->term_id );
|
||||
wp_redirect( $redir_url, 301 );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine active rubrique
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base filter params preserved across all filter links
|
||||
$base_filter_params = array_filter([
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]);
|
||||
|
||||
// Build tax_query: exclude séances + optional category filter
|
||||
$tax_query = [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => $excluded_cat_ids,
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
];
|
||||
if ($active_cat_id) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_cat_id],
|
||||
'include_children' => !$filter_autres,
|
||||
];
|
||||
}
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => $tax_query,
|
||||
'thalim_event_date_order' => true,
|
||||
];
|
||||
if ($active_axe) {
|
||||
$query_args['meta_query'] = [[
|
||||
'key' => 'axes_thematiques',
|
||||
'value' => $active_axe,
|
||||
'type' => 'NUMERIC',
|
||||
]];
|
||||
}
|
||||
if ($active_date_from || $active_date_to) {
|
||||
$query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to];
|
||||
}
|
||||
|
||||
// Axes thématiques for filter dropdown
|
||||
$axes_groups = thalim_get_axes_filter_groups();
|
||||
$current_axes = $axes_groups[0]['terms'] ?? [];
|
||||
$context['filter_axes'] = $current_axes;
|
||||
|
||||
// Rubrique/catégorie filter links (stay on this page with filter_cat param)
|
||||
$page_url = get_permalink();
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $base_filter_params
|
||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||
: get_category_link($cat->term_id),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $base_filter_params
|
||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||
: get_category_link($cat->term_id),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $page_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
|
||||
$posts = Timber::get_posts($query_args);
|
||||
$context['cards'] = thalim_get_cards_data($posts);
|
||||
$context['posts'] = $posts;
|
||||
|
||||
Timber::render('page-annonces.twig', $context);
|
||||
2
page-announcements.php
Normal file
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
require __DIR__ . '/page-annonces.php';
|
||||
110
page-le-laboratoire.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$post = Timber::get_post();
|
||||
$context['post'] = $post;
|
||||
$page_id = $post->ID;
|
||||
|
||||
// ── Liens (internal page links) ──────────────────────────────
|
||||
$lien_ids = get_post_meta( $page_id, 'liens', false );
|
||||
$liens = [];
|
||||
foreach ( $lien_ids as $lid ) {
|
||||
$lid = intval( $lid );
|
||||
if ( ! $lid ) continue;
|
||||
$liens[] = [
|
||||
'title' => get_the_title( $lid ),
|
||||
'url' => get_permalink( $lid ),
|
||||
];
|
||||
}
|
||||
$context['liens'] = $liens;
|
||||
|
||||
$labo_lang = thalim_current_language();
|
||||
|
||||
// ── Images (two side-by-side slots) ──────────────────────────
|
||||
$images = [];
|
||||
foreach ( [ 'image_labo_1', 'image_labo_2' ] as $field ) {
|
||||
$img_id = intval( get_post_meta( $page_id, $field, true ) );
|
||||
if ( ! $img_id ) continue;
|
||||
$src = wp_get_attachment_image_url( $img_id, 'large' );
|
||||
if ( ! $src ) continue;
|
||||
$images[] = [
|
||||
'url' => $src,
|
||||
'alt' => get_post_meta( $img_id, '_wp_attachment_image_alt', true ) ?: '',
|
||||
'title' => thalim_bilingual( get_the_title( $img_id ), $labo_lang ),
|
||||
];
|
||||
}
|
||||
$context['images'] = $images;
|
||||
|
||||
// ── Axes thématiques grouped by period ───────────────────────
|
||||
$terms = get_terms( [ 'taxonomy' => 'axe_thematique', 'hide_empty' => false ] );
|
||||
$axes_map = [];
|
||||
|
||||
$label_prefix = $labo_lang === 'en' ? 'Research areas ' : 'Axes thématiques ';
|
||||
$label_passes = $labo_lang === 'en' ? 'Past research areas' : 'Axes thématiques antérieurs';
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$debut = trim( get_term_meta( $term->term_id, 'annee_debut', true ) );
|
||||
$fin = trim( get_term_meta( $term->term_id, 'annee_fin', true ) );
|
||||
|
||||
if ( $debut && $fin ) {
|
||||
$key = $debut . '-' . $fin;
|
||||
$label = $label_prefix . $debut . ' – ' . $fin;
|
||||
} else {
|
||||
$key = 'passes';
|
||||
$label = $label_passes;
|
||||
}
|
||||
|
||||
if ( ! isset( $axes_map[ $key ] ) ) {
|
||||
$axes_map[ $key ] = [
|
||||
'label' => $label,
|
||||
'debut' => intval( $debut ),
|
||||
'terms' => [],
|
||||
];
|
||||
}
|
||||
$ordre = trim( get_term_meta( $term->term_id, 'ordre_daffichage', true ) );
|
||||
$axes_map[ $key ]['terms'][] = [
|
||||
'name' => $term->name,
|
||||
'url' => get_term_link( $term ),
|
||||
'ordre' => $ordre !== '' ? intval( $ordre ) : null,
|
||||
];
|
||||
}
|
||||
|
||||
// Sort: newest first by annee_debut, 'passes' always last (debut === 0)
|
||||
uasort( $axes_map, function ( $a, $b ) {
|
||||
if ( $a['debut'] === 0 ) return 1;
|
||||
if ( $b['debut'] === 0 ) return -1;
|
||||
return $b['debut'] - $a['debut'];
|
||||
} );
|
||||
|
||||
// Within each group: numbered items first (ascending), then unnumbered alphabetically
|
||||
foreach ( $axes_map as &$group ) {
|
||||
usort( $group['terms'], function ( $a, $b ) {
|
||||
$a_has = $a['ordre'] !== null;
|
||||
$b_has = $b['ordre'] !== null;
|
||||
if ( $a_has && $b_has ) return $a['ordre'] - $b['ordre'];
|
||||
if ( $a_has ) return -1;
|
||||
if ( $b_has ) return 1;
|
||||
return strcmp( $a['name'], $b['name'] );
|
||||
} );
|
||||
}
|
||||
unset( $group );
|
||||
|
||||
$context['axes_groups'] = array_values( $axes_map );
|
||||
|
||||
// ── Body (English override) ──────────────────────────────────
|
||||
$context['body_en'] = apply_filters( 'the_content', get_post_meta( $page_id, 'body_en', true ) ?: '' );
|
||||
|
||||
// ── WYSIWYG fields ────────────────────────────────────────────
|
||||
$context['partenaires_internationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) )
|
||||
? get_post_meta( $page_id, 'partenaires_internationaux_en', true )
|
||||
: ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) );
|
||||
$context['partenaires_nationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) )
|
||||
? get_post_meta( $page_id, 'partenaires_nationaux_en', true )
|
||||
: ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) );
|
||||
$context['bibliotheques'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) )
|
||||
? get_post_meta( $page_id, 'bibliotheques_en', true )
|
||||
: ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) );
|
||||
|
||||
// ── Edit link ─────────────────────────────────────────────────
|
||||
$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : '';
|
||||
|
||||
Timber::render( 'page-le-laboratoire.twig', $context );
|
||||
8
page-membres.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/inc/membres-helpers.php';
|
||||
|
||||
$context = Timber::context();
|
||||
$context['groups'] = thalim_get_membres_groups();
|
||||
$context['filter_roles'] = thalim_get_role_terms();
|
||||
|
||||
Timber::render( 'page-membres.twig', $context );
|
||||
48
page-programmes-de-recherche.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$post = Timber::get_post();
|
||||
$context['post'] = $post;
|
||||
|
||||
$terms = get_terms( [ 'taxonomy' => 'programme_de_recherche', 'hide_empty' => false ] );
|
||||
|
||||
$sections = [
|
||||
'subventionne' => [ 'label' => 'Programmes subventionnés', 'items' => [] ],
|
||||
'autre' => [ 'label' => 'Autres programmes', 'items' => [] ],
|
||||
'ancien' => [ 'label' => 'Anciens programmes', 'items' => [] ],
|
||||
];
|
||||
|
||||
foreach ( $terms as $term ) {
|
||||
$type = get_term_meta( $term->term_id, 'type_de_programme', true );
|
||||
|
||||
$item = [
|
||||
'name' => $term->name,
|
||||
'description' => wpautop( $term->description ),
|
||||
'url' => get_term_link( $term ),
|
||||
'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ),
|
||||
];
|
||||
|
||||
if ( $type === 'Programme subventionné' ) {
|
||||
$sections['subventionne']['items'][] = $item;
|
||||
} elseif ( $type === 'Ancien programme' ) {
|
||||
$sections['ancien']['items'][] = $item;
|
||||
} else {
|
||||
// "Autre programme" or no type set
|
||||
$sections['autre']['items'][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by annee_fin descending (most recent end year first); items without a year go last
|
||||
foreach ( $sections as &$section ) {
|
||||
usort( $section['items'], function( $a, $b ) {
|
||||
if ( $a['annee_fin'] === $b['annee_fin'] ) return strcmp( $a['name'], $b['name'] );
|
||||
if ( ! $a['annee_fin'] ) return 1;
|
||||
if ( ! $b['annee_fin'] ) return -1;
|
||||
return $b['annee_fin'] - $a['annee_fin'];
|
||||
} );
|
||||
}
|
||||
unset( $section );
|
||||
|
||||
$context['sections'] = array_values( $sections );
|
||||
$context['page_edit_link'] = current_user_can( 'edit_page', $post->ID ) ? get_edit_post_link( $post->ID ) : '';
|
||||
|
||||
Timber::render( 'page-programmes-de-recherche.twig', $context );
|
||||
8
page.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$post = Timber::get_post();
|
||||
$context['post'] = $post;
|
||||
|
||||
$context['page_edit_link'] = current_user_can( 'edit_page', $post->ID ) ? get_edit_post_link( $post->ID ) : '';
|
||||
|
||||
Timber::render( 'page.twig', $context );
|
||||
164
scss/_author.scss
Normal file
@@ -0,0 +1,164 @@
|
||||
// ====================================
|
||||
// AUTHOR PROFILE PAGE
|
||||
// ====================================
|
||||
|
||||
.author-header {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: flex-start;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
|
||||
@media ($tablet) {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.author-identity {
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
& + .author-bio {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.author-role {
|
||||
font-family: $font-primary;
|
||||
margin-top: 0.4rem;
|
||||
opacity: 0.85;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.author-bio {
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
|
||||
> p {
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.author-resume-these {
|
||||
margin-top: 1rem;
|
||||
line-height: 1.6;
|
||||
|
||||
> p {
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.author-cat-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid $light-gray;
|
||||
}
|
||||
|
||||
.author-titre-these {
|
||||
font-family: $font-heading;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.3 !important;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.these-inline-title {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// WYSIWYG article-fields (domaines de recherches, recherches en cours):
|
||||
// the wpautop'd <p> follows the inline title — give it a small breathing space.
|
||||
.domaines-autres,
|
||||
.recherches-en-cours {
|
||||
> p:first-of-type {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Author posts dropdowns ────────────────────────────────────
|
||||
.author-posts-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.author-posts-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.2rem;
|
||||
padding: 0.8rem;
|
||||
background-color: $light-gray;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.author-posts-chevron {
|
||||
font-size: 1.2rem;
|
||||
transition: transform 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
.author-posts-item.is-open & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.author-posts-content {
|
||||
padding: 1rem;
|
||||
background-color: $light-light-gray;
|
||||
position: relative;
|
||||
padding-bottom: 3rem;
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
}
|
||||
|
||||
article a {
|
||||
text-decoration: none !important;
|
||||
h2 {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.author-post-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
|
||||
h2::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
3
scss/_base.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
#wpadminbar {
|
||||
position: fixed !important;
|
||||
}
|
||||
557
scss/_category.scss
Normal file
@@ -0,0 +1,557 @@
|
||||
// Category archive pages
|
||||
|
||||
.category-archive {
|
||||
margin-top: 0;
|
||||
.category-header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
.breadcrumb {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.3;
|
||||
|
||||
&__separator {
|
||||
margin: 0 0.4rem;
|
||||
}
|
||||
}
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: Gelasio;
|
||||
font-weight: normal;
|
||||
font-size: 1.8rem;
|
||||
position: relative;
|
||||
margin-top: 2rem;
|
||||
display: inline-block;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 6px;
|
||||
width: 100%;
|
||||
bottom: -1.2rem;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
@media ($tablet) {
|
||||
font-size: 2.6rem;
|
||||
&::after {
|
||||
bottom: -0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category--le-laboratoire h1::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $laboratoire 50%);
|
||||
}
|
||||
|
||||
.category--manifestations-scientifiques h1::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $manifestations 50%);
|
||||
}
|
||||
|
||||
.category--publications-et-productions h1::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $publications 50%);
|
||||
}
|
||||
|
||||
.category--mediation-scientifique h1::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $mediations 50%);
|
||||
}
|
||||
|
||||
.category--ressources h1::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $ressources 50%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Breadcrumb: non-category links (Accueil, etc.) underlined in muted gray
|
||||
.breadcrumb a:not(.breadcrumb__cat) { text-decoration: underline; text-decoration-color: $less-light-gray; text-underline-offset: 3px; }
|
||||
|
||||
// Breadcrumb category color underlines — links and current page indicator
|
||||
.category--le-laboratoire .breadcrumb__cat,
|
||||
.category--le-laboratoire .breadcrumb__current { text-decoration: underline; text-decoration-color: $laboratoire; text-underline-offset: 3px; }
|
||||
.category--manifestations-scientifiques .breadcrumb__cat,
|
||||
.category--manifestations-scientifiques .breadcrumb__current { text-decoration: underline; text-decoration-color: $manifestations; text-underline-offset: 3px; }
|
||||
.category--publications-et-productions .breadcrumb__cat,
|
||||
.category--publications-et-productions .breadcrumb__current { text-decoration: underline; text-decoration-color: $publications; text-underline-offset: 3px; }
|
||||
.category--mediation-scientifique .breadcrumb__cat,
|
||||
.category--mediation-scientifique .breadcrumb__current { text-decoration: underline; text-decoration-color: $mediations; text-underline-offset: 3px; }
|
||||
.category--ressources .breadcrumb__cat,
|
||||
.category--ressources .breadcrumb__current { text-decoration: underline; text-decoration-color: $ressources; text-underline-offset: 3px; }
|
||||
|
||||
.category-archive {
|
||||
|
||||
.taxonomy-description {
|
||||
font-family: $font-primary;
|
||||
margin-top: 2rem;
|
||||
line-height: 1.6;
|
||||
max-width: 70ch;
|
||||
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.6;
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
&:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
line-height: 1.6;
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside "· ";
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: inside decimal;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
padding-left: 1rem;
|
||||
margin-left: 1.5rem;
|
||||
border-left: solid 1px $light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.subcategory-section {
|
||||
margin-top: 5rem;
|
||||
&:first-of-type {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.subcategory-section__title {
|
||||
font-family: NewsCycle;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1.2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-grid {
|
||||
display: grid;
|
||||
align-items: start;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
|
||||
.post-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media ($desktop) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.category-section-footer {
|
||||
margin-top: 3rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
.category-header {
|
||||
width: 100%;
|
||||
margin-bottom: 3vh;
|
||||
|
||||
&__back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
i {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
@media ($tablet) {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-category sections on parent category page
|
||||
.subcategory-section {
|
||||
width: 100%;
|
||||
margin-bottom: 4vh;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 2vh;
|
||||
border-bottom: 2px solid $publications;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 1.5rem;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__see-all {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
color: $less-less-light-gray;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
// Post grid
|
||||
.post-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media ($desktop) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
// Post card
|
||||
.post-card {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.3;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: $less-dark-gray;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 0.8rem;
|
||||
color: $less-less-light-gray;
|
||||
}
|
||||
|
||||
&__authors {
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2rem;
|
||||
|
||||
.post-card__author {
|
||||
&:not(:last-child)::after {
|
||||
content: ',';
|
||||
}
|
||||
}
|
||||
|
||||
&--external {
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__role,
|
||||
&__publisher,
|
||||
&__journal {
|
||||
font-size: 0.85rem;
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
|
||||
&__axes,
|
||||
&__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
&__axe,
|
||||
&__tag {
|
||||
font-size: 0.75rem;
|
||||
background-color: $light-gray;
|
||||
padding: 0.15rem 0.5rem;
|
||||
}
|
||||
|
||||
&__axe {
|
||||
border-left: 2px solid $publications;
|
||||
}
|
||||
|
||||
&__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
&__link {
|
||||
font-size: 0.8rem;
|
||||
|
||||
&--hal {
|
||||
color: $publications;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Infinite scroll spinner
|
||||
.scroll-spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 2rem 0;
|
||||
|
||||
&__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: $publications;
|
||||
animation: scroll-spinner-bounce 1.2s infinite ease-in-out;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scroll-spinner-bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.6);
|
||||
opacity: 0.4;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// ── Agenda slider view ────────────────────────────────────────────────────────
|
||||
|
||||
.agenda-view-container {
|
||||
display: none;
|
||||
margin-top: 3rem;
|
||||
&.is-active { display: block; }
|
||||
}
|
||||
|
||||
.agenda-view-title {
|
||||
font-family: NewsCycle;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 1.2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-swiper-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.agenda-swiper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.agenda-swiper-prev,
|
||||
.agenda-swiper-next {
|
||||
flex-shrink: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
padding: 0.4rem;
|
||||
color: $dark-gray;
|
||||
line-height: 1;
|
||||
|
||||
&:hover { color: black; }
|
||||
|
||||
&.swiper-button-disabled {
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid $light-gray;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
transition: transform 0.2s ease-out;
|
||||
margin-right: 0 !important;
|
||||
|
||||
// Timeline line running through the center of the date boxes
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1.7rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: $less-light-gray;
|
||||
z-index: 0;
|
||||
width: calc(100% + 20px);
|
||||
@media ($tablet) {
|
||||
width: calc(100% + 24px);
|
||||
}
|
||||
@media ($desktop) {
|
||||
width: calc(100% + 32px);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover { transform: scale(0.98); }
|
||||
|
||||
&__dates {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__body { flex: 1; }
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem 0.8rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.65;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: $font-heading;
|
||||
font-size: 1rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
}
|
||||
|
||||
// Category-specific accent color on agenda card border + date box gradient
|
||||
.category--manifestations-scientifiques .agenda-card { border-bottom-color: $manifestations; }
|
||||
.category--le-laboratoire .agenda-card { border-bottom-color: $laboratoire; }
|
||||
.category--publications-et-productions .agenda-card { border-bottom-color: $publications; }
|
||||
.category--mediation-scientifique .agenda-card { border-bottom-color: $mediations; }
|
||||
.category--ressources .agenda-card { border-bottom-color: $ressources; }
|
||||
|
||||
.category--manifestations-scientifiques .agenda-date-box { @include category-gradient($manifestations); }
|
||||
.category--le-laboratoire .agenda-date-box { @include category-gradient($laboratoire); }
|
||||
.category--publications-et-productions .agenda-date-box { @include category-gradient($publications); }
|
||||
.category--mediation-scientifique .agenda-date-box { @include category-gradient($mediations); }
|
||||
.category--ressources .agenda-date-box { @include category-gradient($ressources); }
|
||||
|
||||
.agenda-date-box {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background-color: $light-gray;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 0.4rem 0.55rem;
|
||||
flex-shrink: 0;
|
||||
min-width: 2.6rem;
|
||||
|
||||
.agenda-date-day { display: block; font-size: 1.3rem; line-height: 1; }
|
||||
.agenda-date-month { display: block; font-size: 0.65rem; margin-top: 2px; }
|
||||
.agenda-date-year { display: block; font-size: 0.6rem; opacity: 0.65; }
|
||||
|
||||
sup {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-date-arrow {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
344
scss/_filters.scss
Normal file
@@ -0,0 +1,344 @@
|
||||
.filters-bar {
|
||||
margin-top: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
padding-bottom: 0.6rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -5vw;
|
||||
width: calc(100% + 10vw);
|
||||
height: 1px;
|
||||
background-color: $light-gray;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
&::after {
|
||||
left: -3vw;
|
||||
width: calc(100% + 6vw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters-toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
background-color: $light-gray;
|
||||
border: unset;
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.8rem;
|
||||
color: $dark-gray;
|
||||
flex-shrink: 0;
|
||||
|
||||
.filters-chevron {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
&.is-open .filters-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.filters-active-chips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
background-color: $light-gray;
|
||||
padding: 0.3rem 0.5rem;
|
||||
text-decoration: none;
|
||||
color: $dark-gray;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
|
||||
i { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
font-size: 0.9rem;
|
||||
font-family: $font-primary;
|
||||
margin-top: 0;
|
||||
display: none;
|
||||
padding: 2rem 5vw;
|
||||
padding-bottom: 0.5rem !important;
|
||||
margin-left: -5vw;
|
||||
width: calc(100% + 10vw);
|
||||
justify-content: space-between;
|
||||
background-color: $light-light-gray;
|
||||
border-top: solid 1px $light-gray;
|
||||
border-bottom: solid 1px $light-gray;
|
||||
flex-direction: column;
|
||||
|
||||
&.is-open {
|
||||
display: flex;
|
||||
}
|
||||
> div {
|
||||
margin-top: 1.2rem;
|
||||
margin-bottom: 1.2rem;
|
||||
flex: auto;
|
||||
|
||||
&.filtre-rubrique {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
&.filtre-categorie {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.filtre-date {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.filtre-axe {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:not(:last-of-type) {
|
||||
padding-bottom: 1.8rem;
|
||||
border-bottom: solid 1px $less-light-gray;
|
||||
}
|
||||
|
||||
.filter-section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
flex-wrap: wrap;
|
||||
align-items: start;
|
||||
gap: 0.5rem 0.2rem;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.6rem;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-dd {
|
||||
position: relative;
|
||||
margin-top: 0;
|
||||
display: inline-block;
|
||||
|
||||
.dd-title {
|
||||
padding: 0.3rem 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
cursor: pointer;
|
||||
|
||||
> p,
|
||||
> i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
> i {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-open .dd-title > i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&.is-active .dd-title {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
|
||||
.dd-content {
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
z-index: 5;
|
||||
background: white;
|
||||
border: 1px solid $less-light-gray;
|
||||
min-width: 100%;
|
||||
|
||||
ul {
|
||||
gap: 0.3rem;
|
||||
padding: 0.5rem;
|
||||
|
||||
li {
|
||||
padding: 0.4rem;
|
||||
white-space: nowrap;
|
||||
background-color: $light-gray;
|
||||
}
|
||||
}
|
||||
&#filter-axe-popover {
|
||||
right: auto;
|
||||
left: 0;
|
||||
min-width: 280px;
|
||||
max-width: min(420px, 90vw);
|
||||
|
||||
ul {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
li {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
white-space: normal;
|
||||
&:nth-of-type(odd) {
|
||||
background-color: white;
|
||||
}
|
||||
&:first-of-type {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
&.dd-axe-group-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: $less-dark-gray;
|
||||
background-color: $light-gray !important;
|
||||
padding: 0.5rem 0.4rem;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
border-top: solid 1px $less-light-gray;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dd-date-fields {
|
||||
padding: 0.6rem;
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
border-top: 1px solid $less-light-gray;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input[type="date"] {
|
||||
padding: 0.2rem 0.4rem;
|
||||
border: 1px solid $less-light-gray;
|
||||
font-size: 0.85rem;
|
||||
font-family: $font-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.dd-date-apply {
|
||||
margin-top: 0.6rem;
|
||||
width: 100%;
|
||||
padding: 0.3rem;
|
||||
border: none;
|
||||
font-size: 0.85rem;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.membres-search-input.is-active {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
|
||||
.date-reset-link {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
opacity: 0.6;
|
||||
padding: 0 0.5rem 0.4rem;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
padding: 2rem 3vw;
|
||||
margin-left: -3vw;
|
||||
width: calc(100% + 6vw);
|
||||
flex-direction: row;
|
||||
> div {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-left: 1.5rem;
|
||||
&:not(:last-of-type) {
|
||||
padding-right: 0.8rem;
|
||||
border-bottom: unset;
|
||||
border-right: solid 1px $less-light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
scss/_footer.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
footer {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
padding: 1rem 2rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
@media ($tablet) {
|
||||
gap: unset;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
ul#menu-footer,
|
||||
ul#menu-footer-en {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem 2rem;
|
||||
@media ($tablet) {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
li {
|
||||
a {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-logos {
|
||||
display: inline-flex;
|
||||
height: 3rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
align-items: start;
|
||||
|
||||
> a {
|
||||
height: 100%;
|
||||
> img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
330
scss/_header.scss
Normal file
@@ -0,0 +1,330 @@
|
||||
body >
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: space-between;
|
||||
transition: height 0.3s ease-out;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
@media ($tablet) {
|
||||
position: fixed;
|
||||
height: 12vh;
|
||||
min-height: 100px;
|
||||
max-height: 130px;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
height: 6rem;
|
||||
margin-top: 3rem;
|
||||
@media ($tablet) {
|
||||
margin-top: unset;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main-logo-container {
|
||||
display: inline-block;
|
||||
background-color: $light_gray;
|
||||
height: 100%;
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.main-logo {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.6rem;
|
||||
height: 100%;
|
||||
@include hover-gradient-background;
|
||||
@media ($tablet) {
|
||||
padding: 1.5rem 2rem;
|
||||
}
|
||||
|
||||
> img,
|
||||
> a > img {
|
||||
height: 100%;
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s ease-out;
|
||||
@media ($tablet) {
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > img,
|
||||
&:hover > a > img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
gap: 0.3rem;
|
||||
margin-left: 1.2rem;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease-out;
|
||||
|
||||
> div:first-of-type {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
background-color: $light_gray;
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
> div:last-of-type {
|
||||
font-size: 0.8rem;
|
||||
> sup {
|
||||
font-size: 0.6rem;
|
||||
vertical-align: super;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
height: 3rem;
|
||||
@media ($tablet) {
|
||||
padding-left: unset;
|
||||
height: unset;
|
||||
position: relative;
|
||||
width: unset;
|
||||
}
|
||||
&.scrolled {
|
||||
.secondary-logo-container {
|
||||
max-width: 40vw;
|
||||
.main-logo:hover {
|
||||
background-position: unset;
|
||||
img {
|
||||
transform: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-logo-container {
|
||||
height: 100%;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
transition: max-width 0.6s ease-out;
|
||||
@media ($tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.lang-switch {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
transition: font-size 0.2s ease-out;
|
||||
> ul {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
> li:not(.active) {
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-button {
|
||||
margin-left: auto;
|
||||
> div {
|
||||
background-color: $light_gray;
|
||||
padding: 0.5rem;
|
||||
border-radius: 5rem;
|
||||
min-width: 2rem;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: background-color 0.3s ease-out;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $less_light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: inline-block;
|
||||
background-color: $light_gray;
|
||||
width: fit-content;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.3rem;
|
||||
@include hover-gradient-background;
|
||||
@media ($tablet) {
|
||||
gap: unset;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
font-size: 1rem;
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s ease-out, font-size 0.2s ease-out;
|
||||
@media ($tablet) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
> p {
|
||||
font-family: NewsCycle;
|
||||
text-transform: uppercase;
|
||||
transform: scale(1);
|
||||
font-size: 0.8rem;
|
||||
transition: transform 0.2s ease-out, font-size 0.2s ease-out;
|
||||
@media ($tablet) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > div > div,
|
||||
&:hover > div > p {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
z-index: 4;
|
||||
border-top: 2px solid $light-gray;
|
||||
transition: top 0.4s ease-out, opacity 0.2s ease-out;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
@media ($tablet) {
|
||||
position: fixed;
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&__inner {
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: $font-primary;
|
||||
font-size: 1.1rem;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
align-self: start;
|
||||
position: relative;
|
||||
line-height: 1.6;
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-family: $font-primary;
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
|
||||
&__input-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__icon-btn {
|
||||
position: absolute;
|
||||
right: 0.8rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: $less-dark-gray;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
color: $dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0.6rem 2.5rem 0.6rem 0.8rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
background-color: $light-gray;
|
||||
|
||||
&::placeholder {
|
||||
color: $less-dark-gray;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__submit {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
background-color: $light-gray;
|
||||
padding: 0.5rem 1rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $less_light-gray;
|
||||
opacity: 0;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease-out;
|
||||
|
||||
&.active {
|
||||
opacity: 0.6;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
562
scss/_index.scss
Normal file
@@ -0,0 +1,562 @@
|
||||
.hero-header {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
position: relative;
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-logos {
|
||||
position: absolute;
|
||||
top: 2.5vh;
|
||||
display: inline-flex;
|
||||
height: 3rem;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
align-items: start;
|
||||
background-color: $light_gray;
|
||||
@media ($tablet) {
|
||||
position: unset;
|
||||
top: unset;
|
||||
}
|
||||
|
||||
> a {
|
||||
height: 100%;
|
||||
> img {
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-changer {
|
||||
transition: color 0.3s ease-out;
|
||||
}
|
||||
|
||||
.hero-presentation {
|
||||
font-family: $font-heading;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.1;
|
||||
@media ($tablet) {
|
||||
margin-top: 2rem;
|
||||
font-size: 2.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-presentation-detail {
|
||||
margin-top: 1.5rem;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.hero-content > .link-button {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
@media ($tablet) {
|
||||
width: 75%;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// SKETCH
|
||||
// ====================================
|
||||
|
||||
#sketch {
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 200px;
|
||||
// background-color: white;
|
||||
@media ($tablet) {
|
||||
height: unset;
|
||||
width: 25%;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Floating shapes (DOM-based implementation)
|
||||
.floating-shape {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
svg {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
path, polyline, polygon, line, circle, ellipse, rect {
|
||||
transition: fill-opacity 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.thalim-text {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease-out;
|
||||
display: flex;
|
||||
gap: 0;
|
||||
z-index: 20;
|
||||
font-family: 'NewsCycle', sans-serif;
|
||||
font-size: 26px;
|
||||
@media ($tablet) {
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
// ====================================
|
||||
// END SKETCH
|
||||
// ====================================
|
||||
|
||||
// ====================================
|
||||
// SWIPER SECTIONS (annonces, publications…)
|
||||
// ====================================
|
||||
|
||||
.swiper-section {
|
||||
margin-top: 3rem;
|
||||
position: relative;
|
||||
|
||||
.section-title {
|
||||
font-family: NewsCycle;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper_content_controls {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
|
||||
.swiper {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.swiper-button-prev,
|
||||
.swiper-button-next {
|
||||
position: static;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
aspect-ratio: 1;
|
||||
margin: 0;
|
||||
background-color: $light-gray;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $dark-gray;
|
||||
transition: background-color 0.2s ease-out;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&::after {
|
||||
display: none; // hide Swiper default arrow
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-annonces {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// END SWIPER SECTIONS
|
||||
// ====================================
|
||||
|
||||
// ====================================
|
||||
// MESSAGE DU LABORATOIRE + AGENDA
|
||||
// ====================================
|
||||
|
||||
.message-agenda-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 2rem;
|
||||
margin-top: 3rem;
|
||||
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.message-du-labo,
|
||||
.agenda {
|
||||
position: relative;
|
||||
padding: 2.5vh 5vw;
|
||||
padding-bottom: 6vh;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
padding: 3vh 3vw;
|
||||
padding-bottom: 8vh;
|
||||
}
|
||||
}
|
||||
|
||||
.message-du-labo {
|
||||
@media ($tablet) {
|
||||
flex: 4;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-list {
|
||||
width: 100%;
|
||||
|
||||
@media ($tablet) {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda {
|
||||
@media ($tablet) {
|
||||
flex: 3;
|
||||
// border-left: 1px solid $light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.message-date {
|
||||
font-size: 0.75rem;
|
||||
color: $less-dark-gray;
|
||||
display: block;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
& + .message-item {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid $light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-right: 2rem;
|
||||
position: relative;
|
||||
|
||||
> p {
|
||||
margin: 0.7rem 0;
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.6;
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
line-height: 1.6;
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside "· ";
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: inside decimal;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
padding-left: 1rem;
|
||||
margin-left: 1.5rem;
|
||||
border-left: solid 1px $light-gray;
|
||||
}
|
||||
|
||||
|
||||
@media ($tablet) {
|
||||
overflow: hidden;
|
||||
|
||||
&.is-overflowing::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8rem;
|
||||
background: linear-gradient(to bottom, transparent 0%, white 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-messages,
|
||||
.button-agenda {
|
||||
align-self: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.message-read-more {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
font-size: 0.85rem;
|
||||
text-decoration: none;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
|
||||
.is-overflowing & {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-content {
|
||||
width: 100%;
|
||||
|
||||
.agenda-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
transform: scale(1);
|
||||
transition: transform 0.2s ease-out;
|
||||
margin-bottom: 2rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
.date-container {
|
||||
background-color: $light-gray;
|
||||
text-transform: uppercase;
|
||||
font-family: $font-primary;
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
min-width: 3rem;
|
||||
|
||||
> p {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&:first-of-type {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to bottom, $light-gray 0%, $manifestations 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.event-content {
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid $manifestations;
|
||||
flex: 1;
|
||||
|
||||
.meta {
|
||||
font-family: $font-primary;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
flex-wrap: wrap;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-family: $font-heading;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// END MESSAGE DU LABORATOIRE + AGENDA
|
||||
// ====================================
|
||||
|
||||
// ====================================
|
||||
// NUAGE DE MOTS-CLÉS
|
||||
// ====================================
|
||||
|
||||
.keyword-cloud {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 3rem;
|
||||
width: 100%;
|
||||
|
||||
@media ($tablet) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.section-title {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#keyword-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.keyword {
|
||||
position: absolute;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
color: $dark-gray;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
|
||||
@media ($tablet) {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
&.keyword--visible {
|
||||
animation: keywordFadeIn 0.7s ease-out forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keywordFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// END NUAGE DE MOTS-CLÉS
|
||||
// ====================================
|
||||
// Quick links widget
|
||||
.quick-links {
|
||||
position: fixed;
|
||||
display: block;
|
||||
background-color: $light-light-gray;
|
||||
right: 0;
|
||||
top: 35vh;
|
||||
z-index: 3;
|
||||
font-family: $font-primary;
|
||||
max-width: 2.2rem;
|
||||
overflow: hidden;
|
||||
transition: max-width 0.9s ease-out, top 0.2s ease;
|
||||
text-decoration: none;
|
||||
z-index: 10;
|
||||
|
||||
@media ($tablet) {
|
||||
right: 2vw;
|
||||
}
|
||||
|
||||
@media ($desktop) {
|
||||
right: 4vw;
|
||||
}
|
||||
|
||||
> ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: 0.8rem;
|
||||
padding: 0.8rem 0.6rem;
|
||||
|
||||
> li > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
max-width: 40vw;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 10px;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to bottom, transparent 0%, $yellow 100%);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
44
scss/_layout.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: $light_gray;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@media ($tablet) {
|
||||
margin-top: 12vh;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 4vh 5vw;
|
||||
max-width: 1640px;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.full-block {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
padding: 2.5vh 5vw;
|
||||
position: relative;
|
||||
padding-bottom: 6vh;
|
||||
@media ($tablet) {
|
||||
padding: 3vh 3vw;
|
||||
padding-bottom: 8vh;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
}
|
||||
}
|
||||
211
scss/_membres.scss
Normal file
@@ -0,0 +1,211 @@
|
||||
.membres-section {
|
||||
margin-top: 4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.membres-item.is-open {
|
||||
position: relative;
|
||||
padding-bottom: 1rem;
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
}
|
||||
}
|
||||
|
||||
.membres-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.2rem;
|
||||
padding: 0.8rem;
|
||||
background-color: $light-gray;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.membres-chevron {
|
||||
font-size: 1.2rem;
|
||||
transition: transform 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
.membres-item.is-open & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.membres-sort-chevron {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.3;
|
||||
transition: transform 0.2s, opacity 0.15s;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
|
||||
.membres-content {
|
||||
padding: 1rem;
|
||||
background-color: $light-light-gray;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.membres-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
|
||||
th {
|
||||
width: 33.333%;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
font-family: $font-primary;
|
||||
font-weight: normal;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
|
||||
background-color: $light-gray;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover .membres-sort-chevron {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.sort-asc .membres-sort-chevron {
|
||||
opacity: 1;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&.sort-desc .membres-sort-chevron {
|
||||
opacity: 1;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
background-color: white;
|
||||
|
||||
&.is-even-row {
|
||||
background-color: $light-gray;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.6rem 1.5rem 0.6rem 0.5rem;
|
||||
vertical-align: top;
|
||||
line-height: 1.4;
|
||||
|
||||
// Name column
|
||||
&:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Member hover popover
|
||||
#membre-popover {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: white;
|
||||
pointer-events: none;
|
||||
max-width: 380px;
|
||||
min-width: 180px;
|
||||
padding: 1rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
border: solid 1px $light-gray;
|
||||
|
||||
&.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.membre-popover-inner {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.membre-popover-pic {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.membre-popover-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: normal;
|
||||
margin: 0 0 0.3rem;
|
||||
}
|
||||
|
||||
.membre-popover-status {
|
||||
text-transform: uppercase;
|
||||
color: $less-dark-gray;
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
|
||||
.membre-popover-domaines {
|
||||
font-size: 0.8rem;
|
||||
margin: 0 0 0.3rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.membre-popover-autres {
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter bar sizing
|
||||
.filtre-role { flex: 2; }
|
||||
.filtre-recherche { flex: 1; }
|
||||
|
||||
// Member search input
|
||||
.membres-search-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0.6rem 0.8rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
background-color: $light-gray;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
|
||||
@media ($desktop) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
32
scss/_mixins.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
@mixin hover-gradient-background {
|
||||
background: linear-gradient(to bottom, $light_gray 60%, $yellow 100%);
|
||||
background-position: bottom 0px left 0px;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease-out, padding 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-position: bottom -10px left 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin yellow-gradient {
|
||||
background: linear-gradient(to bottom, transparent 50%, $yellow 100%);
|
||||
}
|
||||
|
||||
@mixin yellow-gradient-after($height: 30px) {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: $height;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
@include yellow-gradient;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@mixin category-gradient($color) {
|
||||
background: linear-gradient(to bottom, $light_gray 60%, $color);
|
||||
}
|
||||
205
scss/_navigation.scss
Normal file
@@ -0,0 +1,205 @@
|
||||
.main-menu {
|
||||
overflow-y: scroll;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
z-index: 4;
|
||||
padding-bottom: 4vh;
|
||||
transition: top 0.4s ease-out, opacity 0.2s ease-out;
|
||||
border-top: 2px solid $light_gray;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
@media ($tablet) {
|
||||
overflow-y: unset;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after();
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-out 0.3s;
|
||||
@media ($tablet) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
opacity: 1;
|
||||
&::after {
|
||||
position: fixed;
|
||||
opacity: 1;
|
||||
@media ($tablet) {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-navigation-container,
|
||||
.menu-navigation-en-container {
|
||||
> ul {
|
||||
display: grid;
|
||||
gap: 3rem;
|
||||
padding: 2rem 1.5rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
@media ($tablet) {
|
||||
padding: 3rem 4rem;
|
||||
grid-template-columns: repeat(3, auto);
|
||||
}
|
||||
@media ($desktop) {
|
||||
grid-template-columns: repeat(5, auto);
|
||||
}
|
||||
> li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
align-items: start;
|
||||
> a {
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
padding-bottom: 0.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 8px;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
&:nth-of-type(1) {
|
||||
a:hover {
|
||||
color: $laboratoire;
|
||||
}
|
||||
> a::after {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $laboratoire 100%);
|
||||
}
|
||||
> ul.sub-menu > li:first-of-type {
|
||||
border-bottom: 1px solid $laboratoire;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
a:hover {
|
||||
color: $manifestations;
|
||||
}
|
||||
> a::after {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $manifestations 100%);
|
||||
}
|
||||
> ul.sub-menu > li:first-of-type {
|
||||
border-bottom: 1px solid $manifestations;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(3) {
|
||||
a:hover {
|
||||
color: $publications;
|
||||
}
|
||||
> a::after {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $publications 100%);
|
||||
}
|
||||
> ul.sub-menu > li:first-of-type {
|
||||
border-bottom: 1px solid $publications;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-of-type(4) {
|
||||
a:hover {
|
||||
color: $mediations;
|
||||
}
|
||||
> a::after {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $mediations 100%);
|
||||
}
|
||||
> ul.sub-menu > li:first-of-type {
|
||||
border-bottom: 1px solid $mediations;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:nth-of-type(5) {
|
||||
a:hover {
|
||||
color: $ressources;
|
||||
}
|
||||
> a::after {
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, $ressources 100%);
|
||||
}
|
||||
> ul.sub-menu > li:first-of-type {
|
||||
border-bottom: 1px solid $ressources;
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
> ul.sub-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
> li {
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Axes thématiques dropdown inside nav first column
|
||||
.nav-axes-item {
|
||||
.nav-axes-trigger {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font-family: $font-primary;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
|
||||
i {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-open .nav-axes-trigger i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.nav-axes-list {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
padding-top: 0.8rem;
|
||||
|
||||
li a {
|
||||
font-size: 0.8rem;
|
||||
padding-left: 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-open .nav-axes-list {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-navigation-container,
|
||||
.menu-navigation-en-container {
|
||||
> ul > li:nth-of-type(1) {
|
||||
.nav-axes-trigger:hover {
|
||||
color: $laboratoire;
|
||||
}
|
||||
}
|
||||
}
|
||||
183
scss/_page-laboratoire.scss
Normal file
@@ -0,0 +1,183 @@
|
||||
// ====================================
|
||||
// PAGE LE LABORATOIRE
|
||||
// ====================================
|
||||
|
||||
// ── Images ────────────────────────────────────────────────────
|
||||
.labo-images {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.labo-image {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
margin-top: 2rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
color: $less-dark-gray;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
width: calc(50% - 0.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Section titles ────────────────────────────────────────────
|
||||
.labo-section {
|
||||
margin-top: 5rem;
|
||||
|
||||
> h3 {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dropdown wrapper ──────────────────────────────────────────
|
||||
.labo-dropdowns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 5rem;
|
||||
|
||||
.labo-section & {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dropdown item ─────────────────────────────────────────────
|
||||
.labo-dropdown-item.is-open {
|
||||
position: relative;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after;
|
||||
}
|
||||
}
|
||||
|
||||
.labo-dropdown-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.2rem;
|
||||
padding: 0.8rem;
|
||||
background-color: $light-gray;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
font-family: $font-primary;
|
||||
font-size: inherit;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.labo-dropdown-chevron {
|
||||
font-size: 1.2rem;
|
||||
transition: transform 0.2s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
.labo-dropdown-item.is-open & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.labo-dropdown-content {
|
||||
padding: 1rem 1rem 1rem 1rem;
|
||||
background-color: $light-light-gray;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
p + ul {
|
||||
margin-top: -1rem;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 1.2rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Axes list ─────────────────────────────────────────────────
|
||||
.labo-axes-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid $light-gray;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bibliothèques ─────────────────────────────────────────────
|
||||
.labo-bibliotheques {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
p + ul {
|
||||
margin-top: -1rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Programme de recherche ────────────────────────────────────
|
||||
.programme-description {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
p { margin-bottom: 0.6rem; }
|
||||
ul, ol { padding-left: 1.2rem; }
|
||||
a { text-decoration: underline; }
|
||||
}
|
||||
|
||||
.programme-link {
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid $light-gray;
|
||||
}
|
||||
114
scss/_postcard.scss
Normal file
@@ -0,0 +1,114 @@
|
||||
.post-card {
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: solid 1px;
|
||||
|
||||
// Category-specific gradients
|
||||
&.gradient--le-laboratoire {
|
||||
.gradient-container {
|
||||
@include category-gradient($laboratoire);
|
||||
}
|
||||
border-color: $laboratoire
|
||||
}
|
||||
&.gradient--manifestations-scientifiques {
|
||||
.gradient-container {
|
||||
@include category-gradient($manifestations);
|
||||
}
|
||||
border-color: $manifestations;
|
||||
}
|
||||
&.gradient--publications-et-productions {
|
||||
.gradient-container {
|
||||
@include category-gradient($publications);
|
||||
}
|
||||
border-color: $publications;
|
||||
}
|
||||
&.gradient--mediation-scientifique {
|
||||
.gradient-container {
|
||||
@include category-gradient($mediations);
|
||||
}
|
||||
border-color: $mediations
|
||||
}
|
||||
&.gradient--ressources {
|
||||
.gradient-container {
|
||||
@include category-gradient($ressources);
|
||||
}
|
||||
border-color: $ressources
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.gradient-container {
|
||||
img, h2 {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
}
|
||||
.gradient-container {
|
||||
height: 25vh;
|
||||
padding: 0.7rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: unset;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
transition: transform 0.2s ease-out;
|
||||
transform: scale(1);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: Gelasio;
|
||||
font-size: 1.7rem;
|
||||
line-height: 1.1;
|
||||
padding: 1.5rem;
|
||||
transition: transform 0.2s ease-out;
|
||||
transform: scale(1);
|
||||
text-decoration: unset;
|
||||
}
|
||||
|
||||
|
||||
&.text-only {
|
||||
font-family: Gelasio;
|
||||
font-size: 1.7rem;
|
||||
padding: 1.5rem;
|
||||
|
||||
p {
|
||||
transition: transform 0.2s ease-out;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextual-infos {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.6rem;
|
||||
line-height: 1.3;
|
||||
.authors {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
> span:not(:last-of-type)::after {
|
||||
content: ", ";
|
||||
}
|
||||
}
|
||||
.date-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title-bottom {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.8rem;
|
||||
line-height: 1.2;
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
scss/_reset.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
*, *:before, *:after{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, div, span, object, iframe, figure, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, code, em, img, small, strike, strong, sub, sup, tt, b, u, i, ol, ul, li, fieldset, form, label, table, caption, tbody, tfoot, thead, tr, th, td, main, canvas, embed, footer, header, nav, section, video{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
footer, header, nav, section, main{
|
||||
display: block;
|
||||
}
|
||||
|
||||
body{
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q{
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after, q:before, q:after{
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table{
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input{
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
153
scss/_search.scss
Normal file
@@ -0,0 +1,153 @@
|
||||
.search-page-form {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 3rem;
|
||||
@media ($desktop) {
|
||||
width: 50%;
|
||||
}
|
||||
.search-panel__desc {
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search-page-form + #category-filters {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
// Author search results
|
||||
.author-results {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
&__title {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 1.2rem;
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
// Taxonomy search results (axes & programmes)
|
||||
.taxonomy-results {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
&__title {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 1.2rem;
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
|
||||
&__list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
&__link {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid $light-gray;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: $dark-gray;
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-family: $font-heading;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: block;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
color: $less-dark-gray;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.author-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media ($desktop) {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.author-card {
|
||||
border-bottom: solid 1px $laboratoire;
|
||||
padding-bottom: 0.4rem;
|
||||
|
||||
&__visual {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 14vh;
|
||||
padding: 0.7rem;
|
||||
background-color: lighten($laboratoire, 28%);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
transition: transform 0.2s ease-out;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
&__initials {
|
||||
font-family: $font-heading;
|
||||
font-size: 2rem;
|
||||
color: $laboratoire;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&__info {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-family: $font-heading;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 0.3rem;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__role,
|
||||
&__affiliation {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.3;
|
||||
color: $less-dark-gray;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
584
scss/_single.scss
Normal file
@@ -0,0 +1,584 @@
|
||||
.article {
|
||||
margin-top: 0;
|
||||
width: 100%;
|
||||
|
||||
.category-header-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
.breadcrumb {
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1.5rem;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.3;
|
||||
|
||||
&__separator {
|
||||
margin: 0 0.4rem;
|
||||
}
|
||||
}
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
font-family: Gelasio;
|
||||
font-weight: normal;
|
||||
font-size: 1.8rem;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
// bottom: -1.1rem;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.3;
|
||||
&:last-of-type {
|
||||
margin-top: 0.3rem;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
p + p {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
p {
|
||||
font-size: 2.2rem !important;
|
||||
}
|
||||
|
||||
p + p {
|
||||
font-size: 1.6rem !important;
|
||||
}
|
||||
&::after {
|
||||
bottom: -0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Category color gradients on h2::after
|
||||
&.category--le-laboratoire h2::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $laboratoire 30%);
|
||||
}
|
||||
&.category--manifestations-scientifiques h2::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $manifestations 30%);
|
||||
}
|
||||
&.category--publications-et-productions h2::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $publications 30%);
|
||||
}
|
||||
&.category--mediation-scientifique h2::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $mediations 30%);
|
||||
}
|
||||
&.category--ressources h2::after {
|
||||
background: linear-gradient(to bottom, transparent 0%, $ressources 30%);
|
||||
}
|
||||
|
||||
.article-type {
|
||||
display: inline-block;
|
||||
margin-top: 2rem;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
background-color: $light-gray;
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
|
||||
.maj {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem !important;
|
||||
color: $less-dark-gray;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.imgs {
|
||||
width: 100%;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
figure {
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
color: $less-dark-gray;
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&--swiper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
@media ($tablet) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
figure {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.swiper-pagination {
|
||||
position: static;
|
||||
margin-top: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet-active {
|
||||
background-color: $less-dark-gray !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.article-content {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
gap: 3rem;
|
||||
|
||||
@media ($tablet) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $light-light-gray;
|
||||
@media ($tablet) {
|
||||
width: 25%;
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
position: sticky;
|
||||
top: 6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
line-height: 1.4;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.reference-bibliographique {
|
||||
display: inline-block;
|
||||
em, i {
|
||||
font-style: italic;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: $font-primary;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.imgs--portrait {
|
||||
@media ($tablet) {
|
||||
width: calc(100% + 1.6rem);
|
||||
margin-left: -0.8rem;
|
||||
margin-right: -0.8rem;
|
||||
}
|
||||
|
||||
.sidebar-portrait {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-portrait {
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
color: $less-dark-gray;
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content-text {
|
||||
margin-top: 1rem;
|
||||
flex: 1;
|
||||
min-height: unset;
|
||||
|
||||
/* Affichage posts newsletter */
|
||||
&:has(table[role=presentation]) {
|
||||
p:not(table[role=presentation] p):not(.maj) {
|
||||
display: none;
|
||||
}
|
||||
table {
|
||||
&[role=presentation] p {
|
||||
margin: unset;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> *:not(.article-field) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.4;
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
&:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
line-height: 1.4;
|
||||
padding-left: 0.8rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside "· ";
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: inside decimal;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
padding-left: 1rem;
|
||||
margin-left: 1.5rem;
|
||||
border-left: solid 1px $light-gray;
|
||||
}
|
||||
|
||||
p:first-of-type + .mots-cles {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.article-field {
|
||||
font-size: 0.9rem !important;
|
||||
line-height: 1.4 !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
i {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.mots-cles {
|
||||
margin-top: 4rem; // style par défaut = style du premier
|
||||
}
|
||||
.mots-cles ~ .mots-cles {
|
||||
margin-top: 1rem; // les 2e et 3e ont moins d'espace
|
||||
}
|
||||
|
||||
.canal-u-embeds,
|
||||
.youtube-embeds {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.canal-u-embed,
|
||||
.video-embed {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-bottom: 56.25%; // 16:9
|
||||
height: 0;
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.inline-title {
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
@include yellow-gradient;
|
||||
}
|
||||
}
|
||||
|
||||
.related-posts,
|
||||
.seances-section {
|
||||
margin-top: 5rem;
|
||||
|
||||
h3 {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
&::after {
|
||||
@include yellow-gradient-after(10px);
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.related-posts {
|
||||
.post-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
|
||||
.post-card {
|
||||
min-width: 0;
|
||||
a {
|
||||
text-decoration: none;
|
||||
h2::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.seances-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.seance-header {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1.2rem;
|
||||
padding: 0.1rem;
|
||||
background-color: $light-gray;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: $less-light-gray;
|
||||
}
|
||||
@media ($tablet) {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.seance-date {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 3rem;
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.2;
|
||||
padding: 0.4rem 0;
|
||||
background: linear-gradient(to bottom, $light_light_gray 60%, $manifestations);
|
||||
|
||||
&__day {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
&__month {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
&__year {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.seance-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
|
||||
.seance-title {
|
||||
font-family: $font-heading;
|
||||
font-size: 1.15rem;
|
||||
line-height: 1.2;
|
||||
text-decoration: none;
|
||||
padding: 0.6rem 0;
|
||||
@media ($tablet) {
|
||||
padding: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.seance-intervenants {
|
||||
font-family: $font-primary;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.1rem;
|
||||
color: black;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.seance-chevron {
|
||||
font-size: 1.2rem;
|
||||
transition: transform 0.2s;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.seance-item.is-open .seance-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.seance-content {
|
||||
padding: 1rem 1rem 1rem 1rem;
|
||||
background-color: $light-light-gray;
|
||||
font-family: $font-primary;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
|
||||
.seance-content-infos {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
> .seance-content-lieu {
|
||||
text-align: right;
|
||||
> p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.seance-images {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
figure {
|
||||
@media ($tablet) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.seance-extras {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 0.8rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.seance-related {
|
||||
margin-top: 1.5rem;
|
||||
|
||||
h4 {
|
||||
font-family: $font-primary;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.post-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
|
||||
.post-card {
|
||||
min-width: 0;
|
||||
a {
|
||||
text-decoration: none;
|
||||
h2::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
padding: 1rem 1rem 1rem 5.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
scss/_typography.scss
Normal file
@@ -0,0 +1,69 @@
|
||||
@font-face {
|
||||
font-family: 'Gelasio';
|
||||
src: url('../assets/fonts/Gelasio-Regular.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NewsCycle';
|
||||
src: url('../assets/fonts/NewsCycle-Regular.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-primary;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: $font-heading;
|
||||
}
|
||||
|
||||
a,
|
||||
a:active {
|
||||
color: $dark-gray;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $less-dark-gray;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
display: inline-flex;
|
||||
background-color: $light_gray;
|
||||
color: $dark_gray;
|
||||
padding: 0.6rem 0.7rem;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.3s ease-out;
|
||||
text-decoration: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
|
||||
> i {
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $less_light-gray;
|
||||
}
|
||||
|
||||
@media ($tablet) {
|
||||
font-size: unset;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
// Multi-word titles: break at word boundaries; only split a word mid-letter
|
||||
// as a last resort when it overflows the container.
|
||||
&--wrap-word {
|
||||
word-break: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
31
scss/_variables.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
// Neutral colors
|
||||
$light-light-gray: #fcfcfc;
|
||||
$light-gray: #eeeeee;
|
||||
$less-light-gray: #cccccc;
|
||||
$less-less-light-gray: #bbbbbb;
|
||||
$yellow: #f7ff29;
|
||||
$dark-gray: #1a1a1a;
|
||||
$less-dark-gray: #3e3e3e;
|
||||
|
||||
// Theme colors
|
||||
$laboratoire: #e0775d;
|
||||
$manifestations: #7cc0c6;
|
||||
$mediations: #e05680;
|
||||
$publications: #46ae51;
|
||||
$ressources: #bb8dd9;
|
||||
|
||||
// Fonts
|
||||
$font-primary: 'NewsCycle', sans-serif;
|
||||
$font-heading: 'Gelasio', serif;
|
||||
|
||||
// Breakpoints
|
||||
$breakpoint-tablet: 768px;
|
||||
$breakpoint-desktop: 1024px;
|
||||
$breakpoint-large: 1440px;
|
||||
|
||||
// Media queries (mobile first)
|
||||
$tablet: 'min-width: #{$breakpoint-tablet}';
|
||||
$desktop: 'min-width: #{$breakpoint-desktop}';
|
||||
$large: 'min-width: #{$breakpoint-large}';
|
||||
|
||||
// Fonts sizes
|
||||
25
scss/style.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
@import 'reset';
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
@import 'base';
|
||||
@import 'typography';
|
||||
@import 'layout';
|
||||
@import 'header';
|
||||
@import 'navigation';
|
||||
@import 'footer';
|
||||
@import 'index';
|
||||
@import 'postcard';
|
||||
@import 'category';
|
||||
@import 'filters';
|
||||
@import 'single';
|
||||
@import 'author';
|
||||
@import 'membres';
|
||||
@import 'page-laboratoire';
|
||||
@import 'search';
|
||||
|
||||
/*
|
||||
Theme Name: Thalim
|
||||
Author: Valentin Le Moign
|
||||
Version: 1.0
|
||||
*/
|
||||
|
||||
248
search.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
|
||||
// Séances de séminaire (cat 12) are included: post-card-helpers rewrites their
|
||||
// link to the parent séminaire + #seance-{ID} hash.
|
||||
$excluded_cat_ids = [31]; // Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
||||
$search_query = get_search_query();
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
|
||||
$context['search_query'] = $search_query;
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
$context['active_date_to'] = $active_date_to;
|
||||
$context['active_category_id'] = $filter_autres ? 'autres' : $active_cat_id;
|
||||
$context['active_cat_id'] = $active_cat_id;
|
||||
$context['filter_autres'] = $filter_autres;
|
||||
|
||||
// Determine active rubrique
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base URL for search filter links (language-aware)
|
||||
$search_base = thalim_en_url( home_url('/') );
|
||||
|
||||
// Override annonces_url: rubrique reset stays on search page (no filter_cat)
|
||||
$context['annonces_url'] = add_query_arg(['s' => $search_query], $search_base);
|
||||
|
||||
// Base params preserved across filter links (preserves search term)
|
||||
$base_filter_params = array_filter([
|
||||
's' => $search_query,
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]);
|
||||
|
||||
// Build tax_query
|
||||
$tax_query = [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => $excluded_cat_ids,
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
];
|
||||
if ($active_cat_id) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_cat_id],
|
||||
'include_children' => !$filter_autres,
|
||||
];
|
||||
}
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'post',
|
||||
's' => $search_query,
|
||||
'relevanssi' => true,
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'relevance',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => $tax_query,
|
||||
];
|
||||
if ($active_axe) {
|
||||
$query_args['meta_query'] = [[
|
||||
'key' => 'axes_thematiques',
|
||||
'value' => $active_axe,
|
||||
'type' => 'NUMERIC',
|
||||
]];
|
||||
}
|
||||
if ($active_date_from || $active_date_to) {
|
||||
$date_query = ['inclusive' => true];
|
||||
if ($active_date_from) $date_query['after'] = $active_date_from;
|
||||
if ($active_date_to) $date_query['before'] = $active_date_to;
|
||||
$query_args['date_query'] = [$date_query];
|
||||
}
|
||||
|
||||
// Axes thématiques for filter dropdown
|
||||
$axes_groups = thalim_get_axes_filter_groups();
|
||||
$current_axes = $axes_groups[0]['terms'] ?? [];
|
||||
$context['filter_axes'] = $current_axes;
|
||||
$context['axe_stay_on_page'] = true;
|
||||
|
||||
// Rubrique/catégorie filter links (all preserve search term)
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
|
||||
$posts = Timber::get_posts($query_args);
|
||||
$context['cards'] = thalim_get_cards_data($posts);
|
||||
$context['posts'] = $posts;
|
||||
|
||||
// Search users (members) by display_name
|
||||
$author_cards = [];
|
||||
if ( $search_query ) {
|
||||
$excluded_role_ids = [ 600, 598 ]; // "À ranger", "Archive"
|
||||
$user_query = new WP_User_Query([
|
||||
'search' => '*' . $search_query . '*',
|
||||
'search_columns' => ['display_name'],
|
||||
'number' => 6,
|
||||
'orderby' => 'display_name',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => 'role_1',
|
||||
'value' => $excluded_role_ids,
|
||||
'compare' => 'NOT IN',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$lang = thalim_current_language();
|
||||
|
||||
// Direction IDs (same source as membres page and author page)
|
||||
$labo_page = get_page_by_path( 'le-laboratoire' );
|
||||
$labo_directeur_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur', true ) ) : 0;
|
||||
$labo_adjoint_id = $labo_page ? intval( get_post_meta( $labo_page->ID, 'directeur_adjoint', true ) ) : 0;
|
||||
|
||||
foreach ( $user_query->get_results() as $user ) {
|
||||
$avatar_url = thalim_get_user_avatar_url( $user->ID );
|
||||
|
||||
$role_id = get_user_meta( $user->ID, 'role_1', true );
|
||||
$role_label = '';
|
||||
if ( $role_id ) {
|
||||
$role_term = get_term( intval( $role_id ), 'role' );
|
||||
if ( $role_term && ! is_wp_error( $role_term ) ) {
|
||||
$override = thalim_bilingual( get_user_meta( $user->ID, 'affichage_du_statut_1', true ) ?: '', $lang );
|
||||
$role_label = $override ?: $role_term->name;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $user->ID === $labo_directeur_id ) {
|
||||
$role_label = 'Directeur' . ( $role_label ? ', ' . $role_label : '' );
|
||||
} elseif ( $user->ID === $labo_adjoint_id ) {
|
||||
$role_label = 'Directeur adjoint' . ( $role_label ? ', ' . $role_label : '' );
|
||||
}
|
||||
|
||||
$affiliation = get_user_meta( $user->ID, 'affiliation', true ) ?: '';
|
||||
if ( strtolower( $affiliation ) === 'autre' ) {
|
||||
$affiliation = thalim_bilingual( get_user_meta( $user->ID, 'affiliation_autre', true ) ?: '', $lang );
|
||||
}
|
||||
|
||||
$words = preg_split( '/\s+/', trim( $user->display_name ) );
|
||||
$initials = implode( '', array_map( fn( $w ) => mb_substr( $w, 0, 1 ), $words ) );
|
||||
|
||||
$author_cards[] = [
|
||||
'id' => $user->ID,
|
||||
'name' => $user->display_name,
|
||||
'url' => get_author_posts_url( $user->ID ),
|
||||
'avatar_url' => $avatar_url,
|
||||
'initials' => mb_strtoupper( $initials ),
|
||||
'role_label' => $role_label,
|
||||
'affiliation' => $affiliation,
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['author_cards'] = $author_cards;
|
||||
|
||||
// Search taxonomy terms (axes thématiques + programmes de recherche)
|
||||
$taxonomy_cards = [];
|
||||
if ( $search_query ) {
|
||||
$matching_terms = get_terms([
|
||||
'taxonomy' => [ 'axe_thematique', 'programme_de_recherche' ],
|
||||
'hide_empty' => false,
|
||||
'name__like' => $search_query,
|
||||
]);
|
||||
if ( ! is_wp_error( $matching_terms ) ) {
|
||||
foreach ( $matching_terms as $term ) {
|
||||
$tax_obj = get_taxonomy( $term->taxonomy );
|
||||
$taxonomy_cards[] = [
|
||||
'name' => $term->name,
|
||||
'url' => get_term_link( $term ),
|
||||
'taxonomy_label' => $tax_obj ? $tax_obj->labels->singular_name : $term->taxonomy,
|
||||
'count' => $term->count,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$context['taxonomy_cards'] = $taxonomy_cards;
|
||||
|
||||
Timber::render('search.twig', $context);
|
||||
21
single.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$post = Timber::get_post();
|
||||
$context['post'] = $post;
|
||||
$context['article'] = thalim_get_single_data($post->ID);
|
||||
|
||||
// Card data for related posts (main + séances)
|
||||
$related_cards = [];
|
||||
if (!empty($context['article']['annonces_liees'])) {
|
||||
$related_cards += thalim_get_cards_data($context['article']['annonces_liees']);
|
||||
}
|
||||
foreach (['seances_a_venir', 'seances_passees'] as $seance_group) {
|
||||
foreach ($context['article'][$seance_group] as $s) {
|
||||
if (!empty($s['annonces_liees'])) {
|
||||
$related_cards += thalim_get_cards_data($s['annonces_liees']);
|
||||
}
|
||||
}
|
||||
}
|
||||
$context['related_cards'] = $related_cards;
|
||||
|
||||
Timber::render('single.twig', $context);
|
||||
9
style.css
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
Theme Name: Thalim
|
||||
Author: THALIM — Théorie et Histoire des Arts et des Littératures de la Modernité
|
||||
Description: Thème personnalisé pour le laboratoire THALIM (UMR 7172). Basé sur Timber/Twig.
|
||||
Version: 1.0.0
|
||||
Requires at least: 6.0
|
||||
Requires PHP: 7.4
|
||||
Text Domain: thalim
|
||||
*/
|
||||
186
taxonomy.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
$term = get_queried_object();
|
||||
$taxonomy = $term->taxonomy;
|
||||
|
||||
$context['term'] = Timber::get_term($term);
|
||||
$context['taxonomy_slug'] = $taxonomy;
|
||||
$context['term_id'] = $term->term_id;
|
||||
$context['parent_slug'] = '';
|
||||
$tax_object = get_taxonomy($taxonomy);
|
||||
$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy;
|
||||
|
||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
$context['active_date_to'] = $active_date_to;
|
||||
$context['active_category_id'] = $filter_autres ? 'autres' : $active_cat_id;
|
||||
$context['active_cat_id'] = $active_cat_id;
|
||||
$context['filter_autres'] = $filter_autres;
|
||||
|
||||
// Determine active rubrique from active category (parent if subcategory, itself if top-level)
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base params shared across all filter links (preserves active filters when navigating)
|
||||
$base_filter_params = array_filter([
|
||||
'axe' => $active_axe ?: null,
|
||||
'date_from' => $active_date_from ?: null,
|
||||
'date_to' => $active_date_to ?: null,
|
||||
]);
|
||||
|
||||
// Build tax_query — combine all clauses with AND
|
||||
$tax_query = [
|
||||
'relation' => 'AND',
|
||||
// Terme de la taxonomie courante
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term->term_id],
|
||||
],
|
||||
// Exclure les séances de séminaire (catégorie 12)
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [12],
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
];
|
||||
if ($active_cat_id) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_cat_id],
|
||||
'include_children' => !$filter_autres,
|
||||
];
|
||||
}
|
||||
|
||||
// On axe_thematique pages, the current term IS the active axe (for display only — taxonomy query handles filtering)
|
||||
$axe_taxonomy_mode = ($taxonomy === 'axe_thematique');
|
||||
if ($axe_taxonomy_mode) {
|
||||
$active_axe = $term->term_id;
|
||||
$context['active_axe'] = $active_axe;
|
||||
}
|
||||
|
||||
// Build remaining query args (meta/date)
|
||||
$extra_query_args = [];
|
||||
if ($active_axe && !$axe_taxonomy_mode) {
|
||||
$extra_query_args['meta_query'] = [[
|
||||
'key' => 'axes_thematiques',
|
||||
'value' => $active_axe,
|
||||
'type' => 'NUMERIC',
|
||||
]];
|
||||
}
|
||||
if ($active_date_from || $active_date_to) {
|
||||
$extra_query_args['thalim_event_date_filter'] = ['from' => $active_date_from, 'to' => $active_date_to];
|
||||
}
|
||||
|
||||
// Axes thématiques filter
|
||||
$axes_groups = thalim_get_axes_filter_groups();
|
||||
$current_axes = $axes_groups[0]['terms'] ?? [];
|
||||
$context['filter_axes'] = $current_axes;
|
||||
$context['axe_taxonomy_mode'] = $axe_taxonomy_mode;
|
||||
$context['axe_stay_on_page'] = !$axe_taxonomy_mode;
|
||||
|
||||
// Build rubrique/catégorie filter links pointing back to the current taxonomy URL
|
||||
$current_term_url = get_term_link($term);
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term->term_id],
|
||||
],
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
],
|
||||
],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
|
||||
$posts = Timber::get_posts(array_merge([
|
||||
'post_type' => 'post',
|
||||
'tax_query' => $tax_query,
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args));
|
||||
$context['cards'] = thalim_get_cards_data($posts);
|
||||
$context['posts'] = $posts;
|
||||
|
||||
// Custom Pods presentation fields (not the WP built-in description)
|
||||
$tax_lang = thalim_current_language();
|
||||
$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: '';
|
||||
$pres_en = get_term_meta($term->term_id, 'presentation_en', true) ?: '';
|
||||
$context['term_presentation'] = wpautop( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr );
|
||||
|
||||
Timber::render('taxonomy.twig', $context);
|
||||
21
templates/404.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="full-block hero-header" style="min-height: 300px;">
|
||||
<div class="hero-content">
|
||||
<p class="error-404__code">404</p>
|
||||
<p class="error-404__title">
|
||||
{{ current_language == 'en' ? 'Page not found' : 'Page introuvable' }}
|
||||
</p>
|
||||
<h1 style="font-size: 1.2rem; margin-top: 1rem;">
|
||||
{{ current_language == 'en'
|
||||
? 'The page you are looking for does not exist or has been moved.'
|
||||
: "La page que vous cherchez n'existe pas ou a été déplacée." }}
|
||||
</h1>
|
||||
<a href="{{ function('home_url', '/') }}" class="link-button">
|
||||
{{ current_language == 'en' ? '← Back to home' : "← Retour à l'accueil" }}
|
||||
</a>
|
||||
</div>
|
||||
<div id="sketch"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
181
templates/author.twig
Normal file
@@ -0,0 +1,181 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="article category--le-laboratoire">
|
||||
<div class="full-block">
|
||||
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}">{{ current_language == 'en' ? 'The department' : 'Le laboratoire' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/membres/') : function('home_url', '/membres/') }}">{{ current_language == 'en' ? 'Lab members' : 'Membres du laboratoire' }}</a>
|
||||
</nav>
|
||||
{% if author_edit_link %}
|
||||
<a href="{{ author_edit_link }}" class="link-button" target="_blank" rel="noopener">
|
||||
<i class="iconoir-edit-pencil"></i>{{ current_language == 'en' ? 'Edit profile' : 'Éditer le profil' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if author.avatar_url %}
|
||||
<div class="author-header">
|
||||
<div class="author-avatar">
|
||||
<img src="{{ author.avatar_url }}" alt="{{ author.display_name }}">
|
||||
</div>
|
||||
<div class="author-identity">
|
||||
<h2><p>{{ author.display_name }}</p></h2>
|
||||
{% if author.role_label or author.role_complement or author.affiliation %}
|
||||
<p class="author-role">
|
||||
{{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="article-content">
|
||||
|
||||
{% if author.email or author.liens_externes or author.documents or author.hal_publications_url %}
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-container">
|
||||
<div class="sidebar-section">
|
||||
{% if author.email %}
|
||||
<a href="mailto:{{ author.email }}" class="link-button"><i class="iconoir-mail"></i>{{ author.email }}</a>
|
||||
{% endif %}
|
||||
{% for doc in author.documents %}
|
||||
<a href="{{ doc.url }}" class="link-button{% if ' ' in doc.title %} link-button--wrap-word{% endif %}" target="_blank" rel="noopener"><i class="iconoir-page"></i>{{ doc.title }}</a>
|
||||
{% endfor %}
|
||||
{% for lien in author.liens_externes %}
|
||||
<a href="{{ lien.url }}" class="link-button{% if ' ' in lien.titre %} link-button--wrap-word{% endif %}" target="_blank" rel="noopener"><i class="iconoir-open-new-window"></i>{{ lien.titre }}</a>
|
||||
{% endfor %}
|
||||
{% if author.hal_publications_url %}
|
||||
<a href="{{ author.hal_publications_url }}" class="link-button link-button--wrap-word" target="_blank" rel="noopener"><i class="iconoir-open-new-window"></i>Publications HAL</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="main-content-text">
|
||||
|
||||
{% if not author.avatar_url %}
|
||||
<div class="author-identity">
|
||||
<h2><p>{{ author.display_name }}</p></h2>
|
||||
{% if author.role_label or author.role_complement or author.affiliation %}
|
||||
<p class="author-role">
|
||||
{{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_language == 'en' and author.bio_en %}
|
||||
<div class="author-bio">{{ author.bio_en|raw }}</div>
|
||||
{% elseif author.bio %}
|
||||
<div class="author-bio">{{ author.bio|raw }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if author.domaines_tags %}
|
||||
<p class="article-field mots-cles">
|
||||
<span class="inline-title">{{ current_language == 'en' ? 'Research areas' : 'Domaines de recherches' }} :</span>
|
||||
{% for tag in author.domaines_tags %}
|
||||
<a href="{{ tag.url }}" class="keyword-link">{{ tag.name }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_language == 'en' and author.domaines_en %}
|
||||
<div class="article-field domaines-autres">
|
||||
{% if not author.domaines_tags %}<span class="inline-title">Research areas :</span>{% endif %}
|
||||
{{ author.domaines_en|raw }}
|
||||
</div>
|
||||
{% elseif author.domaines %}
|
||||
<div class="article-field domaines-autres">
|
||||
{% if not author.domaines_tags %}<span class="inline-title">{{ current_language == 'en' ? 'Research areas' : 'Domaines de recherches' }} :</span>{% endif %}
|
||||
{{ author.domaines|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_language == 'en' and author.recherches_en %}
|
||||
<div class="article-field recherches-en-cours">
|
||||
<span class="inline-title">Current research :</span>
|
||||
{{ author.recherches_en|raw }}
|
||||
</div>
|
||||
{% elseif author.recherches %}
|
||||
<div class="article-field recherches-en-cours">
|
||||
<span class="inline-title">{{ current_language == 'en' ? 'Current research' : 'Recherches en cours' }} :</span>
|
||||
{{ author.recherches|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if author.axes %}
|
||||
<p class="article-field mots-cles">
|
||||
<span class="inline-title">{{ current_language == 'en' ? 'Thematic axes' : 'Axes thématiques' }} :</span>
|
||||
{% for axe in author.axes %}
|
||||
<a href="{{ axe.url }}" class="keyword-link">{{ axe.name }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if author.titre_these or author.resume_these or author.resume_these_en %}
|
||||
<section class="seances-section">
|
||||
<h3>{{ current_language == 'en' ? 'Thesis' : 'Thèse' }}</h3>
|
||||
{% if author.titre_these %}
|
||||
<p class="author-titre-these">{{ author.titre_these }}</p>
|
||||
{% endif %}
|
||||
{% if author.date_soutenance %}
|
||||
<p class="article-field">
|
||||
<span class="these-inline-title">{{ current_language == 'en' ? 'Defended in' : 'Soutenue en' }} </span>
|
||||
{{ author.date_soutenance }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if author.directeur_thalim or author.autre_directeur %}
|
||||
<p class="article-field">
|
||||
<span class="these-inline-title">{{ current_language == 'en' ? 'Supervisor' : 'Direction' }} :</span>
|
||||
{% if author.directeur_thalim %}
|
||||
<a href="{{ author.directeur_thalim.url }}">{{ author.directeur_thalim.name }}</a>{% if author.autre_directeur %}, {% endif %}
|
||||
{% endif %}
|
||||
{% if author.autre_directeur %}{{ author.autre_directeur }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if current_language == 'en' and author.resume_these_en %}
|
||||
<div class="author-resume-these">{{ author.resume_these_en|raw }}</div>
|
||||
{% elseif author.resume_these %}
|
||||
<div class="author-resume-these">{{ author.resume_these|raw }}</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if author_posts %}
|
||||
<div class="author-posts-section">
|
||||
{% for group in author_posts %}
|
||||
<div class="author-posts-item">
|
||||
<h3 class="author-posts-header" data-seance-toggle>
|
||||
{{ group.cat_name }}
|
||||
<i class="iconoir-nav-arrow-down author-posts-chevron"></i>
|
||||
</h3>
|
||||
<div class="author-posts-content" style="display: none;">
|
||||
<div class="author-post-grid">
|
||||
{% for post in group.posts %}
|
||||
{% include 'partials/post-card.twig' with {
|
||||
post: post,
|
||||
card: group.cards[post.ID],
|
||||
show_category: false
|
||||
} %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
21
templates/base.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html {{ site.language_attributes }}>
|
||||
<head>
|
||||
<meta charset="{{ site.charset }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{{ function('wp_head') }}
|
||||
</head>
|
||||
<body class="{{ body_class }}">
|
||||
{% include 'partials/header.twig' %}
|
||||
|
||||
<main>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% include 'partials/footer.twig' %}
|
||||
|
||||
{{ function('wp_footer') }}
|
||||
</body>
|
||||
</html>
|
||||
132
templates/category.twig
Normal file
@@ -0,0 +1,132 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<main class="category-archive">
|
||||
<div class="container">
|
||||
<div class="full-block category--{{ parent_slug }}">
|
||||
<header class="category-header">
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
{% if not is_parent and category.parent %}
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ function('get_category_link', category.parent) }}">{{ function('thalim_cat_name', category.parent) }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ function('get_category_link', category.term_id) }}"><span class="breadcrumb__current">{{ category|cat_name }}</span></a>
|
||||
{% elseif is_direct %}
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ function('get_category_link', category.term_id) }}">{{ category|cat_name }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ current_language == 'en' ? 'Other' : 'Autre' }}</span>
|
||||
{% else %}
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ function('get_category_link', category.term_id) }}"><span class="breadcrumb__current">{{ category|cat_name }}</span></a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<a href="{{ agenda_toggle_url }}" class="link-button agenda-toggle-btn">
|
||||
{% if view_mode == 'agenda' %}
|
||||
<i class="iconoir-view-grid"></i>{{ current_language == 'en' ? 'Switch to grid view' : 'Passer à la vue grille' }}
|
||||
{% else %}
|
||||
<i class="iconoir-calendar"></i>{{ current_language == 'en' ? 'Switch to agenda view' : 'Passer à la vue agenda' }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="category-header__title">
|
||||
{%- if is_direct -%}
|
||||
{{ current_language == 'en' ? 'Other' : 'Autres' }} {{ (category|cat_name)|lower }}
|
||||
{%- else -%}
|
||||
{{ category|cat_name }}
|
||||
{%- endif -%}
|
||||
</h1>
|
||||
{% if term_presentation %}
|
||||
<div class="taxonomy-description">{{ term_presentation|raw }}</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
{% include 'partials/category-filters.twig' %}
|
||||
<div id="grid-sections"{% if view_mode == 'agenda' %} style="display:none"{% endif %}>
|
||||
{% if is_parent %}
|
||||
{% for subcategory in subcategories %}
|
||||
{% if subcategory.posts is not empty %}
|
||||
<section class="subcategory-section">
|
||||
<h2 class="subcategory-section__title">{{ subcategory.term|cat_name }}</h2>
|
||||
<div class="post-grid">
|
||||
{% for post in subcategory.posts %}
|
||||
{% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true, type_only: true } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="category-section-footer">
|
||||
{% if current_language == 'en' %}
|
||||
<a href="{{ subcategory.term.link }}" class="link-button"><i class="iconoir-plus-circle"></i>See all {{ (subcategory.term|cat_name)|lower }}</a>
|
||||
{% else %}
|
||||
{% set is_fem = subcategory.term.description == 'f' %}
|
||||
<a href="{{ subcategory.term.link }}" class="link-button"><i class="iconoir-plus-circle"></i>Voir {{ is_fem ? 'toutes les' : 'tous les' }} {{ (subcategory.term|cat_name)|lower }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if direct_posts is defined and direct_posts is not empty %}
|
||||
<section class="subcategory-section">
|
||||
<h2 class="subcategory-section__title">{{ current_language == 'en' ? 'Other' : 'Autres' }}</h2>
|
||||
<div class="post-grid">
|
||||
{% for post in direct_posts %}
|
||||
{% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true, type_only: true } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="category-section-footer">
|
||||
{% if current_language == 'en' %}
|
||||
<a href="{{ autres_link }}" class="link-button"><i class="iconoir-plus-circle"></i>See all other {{ (category|cat_name)|lower }}</a>
|
||||
{% else %}
|
||||
<a href="{{ autres_link }}" class="link-button"><i class="iconoir-plus-circle"></i>Voir toutes les autres {{ (category|cat_name)|lower }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<section class="subcategory-section">
|
||||
<div class="post-grid" id="post-grid">
|
||||
{% for post in posts %}
|
||||
{% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true, type_only: true } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div id="scroll-sentinel"
|
||||
data-category="{{ category_id }}"
|
||||
data-axe="{{ active_axe }}"
|
||||
data-date-from="{{ active_date_from }}"
|
||||
data-date-to="{{ active_date_to }}"></div>
|
||||
<div id="scroll-spinner" class="scroll-spinner" style="display: none;">
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Agenda view — shared by parent and leaf categories #}
|
||||
<div class="agenda-view-container{% if view_mode == 'agenda' %} is-active{% endif %}"
|
||||
id="agenda-view"
|
||||
data-category="{{ category_id }}"
|
||||
data-include-children="{{ agenda_include_children }}"
|
||||
data-axe="{{ active_axe }}"
|
||||
data-date-from="{{ active_date_from }}"
|
||||
data-date-to="{{ active_date_to }}">
|
||||
<h2 class="agenda-view-title">Agenda</h2>
|
||||
<div class="agenda-swiper-wrap">
|
||||
<button class="agenda-swiper-prev" aria-label="{{ current_language == 'en' ? 'Previous' : 'Précédent' }}"><i class="iconoir-arrow-left"></i></button>
|
||||
<div class="swiper agenda-swiper">
|
||||
<div class="swiper-wrapper" id="agenda-swiper-wrapper"></div>
|
||||
</div>
|
||||
<button class="agenda-swiper-next" aria-label="{{ current_language == 'en' ? 'Next' : 'Suivant' }}"><i class="iconoir-arrow-right"></i></button>
|
||||
</div>
|
||||
<div id="agenda-spinner" class="scroll-spinner" style="display:none;">
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
135
templates/index.twig
Normal file
@@ -0,0 +1,135 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="full-block hero-header">
|
||||
<div class="hero-content">
|
||||
<div class="hero-logos">
|
||||
<a href="https://www.cnrs.fr/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/cnrs.png" alt="Logo CNRS">
|
||||
</a>
|
||||
<a href="https://www.sorbonne-nouvelle.fr/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/sorbonne.png" alt="Logo Sorbonne Nouvelle">
|
||||
</a>
|
||||
<a href="https://www.ens.psl.eu/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/ens.png" alt="Logo ENS">
|
||||
</a>
|
||||
</div>
|
||||
<p class="hero-presentation">
|
||||
{{ gc.presentation }}
|
||||
</p>
|
||||
<p class="hero-presentation-detail">
|
||||
{{ gc.presentation_detail }}
|
||||
</p>
|
||||
<a href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}" class="link-button">
|
||||
{{ current_language == 'fr' ? 'En savoir plus' : 'Learn more' }}
|
||||
</a>
|
||||
</div>
|
||||
<div id="sketch"></div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/swiper-section.twig' with {
|
||||
section_posts: annonces,
|
||||
section_cards: annonces_cards,
|
||||
section_title: current_language == 'en' ? 'Announcements' : 'Annonces',
|
||||
all_link: annonces_link,
|
||||
all_label: current_language == 'en' ? 'All announcements' : 'Toutes les annonces'
|
||||
} %}
|
||||
|
||||
{% if messages_labo or agenda_items %}
|
||||
<section class="message-agenda-section">
|
||||
{% if messages_labo %}
|
||||
<div class="message-du-labo">
|
||||
<div class="section-title">
|
||||
<p>{{ current_language == 'en' ? 'Laboratory messages' : 'Messages du laboratoire' }}</p>
|
||||
</div>
|
||||
<div class="messages-list">
|
||||
{% for message in messages_labo %}
|
||||
<div class="message-item">
|
||||
<time class="message-date" datetime="{{ message.date('Y-m-d') }}">{{ message.date('d/m/Y') }}</time>
|
||||
<div class="message-content">
|
||||
{{ message.content | raw }}
|
||||
<a href="{{ message.link | en_url }}" class="message-read-more">
|
||||
{{ current_language == 'en' ? 'Read more' : 'Lire la suite' }} →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="button-messages">
|
||||
<a href="{{ message_labo_link }}" class="link-button">
|
||||
{{ current_language == 'en' ? 'All laboratory messages' : 'Tous les messages du laboratoire' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if agenda_items %}
|
||||
<div class="agenda">
|
||||
<div class="section-title">
|
||||
<p>{{ current_language == 'en' ? 'Upcoming' : 'À venir' }}</p>
|
||||
</div>
|
||||
<div class="agenda-content">
|
||||
{% for item in agenda_items %}
|
||||
<a href="{{ item.link | en_url }}" class="agenda-item">
|
||||
<div class="date-container">
|
||||
<p>{% if item.day == 1 %}1<sup>{{ current_language == 'en' ? 'st' : 'er' }}</sup>{% else %}{{ item.day }}{% endif %}</p>
|
||||
<p>{{ item.month }}</p>
|
||||
</div>
|
||||
<div class="event-content">
|
||||
<div class="meta">
|
||||
{% if item.type_label %}<p>{{ item.type_label }}</p>{% endif %}
|
||||
{% if item.lieu %}<p>{{ item.lieu }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="event-title">
|
||||
<p>{{ item.post.title | bilingual(current_language) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="button-agenda">
|
||||
<a href="{{ manifestations_link }}" class="link-button">
|
||||
{{ current_language == 'en' ? 'All events' : 'Tous les événements' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% include 'partials/swiper-section.twig' with {
|
||||
section_posts: publications,
|
||||
section_cards: publications_cards,
|
||||
section_title: current_language == 'en' ? 'Books & journals' : 'Ouvrages et Revues',
|
||||
all_link: publications_link,
|
||||
all_label: current_language == 'en' ? 'All publications' : 'Toutes les publications'
|
||||
} %}
|
||||
|
||||
{% if has_tags %}
|
||||
<section class="keyword-cloud">
|
||||
<div id="keyword-container"></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="quick-links">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ quick_links.agenda }}">
|
||||
<p>Agenda</p>
|
||||
<i class="iconoir-calendar"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ quick_links.contacts }}">
|
||||
<p>Contacts</p>
|
||||
<i class="iconoir-mail"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ quick_links.newsletter }}">
|
||||
<p>Newsletter</p>
|
||||
<i class="iconoir-message-text"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
43
templates/page-annonces.twig
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<main class="category-archive">
|
||||
<div class="container">
|
||||
<div class="full-block">
|
||||
<header class="category-header">
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ current_language == 'en' ? 'Announcements' : 'Annonces' }}</span>
|
||||
</nav>
|
||||
</div>
|
||||
<h1 class="category-header__title">{{ current_language == 'en' ? 'Announcements' : 'Annonces' }}</h1>
|
||||
</header>
|
||||
|
||||
{% include 'partials/category-filters.twig' %}
|
||||
|
||||
<section class="subcategory-section">
|
||||
<div class="post-grid" id="post-grid">
|
||||
{% for post in posts %}
|
||||
{% include 'partials/post-card.twig' with { post: post, card: cards[post.ID], show_category: true } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div id="scroll-sentinel"
|
||||
data-exclude-cats="12,31"
|
||||
data-filter-cat="{{ active_cat_id }}"
|
||||
data-filter-autres="{{ filter_autres }}"
|
||||
data-axe="{{ active_axe }}"
|
||||
data-date-from="{{ active_date_from }}"
|
||||
data-date-to="{{ active_date_to }}"></div>
|
||||
<div id="scroll-spinner" class="scroll-spinner" style="display: none;">
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
<div class="scroll-spinner__dot"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
123
templates/page-le-laboratoire.twig
Normal file
@@ -0,0 +1,123 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="article category--le-laboratoire">
|
||||
<div class="full-block">
|
||||
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}">{{ current_language == 'en' ? 'The department' : 'Le laboratoire' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ post.title | bilingual(current_language) }}</span>
|
||||
</nav>
|
||||
{% if page_edit_link %}
|
||||
<a href="{{ page_edit_link }}" class="link-button" target="_blank" rel="noopener">
|
||||
<i class="iconoir-edit-pencil"></i>{{ current_language == 'en' ? 'Edit page' : 'Éditer la page' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2><p>{{ post.title | bilingual(current_language) }}</p></h2>
|
||||
|
||||
{% if images %}
|
||||
<div class="labo-images">
|
||||
{% for img in images %}
|
||||
<figure class="labo-image">
|
||||
<img src="{{ img.url }}" alt="{{ img.alt }}" loading="lazy">
|
||||
{% if img.title %}
|
||||
<figcaption>{{ img.title }}</figcaption>
|
||||
{% endif %}
|
||||
</figure>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="article-content">
|
||||
|
||||
{% if liens %}
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-container">
|
||||
<div class="sidebar-section">
|
||||
{% for lien in liens %}
|
||||
<a href="{{ lien.url }}" class="link-button">{{ lien.title }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="main-content-text">
|
||||
|
||||
{% if current_language == 'en' and body_en %}
|
||||
{{ body_en|raw }}
|
||||
{% else %}
|
||||
{{ post.content }}
|
||||
{% endif %}
|
||||
|
||||
{% if axes_groups %}
|
||||
<div class="labo-dropdowns">
|
||||
{% for group in axes_groups %}
|
||||
<div class="labo-dropdown-item">
|
||||
<h3 class="labo-dropdown-header" data-seance-toggle>
|
||||
{{ group.label }}
|
||||
<i class="iconoir-nav-arrow-down labo-dropdown-chevron"></i>
|
||||
</h3>
|
||||
<div class="labo-dropdown-content" style="display: none;">
|
||||
<ul class="labo-axes-list">
|
||||
{% for axe in group.terms %}
|
||||
<li><a href="{{ axe.url }}">{{ axe.name | bilingual(current_language) }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if partenaires_internationaux or partenaires_nationaux %}
|
||||
<section class="labo-section">
|
||||
<h3>{{ current_language == 'en' ? 'Partner institutions' : 'Institutions partenaires' }}</h3>
|
||||
<div class="labo-dropdowns">
|
||||
{% if partenaires_internationaux %}
|
||||
<div class="labo-dropdown-item">
|
||||
<h4 class="labo-dropdown-header" data-seance-toggle>
|
||||
{{ current_language == 'en' ? 'International partners' : 'Partenaires internationaux' }}
|
||||
<i class="iconoir-nav-arrow-down labo-dropdown-chevron"></i>
|
||||
</h4>
|
||||
<div class="labo-dropdown-content" style="display: none;">
|
||||
{{ partenaires_internationaux|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if partenaires_nationaux %}
|
||||
<div class="labo-dropdown-item">
|
||||
<h4 class="labo-dropdown-header" data-seance-toggle>
|
||||
{{ current_language == 'en' ? 'National partners' : 'Partenaires nationaux' }}
|
||||
<i class="iconoir-nav-arrow-down labo-dropdown-chevron"></i>
|
||||
</h4>
|
||||
<div class="labo-dropdown-content" style="display: none;">
|
||||
{{ partenaires_nationaux|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if bibliotheques %}
|
||||
<section class="labo-section">
|
||||
<h3>{{ current_language == 'en' ? 'Libraries' : 'Bibliothèques' }}</h3>
|
||||
<div class="labo-bibliotheques">
|
||||
{{ bibliotheques|raw }}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
107
templates/page-membres.twig
Normal file
@@ -0,0 +1,107 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<main class="category-archive">
|
||||
<div class="container">
|
||||
<div class="full-block category--le-laboratoire">
|
||||
<header class="category-header">
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}">{{ current_language == 'en' ? 'The department' : 'Le laboratoire' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ current_language == 'en' ? 'Lab members' : 'Membres du laboratoire' }}</span>
|
||||
</nav>
|
||||
</div>
|
||||
<h1 class="category-header__title">{{ current_language == 'en' ? 'Lab members' : 'Membres du laboratoire' }}</h1>
|
||||
</header>
|
||||
|
||||
<div class="filters-bar">
|
||||
<button class="filters-toggle-btn" id="membres-filters-toggle" aria-expanded="false">
|
||||
{{ current_language == 'en' ? 'Filters' : 'Filtres' }}
|
||||
<i class="iconoir-nav-arrow-down filters-chevron"></i>
|
||||
</button>
|
||||
<div class="filters-active-chips" id="membres-active-chips"></div>
|
||||
</div>
|
||||
<div class="category-filters" id="membres-filters">
|
||||
|
||||
<div class="filtre-role">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Filter by status' : 'Filtrer par statut' }}</p>
|
||||
<a href="#" class="date-reset-link" id="role-reset" style="display:none">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="filter-dd" id="filter-role-dd">
|
||||
<div class="dd-title" id="filter-role-btn">
|
||||
<p id="filter-role-label">{{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}</p>
|
||||
<i class="iconoir-nav-arrow-down"></i>
|
||||
</div>
|
||||
<div class="dd-content" id="filter-role-popover" style="display:none;">
|
||||
<ul>
|
||||
<li data-role="">{{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}</li>
|
||||
{% for role in filter_roles %}
|
||||
<li data-role="{{ role.name }}">{{ role.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filtre-recherche">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Search a member' : 'Rechercher un membre' }}</p>
|
||||
<a href="#" class="date-reset-link" id="search-reset" style="display:none">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
</div>
|
||||
<input type="search" id="membres-search" class="membres-search-input"
|
||||
placeholder="{{ current_language == 'en' ? 'Name…' : 'Nom…' }}"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<section class="membres-section">
|
||||
{% for group in groups %}
|
||||
<div class="membres-item">
|
||||
<h3 class="membres-header" data-seance-toggle>
|
||||
{{ group.title }}
|
||||
<i class="iconoir-nav-arrow-down membres-chevron"></i>
|
||||
</h3>
|
||||
<div class="membres-content" style="display: none;">
|
||||
<table class="membres-table"{% if group.fixed_order %} data-fixed-order{% endif %}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="nom">{{ current_language == 'en' ? 'Name' : 'Nom' }} <i class="iconoir-nav-arrow-down membres-sort-chevron"></i></th>
|
||||
<th data-sort="statut">{{ current_language == 'en' ? 'Status' : 'Statut' }} <i class="iconoir-nav-arrow-down membres-sort-chevron"></i></th>
|
||||
<th data-sort="affiliation">{{ current_language == 'en' ? 'Affiliation' : 'Affiliation' }} <i class="iconoir-nav-arrow-down membres-sort-chevron"></i></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in group.members %}
|
||||
<tr onclick="window.location.href='{{ member.url }}'"
|
||||
data-name="{{ member.display_name }}"
|
||||
data-sort-name="{{ member.sort_key }}"
|
||||
data-roles="{{ member.role_names|join('|') }}"
|
||||
data-avatar="{{ member.avatar_url }}"
|
||||
data-status="{{ member.status }}"
|
||||
data-affiliation="{{ member.affiliation }}"
|
||||
data-domaines="{{ member.domaines|join(', ') }}"
|
||||
data-autres-domaines="{{ member.autres_domaines }}">
|
||||
<td>{{ member.display_name }}</td>
|
||||
<td>{{ member.status }}</td>
|
||||
<td>{{ member.affiliation }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
60
templates/page-programmes-de-recherche.twig
Normal file
@@ -0,0 +1,60 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="article category--le-laboratoire">
|
||||
<div class="full-block">
|
||||
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}">{{ current_language == 'en' ? 'The department' : 'Le laboratoire' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ current_language == 'en' ? 'Research programs' : 'Programmes de recherche' }}</span>
|
||||
</nav>
|
||||
{% if page_edit_link %}
|
||||
<a href="{{ page_edit_link }}" class="link-button" target="_blank" rel="noopener">
|
||||
<i class="iconoir-edit-pencil"></i>{{ current_language == 'en' ? 'Edit page' : 'Éditer la page' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2><p>{{ post.title | bilingual(current_language) }}</p></h2>
|
||||
|
||||
<div class="article-content">
|
||||
<div class="main-content-text">
|
||||
|
||||
{% for section in sections %}
|
||||
{% if section.items %}
|
||||
<section class="labo-section">
|
||||
<h3>{{ section.label }}</h3>
|
||||
<div class="labo-dropdowns">
|
||||
{% for programme in section.items %}
|
||||
<div class="labo-dropdown-item">
|
||||
<h4 class="labo-dropdown-header" data-seance-toggle>
|
||||
{{ programme.name }}
|
||||
<i class="iconoir-nav-arrow-down labo-dropdown-chevron"></i>
|
||||
</h4>
|
||||
<div class="labo-dropdown-content" style="display: none;">
|
||||
{% if programme.description %}
|
||||
<div class="programme-description">{{ programme.description|raw }}</div>
|
||||
{% endif %}
|
||||
<div class="programme-link">
|
||||
<a href="{{ programme.url }}" class="link-button">
|
||||
<i class="iconoir-open-new-window"></i>{{ current_language == 'en' ? 'View programme' : 'Voir le programme' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
30
templates/page.twig
Normal file
@@ -0,0 +1,30 @@
|
||||
{% extends "base.twig" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="article category--le-laboratoire">
|
||||
<div class="full-block">
|
||||
|
||||
<div class="category-header-top">
|
||||
<nav class="breadcrumb" aria-label="Fil d'Ariane">
|
||||
<a href="{{ function('home_url', '/') }}">{{ current_language == 'en' ? 'Home' : 'Accueil' }}</a>
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ current_language == 'en' ? function('home_url', '/en/le-laboratoire/') : function('home_url', '/le-laboratoire/') }}">{{ current_language == 'en' ? 'The department' : 'Le laboratoire' }}</a>
|
||||
</nav>
|
||||
{% if page_edit_link %}
|
||||
<a href="{{ page_edit_link }}" class="link-button" target="_blank" rel="noopener">
|
||||
<i class="iconoir-edit-pencil"></i>{{ current_language == 'en' ? 'Edit page' : 'Éditer la page' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2><p>{{ post.title | bilingual(current_language) }}</p></h2>
|
||||
|
||||
<div class="article-content">
|
||||
<div class="main-content-text">
|
||||
{{ post.content }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
25
templates/partials/agenda-card.twig
Normal file
@@ -0,0 +1,25 @@
|
||||
<a href="{{ link ?: post.link }}" class="swiper-slide agenda-card" dir="ltr">
|
||||
<div class="agenda-card__dates">
|
||||
<div class="agenda-date-box">
|
||||
<span class="agenda-date-day">{{ day }}{% if day == 1 %}<sup>er</sup>{% endif %}</span>
|
||||
<span class="agenda-date-month">{{ month }}</span>
|
||||
<span class="agenda-date-year">{{ year }}</span>
|
||||
</div>
|
||||
{% if end_day %}
|
||||
<span class="agenda-date-arrow">→</span>
|
||||
<div class="agenda-date-box">
|
||||
<span class="agenda-date-day">{{ end_day }}</span>
|
||||
<span class="agenda-date-month">{{ end_month }}</span>
|
||||
<span class="agenda-date-year">{{ end_year }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="agenda-card__body">
|
||||
<div class="agenda-card__meta">
|
||||
{% if date_label %}<span class="agenda-card__date-label">{{ date_label }}</span>{% endif %}
|
||||
{% if type_label %}<span>{{ type_label }}</span>{% endif %}
|
||||
{% if lieu %}<span>{{ lieu }}</span>{% endif %}
|
||||
</div>
|
||||
<p class="agenda-card__title">{{ post.title }}</p>
|
||||
</div>
|
||||
</a>
|
||||
18
templates/partials/author-card.twig
Normal file
@@ -0,0 +1,18 @@
|
||||
<article class="author-card">
|
||||
<a href="{{ author.url }}" class="author-card__visual" tabindex="-1" aria-hidden="true">
|
||||
{% if author.avatar_url %}
|
||||
<img src="{{ author.avatar_url }}" alt="{{ author.name }}" loading="lazy">
|
||||
{% else %}
|
||||
<span class="author-card__initials">{{ author.initials }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="author-card__info">
|
||||
<h2 class="author-card__name"><a href="{{ author.url }}">{{ author.name }}</a></h2>
|
||||
{% if author.role_label %}
|
||||
<p class="author-card__role">{{ author.role_label }}</p>
|
||||
{% endif %}
|
||||
{% if author.affiliation %}
|
||||
<p class="author-card__affiliation">{{ author.affiliation }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
171
templates/partials/category-filters.twig
Normal file
@@ -0,0 +1,171 @@
|
||||
<div class="filters-bar">
|
||||
<button class="filters-toggle-btn" id="category-filters-toggle" aria-expanded="false">
|
||||
{{ current_language == 'en' ? 'Filters' : 'Filtres' }}
|
||||
<i class="iconoir-nav-arrow-down filters-chevron"></i>
|
||||
</button>
|
||||
<div class="filters-active-chips">
|
||||
{% if filter_parents is defined and active_rubrique %}
|
||||
{% for parent in filter_parents %}
|
||||
{% if parent.id == active_rubrique %}
|
||||
<a href="{{ annonces_url }}" class="filter-chip">{{ parent.name }}<i class="iconoir-xmark"></i></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if filter_categories is defined and active_category_id and active_category_id != active_rubrique %}
|
||||
{% for cat in filter_categories %}
|
||||
{% if cat.id == active_category_id %}
|
||||
{% set cat_reset_url = annonces_url %}
|
||||
{% if filter_parents is defined %}
|
||||
{% for parent in filter_parents %}
|
||||
{% if parent.id == active_rubrique %}{% set cat_reset_url = parent.link %}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<a href="{{ cat_reset_url }}" class="filter-chip">{{ cat.name }}<i class="iconoir-xmark"></i></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if active_date_from or active_date_to %}
|
||||
<a href="{{ function('remove_query_arg', ['date_from', 'date_to'])|en_url }}" class="filter-chip">
|
||||
{%- if active_date_from %}{{ active_date_from|date('d/m/Y') }}{% endif -%}
|
||||
{{- active_date_from and active_date_to ? ' → ' : '' -}}
|
||||
{%- if active_date_to %}{{ active_date_to|date('d/m/Y') }}{% endif -%}
|
||||
<i class="iconoir-xmark"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if active_axe is defined and active_axe and filter_axes is defined %}
|
||||
{% for axe in filter_axes %}
|
||||
{% if axe.id == active_axe %}
|
||||
<a href="{{ (axe_taxonomy_mode ? annonces_url : function('remove_query_arg', ['axe']))|en_url }}" class="filter-chip">{{ axe.name }}<i class="iconoir-xmark"></i></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="category-filters" id="category-filters">
|
||||
{% if filter_parents is defined and filter_parents %}
|
||||
<div class="filtre-rubrique">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Filter by section' : 'Filtrer par rubrique' }}</p>
|
||||
{% if active_rubrique %}
|
||||
<a href="{{ annonces_url }}" class="date-reset-link">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<ul>
|
||||
{% for parent in filter_parents %}
|
||||
<li{% if parent.id == active_rubrique %} class="is-active"{% endif %}>
|
||||
<a href="{{ parent.link }}">{{ parent.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if filter_categories is defined and filter_categories %}
|
||||
<div class="filtre-categorie">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Filter by category' : 'Filtrer par catégorie' }}</p>
|
||||
{% if active_category_id and active_category_id != active_rubrique %}
|
||||
{% for parent in filter_parents %}
|
||||
{% if parent.id == active_rubrique %}
|
||||
<a href="{{ parent.link }}" class="date-reset-link">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<ul>
|
||||
{% for cat in filter_categories %}
|
||||
<li{% if cat.id == active_category_id %} class="is-active"{% endif %}>
|
||||
<a href="{{ cat.link }}">{{ cat.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="filtre-date">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Filter by date' : 'Filtrer par date' }}</p>
|
||||
{% if active_date_from or active_date_to %}
|
||||
<a href="{{ function('remove_query_arg', ['date_from', 'date_to'])|en_url }}" class="date-reset-link">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="filter-dd{% if active_date_from or active_date_to %} is-active{% endif %}" id="filter-date-dd">
|
||||
<div class="dd-title" id="filter-date-btn">
|
||||
<p id="filter-date-label">
|
||||
{%- if active_date_from or active_date_to -%}
|
||||
{%- if active_date_from -%}{{ active_date_from|date('d/m/Y') }}{%- endif -%}
|
||||
{{- active_date_from and active_date_to ? ' → ' : '' -}}
|
||||
{%- if active_date_to -%}{{ active_date_to|date('d/m/Y') }}{%- endif -%}
|
||||
{%- else -%}
|
||||
{{ current_language == 'en' ? 'Show all' : 'Tout afficher' }}
|
||||
{%- endif -%}
|
||||
</p>
|
||||
<i class="iconoir-nav-arrow-down"></i>
|
||||
</div>
|
||||
<div class="dd-content" id="filter-date-popover" style="display: none;">
|
||||
<ul>
|
||||
<li data-preset="week">{{ current_language == 'en' ? 'This week' : 'Cette semaine' }}</li>
|
||||
<li data-preset="month">{{ current_language == 'en' ? 'This month' : 'Ce mois-ci' }}</li>
|
||||
<li data-preset="upcoming">{{ current_language == 'en' ? 'Upcoming' : 'À venir' }}</li>
|
||||
<li data-preset="lastmonth">{{ current_language == 'en' ? 'Last month' : 'Le mois dernier' }}</li>
|
||||
</ul>
|
||||
<div class="dd-date-fields">
|
||||
<label>
|
||||
{{ current_language == 'en' ? 'From' : 'De' }}
|
||||
<input type="date" id="filter-date-from" value="{{ active_date_from }}" lang="{{ current_language == 'en' ? 'en-GB' : 'fr-FR' }}">
|
||||
</label>
|
||||
<label>
|
||||
{{ current_language == 'en' ? 'To' : 'À' }}
|
||||
<input type="date" id="filter-date-to" value="{{ active_date_to }}" lang="{{ current_language == 'en' ? 'en-GB' : 'fr-FR' }}">
|
||||
</label>
|
||||
<button type="button" class="link-button dd-date-apply" id="filter-date-apply">
|
||||
{{ current_language == 'en' ? 'Apply' : 'Appliquer' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if filter_axes is defined and filter_axes %}
|
||||
<div class="filtre-axe">
|
||||
<div class="filter-section-header">
|
||||
<p class="section-title">{{ current_language == 'en' ? 'Filter by thematic axis' : 'Filtrer par axe thématique' }}</p>
|
||||
{% if active_axe and not axe_taxonomy_mode %}
|
||||
<a href="{{ function('remove_query_arg', ['axe'])|en_url }}" class="date-reset-link">
|
||||
{{ current_language == 'en' ? 'Reset' : 'Réinitialiser' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="filter-dd{% if active_axe %} is-active{% endif %}" id="filter-axe-dd">
|
||||
<div class="dd-title" id="filter-axe-btn">
|
||||
<p id="filter-axe-label">
|
||||
{%- set axe_label = current_language == 'en' ? 'All axes' : 'Tous les axes' -%}
|
||||
{%- for axe in filter_axes -%}
|
||||
{%- if axe.id == active_axe -%}
|
||||
{%- set axe_label = axe.name -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{{ axe_label }}
|
||||
</p>
|
||||
<i class="iconoir-nav-arrow-down"></i>
|
||||
</div>
|
||||
<div class="dd-content" id="filter-axe-popover" style="display: none;">
|
||||
<ul>
|
||||
{% if not axe_taxonomy_mode %}
|
||||
<li data-axe-id="">{{ current_language == 'en' ? 'All axes' : 'Tous les axes' }}</li>
|
||||
{% endif %}
|
||||
{% for axe in filter_axes %}
|
||||
<li data-axe-id="{{ axe.id }}"{% if not axe_stay_on_page %} data-axe-href="{{ axe.href }}"{% endif %}>{{ axe.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
24
templates/partials/footer.twig
Normal file
@@ -0,0 +1,24 @@
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<nav class="footer-nav">
|
||||
<ul id="{{ current_language == 'en' ? 'menu-footer-en' : 'menu-footer' }}">
|
||||
{% for item in footer_menu.items %}
|
||||
<li class="{{ item.classes|join(' ') }}">
|
||||
<a href="{{ item.url }}">{{ item.title | bilingual(current_language) }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="footer-logos">
|
||||
<a href="https://www.cnrs.fr/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/cnrs.png" alt="Logo CNRS">
|
||||
</a>
|
||||
<a href="https://www.sorbonne-nouvelle.fr/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/sorbonne.png" alt="Logo Sorbonne Nouvelle">
|
||||
</a>
|
||||
<a href="https://www.ens.psl.eu/" target="_blank">
|
||||
<img src="{{ theme.uri }}/assets/images/ens.png" alt="Logo ENS">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
57
templates/partials/header.twig
Normal file
@@ -0,0 +1,57 @@
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<div class="main-logo-container">
|
||||
<div class="main-logo">
|
||||
<a href="{{ current_language == 'en' ? function('home_url', '/en/') : site.url }}">
|
||||
<img src="{{ theme.uri }}/assets/images/thalim-logo.svg" alt="Logo Thalim">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
<div>{{ gc.umr }}</div>
|
||||
<div>{{ gc.thalim }}</div>
|
||||
<div>{{ gc.siecles }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="secondary-logo-container">
|
||||
<div class="main-logo">
|
||||
<a href="{{ current_language == 'en' ? function('home_url', '/en/') : site.url }}">
|
||||
<img src="{{ theme.uri }}/assets/images/thalim-logo.svg" alt="Logo Thalim">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lang-switch">
|
||||
{% if languages %}
|
||||
<ul class="language-switcher">
|
||||
{% for lang in languages %}
|
||||
<li class="{{ lang.current_lang ? 'active' : '' }}">
|
||||
<a href="{{ lang.url }}">
|
||||
{{ lang.slug == 'fr' ? 'Français' : 'English' }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="search-button">
|
||||
<div>
|
||||
<i class="iconoir-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-toggle">
|
||||
<div>
|
||||
<div>
|
||||
<i class="iconoir-menu"></i>
|
||||
</div>
|
||||
<p>Menu</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="overlay"></div>
|
||||
|
||||
{% include 'partials/search-panel.twig' %}
|
||||
|
||||
{% include 'partials/navigation.twig' %}
|
||||
34
templates/partials/navigation.twig
Normal file
@@ -0,0 +1,34 @@
|
||||
<nav class="main-menu">
|
||||
<div class="{{ current_language == 'en' ? 'menu-navigation-en-container' : 'menu-navigation-container' }}">
|
||||
<ul>
|
||||
{% for item in menu.items %}
|
||||
{% set is_first_col = loop.index == 1 %}
|
||||
<li class="{{ item.classes|join(' ') }}">
|
||||
<a href="{{ item.url }}">{{ item.title | bilingual(current_language) }}</a>
|
||||
{% if item.children %}
|
||||
<ul class="sub-menu">
|
||||
{% for child in item.children %}
|
||||
<li class="{{ child.classes|join(' ') }}">
|
||||
<a href="{{ child.url }}">{{ child.title | bilingual(current_language) }}</a>
|
||||
</li>
|
||||
{% if is_first_col and loop.index == 2 and axes_courants %}
|
||||
<li class="nav-axes-item">
|
||||
<div class="nav-axes-trigger" aria-expanded="false">
|
||||
{{ current_language == 'en' ? 'Thematic axes' : 'Axes thématiques' }}
|
||||
<i class="iconoir-nav-arrow-down"></i>
|
||||
</div>
|
||||
<ul class="nav-axes-list">
|
||||
{% for axe in axes_courants %}
|
||||
<li><a href="{{ axe.link }}">{{ axe.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
116
templates/partials/post-card.twig
Normal file
@@ -0,0 +1,116 @@
|
||||
<article class="post-card gradient--{{ card.parent_slug }}">
|
||||
<a href="{{ card.card_link ?: post.link }}" class="gradient-container">
|
||||
{% if card.card_image %}
|
||||
<img src="{{ card.card_image }}" alt="{{ post.title|bilingual(current_language) }}" loading="lazy">
|
||||
{% else %}
|
||||
<h2>{{ post.title|bilingual(current_language) }}</h2>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="contextual-infos">
|
||||
<div class="authors">
|
||||
{% set autres = post.meta('autrepersonnes') %}
|
||||
{% set autres_list = autres ? autres|split(', ') : [] %}
|
||||
{% set membres_count = card.card_membres|length %}
|
||||
{% set total = membres_count + autres_list|length %}
|
||||
{% set slots_left = 3 - membres_count %}
|
||||
{% for membre in card.card_membres|slice(0, 3) %}
|
||||
<span><a href="{{ membre.url }}">{{ membre.name }}</a></span>
|
||||
{% endfor %}
|
||||
{% if slots_left > 0 and autres_list|length > 0 %}
|
||||
<span>{{ autres_list|slice(0, slots_left)|join(', ') }}</span>
|
||||
{% endif %}
|
||||
{% if total > 3 %}…{% endif %}
|
||||
</div>
|
||||
{% if show_category %}
|
||||
<div class="date-category">
|
||||
<time class="date" datetime="{{ card.card_event_date_iso ?: post.date('Y-m-d') }}">{{ card.card_event_date ?: post.date('d/m/Y') }}</time>
|
||||
{% if card.card_type %}
|
||||
<a class="card-type" href="{{ card.card_category_url }}">{{ card.card_type }}</a>
|
||||
{% elseif card.card_category_name and not type_only %}
|
||||
<a class="card-type" href="{{ card.card_category_url }}">{{ card.card_category_name }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<time class="date" datetime="{{ card.card_event_date_iso ?: post.date('Y-m-d') }}">{{ card.card_event_date ?: post.date('d/m/Y') }}</time>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if card.card_image %}
|
||||
<h2 class="title-bottom">
|
||||
<a href="{{ card.card_link ?: post.link }}">{{ post.title|bilingual(current_language) }}</a>
|
||||
</h2>
|
||||
{% endif %}
|
||||
</article>
|
||||
{#
|
||||
<article class="post-card">
|
||||
{% if card.card_image %}
|
||||
<div class="post-card__image">
|
||||
<img src="{{ card.card_image }}" alt="{{ post.title }}" loading="lazy">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="post-card__content">
|
||||
<h3 class="post-card__title">
|
||||
<a href="{{ post.link }}">{{ post.title }}</a>
|
||||
</h3>
|
||||
|
||||
{% if post.meta('sous-titre') %}
|
||||
<p class="post-card__subtitle">{{ post.meta('sous-titre') }}</p>
|
||||
{% endif %}
|
||||
|
||||
<time class="post-card__date" datetime="{{ post.date('Y-m-d') }}">{{ post.date('d/m/Y') }}</time>
|
||||
|
||||
{% if card.card_membres is not empty or post.meta('autrepersonnes') %}
|
||||
<div class="post-card__authors">
|
||||
{% for name in card.card_membres %}
|
||||
<span class="post-card__author">{{ name }}</span>
|
||||
{% endfor %}
|
||||
{% if post.meta('autrepersonnes') %}
|
||||
<span class="post-card__author post-card__author--external">{{ post.meta('autrepersonnes') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if post.meta('fonction_auteur') %}
|
||||
<span class="post-card__role">{{ post.meta('fonction_auteur') }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if post.meta('editeur') %}
|
||||
<span class="post-card__publisher">{{ post.meta('editeur') }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if post.meta('journal') %}
|
||||
<span class="post-card__journal">{{ post.meta('journal') }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if card.card_axes is not empty %}
|
||||
<div class="post-card__axes">
|
||||
{% for axe in card.card_axes %}
|
||||
<span class="post-card__axe">{{ axe }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if card.card_etiquettes is not empty %}
|
||||
<div class="post-card__tags">
|
||||
{% for tag in card.card_etiquettes %}
|
||||
<span class="post-card__tag">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="post-card__links">
|
||||
{% if post.meta('lien_externe_1') %}
|
||||
<a href="{{ post.meta('lien_externe_1') }}" class="post-card__link" target="_blank" rel="noopener">
|
||||
{{ post.meta('titre_du_lien_externe_1') ?: post.meta('lien_externe_1') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if post.meta('hal_url') %}
|
||||
<a href="{{ post.meta('hal_url') }}" class="post-card__link post-card__link--hal" target="_blank" rel="noopener">
|
||||
HAL
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
#}
|
||||
17
templates/partials/search-panel.twig
Normal file
@@ -0,0 +1,17 @@
|
||||
<div class="search-panel">
|
||||
<div class="search-panel__inner">
|
||||
<p class="search-panel__title">{{ current_language == 'en' ? 'Search the site' : 'Rechercher sur le site' }}</p>
|
||||
<p class="search-panel__desc">{{ current_language == 'en' ? 'Search for an event, a lab member, a publication…' : 'Rechercher un événement, un membre du laboratoire, un ouvrage…' }}</p>
|
||||
<form class="search-panel__form" role="search" method="get" action="{{ function('home_url', current_language == 'en' ? '/en/' : '/') }}">
|
||||
<div class="search-panel__input-wrap">
|
||||
<input type="search" name="s" class="search-panel__input"
|
||||
placeholder="{{ current_language == 'en' ? 'Type your search…' : 'Écrire la recherche…' }}"
|
||||
autocomplete="off">
|
||||
<button type="submit" class="search-panel__icon-btn" aria-label="{{ current_language == 'en' ? 'Search' : 'Rechercher sur le site' }}"><i class="iconoir-search"></i></button>
|
||||
</div>
|
||||
<button type="submit" class="search-panel__submit">
|
||||
{{ current_language == 'en' ? 'Search' : 'Rechercher sur le site' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||