Loading XML2HTMLPlugin.php +64 −50 Original line number Diff line number Diff line Loading @@ -80,8 +80,9 @@ class XML2HTMLPlugin extends GenericPlugin $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($request)); $templateMgr->assign('dcMetas', $dcMetas); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($request, 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($request, 'cssSideViewFile')); // On réutilise ton template standard pour le galley complet $templateMgr->display($this->getTemplateResource('HTMLGalleyView.tpl')); return true; Loading @@ -95,31 +96,28 @@ class XML2HTMLPlugin extends GenericPlugin $submission =& $params[2]; try { // Get the galleys for this submission $galleys = $submission->getGalleys(); foreach ($galleys as $galley) { if ($galley->getFileType() === 'application/zip') { $file = $galley->getFile(); if ($file) { $html = file_get_contents($this->landingPageView( $this->landingPageView( $file->getData('path'), $submission->getId(), $galley->getId(), 'article' )); ); return true; } } } $html = 'No .zip file available among galleys'; return true; return false; } catch (\Exception $e) { error_log('XML2HTMLPlugin error: ' . $e->getMessage()); $html = 'Error processing submission'; return true; return false; } } Loading Loading @@ -173,6 +171,8 @@ class XML2HTMLPlugin extends GenericPlugin $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($this->getRequest())); $templateMgr->assign('dcMetas', $dcMetas); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($this->getRequest(), 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($this->getRequest(), 'cssSideViewFile')); $html = $templateMgr->display($this->getTemplateResource('HTMLGalleyView.tpl')); file_put_contents($htmlFile, $html, LOCK_EX); } Loading @@ -190,14 +190,7 @@ class XML2HTMLPlugin extends GenericPlugin ->getContext(); if (!$html) { $context = Application::get() ->getRequest() ->getContext(); $format = $this->getSetting( $context->getId(), 'format' ); $format = $this->getSetting($context->getId(), 'format'); if ($format == NULL || $format == '') $format = 'JATS';//default value is JATS Loading @@ -212,12 +205,14 @@ class XML2HTMLPlugin extends GenericPlugin //store transfo result file_put_contents($htmlFile, $html, LOCK_EX); } $templateMgr = TemplateManager::getManager($this->getRequest()); $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($this->getRequest())); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($this->getRequest(), 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($this->getRequest(), 'cssSideViewFile')); $templateMgr->display($this->getTemplateResource('LandingPage.tpl')); return false; } /** Loading Loading @@ -325,6 +320,26 @@ class XML2HTMLPlugin extends GenericPlugin return $request->getBaseUrl() . '/' . $this->getPluginPath() . '/resources/'; } /** * Returns the public URL to an uploaded custom CSS file, or null if none is set. * * @param Request $request * @param string $settingKey 'cssLandingPageFile' or 'cssSideViewFile' * @return string|null */ function getCssFileUrl($request, string $settingKey): ?string { $context = $request->getContext(); if (!$context) return null; $filename = $this->getSetting($context->getId(), $settingKey); if (!$filename) return null; return $request->getBaseUrl() . '/' . $this->getPluginPath() . '/resources/custom/' . $filename; } static function transform( string $xmlFilePath, string $format Loading Loading @@ -505,4 +520,3 @@ class XML2HTMLPlugin extends GenericPlugin } } No newline at end of file XML2HTMLSettingsForm.inc.php +111 −68 Original line number Diff line number Diff line Loading @@ -6,75 +6,34 @@ import('classes.notification.NotificationManager'); class XML2HTMLSettingsForm extends Form { private static $FORMAT = "format"; /*private $_contextId;*/ private $plugin; /** * */ function __construct(XML2HTMLPlugin $plugin) { /*$this->_contextId = $contextId;*/ $this->plugin = $plugin; parent::__construct($plugin->getTemplateResource('settingsForm.tpl')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); } /** * Load settings already saved in the database * * Settings are stored by context, so that each journal, press, * or preprint server can have different settings. */ public function initData() { $context = Application::get() ->getRequest() ->getContext(); $this->setData( 'format', $this->plugin->getSetting( $context->getId(), 'format' ) ); $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $this->setData( 'landingpage', $this->plugin->getSetting($context->getId(), 'landingpage') ); $this->setData('format', $this->plugin->getSetting($contextId, 'format')); $this->setData('landingpage', $this->plugin->getSetting($contextId, 'landingpage')); $this->setData('cssLandingPageFile', $this->plugin->getSetting($contextId, 'cssLandingPageFile')); $this->setData('cssSideViewFile', $this->plugin->getSetting($contextId, 'cssSideViewFile')); parent::initData(); } /** * Load data that was submitted with the form */ public function readInputData() { $this->readUserVars(['format', 'landingpage']); $this->readUserVars(['format', 'landingpage', 'deleteCssLandingPage', 'deleteCssSideView']); parent::readInputData(); } /** * Fetch any additional data needed for your form. * * Data assigned to the form using $this->setData() during the * initData() or readInputData() methods will be passed to the * template. * * In the example below, the plugin name is passed to the * template so that it can be used in the URL that the form is * submitted to. */ public function fetch($request, $template = null, $display = false) { $templateMgr = TemplateManager::getManager($request); Loading @@ -82,32 +41,45 @@ class XML2HTMLSettingsForm extends Form { $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $templateMgr->assign('landingpage', $this->plugin->getSetting($contextId, 'landingpage')); // URL CSS Landing Page $landingFile = $this->plugin->getSetting($contextId, 'cssLandingPageFile'); $templateMgr->assign('cssLandingPageUrl', $landingFile ? $request->getBaseUrl() . '/' . $this->plugin->getPluginPath() . '/resources/custom/' . $landingFile : null ); // URL CSS Side View $sideViewFile = $this->plugin->getSetting($contextId, 'cssSideViewFile'); $templateMgr->assign('cssSideViewUrl', $sideViewFile ? $request->getBaseUrl() . '/' . $this->plugin->getPluginPath() . '/resources/custom/' . $sideViewFile : null ); return parent::fetch($request, $template, $display); } /** * Save the plugin settings and notify the user * that the save was successful */ public function execute(...$functionArgs) { $context = Application::get() ->getRequest() ->getContext(); $this->plugin->updateSetting( $context->getId(), 'format', $this->getData('format') ); $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $this->plugin->updateSetting( $context->getId(), 'landingpage', $this->getData('landingpage') ); $this->plugin->updateSetting($contextId, 'format', $this->getData('format')); $this->plugin->updateSetting($contextId, 'landingpage', $this->getData('landingpage')); // --- CSS Landing Page --- if ($this->getData('deleteCssLandingPage')) { $this->_deleteCssFile($contextId, 'cssLandingPageFile'); } $this->_handleUpload($contextId, 'cssLandingPageFile', 'custom-landing-page.css'); // --- CSS Side View --- if ($this->getData('deleteCssSideView')) { $this->_deleteCssFile($contextId, 'cssSideViewFile'); } $this->_handleUpload($contextId, 'cssSideViewFile', 'custom-sideview.css'); $notificationMgr = new NotificationManager(); $notificationMgr->createTrivialNotification( Loading @@ -118,4 +90,75 @@ class XML2HTMLSettingsForm extends Form { return parent::execute(); } /** * Handles upload for a given file input and setting key. * * @param int $contextId Journal context ID * @param string $settingKey Plugin setting key to store the filename * @param string $destName Target filename on disk (e.g. 'custom-landing-page.css') */ private function _handleUpload(int $contextId, string $settingKey, string $destName): void { $inputName = $settingKey; // the <input name="..."> matches the setting key if (!isset($_FILES[$inputName]) || $_FILES[$inputName]['error'] !== UPLOAD_ERR_OK) { return; } $tmpPath = $_FILES[$inputName]['tmp_name']; $origName = $_FILES[$inputName]['name']; if (strtolower(pathinfo($origName, PATHINFO_EXTENSION)) !== 'css') { return; } $destDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'custom'; if (!is_dir($destDir)) { mkdir($destDir, 0755, true); } // Remove old file before replacing $this->_deleteCssFile($contextId, $settingKey); // Suffix the filename with the journal path (slug) // e.g. "custom-landing-page.css" → "custom-landing-page-my_journal.css" $context = Application::get()->getRequest()->getContext(); $journalSlug = preg_replace('/[^a-z0-9_-]/i', '_', $context->getPath()); $baseName = pathinfo($destName, PATHINFO_FILENAME); // "custom-landing-page" $ext = pathinfo($destName, PATHINFO_EXTENSION); // "css" $finalName = $baseName . '-' . $journalSlug . '.' . $ext; $destPath = $destDir . DIRECTORY_SEPARATOR . $finalName; if (move_uploaded_file($tmpPath, $destPath)) { $this->plugin->updateSetting($contextId, $settingKey, $finalName, 'string'); } } /** * Deletes the CSS file from disk and clears the setting. * * @param int $contextId Journal context ID * @param string $settingKey Plugin setting key holding the filename */ private function _deleteCssFile(int $contextId, string $settingKey): void { $existing = $this->plugin->getSetting($contextId, $settingKey); if (!$existing) return; $path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR . $existing; if (file_exists($path)) { unlink($path); } $this->plugin->updateSetting($contextId, $settingKey, null, 'string'); } } No newline at end of file locale/en_US/locale.po +19 −1 Original line number Diff line number Diff line Loading @@ -16,3 +16,21 @@ msgstr "Format / XML Vocabulary" msgid "plugins.generic.xml2html.landingpage" msgstr "Display HTML on landing page" msgid "plugins.generic.xml2html.cssLandingPage" msgstr "Landing Page CSS" msgid "plugins.generic.xml2html.cssSideView" msgstr "Side View CSS" msgid "plugins.generic.xml2html.customCss.current" msgstr "Current file:" msgid "plugins.generic.xml2html.customCss.view" msgstr "View" msgid "plugins.generic.xml2html.customCss.delete" msgstr "Delete this file" msgid "plugins.generic.xml2html.settingsFor" msgstr "Settings for" No newline at end of file locale/fr_FR/locale.po +19 −1 Original line number Diff line number Diff line Loading @@ -16,3 +16,21 @@ msgstr "Format / Vocabulaire XML" msgid "plugins.generic.xml2html.landingpage" msgstr "Affichage du texte sur la page d'accueil" msgid "plugins.generic.xml2html.cssLandingPage" msgstr "CSS de la page d'accueil" msgid "plugins.generic.xml2html.cssSideView" msgstr "CSS de la vue de lecture" msgid "plugins.generic.xml2html.customCss.current" msgstr "Fichier actuel :" msgid "plugins.generic.xml2html.customCss.view" msgstr "Voir" msgid "plugins.generic.xml2html.customCss.delete" msgstr "Supprimer ce fichier" msgid "plugins.generic.xml2html.settingsFor" msgstr "Paramétrage de la revue" No newline at end of file resources/javascript/xml_2_html.js +194 −48 Original line number Diff line number Diff line Loading @@ -249,12 +249,7 @@ window.addEventListener('DOMContentLoaded', (event) => { // Appel fonction pour tableaux déroulants initialiseTableaux(); // index collapsible initializeCollapsibleLists(); // open new windows openFigureWindow(elementId); initCitationCopy(); //toc hightlighing on scroll document.querySelector('.left-contents').addEventListener("scroll", () => { let scrollTop = document.querySelector('.left-contents').scrollTop; Loading Loading @@ -326,34 +321,35 @@ function darkMode(bool) { function bindFootnotesAndImages() { let figAppels = document.getElementsByClassName('fig-call'); let notesAppels = document.getElementsByClassName('xref-call'); let bibRefAppels = document.getElementsByClassName('bib-ref-call'); // fusionne les deux tableaux ('arrays') dans un 3e : let tousLesAppels = [...figAppels, ...notesAppels, ...bibRefAppels]; let bibAppels = document.getElementsByClassName('bib-ref-call'); // ← AJOUT let tousLesAppels = [...figAppels, ...notesAppels, ...bibAppels]; // ← AJOUT for (let i = 0; i < tousLesAppels.length; i++) { let focusTarget = document.getElementById(tousLesAppels[i].dataset.target); let target = tousLesAppels[i].dataset.target; // Retirer le # pour getElementById let targetId = target ? target.replace('#', '') : null; let focusTarget = targetId ? document.getElementById(targetId) : null; if (!focusTarget) continue; tousLesAppels[i].addEventListener('click', () => { if (tousLesAppels[i].classList.contains('fn-call')) { openTab('footnotes'); } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { openTab('refs'); } else if (tousLesAppels[i].classList.contains('fig-call')) { let cible = tousLesAppels[i].getAttribute('data-target'); openTab('figures'); scrollToElementById(cible); scrollToElementById(targetId); } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { openTab('refs'); // ← ouvre l'onglet références } else { openTab('refs'); } document.getElementById(tousLesAppels[i].dataset.target).scrollIntoView({ block: 'center', behavior: "smooth" }); focusTarget.scrollIntoView({ block: 'center', behavior: 'smooth' }); document.querySelectorAll('.footnote, .ref, .bibl').forEach( (fn) => fn.classList.remove('focus') ); focusTarget.classList.add('focus'); }); if(tousLesAppels[i].classList.contains('fn-call')) { Loading @@ -368,18 +364,6 @@ function bindFootnotesAndImages() { ); focusTarget.classList.add('focus'); }) } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { let bibRefLabel = focusTarget.querySelector('span.bibRef'); bibRefLabel.addEventListener('click', () => { tousLesAppels[i].scrollIntoView({ block: 'start', behavior: "smooth" }); document.querySelectorAll('.bibl').forEach( (fn) => fn.classList.remove('focus') ); focusTarget.classList.add('focus'); }) } } Loading Loading @@ -417,20 +401,19 @@ function initialiseTableaux() { ///////////////////////////////////////////////////////////// ////// Fonctions pour les index (collapsible) [EC] /// ///////////////////////////////////////////////////////////// function initializeCollapsibleLists() { var toggleButtons = document.querySelectorAll('.toggle-button'); toggleButtons.forEach(function (button) { button.addEventListener('click', function () { var parentLi = this.closest('li'); // Get the parent <li> element var indexLevel2Elements = parentLi.querySelectorAll('.index-level2'); // Get descendant elements with class .index-level2 indexLevel2Elements.forEach(function (element) { element.style.display = (element.style.display === 'none' || element.style.display === '') ? 'block' : 'none'; // Toggle display function toggleIndexItem(li) { const subLists = li.querySelectorAll(':scope > .index-level2'); if (!subLists.length) return; const isCollapsed = li.classList.contains('collapsed'); li.classList.toggle('collapsed', !isCollapsed); li.classList.toggle('expanded', isCollapsed); subLists.forEach(ul => { ul.style.display = isCollapsed ? 'block' : 'none'; }); // Toggle button text on the clicked toggle button this.textContent = (indexLevel2Elements[0].style.display === 'none') ? '[+]' : '[-]'; // Use the first element's display property }); }); // Empêcher la propagation si on clique sur un lien enfant event.stopPropagation(); } Loading Loading @@ -561,3 +544,166 @@ document.addEventListener('DOMContentLoaded', function() { } }); }); function toggleFiguresFilter() { const articleWrap = document.querySelector('.article-wrap'); const btn = document.getElementById('btn-filter-figures'); const label = document.getElementById('filter-label'); const isActive = btn.classList.toggle('active'); if (isActive) { // Masquer tout le contenu de l'article articleWrap.querySelectorAll('p, table.linguistic, ul.index-unordered, h1, h2, h3').forEach(el => { el.style.display = 'none'; }); articleWrap.querySelectorAll('section').forEach(el => { el.style.display = 'block'; // garder visible pour que les enfants le soient }); // S'assurer que les figures et tableaux (fig-table, fig-ill) sont visibles articleWrap.querySelectorAll('figure').forEach(el => { el.style.display = 'block'; let parent = el.parentElement; while (parent && parent !== articleWrap) { parent.style.display = 'block'; parent = parent.parentElement; } }); } else { articleWrap.querySelectorAll('*').forEach(el => { el.style.removeProperty('display'); }); } } function openGalleyIframe() { // Récupère le lien galley présent dans la page const galleyLink = document.querySelector('.galleys_links .obj_galley_link'); if (!galleyLink) { alert('Aucun lien galley trouvé dans la page.'); return; } const overlay = document.getElementById('galley-overlay'); const iframe = document.getElementById('galley-iframe'); const btn = document.getElementById('btn-galley'); iframe.src = galleyLink.href; overlay.style.display = 'block'; btn.classList.add('active'); // Fermer en cliquant sur le fond overlay.addEventListener('click', function(e) { if (e.target === overlay) closeGalleyIframe(); }, { once: true }); } function closeGalleyIframe() { const overlay = document.getElementById('galley-overlay'); const iframe = document.getElementById('galley-iframe'); const btn = document.getElementById('btn-galley'); overlay.style.display = 'none'; iframe.src = ''; // libère la ressource btn.classList.remove('active'); } document.querySelectorAll('.galleys_links li').forEach(li => { if (li.textContent.trim() === 'HTML') { li.style.display = 'none'; } }); function openModal(url) { // Crée la modale si elle n'existe pas encore if (!document.getElementById('iframeModal')) { const modal = document.createElement('div'); modal.id = 'iframeModal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; justify-content: center; align-items: center; `; modal.innerHTML = ` <div style="position:relative; width:90%; height:90%; background:white; border-radius:8px; overflow:hidden;"> <button onclick="closeModal()" style="position:absolute; top:8px; right:12px; font-size:24px; background:none; border:none; cursor:pointer; z-index:10;">✕</button> <iframe id="modalIframe" src="" width="100%" height="100%" style="border:none; display:block;"></iframe> </div> `; modal.addEventListener('click', function(e) { if (e.target === this) closeModal(); }); document.body.appendChild(modal); } document.getElementById('modalIframe').src = url; document.getElementById('iframeModal').style.display = 'flex'; } function closeModal() { const modal = document.getElementById('iframeModal'); if (modal) { modal.style.display = 'none'; document.getElementById('modalIframe').src = ''; } } function initCitationCopy() { const els = document.querySelectorAll('.data-citation'); console.log('[citationCopy] éléments trouvés :', els.length); els.forEach(function(el) { el.addEventListener('click', function() { const text = el.innerText.trim(); console.log('[citationCopy] clic, texte :', text); function showFeedback() { const original = el.innerText; el.classList.add('copied'); el.setAttribute('data-original', original); el.innerText = '✓ Copié dans le presse-papier'; setTimeout(function() { el.innerText = el.getAttribute('data-original'); el.classList.remove('copied'); }, 2000); } function fallbackCopy() { const ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;top:50%;left:50%;width:1px;height:1px;opacity:0;'; document.body.appendChild(ta); ta.focus(); ta.select(); try { const ok = document.execCommand('copy'); console.log('[citationCopy] execCommand résultat :', ok); if (ok) showFeedback(); } catch(e) { console.error('[citationCopy] execCommand erreur :', e); } document.body.removeChild(ta); } if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text) .then(showFeedback) .catch(function(e) { console.warn('[citationCopy] clipboard API échouée, fallback :', e); fallbackCopy(); }); } else { fallbackCopy(); } }); }); } No newline at end of file Loading
XML2HTMLPlugin.php +64 −50 Original line number Diff line number Diff line Loading @@ -80,8 +80,9 @@ class XML2HTMLPlugin extends GenericPlugin $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($request)); $templateMgr->assign('dcMetas', $dcMetas); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($request, 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($request, 'cssSideViewFile')); // On réutilise ton template standard pour le galley complet $templateMgr->display($this->getTemplateResource('HTMLGalleyView.tpl')); return true; Loading @@ -95,31 +96,28 @@ class XML2HTMLPlugin extends GenericPlugin $submission =& $params[2]; try { // Get the galleys for this submission $galleys = $submission->getGalleys(); foreach ($galleys as $galley) { if ($galley->getFileType() === 'application/zip') { $file = $galley->getFile(); if ($file) { $html = file_get_contents($this->landingPageView( $this->landingPageView( $file->getData('path'), $submission->getId(), $galley->getId(), 'article' )); ); return true; } } } $html = 'No .zip file available among galleys'; return true; return false; } catch (\Exception $e) { error_log('XML2HTMLPlugin error: ' . $e->getMessage()); $html = 'Error processing submission'; return true; return false; } } Loading Loading @@ -173,6 +171,8 @@ class XML2HTMLPlugin extends GenericPlugin $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($this->getRequest())); $templateMgr->assign('dcMetas', $dcMetas); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($this->getRequest(), 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($this->getRequest(), 'cssSideViewFile')); $html = $templateMgr->display($this->getTemplateResource('HTMLGalleyView.tpl')); file_put_contents($htmlFile, $html, LOCK_EX); } Loading @@ -190,14 +190,7 @@ class XML2HTMLPlugin extends GenericPlugin ->getContext(); if (!$html) { $context = Application::get() ->getRequest() ->getContext(); $format = $this->getSetting( $context->getId(), 'format' ); $format = $this->getSetting($context->getId(), 'format'); if ($format == NULL || $format == '') $format = 'JATS';//default value is JATS Loading @@ -212,12 +205,14 @@ class XML2HTMLPlugin extends GenericPlugin //store transfo result file_put_contents($htmlFile, $html, LOCK_EX); } $templateMgr = TemplateManager::getManager($this->getRequest()); $templateMgr->assign('assetsPath', $this->getPluginAssetsPath($this->getRequest())); $templateMgr->assign('text', $html); $templateMgr->assign('cssLandingPageUrl', $this->getCssFileUrl($this->getRequest(), 'cssLandingPageFile')); $templateMgr->assign('cssSideViewUrl', $this->getCssFileUrl($this->getRequest(), 'cssSideViewFile')); $templateMgr->display($this->getTemplateResource('LandingPage.tpl')); return false; } /** Loading Loading @@ -325,6 +320,26 @@ class XML2HTMLPlugin extends GenericPlugin return $request->getBaseUrl() . '/' . $this->getPluginPath() . '/resources/'; } /** * Returns the public URL to an uploaded custom CSS file, or null if none is set. * * @param Request $request * @param string $settingKey 'cssLandingPageFile' or 'cssSideViewFile' * @return string|null */ function getCssFileUrl($request, string $settingKey): ?string { $context = $request->getContext(); if (!$context) return null; $filename = $this->getSetting($context->getId(), $settingKey); if (!$filename) return null; return $request->getBaseUrl() . '/' . $this->getPluginPath() . '/resources/custom/' . $filename; } static function transform( string $xmlFilePath, string $format Loading Loading @@ -505,4 +520,3 @@ class XML2HTMLPlugin extends GenericPlugin } } No newline at end of file
XML2HTMLSettingsForm.inc.php +111 −68 Original line number Diff line number Diff line Loading @@ -6,75 +6,34 @@ import('classes.notification.NotificationManager'); class XML2HTMLSettingsForm extends Form { private static $FORMAT = "format"; /*private $_contextId;*/ private $plugin; /** * */ function __construct(XML2HTMLPlugin $plugin) { /*$this->_contextId = $contextId;*/ $this->plugin = $plugin; parent::__construct($plugin->getTemplateResource('settingsForm.tpl')); $this->addCheck(new FormValidatorPost($this)); $this->addCheck(new FormValidatorCSRF($this)); } /** * Load settings already saved in the database * * Settings are stored by context, so that each journal, press, * or preprint server can have different settings. */ public function initData() { $context = Application::get() ->getRequest() ->getContext(); $this->setData( 'format', $this->plugin->getSetting( $context->getId(), 'format' ) ); $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $this->setData( 'landingpage', $this->plugin->getSetting($context->getId(), 'landingpage') ); $this->setData('format', $this->plugin->getSetting($contextId, 'format')); $this->setData('landingpage', $this->plugin->getSetting($contextId, 'landingpage')); $this->setData('cssLandingPageFile', $this->plugin->getSetting($contextId, 'cssLandingPageFile')); $this->setData('cssSideViewFile', $this->plugin->getSetting($contextId, 'cssSideViewFile')); parent::initData(); } /** * Load data that was submitted with the form */ public function readInputData() { $this->readUserVars(['format', 'landingpage']); $this->readUserVars(['format', 'landingpage', 'deleteCssLandingPage', 'deleteCssSideView']); parent::readInputData(); } /** * Fetch any additional data needed for your form. * * Data assigned to the form using $this->setData() during the * initData() or readInputData() methods will be passed to the * template. * * In the example below, the plugin name is passed to the * template so that it can be used in the URL that the form is * submitted to. */ public function fetch($request, $template = null, $display = false) { $templateMgr = TemplateManager::getManager($request); Loading @@ -82,32 +41,45 @@ class XML2HTMLSettingsForm extends Form { $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $templateMgr->assign('landingpage', $this->plugin->getSetting($contextId, 'landingpage')); // URL CSS Landing Page $landingFile = $this->plugin->getSetting($contextId, 'cssLandingPageFile'); $templateMgr->assign('cssLandingPageUrl', $landingFile ? $request->getBaseUrl() . '/' . $this->plugin->getPluginPath() . '/resources/custom/' . $landingFile : null ); // URL CSS Side View $sideViewFile = $this->plugin->getSetting($contextId, 'cssSideViewFile'); $templateMgr->assign('cssSideViewUrl', $sideViewFile ? $request->getBaseUrl() . '/' . $this->plugin->getPluginPath() . '/resources/custom/' . $sideViewFile : null ); return parent::fetch($request, $template, $display); } /** * Save the plugin settings and notify the user * that the save was successful */ public function execute(...$functionArgs) { $context = Application::get() ->getRequest() ->getContext(); $this->plugin->updateSetting( $context->getId(), 'format', $this->getData('format') ); $context = Application::get()->getRequest()->getContext(); $contextId = $context->getId(); $this->plugin->updateSetting( $context->getId(), 'landingpage', $this->getData('landingpage') ); $this->plugin->updateSetting($contextId, 'format', $this->getData('format')); $this->plugin->updateSetting($contextId, 'landingpage', $this->getData('landingpage')); // --- CSS Landing Page --- if ($this->getData('deleteCssLandingPage')) { $this->_deleteCssFile($contextId, 'cssLandingPageFile'); } $this->_handleUpload($contextId, 'cssLandingPageFile', 'custom-landing-page.css'); // --- CSS Side View --- if ($this->getData('deleteCssSideView')) { $this->_deleteCssFile($contextId, 'cssSideViewFile'); } $this->_handleUpload($contextId, 'cssSideViewFile', 'custom-sideview.css'); $notificationMgr = new NotificationManager(); $notificationMgr->createTrivialNotification( Loading @@ -118,4 +90,75 @@ class XML2HTMLSettingsForm extends Form { return parent::execute(); } /** * Handles upload for a given file input and setting key. * * @param int $contextId Journal context ID * @param string $settingKey Plugin setting key to store the filename * @param string $destName Target filename on disk (e.g. 'custom-landing-page.css') */ private function _handleUpload(int $contextId, string $settingKey, string $destName): void { $inputName = $settingKey; // the <input name="..."> matches the setting key if (!isset($_FILES[$inputName]) || $_FILES[$inputName]['error'] !== UPLOAD_ERR_OK) { return; } $tmpPath = $_FILES[$inputName]['tmp_name']; $origName = $_FILES[$inputName]['name']; if (strtolower(pathinfo($origName, PATHINFO_EXTENSION)) !== 'css') { return; } $destDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'custom'; if (!is_dir($destDir)) { mkdir($destDir, 0755, true); } // Remove old file before replacing $this->_deleteCssFile($contextId, $settingKey); // Suffix the filename with the journal path (slug) // e.g. "custom-landing-page.css" → "custom-landing-page-my_journal.css" $context = Application::get()->getRequest()->getContext(); $journalSlug = preg_replace('/[^a-z0-9_-]/i', '_', $context->getPath()); $baseName = pathinfo($destName, PATHINFO_FILENAME); // "custom-landing-page" $ext = pathinfo($destName, PATHINFO_EXTENSION); // "css" $finalName = $baseName . '-' . $journalSlug . '.' . $ext; $destPath = $destDir . DIRECTORY_SEPARATOR . $finalName; if (move_uploaded_file($tmpPath, $destPath)) { $this->plugin->updateSetting($contextId, $settingKey, $finalName, 'string'); } } /** * Deletes the CSS file from disk and clears the setting. * * @param int $contextId Journal context ID * @param string $settingKey Plugin setting key holding the filename */ private function _deleteCssFile(int $contextId, string $settingKey): void { $existing = $this->plugin->getSetting($contextId, $settingKey); if (!$existing) return; $path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR . $existing; if (file_exists($path)) { unlink($path); } $this->plugin->updateSetting($contextId, $settingKey, null, 'string'); } } No newline at end of file
locale/en_US/locale.po +19 −1 Original line number Diff line number Diff line Loading @@ -16,3 +16,21 @@ msgstr "Format / XML Vocabulary" msgid "plugins.generic.xml2html.landingpage" msgstr "Display HTML on landing page" msgid "plugins.generic.xml2html.cssLandingPage" msgstr "Landing Page CSS" msgid "plugins.generic.xml2html.cssSideView" msgstr "Side View CSS" msgid "plugins.generic.xml2html.customCss.current" msgstr "Current file:" msgid "plugins.generic.xml2html.customCss.view" msgstr "View" msgid "plugins.generic.xml2html.customCss.delete" msgstr "Delete this file" msgid "plugins.generic.xml2html.settingsFor" msgstr "Settings for" No newline at end of file
locale/fr_FR/locale.po +19 −1 Original line number Diff line number Diff line Loading @@ -16,3 +16,21 @@ msgstr "Format / Vocabulaire XML" msgid "plugins.generic.xml2html.landingpage" msgstr "Affichage du texte sur la page d'accueil" msgid "plugins.generic.xml2html.cssLandingPage" msgstr "CSS de la page d'accueil" msgid "plugins.generic.xml2html.cssSideView" msgstr "CSS de la vue de lecture" msgid "plugins.generic.xml2html.customCss.current" msgstr "Fichier actuel :" msgid "plugins.generic.xml2html.customCss.view" msgstr "Voir" msgid "plugins.generic.xml2html.customCss.delete" msgstr "Supprimer ce fichier" msgid "plugins.generic.xml2html.settingsFor" msgstr "Paramétrage de la revue" No newline at end of file
resources/javascript/xml_2_html.js +194 −48 Original line number Diff line number Diff line Loading @@ -249,12 +249,7 @@ window.addEventListener('DOMContentLoaded', (event) => { // Appel fonction pour tableaux déroulants initialiseTableaux(); // index collapsible initializeCollapsibleLists(); // open new windows openFigureWindow(elementId); initCitationCopy(); //toc hightlighing on scroll document.querySelector('.left-contents').addEventListener("scroll", () => { let scrollTop = document.querySelector('.left-contents').scrollTop; Loading Loading @@ -326,34 +321,35 @@ function darkMode(bool) { function bindFootnotesAndImages() { let figAppels = document.getElementsByClassName('fig-call'); let notesAppels = document.getElementsByClassName('xref-call'); let bibRefAppels = document.getElementsByClassName('bib-ref-call'); // fusionne les deux tableaux ('arrays') dans un 3e : let tousLesAppels = [...figAppels, ...notesAppels, ...bibRefAppels]; let bibAppels = document.getElementsByClassName('bib-ref-call'); // ← AJOUT let tousLesAppels = [...figAppels, ...notesAppels, ...bibAppels]; // ← AJOUT for (let i = 0; i < tousLesAppels.length; i++) { let focusTarget = document.getElementById(tousLesAppels[i].dataset.target); let target = tousLesAppels[i].dataset.target; // Retirer le # pour getElementById let targetId = target ? target.replace('#', '') : null; let focusTarget = targetId ? document.getElementById(targetId) : null; if (!focusTarget) continue; tousLesAppels[i].addEventListener('click', () => { if (tousLesAppels[i].classList.contains('fn-call')) { openTab('footnotes'); } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { openTab('refs'); } else if (tousLesAppels[i].classList.contains('fig-call')) { let cible = tousLesAppels[i].getAttribute('data-target'); openTab('figures'); scrollToElementById(cible); scrollToElementById(targetId); } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { openTab('refs'); // ← ouvre l'onglet références } else { openTab('refs'); } document.getElementById(tousLesAppels[i].dataset.target).scrollIntoView({ block: 'center', behavior: "smooth" }); focusTarget.scrollIntoView({ block: 'center', behavior: 'smooth' }); document.querySelectorAll('.footnote, .ref, .bibl').forEach( (fn) => fn.classList.remove('focus') ); focusTarget.classList.add('focus'); }); if(tousLesAppels[i].classList.contains('fn-call')) { Loading @@ -368,18 +364,6 @@ function bindFootnotesAndImages() { ); focusTarget.classList.add('focus'); }) } else if (tousLesAppels[i].classList.contains('bib-ref-call')) { let bibRefLabel = focusTarget.querySelector('span.bibRef'); bibRefLabel.addEventListener('click', () => { tousLesAppels[i].scrollIntoView({ block: 'start', behavior: "smooth" }); document.querySelectorAll('.bibl').forEach( (fn) => fn.classList.remove('focus') ); focusTarget.classList.add('focus'); }) } } Loading Loading @@ -417,20 +401,19 @@ function initialiseTableaux() { ///////////////////////////////////////////////////////////// ////// Fonctions pour les index (collapsible) [EC] /// ///////////////////////////////////////////////////////////// function initializeCollapsibleLists() { var toggleButtons = document.querySelectorAll('.toggle-button'); toggleButtons.forEach(function (button) { button.addEventListener('click', function () { var parentLi = this.closest('li'); // Get the parent <li> element var indexLevel2Elements = parentLi.querySelectorAll('.index-level2'); // Get descendant elements with class .index-level2 indexLevel2Elements.forEach(function (element) { element.style.display = (element.style.display === 'none' || element.style.display === '') ? 'block' : 'none'; // Toggle display function toggleIndexItem(li) { const subLists = li.querySelectorAll(':scope > .index-level2'); if (!subLists.length) return; const isCollapsed = li.classList.contains('collapsed'); li.classList.toggle('collapsed', !isCollapsed); li.classList.toggle('expanded', isCollapsed); subLists.forEach(ul => { ul.style.display = isCollapsed ? 'block' : 'none'; }); // Toggle button text on the clicked toggle button this.textContent = (indexLevel2Elements[0].style.display === 'none') ? '[+]' : '[-]'; // Use the first element's display property }); }); // Empêcher la propagation si on clique sur un lien enfant event.stopPropagation(); } Loading Loading @@ -561,3 +544,166 @@ document.addEventListener('DOMContentLoaded', function() { } }); }); function toggleFiguresFilter() { const articleWrap = document.querySelector('.article-wrap'); const btn = document.getElementById('btn-filter-figures'); const label = document.getElementById('filter-label'); const isActive = btn.classList.toggle('active'); if (isActive) { // Masquer tout le contenu de l'article articleWrap.querySelectorAll('p, table.linguistic, ul.index-unordered, h1, h2, h3').forEach(el => { el.style.display = 'none'; }); articleWrap.querySelectorAll('section').forEach(el => { el.style.display = 'block'; // garder visible pour que les enfants le soient }); // S'assurer que les figures et tableaux (fig-table, fig-ill) sont visibles articleWrap.querySelectorAll('figure').forEach(el => { el.style.display = 'block'; let parent = el.parentElement; while (parent && parent !== articleWrap) { parent.style.display = 'block'; parent = parent.parentElement; } }); } else { articleWrap.querySelectorAll('*').forEach(el => { el.style.removeProperty('display'); }); } } function openGalleyIframe() { // Récupère le lien galley présent dans la page const galleyLink = document.querySelector('.galleys_links .obj_galley_link'); if (!galleyLink) { alert('Aucun lien galley trouvé dans la page.'); return; } const overlay = document.getElementById('galley-overlay'); const iframe = document.getElementById('galley-iframe'); const btn = document.getElementById('btn-galley'); iframe.src = galleyLink.href; overlay.style.display = 'block'; btn.classList.add('active'); // Fermer en cliquant sur le fond overlay.addEventListener('click', function(e) { if (e.target === overlay) closeGalleyIframe(); }, { once: true }); } function closeGalleyIframe() { const overlay = document.getElementById('galley-overlay'); const iframe = document.getElementById('galley-iframe'); const btn = document.getElementById('btn-galley'); overlay.style.display = 'none'; iframe.src = ''; // libère la ressource btn.classList.remove('active'); } document.querySelectorAll('.galleys_links li').forEach(li => { if (li.textContent.trim() === 'HTML') { li.style.display = 'none'; } }); function openModal(url) { // Crée la modale si elle n'existe pas encore if (!document.getElementById('iframeModal')) { const modal = document.createElement('div'); modal.id = 'iframeModal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; justify-content: center; align-items: center; `; modal.innerHTML = ` <div style="position:relative; width:90%; height:90%; background:white; border-radius:8px; overflow:hidden;"> <button onclick="closeModal()" style="position:absolute; top:8px; right:12px; font-size:24px; background:none; border:none; cursor:pointer; z-index:10;">✕</button> <iframe id="modalIframe" src="" width="100%" height="100%" style="border:none; display:block;"></iframe> </div> `; modal.addEventListener('click', function(e) { if (e.target === this) closeModal(); }); document.body.appendChild(modal); } document.getElementById('modalIframe').src = url; document.getElementById('iframeModal').style.display = 'flex'; } function closeModal() { const modal = document.getElementById('iframeModal'); if (modal) { modal.style.display = 'none'; document.getElementById('modalIframe').src = ''; } } function initCitationCopy() { const els = document.querySelectorAll('.data-citation'); console.log('[citationCopy] éléments trouvés :', els.length); els.forEach(function(el) { el.addEventListener('click', function() { const text = el.innerText.trim(); console.log('[citationCopy] clic, texte :', text); function showFeedback() { const original = el.innerText; el.classList.add('copied'); el.setAttribute('data-original', original); el.innerText = '✓ Copié dans le presse-papier'; setTimeout(function() { el.innerText = el.getAttribute('data-original'); el.classList.remove('copied'); }, 2000); } function fallbackCopy() { const ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;top:50%;left:50%;width:1px;height:1px;opacity:0;'; document.body.appendChild(ta); ta.focus(); ta.select(); try { const ok = document.execCommand('copy'); console.log('[citationCopy] execCommand résultat :', ok); if (ok) showFeedback(); } catch(e) { console.error('[citationCopy] execCommand erreur :', e); } document.body.removeChild(ta); } if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text) .then(showFeedback) .catch(function(e) { console.warn('[citationCopy] clipboard API échouée, fallback :', e); fallbackCopy(); }); } else { fallbackCopy(); } }); }); } No newline at end of file