(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 = '' + ''; 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
' + 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'); }); }); } } // 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(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);