300 lines
13 KiB
JavaScript
300 lines
13 KiB
JavaScript
/**
|
|
* Page d'édition de post (post.php / post-new.php) : onglets FR/EN du corps,
|
|
* réordonnancement des metaboxes, groupement des axes, visibilité
|
|
* conditionnelle des boxes Pods, popovers d'aide, fixes TinyMCE/Gutenberg.
|
|
* Dépend de admin-base.js (window.ThalimAdmin) — enqueue conditionnel.
|
|
*/
|
|
(function($) {
|
|
'use strict';
|
|
|
|
var TA = window.ThalimAdmin;
|
|
var CONFIG = TA.CONFIG;
|
|
var safeRun = TA.safeRun;
|
|
|
|
// 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.querySelector(CONFIG.boxes.bodyEn);
|
|
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çais</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);
|
|
}
|
|
|
|
// 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.querySelector(CONFIG.boxes.bodyEn);
|
|
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) TA.ensureVisualMode('content');
|
|
return;
|
|
}
|
|
|
|
var enEditorId = CONFIG.editors.bodyEn;
|
|
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
|
|
TA.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
|
|
TA.ensureVisualMode('content');
|
|
TA.ensureVisualMode(enEditorId);
|
|
}
|
|
|
|
function groupAxesCheckboxes() {
|
|
if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
|
|
|
|
var row = document.querySelector('.' + CONFIG.rows.axes);
|
|
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 = CONFIG.editors.refBib;
|
|
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('.' + CONFIG.rows.refBib);
|
|
if (!row || row.style.display === 'none') return;
|
|
refBibReinited = true;
|
|
TA.reinitEditor(REF_BIB_EDITOR_ID);
|
|
TA.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, ' + CONFIG.boxes.champsContextuels + ', 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(CONFIG.rows.axes)) {
|
|
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(CONFIG.rows.refBib)) {
|
|
if (target.style.display !== 'none') {
|
|
setTimeout(initRefBibEditor, 100);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
|
|
}
|
|
|
|
// 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(CONFIG.boxes.membres + ' .membres-grid-separator');
|
|
if (!sep) return;
|
|
var autreRows = document.querySelectorAll(CONFIG.boxes.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';
|
|
}
|
|
|
|
function initPostEditPage() {
|
|
// Disable category options (CSS handles the color)
|
|
const categorieSelect = document.querySelector(CONFIG.categorySelect);
|
|
if (categorieSelect) {
|
|
categorieSelect.querySelectorAll('option').forEach(option => {
|
|
if (CONFIG.disabledCategoryIds.includes(option.value)) {
|
|
option.disabled = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Reorder meta boxes
|
|
const sideSortables = document.querySelector('#side-sortables');
|
|
if (sideSortables) {
|
|
const typeDannonce = document.querySelector(CONFIG.boxes.typeDannonce);
|
|
const affichageAccueil = document.querySelector(CONFIG.boxes.affichageAccueil);
|
|
const thematique = document.querySelector(CONFIG.boxes.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(CONFIG.boxes.champsContextuels);
|
|
if (champsContextuels && champsContextuels.parentNode) {
|
|
champsContextuels.parentNode.prepend(champsContextuels);
|
|
}
|
|
|
|
// Chaque sous-module est isolé : une exception dans l'un
|
|
// n'empêche pas les suivants de s'initialiser.
|
|
safeRun('initBodyLanguageTabs', initBodyLanguageTabs);
|
|
safeRun('initRefBibEditor', initRefBibEditor);
|
|
safeRun('groupAxesCheckboxes', groupAxesCheckboxes);
|
|
safeRun('initAxesGroupObserver', initAxesGroupObserver);
|
|
safeRun('updatePostboxVisibility', TA.updatePostboxVisibility);
|
|
safeRun('initDatePickerPopoverFix', initDatePickerPopoverFix);
|
|
safeRun('initInfoPopovers', TA.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(CONFIG.boxes.documentsJoints);
|
|
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(CONFIG.boxes.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();
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
if (!TA.isPostEditPage()) return;
|
|
|
|
safeRun('setupBodyTabsDom', setupBodyTabsDom);
|
|
|
|
setTimeout(function() {
|
|
safeRun('initPostEditPage', initPostEditPage);
|
|
TA.markReady();
|
|
}, 100);
|
|
|
|
$(CONFIG.categorySelect).change(function() {
|
|
setTimeout(function() {
|
|
safeRun('updatePostboxVisibility', TA.updatePostboxVisibility);
|
|
safeRun('updateMembresGridSeparator', updateMembresGridSeparator);
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
})(jQuery);
|