/** * Socle partagé des customisations admin (namespace window.ThalimAdmin). * Chargé sur toutes les pages admin, avant les scripts de contexte * (admin-rename, admin-post-edit, admin-profile, admin-taxonomy-list, * admin-pods-modal) qui en dépendent. */ (function($) { 'use strict'; // ── Configuration ────────────────────────────────────────── // Sélecteurs et identifiants Pods utilisés par les modules admin, // centralisés pour qu'un renommage côté Pods ne demande qu'une édition ici. var CONFIG = { // Options désactivées dans le select Pods « Type d'annonce » (term IDs) disabledCategoryIds: ['1', '12', '5', '20'], // Catégorie « Séance de séminaire » (verrouillée dans la modale Pods) seanceCategoryId: '12', // Select Pods de la catégorie categorySelect: '#pods-form-ui-pods-meta-categorie', // IDs des éditeurs TinyMCE Pods à réparer (reinitEditor) editors: { bodyEn: 'pods-form-ui-pods-meta-body-en', refBib: 'pods-form-ui-pods-meta-reference-bibliographique' }, // Metaboxes Pods déplacées / observées boxes: { bodyEn: '#pods-meta-body-en', typeDannonce: '#pods-meta-type-dannonce', affichageAccueil: '#pods-meta-affichage-sur-laccueil', thematique: '#pods-meta-thematique', champsContextuels: '#pods-meta-champs-contextuels', documentsJoints: '#pods-meta-documents-joints', membres: '#pods-meta-membres' }, // Classes des lignes Pods conditionnelles observées (MutationObserver) rows: { axes: 'pods-form-ui-row-name-axes-thematiques', refBib: 'pods-form-ui-row-name-reference-bibliographique' }, // Taxonomies dont la page liste reçoit l'info-bulle « FR // EN » translateTaxonomies: ['axe_thematique', 'programme_de_recherche', 'post_tag'] }; // Exécute un bloc d'init de façon isolée : une exception dans un module // n'empêche pas les modules suivants de s'initialiser. function safeRun(name, fn) { try { fn(); } catch (err) { if (window.console && console.error) { console.error('[thalim-admin] ' + name + ' failed:', err); } } } 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 updatePostboxVisibility() { document.querySelectorAll('.postbox').forEach(function(postBox) { if (postBox.id.startsWith('pods')) { // body-en is controlled by language tabs — never auto-hide it if ('#' + postBox.id === CONFIG.boxes.bodyEn) return; var fields = postBox.querySelectorAll('tr'); var hasVisibleFields = Array.from(fields).some(function(field) { return 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); } } // 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); } }); } })); } // ── Info-popovers (post / user / taxonomy) ───────────────── var INFO_ICON = ''; var TRANSLATE_ICON = ''; var TRANSLATE_LINES = [ 'Traduction en anglais après //', 'ex : Texte en français // 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ès //', 'ex : Titre de l’annonce // Title of the announcement' ] }, { selector: '#pods-meta-documents-joints .postbox-header h2', lines: [ 'Ajouter les images dans les documents.', 'Ajouter les légendes comme titre du document.' ] }, { selector: '#pods-meta-membres .postbox-header h2', lines: [ 'Le champ fonction change le libellé de la liste de personnes citées.', 'Le champ membre permet de lister les membres de Thalim liés à l’annonce.', 'Le champ autre personnes permet de lister des personnes extérieures à Thalim.' ] }, { selector: '#pods-meta-dates .postbox-header h2', lines: [ 'Pour entrer une date sans l’heure, régler l’heure sur 00 :00.' ] }, { selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2', lines: [ 'Épingler l’annonce dans le diaporama la fait s’afficher avant les autres.' ] }, { selector: '#pods-meta-medias .postbox-header h2', lines: [ 'Pour ajouter un média Canal U, copier le lien depuis « Citer cette ressource ».', 'ex : 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 '
' + line + '
'; }).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'); }); }); } } // ── Reveal (#wpbody est masqué en CSS sur post/profil jusqu'à l'init) ── function markReady() { document.body.classList.add('admin-mods-ready'); } // Fallback global : force le reveal après 2 s même si le script de // contexte a planté ou n'a pas été chargé. $(document).ready(function() { setTimeout(markReady, 2000); }); window.ThalimAdmin = { CONFIG: CONFIG, safeRun: safeRun, isPostEditPage: isPostEditPage, isProfileEditPage: isProfileEditPage, getProfileForm: getProfileForm, isPodsModal: isPodsModal, ensureVisualMode: ensureVisualMode, reinitEditor: reinitEditor, updatePostboxVisibility: updatePostboxVisibility, initInfoPopovers: initInfoPopovers, markReady: markReady }; })(jQuery);