Commit 510e0c5f authored by Charles Bourdot's avatar Charles Bourdot
Browse files

CSS custom et ajustements esthétiques

parent 112cf0a2
Loading
Loading
Loading
Loading
+64 −50
Original line number Diff line number Diff line
@@ -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;
@@ -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;
    }
}

@@ -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);
        }
@@ -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
@@ -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;

    }

    /**
@@ -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
@@ -505,4 +520,3 @@ class XML2HTMLPlugin extends GenericPlugin
    }

}
 No newline at end of file
+111 −68
Original line number Diff line number Diff line
@@ -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);
@@ -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(
@@ -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
+19 −1
Original line number Diff line number Diff line
@@ -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
+19 −1
Original line number Diff line number Diff line
@@ -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
+194 −48
Original line number Diff line number Diff line
@@ -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;
@@ -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')) {
@@ -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');
            })
        }

    }
@@ -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();
}


@@ -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