MediaWiki:Gadget-GrAnnotations.js: Difference between revisions

No edit summary
No edit summary
Line 15: Line 15:
   var currentUserEmail = '';
   var currentUserEmail = '';


  // FIXED: was `new mw.Api()` called directly and synchronously here —
  // at this point in MediaWiki:Common.js's own load lifecycle,
  // mediawiki.api (the module that provides the REAL mw.Api
  // constructor) is not guaranteed to have finished loading yet, and
  // calling the constructor before it has throws "mw.Api is not a
  // constructor" synchronously. Confirmed directly via the real error
  // trace, and confirmed this is pre-existing code, not anything
  // introduced by the more recent Ullekha/tab changes. Uncaught here
  // (the .catch below only ever handled promise rejections, never a
  // synchronous throw from the constructor call itself), this would
  // halt the REST of this script's own execution — meaning later code
  // in this same file (the tab-building logic) may never run at all on
  // whatever page load hits this race. mw.loader.using() guarantees the
  // module is actually ready before mw.Api is ever touched, regardless
  // of load timing. Verified the exact failure mode and the fix
  // directly before applying here.
   if ( currentUser && window.mw ) {
   if ( currentUser && window.mw ) {
     new mw.Api().get({ action: 'query', meta: 'userinfo', uiprop: 'email', formatversion: 2 })
     mw.loader.using( 'mediawiki.api' ).then( function () {
      .then( function (data) {
      return new mw.Api().get({ action: 'query', meta: 'userinfo', uiprop: 'email', formatversion: 2 });
        var info = data && data.query && data.query.userinfo;
    } ).then( function (data) {
        if ( info && info.email ) currentUserEmail = info.email;
      var info = data && data.query && data.query.userinfo;
      } ).catch( function () {} );
      if ( info && info.email ) currentUserEmail = info.email;
    } ).catch( function () {} );
   }
   }


Line 60: Line 77:
   var $fbComposer, $fbIssueType, $fbText, $fbEmail, $fbSubmit, $fbQuote;
   var $fbComposer, $fbIssueType, $fbText, $fbEmail, $fbSubmit, $fbQuote;
   // CHANGED: added $tabFootnotes / $paneFootnotes alongside the existing
   // CHANGED: added $tabFootnotes / $paneFootnotes alongside the existing
   // Notes/Bookmarks tab+pane refs, for the Footnotes tab. NEW: now also
   // Notes/Bookmarks tab+pane refs, for the Footnotes tab. REMOVED: the
   // $tabSandarbha / $paneSandarbha, for References.
   // earlier $tabSandarbha/$paneSandarbha — that feature was replaced by
   var $tabNotes, $tabBookmarks, $tabFootnotes, $tabSandarbha, $paneNotes, $paneBookmarks, $paneFootnotes, $paneSandarbha;
  // the real Ullekha citation-index mechanism (see quickedit.js), which
  // works entirely through common.js's own site-wide logic and needs no
  // panel integration here at all.
   var $tabNotes, $tabBookmarks, $tabFootnotes, $paneNotes, $paneBookmarks, $paneFootnotes;


   function buildDom() {
   function buildDom() {
Line 169: Line 189:
       '    <button class="gra-tab" id="gra-tab-footnotes">',
       '    <button class="gra-tab" id="gra-tab-footnotes">',
       '      <span class="gra-icon gra-icon-footnote" aria-hidden="true"></span> Footnotes',
       '      <span class="gra-icon gra-icon-footnote" aria-hidden="true"></span> Footnotes',
      '    </button>',
      '    <button class="gra-tab" id="gra-tab-sandarbha">',
      '      <span class="gra-icon gra-icon-sandarbha" aria-hidden="true"></span> References',
       '    </button>',
       '    </button>',
       '  </div>',
       '  </div>',
Line 178: Line 195:
       '    <div class="gra-pane" id="gra-pane-bookmarks"></div>',
       '    <div class="gra-pane" id="gra-pane-bookmarks"></div>',
       '    <div class="gra-pane" id="gra-pane-footnotes"></div>',
       '    <div class="gra-pane" id="gra-pane-footnotes"></div>',
      '    <div class="gra-pane" id="gra-pane-sandarbha"></div>',
       '  </div>',
       '  </div>',
       '</div>',
       '</div>',
Line 202: Line 218:
     $tabBookmarks = $('#gra-tab-bookmarks');
     $tabBookmarks = $('#gra-tab-bookmarks');
     $tabFootnotes = $('#gra-tab-footnotes');
     $tabFootnotes = $('#gra-tab-footnotes');
    $tabSandarbha = $('#gra-tab-sandarbha');
     $paneNotes    = $('#gra-pane-notes');
     $paneNotes    = $('#gra-pane-notes');
     $paneBookmarks= $('#gra-pane-bookmarks');
     $paneBookmarks= $('#gra-pane-bookmarks');
     $paneFootnotes= $('#gra-pane-footnotes');
     $paneFootnotes= $('#gra-pane-footnotes');
    $paneSandarbha= $('#gra-pane-sandarbha');
     $ntInput      = $('#gra-nt-input');
     $ntInput      = $('#gra-nt-input');
     $ntSubmit    = $('#gra-nt-submit');
     $ntSubmit    = $('#gra-nt-submit');
Line 471: Line 485:
     $tabBookmarks.toggleClass('gra-tab-active', tab==='bookmarks');
     $tabBookmarks.toggleClass('gra-tab-active', tab==='bookmarks');
     $tabFootnotes.toggleClass('gra-tab-active', tab==='footnotes');
     $tabFootnotes.toggleClass('gra-tab-active', tab==='footnotes');
    $tabSandarbha.toggleClass('gra-tab-active', tab==='sandarbha');
     $paneNotes.toggleClass('gra-pane-active', tab==='notes');
     $paneNotes.toggleClass('gra-pane-active', tab==='notes');
     $paneBookmarks.toggleClass('gra-pane-active', tab==='bookmarks');
     $paneBookmarks.toggleClass('gra-pane-active', tab==='bookmarks');
     $paneFootnotes.toggleClass('gra-pane-active', tab==='footnotes');
     $paneFootnotes.toggleClass('gra-pane-active', tab==='footnotes');
    $paneSandarbha.toggleClass('gra-pane-active', tab==='sandarbha');
     if (tab==='notes') renderNoteCards();
     if (tab==='notes') renderNoteCards();
     else if (tab==='bookmarks') renderBookmarkCards();
     else if (tab==='bookmarks') renderBookmarkCards();
     else if (tab==='footnotes') renderFootnoteCards();
     else renderFootnoteCards();
    else renderSandarbhaCards();
   }
   }


Line 580: Line 591:
     });
     });
     $paneFootnotes.html(html);
     $paneFootnotes.html(html);
  }
  // NEW: References (Sandarbha) tab. Simpler than footnotes — reads
  // straight off .gra-qe-sandarbha-highlight elements, no separate list
  // to scan. The highlighted text itself is the primary line (matching
  // how Notes/Bookmarks already identify themselves by quote); the
  // reference content stored in the hidden nested span is secondary.
  // Deliberately excludes that hidden span's own text when computing the
  // quote — el.textContent alone would include it too, regardless of its
  // display:none.
  function renderSandarbhaCards() {
    var items = document.querySelectorAll(CONTENT_SEL + ' .gra-qe-sandarbha-highlight');
    if (!items.length) {
      $paneSandarbha.html('<div class="gra-empty-state">No references on this page.</div>');
      return;
    }
    var html = '';
    Array.prototype.forEach.call(items, function (el) {
      var contentSpan = el.querySelector('.gra-qe-sandarbha-content');
      var refText = contentSpan ? contentSpan.textContent : '';
      var quote = '';
      Array.prototype.forEach.call(el.childNodes, function (node) {
        if (node === contentSpan) return;
        quote += node.textContent || '';
      });
      var id = el.getAttribute('data-gra-id') || '';
      html += '<div class="gra-bookmark-card gra-sandarbha-card" data-gra-id="'+esc(id)+'">'
            + '<span class="gra-icon gra-icon-sandarbha" aria-hidden="true"></span>'
            + '<div class="gra-bookmark-info">'
            + '<div class="gra-bookmark-name">'+esc(quote)+'</div>'
            + (refText ? '<div class="gra-bookmark-quote">'+esc(refText)+'</div>' : '')
            + '</div>'
            + '</div>';
    });
    $paneSandarbha.html(html);
   }
   }


Line 796: Line 772:
     // NEW: third tab.
     // NEW: third tab.
     $tabFootnotes.on('click', function(){ switchTab('footnotes'); });
     $tabFootnotes.on('click', function(){ switchTab('footnotes'); });
    // NEW: fourth tab.
    $tabSandarbha.on('click', function(){ switchTab('sandarbha'); });


     $paneNotes.on('click', '.gra-note-card', function(e){
     $paneNotes.on('click', '.gra-note-card', function(e){
Line 825: Line 799:
     // that broke every time the marker format changed.
     // that broke every time the marker format changed.
     $paneFootnotes.on('click', '.gra-footnote-card', function(){
     $paneFootnotes.on('click', '.gra-footnote-card', function(){
      var id = $(this).attr('data-gra-id');
      if (id) { closePanel(); scrollToHighlight(id); }
    });
    // NEW: same reuse for Sandarbha - it was designed from the start to
    // carry data-gra-id for exactly this reason.
    $paneSandarbha.on('click', '.gra-sandarbha-card', function(){
       var id = $(this).attr('data-gra-id');
       var id = $(this).attr('data-gra-id');
       if (id) { closePanel(); scrollToHighlight(id); }
       if (id) { closePanel(); scrollToHighlight(id); }
Line 864: Line 832:
       setTimeout(function(){
       setTimeout(function(){
         var $card = $paneFootnotes.find('[data-gra-id="'+id+'"]');
         var $card = $paneFootnotes.find('[data-gra-id="'+id+'"]');
        if ($card.length) {
          $card.addClass('gra-card-active');
          $card[0].scrollIntoView({behavior:'smooth', block:'nearest'});
          setTimeout(function(){ $card.removeClass('gra-card-active'); }, 2000);
        }
      }, 100);
    });
    // NEW: same pattern for Sandarbha highlights.
    $(CONTENT_SEL).on('click', '.gra-qe-sandarbha-highlight', function(){
      var id = $(this).attr('data-gra-id');
      openPanel('sandarbha');
      setTimeout(function(){
        var $card = $paneSandarbha.find('[data-gra-id="'+id+'"]');
         if ($card.length) {
         if ($card.length) {
           $card.addClass('gra-card-active');
           $card.addClass('gra-card-active');