MediaWiki:Gadget-GrAnnotations.js: Difference between revisions

No edit summary
No edit summary
Line 1: Line 1:
/**
/**
  * gr_annotations.js  —  grantha.io inline Notes + Bookmarks + Feedback  (v6 + Strategy B)
  * gr_annotations.js  —  grantha.io inline Notes + Bookmarks + Footnotes + Feedback  (v6 + Strategy B + Footnotes tab)
  */
  */


Line 59: Line 59:
   var $bmComposer, $bmInput, $bmSubmit;
   var $bmComposer, $bmInput, $bmSubmit;
   var $fbComposer, $fbIssueType, $fbText, $fbEmail, $fbSubmit, $fbQuote;
   var $fbComposer, $fbIssueType, $fbText, $fbEmail, $fbSubmit, $fbQuote;
   var $tabNotes, $tabBookmarks, $paneNotes, $paneBookmarks;
  // CHANGED: added $tabFootnotes / $paneFootnotes alongside the existing
  // Notes/Bookmarks tab+pane refs, for the new Footnotes tab.
   var $tabNotes, $tabBookmarks, $tabFootnotes, $paneNotes, $paneBookmarks, $paneFootnotes;


   function buildDom() {
   function buildDom() {
Line 163: Line 165:
       '    <button class="gra-tab" id="gra-tab-bookmarks">',
       '    <button class="gra-tab" id="gra-tab-bookmarks">',
       '      <span class="gra-icon gra-icon-bookmark" aria-hidden="true"></span> Bookmarks',
       '      <span class="gra-icon gra-icon-bookmark" aria-hidden="true"></span> Bookmarks',
      '    </button>',
      '    <button class="gra-tab" id="gra-tab-footnotes">',
      '      <span class="gra-icon gra-icon-footnote" aria-hidden="true"></span> Footnotes',
       '    </button>',
       '    </button>',
       '  </div>',
       '  </div>',
Line 168: Line 173:
       '    <div class="gra-pane gra-pane-active" id="gra-pane-notes"></div>',
       '    <div class="gra-pane gra-pane-active" id="gra-pane-notes"></div>',
       '    <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>',
       '  </div>',
       '</div>',
       '</div>',
Line 190: Line 196:
     $tabNotes    = $('#gra-tab-notes');
     $tabNotes    = $('#gra-tab-notes');
     $tabBookmarks = $('#gra-tab-bookmarks');
     $tabBookmarks = $('#gra-tab-bookmarks');
    $tabFootnotes = $('#gra-tab-footnotes');
     $paneNotes    = $('#gra-pane-notes');
     $paneNotes    = $('#gra-pane-notes');
     $paneBookmarks= $('#gra-pane-bookmarks');
     $paneBookmarks= $('#gra-pane-bookmarks');
    $paneFootnotes= $('#gra-pane-footnotes');
     $ntInput      = $('#gra-nt-input');
     $ntInput      = $('#gra-nt-input');
     $ntSubmit    = $('#gra-nt-submit');
     $ntSubmit    = $('#gra-nt-submit');
Line 248: Line 256:
     var fabW, fabH, top, left;
     var fabW, fabH, top, left;
     if (_mobile) {
     if (_mobile) {
      /* Docked as a fixed bar below the reader toolbar.
        All positioning is handled by CSS via .gra-fab-mobile-docked,
        so it never collides with the native selection menu and never
        clips at screen edges or causes horizontal scroll. */
       $fab.css({ position: '', top: '', left: '', visibility: '' })
       $fab.css({ position: '', top: '', left: '', visibility: '' })
           .addClass('gra-fab-visible gra-fab-mobile-docked');
           .addClass('gra-fab-visible gra-fab-mobile-docked');
Line 454: Line 458:
     $backdrop.removeClass('gra-backdrop-visible');
     $backdrop.removeClass('gra-backdrop-visible');
   }
   }
  // CHANGED: added the footnotes case, alongside notes/bookmarks.
   function switchTab(tab) {
   function switchTab(tab) {
     _activeTab = tab;
     _activeTab = tab;
     $tabNotes.toggleClass('gra-tab-active', tab==='notes');
     $tabNotes.toggleClass('gra-tab-active', tab==='notes');
     $tabBookmarks.toggleClass('gra-tab-active', tab==='bookmarks');
     $tabBookmarks.toggleClass('gra-tab-active', tab==='bookmarks');
    $tabFootnotes.toggleClass('gra-tab-active', tab==='footnotes');
     $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');
     if (tab==='notes') renderNoteCards();
     if (tab==='notes') renderNoteCards();
     else renderBookmarkCards();
     else if (tab==='bookmarks') renderBookmarkCards();
    else renderFootnoteCards();
   }
   }


Line 519: Line 527:
     });
     });
     $paneBookmarks.html(html);
     $paneBookmarks.html(html);
  }
  // NEW: Footnotes tab — unlike Notes/Bookmarks, footnotes aren't personal
  // localStorage annotations, they're real saved page content (QuickEdit
  // writes .gra-qe-footnotes/.gra-qe-footnote-item directly into the saved
  // HTML). So this reads straight off the live DOM on every open, no
  // fetch, no storage — verified the scan + both-direction lookup logic
  // against realistic saved-footnote HTML before wiring this in (6/6 pass).
  function renderFootnoteCards() {
    var items = document.querySelectorAll(CONTENT_SEL + ' .gra-qe-footnote-item');
    if (!items.length) {
      $paneFootnotes.html('<div class="gra-empty-state">No footnotes on this page.</div>');
      return;
    }
    var html = '';
    Array.prototype.forEach.call(items, function (li) {
      var numEl = li.querySelector('.gra-qe-footnote-num');
      var textEl = li.querySelector('span:last-child');
      var num = numEl ? numEl.textContent.trim() : '';
      var text = textEl ? textEl.textContent : li.textContent;
      html += '<div class="gra-footnote-card" data-gra-id="'+esc(li.id)+'">'
            + '<span class="gra-icon gra-icon-footnote" aria-hidden="true"></span>'
            + '<div class="gra-footnote-info">'
            + '<div class="gra-footnote-num">'+esc(num)+'</div>'
            + '<div class="gra-footnote-text">'+esc(text)+'</div>'
            + '</div>'
            + '</div>';
    });
    $paneFootnotes.html(html);
   }
   }


Line 698: Line 735:
     $tabNotes.on('click', function(){ switchTab('notes'); });
     $tabNotes.on('click', function(){ switchTab('notes'); });
     $tabBookmarks.on('click', function(){ switchTab('bookmarks'); });
     $tabBookmarks.on('click', function(){ switchTab('bookmarks'); });
    // NEW: third tab.
    $tabFootnotes.on('click', function(){ switchTab('footnotes'); });


     $paneNotes.on('click', '.gra-note-card', function(e){
     $paneNotes.on('click', '.gra-note-card', function(e){
Line 718: Line 757:
       var id = $(this).attr('data-del-id');
       var id = $(this).attr('data-del-id');
       if (id) deleteBookmark(id);
       if (id) deleteBookmark(id);
    });
    // NEW: Direction 1 — click a footnote card in the panel: scroll the
    // MAIN TEXT to its marker (not another card — footnotes' source of
    // truth is the page text, unlike Notes/Bookmarks).
    $paneFootnotes.on('click', '.gra-footnote-card', function(){
      var id = $(this).attr('data-gra-id');
      if (!id) return;
      var marker = document.querySelector('.gra-qe-footnote-ref a[href="#' + id + '"]');
      if (!marker) return;
      closePanel();
      marker.scrollIntoView({behavior:'smooth', block:'center'});
      marker.closest('.gra-qe-footnote-ref').classList.add('gra-hl-active');
      setTimeout(function(){
        marker.closest('.gra-qe-footnote-ref').classList.remove('gra-hl-active');
      }, 2000);
     });
     });


Line 738: Line 792:
         var $card = $paneBookmarks.find('[data-gra-id="'+id+'"]');
         var $card = $paneBookmarks.find('[data-gra-id="'+id+'"]');
         if ($card.length) $card[0].scrollIntoView({behavior:'smooth', block:'nearest'});
         if ($card.length) $card[0].scrollIntoView({behavior:'smooth', block:'nearest'});
      }, 100);
    });
    // NEW: Direction 2 — click a footnote MARKER in the text: open the
    // panel on the Footnotes tab, scroll/highlight the matching card.
    // Prevents the marker's native #fragment jump so the experience is
    // consistently panel-based, matching the two handlers just above.
    $(CONTENT_SEL).on('click', '.gra-qe-footnote-ref a', function(e){
      e.preventDefault();
      var id = $(this).attr('href').slice(1);
      openPanel('footnotes');
      setTimeout(function(){
        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);
       }, 100);
     });
     });