MediaWiki:Common.js: Difference between revisions

From Disaster Risk Gateway
No edit summary
Tag: Reverted
Notifications icon and unification in user dropdown
 
(23 intermediate revisions by the same user not shown)
Line 1: Line 1:
mw.loader.using('mediawiki.util', function () {
mw.loader.using(['mediawiki.util', 'mediawiki.searchSuggest'], function () {
   $(document).ready(function () {
   $(function () {
     if ($('#drg-navbar').length) return;
     if ($('#drg-navbar').length) return;


     // 1) Logo + DRG label
     // ===== Helpers =====
     const logoSrc = $('.mw-logo-icon').attr('src')
    function htmlFromPortlet(selector) {
      || '/images/3/3f/Myriad_logo_icon_transparent.png';
      // Return array of <li>...</li> HTML for all anchors in a portlet list (if present)
      return $(selector).find('li a').map(function () {
        return '<li>' + $(this).prop('outerHTML') + '</li>';
      }).get();
    }
 
    function setBodyTopPaddingToNavbar() {
      var h = $('#drg-navbar').outerHeight() || 60;
      $('body').css('padding-top', h + 'px');
    }
 
    // Build a clean anchor (no Echo classes) with icon + label (+ optional count badge)
    function buildCombinedNotifications() {
      var $alertA  = $('#pt-notifications-alert a').first();
      var $noticeA = $('#pt-notifications-notice a').first();
 
      // Pick an href we can trust; fall back to Special:Notifications
      var href = ($alertA.attr('href') || $noticeA.attr('href') || mw.util.getUrl('Special:Notifications'));
 
      // Reuse an icon if Echo provides one; otherwise a simple bell glyph
      var $icon = ($alertA.find('span, i, svg').first().clone());
      if (!$icon.length) $icon = ($noticeA.find('span, i, svg').first().clone());
      var iconHtml = $icon.length ? $icon.prop('outerHTML') : '🔔';
 
      // Sum counts if Echo exposes them
      function n(x){ return x && /^\d+$/.test(x) ? parseInt(x,10) : 0; }
      var count = n($alertA.attr('data-counter-num')) + n($noticeA.attr('data-counter-num'));
 
      return [
        '<a href="', href, '" class="drg-echo-link">',
          iconHtml,
          '<span class="drg-echo-label">Notifications</span>',
          (count > 0 ? '<span class="drg-echo-count" aria-label="unread notifications">'+count+'</span>' : ''),
        '</a>'
      ].join('');
    }
 
    // ===== Logo =====
     const logoSrc = $('.mw-logo-icon').attr('src') || '/images/3/3f/Myriad_logo_icon_transparent.png';
     const logoHtml = `
     const logoHtml = `
       <a href="${mw.util.getUrl('Main_Page')}" class="drg-navbar-logo">
       <a href="${mw.util.getUrl('Main_Page')}" class="drg-navbar-logo">
         <img src="${logoSrc}" alt="MYRIAD Logo">
         <img src="${logoSrc}" alt="MYRIAD Logo">
        <span class="drg-logo-text">DRG</span>
       </a>`;
       </a>`;


     // 2) Navigation dropdown
     // ===== Build left-side links in order: About, Navigation, Contribute =====
    const aboutHtml = `<a href="${mw.util.getUrl('Disaster_Risk_Gateway')}" class="flat-link">About</a>`;
 
     const navItems = [
     const navItems = [
       { title: 'Catalogue',  url: mw.util.getUrl('Catalogue')   },
       { title: 'Catalogue',  url: mw.util.getUrl('Catalogue') },
       { title: 'Definitions', url: mw.util.getUrl('Definitions') },
       { title: 'Definitions', url: mw.util.getUrl('Definitions') },
       { title: 'Resources',  url: mw.util.getUrl('Resources')   }
       { title: 'Resources',  url: mw.util.getUrl('Resources') }
     ];
     ];
     const navHtml = `
     const navHtml = `
Line 26: Line 65:
       </div>`;
       </div>`;


    // 3) Flat links
     const contributeHtml = `<a href="${mw.util.getUrl('Contribute')}" class="flat-link">Contribute</a>`;
     const flatHtml = `
      <a href="${mw.util.getUrl('Contribute')}" class="flat-link">Contribute</a>
      <a href="${mw.util.getUrl('Disaster_Risk_Gateway')}" class="flat-link">About</a>`;


     // 4) Search form
     // ===== Search (with autocomplete) =====
     const searchHtml = `
     const searchDropdownHtml = `
       <form class="drg-search" action="${mw.util.getUrl('Special:Search')}" method="get">
       <div id="drg-search-dropdown" class="drg-search-dropdown-wrapper" style="position: relative; display: inline-block;">
        <input type="search" name="search" placeholder="Search Disaster Risk Gateway">
        <span id="drg-search-toggle" class="flat-link" style="cursor:pointer;">Search</span>
        <input type="submit" value="Search">
        <form class="drg-search" id="drg-search-form" action="${mw.util.getUrl('Special:Search')}" method="get"
       </form>`;
              style="display:none; position: absolute; top: 100%; left: 0; background: white; padding: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); z-index: 100;">
          <input type="search" name="search" placeholder="Search..." class="mw-searchInput" autocapitalize="off" autocomplete="off">
          <input type="submit" value="Search">
        </form>
       </div>`;


     // 5) User dropdown
     // ===== User dropdown =====
     let userHtml;
     let userHtml;
     if (mw.config.get('wgUserName')) {
     if (mw.config.get('wgUserName')) {
       const items = [];
       const items = [];
       $('#p-personal ul li').each(function() {
 
         const a = $(this).find('a');
      // --- Single combined Notifications item ---
         if (a.attr('href').includes('Watchlist')) return;
      const notifHtml = buildCombinedNotifications();
         items.push(`<li>${a.prop('outerHTML')}</li>`);
      if (notifHtml) items.push('<li>' + notifHtml + '</li>');
 
      // --- Personal tools (except duplicate logout at the end) ---
       $('#p-personal ul li').each(function () {
         const $a = $(this).find('a');
         const href = ($a.attr('href') || '');
        const titleText = ($a.text() || '').trim();
 
        // Skip Echo items we already handled
        if (href.includes('Notifications') || href.includes('Echo')) return;
 
        // Keep Watchlist
        if (/Watchlist/i.test(href + titleText)) {
          items.push(`<li>${$a.prop('outerHTML')}</li>`);
          return;
        }
 
        // Skip Logout (we add our own)
        if (/^log ?out$/i.test(titleText)) return;
 
        // Include everyone else (Preferences, Contributions, etc.)
         items.push(`<li>${$a.prop('outerHTML')}</li>`);
       });
       });
      // Add a divider then Logout
      items.push('<li class="drg-menu-divider"></li>');
       items.push(`<li><a href="${mw.util.getUrl('Special:UserLogout')}">Logout</a></li>`);
       items.push(`<li><a href="${mw.util.getUrl('Special:UserLogout')}">Logout</a></li>`);
       userHtml = `
       userHtml = `
         <div class="drg-user">
         <div class="drg-user">
Line 57: Line 122:
     }
     }


     // 6) Tools dropdown
     // ===== Tools dropdown (merge Actions + General like production) =====
     const toolsItems = $('#p-cactions ul li').map(function() {
     const actionsLis = htmlFromPortlet('#p-cactions');  // Actions
       return `<li>${$(this).find('a').prop('outerHTML')}</li>`;
    let  generalLis = htmlFromPortlet('#p-tb');        // General
     }).get().join('');
    if (generalLis.length === 0) {
       generalLis = htmlFromPortlet('#vector-page-tools, #p-tb-list, .vector-page-tools .vector-menu-content');
     }
 
     const toolsHtml = `
     const toolsHtml = `
       <div class="drg-tools">
       <div class="drg-tools">
         <span>Tools</span>
         <span>Tools</span>
         <ul class="drg-tools-dropdown">${toolsItems}</ul>
         <ul class="drg-tools-dropdown">
          ${actionsLis.length ? `<li class="drg-menu-header">Actions</li>${actionsLis.join('')}` : ''}
          ${actionsLis.length && generalLis.length ? '<li class="drg-menu-divider"></li>' : ''}
          ${generalLis.length ? `<li class="drg-menu-header">General</li>${generalLis.join('')}` : ''}
        </ul>
       </div>`;
       </div>`;


     // 7) Inject navbar
     // ===== Inject Navbar =====
     $('body').prepend(`
     $('body').prepend(`
       <div id="drg-navbar">
       <div id="drg-navbar">
         <div class="drg-navbar-container">
         <div class="drg-navbar-container">
           ${logoHtml}
           ${logoHtml}
          ${aboutHtml}
           ${navHtml}
           ${navHtml}
           ${flatHtml}
           ${contributeHtml}
           ${searchHtml}
           ${searchDropdownHtml}
           <div class="drg-navbar-actions">
           <div class="drg-navbar-actions">
             ${userHtml}
             ${userHtml}
Line 83: Line 156:
     `);
     `);


     // 8) Footer with inner wrapper for background
     // ===== Toggle search input visibility + autocomplete focus =====
    $('#drg-search-toggle').on('click', function (e) {
      e.preventDefault();
      e.stopPropagation();
      $('#drg-search-form').toggle();
      $('#drg-search-form input[type="search"]').trigger('focus');
    });
 
    // Close search dropdown when clicking outside
    $(document).on('click', function () { $('#drg-search-form').hide(); });
    $('#drg-search-form').on('click', function (e) { e.stopPropagation(); });
 
    // Make sure body top padding matches navbar height (since it can wrap)
    setBodyTopPaddingToNavbar();
    $(window).on('load resize', setBodyTopPaddingToNavbar);
 
    // ===== Footer =====
     function insertFooter() {
     function insertFooter() {
       const icons = document.getElementById('footer-icons');
       const icons = document.getElementById('footer-icons');
Line 89: Line 178:


       icons.insertAdjacentHTML('beforebegin', `
       icons.insertAdjacentHTML('beforebegin', `
         <div id="drg-footer">
         <footer id="drg-footer">
           <div class="drg-footer-inner">
           <div class="drg-footer-inner">
             <div class="drg-columns">
             <div class="drg-columns">
Line 98: Line 187:
                 <a href="mailto:DisasterRiskGateway@bgs.ac.uk"><strong>Contact Us</strong></a>
                 <a href="mailto:DisasterRiskGateway@bgs.ac.uk"><strong>Contact Us</strong></a>
               </div>
               </div>
              <div class="drg-divider"></div>
               <div class="drg-column drg-column-center">
               <div class="drg-column drg-column-center">
                 <div class="drg-eu-flag">
                 <div class="drg-eu-flag">
Line 107: Line 195:
                 </div>
                 </div>
               </div>
               </div>
              <div class="drg-divider"></div>
               <div class="drg-column drg-column-right">
               <div class="drg-column drg-column-right">
                 <a href="https://www.myriadproject.eu" target="_blank" rel="noopener">MYRIAD-EU</a>
                 <a href="https://www.myriadproject.eu" target="_blank">MYRIAD-EU</a>
                 <a href="https://be.linkedin.com/company/myriad-eu-project" target="_blank" rel="noopener">LinkedIn</a>
                 <a href="https://be.linkedin.com/company/myriad-eu-project" target="_blank">LinkedIn</a>
                 <a href="#" target="_blank" rel="noopener">MYRIAD-EU dashboard</a>
                 <a href="#" target="_blank">MYRIAD-EU dashboard</a>
               </div>
               </div>
             </div>
             </div>
 
             <div class="drg-disclaimer-wrapper">
             <div class="drg-disclaimer">
               <div class="drg-disclaimer">
               This site is hosted by the British Geological Survey but responsibility for the content of the site lies with the MYRIAD-EU project, not with the British Geological Survey.
                This site is hosted by the British Geological Survey but responsibility for the content of the site lies with the  
              Questions, suggestions, or comments regarding the contents of this site should be directed to
                <a href="https://www.myriadproject.eu" target="_blank" rel="noopener noreferrer">MYRIAD-EU project</a>.<br>
              <a href="mailto:DisasterRiskGateway@bgs.ac.uk">DisasterRiskGateway@bgs.ac.uk</a>.
                Questions or comments: <a href="mailto:DisasterRiskGateway@bgs.ac.uk">DisasterRiskGateway@bgs.ac.uk</a>
              </div>
             </div>
             </div>
           </div>
           </div>
         </div>
         </footer>
       `);
       `);
     }
     }
     insertFooter();
     insertFooter();
   });
   });
});
});

Latest revision as of 12:43, 13 October 2025

mw.loader.using(['mediawiki.util', 'mediawiki.searchSuggest'], function () {
  $(function () {
    if ($('#drg-navbar').length) return;

    // ===== Helpers =====
    function htmlFromPortlet(selector) {
      // Return array of <li>...</li> HTML for all anchors in a portlet list (if present)
      return $(selector).find('li a').map(function () {
        return '<li>' + $(this).prop('outerHTML') + '</li>';
      }).get();
    }

    function setBodyTopPaddingToNavbar() {
      var h = $('#drg-navbar').outerHeight() || 60;
      $('body').css('padding-top', h + 'px');
    }

    // Build a clean anchor (no Echo classes) with icon + label (+ optional count badge)
    function buildCombinedNotifications() {
      var $alertA  = $('#pt-notifications-alert a').first();
      var $noticeA = $('#pt-notifications-notice a').first();

      // Pick an href we can trust; fall back to Special:Notifications
      var href = ($alertA.attr('href') || $noticeA.attr('href') || mw.util.getUrl('Special:Notifications'));

      // Reuse an icon if Echo provides one; otherwise a simple bell glyph
      var $icon = ($alertA.find('span, i, svg').first().clone());
      if (!$icon.length) $icon = ($noticeA.find('span, i, svg').first().clone());
      var iconHtml = $icon.length ? $icon.prop('outerHTML') : '🔔';

      // Sum counts if Echo exposes them
      function n(x){ return x && /^\d+$/.test(x) ? parseInt(x,10) : 0; }
      var count = n($alertA.attr('data-counter-num')) + n($noticeA.attr('data-counter-num'));

      return [
        '<a href="', href, '" class="drg-echo-link">',
          iconHtml,
          '<span class="drg-echo-label">Notifications</span>',
          (count > 0 ? '<span class="drg-echo-count" aria-label="unread notifications">'+count+'</span>' : ''),
        '</a>'
      ].join('');
    }

    // ===== Logo =====
    const logoSrc = $('.mw-logo-icon').attr('src') || '/images/3/3f/Myriad_logo_icon_transparent.png';
    const logoHtml = `
      <a href="${mw.util.getUrl('Main_Page')}" class="drg-navbar-logo">
        <img src="${logoSrc}" alt="MYRIAD Logo">
      </a>`;

    // ===== Build left-side links in order: About, Navigation, Contribute =====
    const aboutHtml = `<a href="${mw.util.getUrl('Disaster_Risk_Gateway')}" class="flat-link">About</a>`;

    const navItems = [
      { title: 'Catalogue',   url: mw.util.getUrl('Catalogue') },
      { title: 'Definitions', url: mw.util.getUrl('Definitions') },
      { title: 'Resources',   url: mw.util.getUrl('Resources') }
    ];
    const navHtml = `
      <div class="drg-nav">
        <span>Navigation</span>
        <ul class="drg-nav-dropdown">
          ${navItems.map(i => `<li><a href="${i.url}">${i.title}</a></li>`).join('')}
        </ul>
      </div>`;

    const contributeHtml = `<a href="${mw.util.getUrl('Contribute')}" class="flat-link">Contribute</a>`;

    // ===== Search (with autocomplete) =====
    const searchDropdownHtml = `
      <div id="drg-search-dropdown" class="drg-search-dropdown-wrapper" style="position: relative; display: inline-block;">
        <span id="drg-search-toggle" class="flat-link" style="cursor:pointer;">Search</span>
        <form class="drg-search" id="drg-search-form" action="${mw.util.getUrl('Special:Search')}" method="get"
              style="display:none; position: absolute; top: 100%; left: 0; background: white; padding: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); z-index: 100;">
          <input type="search" name="search" placeholder="Search..." class="mw-searchInput" autocapitalize="off" autocomplete="off">
          <input type="submit" value="Search">
        </form>
      </div>`;

    // ===== User dropdown =====
    let userHtml;
    if (mw.config.get('wgUserName')) {
      const items = [];

      // --- Single combined Notifications item ---
      const notifHtml = buildCombinedNotifications();
      if (notifHtml) items.push('<li>' + notifHtml + '</li>');

      // --- Personal tools (except duplicate logout at the end) ---
      $('#p-personal ul li').each(function () {
        const $a = $(this).find('a');
        const href = ($a.attr('href') || '');
        const titleText = ($a.text() || '').trim();

        // Skip Echo items we already handled
        if (href.includes('Notifications') || href.includes('Echo')) return;

        // Keep Watchlist
        if (/Watchlist/i.test(href + titleText)) {
          items.push(`<li>${$a.prop('outerHTML')}</li>`);
          return;
        }

        // Skip Logout (we add our own)
        if (/^log ?out$/i.test(titleText)) return;

        // Include everyone else (Preferences, Contributions, etc.)
        items.push(`<li>${$a.prop('outerHTML')}</li>`);
      });

      // Add a divider then Logout
      items.push('<li class="drg-menu-divider"></li>');
      items.push(`<li><a href="${mw.util.getUrl('Special:UserLogout')}">Logout</a></li>`);

      userHtml = `
        <div class="drg-user">
          <span>Welcome, ${mw.config.get('wgUserName')}</span>
          <ul class="drg-user-dropdown">${items.join('')}</ul>
        </div>`;
    } else {
      userHtml = `<a class="drg-button" href="${mw.util.getUrl('Special:UserLogin')}">Login</a>`;
    }

    // ===== Tools dropdown (merge Actions + General like production) =====
    const actionsLis = htmlFromPortlet('#p-cactions');  // Actions
    let   generalLis = htmlFromPortlet('#p-tb');        // General
    if (generalLis.length === 0) {
      generalLis = htmlFromPortlet('#vector-page-tools, #p-tb-list, .vector-page-tools .vector-menu-content');
    }

    const toolsHtml = `
      <div class="drg-tools">
        <span>Tools</span>
        <ul class="drg-tools-dropdown">
          ${actionsLis.length ? `<li class="drg-menu-header">Actions</li>${actionsLis.join('')}` : ''}
          ${actionsLis.length && generalLis.length ? '<li class="drg-menu-divider"></li>' : ''}
          ${generalLis.length ? `<li class="drg-menu-header">General</li>${generalLis.join('')}` : ''}
        </ul>
      </div>`;

    // ===== Inject Navbar =====
    $('body').prepend(`
      <div id="drg-navbar">
        <div class="drg-navbar-container">
          ${logoHtml}
          ${aboutHtml}
          ${navHtml}
          ${contributeHtml}
          ${searchDropdownHtml}
          <div class="drg-navbar-actions">
            ${userHtml}
            ${toolsHtml}
          </div>
        </div>
      </div>
    `);

    // ===== Toggle search input visibility + autocomplete focus =====
    $('#drg-search-toggle').on('click', function (e) {
      e.preventDefault();
      e.stopPropagation();
      $('#drg-search-form').toggle();
      $('#drg-search-form input[type="search"]').trigger('focus');
    });

    // Close search dropdown when clicking outside
    $(document).on('click', function () { $('#drg-search-form').hide(); });
    $('#drg-search-form').on('click', function (e) { e.stopPropagation(); });

    // Make sure body top padding matches navbar height (since it can wrap)
    setBodyTopPaddingToNavbar();
    $(window).on('load resize', setBodyTopPaddingToNavbar);

    // ===== Footer =====
    function insertFooter() {
      const icons = document.getElementById('footer-icons');
      if (!icons) return setTimeout(insertFooter, 100);

      icons.insertAdjacentHTML('beforebegin', `
        <footer id="drg-footer">
          <div class="drg-footer-inner">
            <div class="drg-columns">
              <div class="drg-column drg-column-left">
                <a href="/index.php/Disaster_Risk_Gateway">About</a>
                <a href="/index.php/Disaster_Risk_Gateway:Privacy_Notice">Privacy Notice</a>
                <a href="/index.php/Disaster_Risk_Gateway:Terms_of_Use">Terms of Use</a>
                <a href="mailto:DisasterRiskGateway@bgs.ac.uk"><strong>Contact Us</strong></a>
              </div>
              <div class="drg-column drg-column-center">
                <div class="drg-eu-flag">
                  <img src="/images/e/e3/Normal-reproduction-high-resolution_2.jpg" alt="EU logo">
                </div>
                <div class="drg-eu-text">
                  MYRIAD-EU project has received funding from the European Union’s Horizon 2020 Research and Innovation Programme under Grant Agreement No. 101003276.
                </div>
              </div>
              <div class="drg-column drg-column-right">
                <a href="https://www.myriadproject.eu" target="_blank">MYRIAD-EU</a>
                <a href="https://be.linkedin.com/company/myriad-eu-project" target="_blank">LinkedIn</a>
                <a href="#" target="_blank">MYRIAD-EU dashboard</a>
              </div>
            </div>
            <div class="drg-disclaimer-wrapper">
              <div class="drg-disclaimer">
                This site is hosted by the British Geological Survey but responsibility for the content of the site lies with the 
                <a href="https://www.myriadproject.eu" target="_blank" rel="noopener noreferrer">MYRIAD-EU project</a>.<br>
                Questions or comments: <a href="mailto:DisasterRiskGateway@bgs.ac.uk">DisasterRiskGateway@bgs.ac.uk</a>
              </div>
            </div>
          </div>
        </footer>
      `);
    }
    insertFooter();
  });
});