MediaWiki:Gadget-GrAnnotations.js: Difference between revisions

No edit summary
Tag: Reverted
No edit summary
 
(18 intermediate revisions by the same user not shown)
Line 1: Line 1:
/**
/**
  * gr_annotations.js  —  grantha.io inline Notes + Bookmarks + Feedback  (v6)
  * gr_annotations.js  —  grantha.io inline Notes + Bookmarks + Feedback  (v6 + Strategy B)
  */
  */


Line 37: Line 37:
   var _fabSelVer  = -1;
   var _fabSelVer  = -1;
   var _mobile    = window.innerWidth < 768 || 'ontouchstart' in window;
   var _mobile    = window.innerWidth < 768 || 'ontouchstart' in window;
   var _fabTouched = false;  // NEW: flag to prevent hideActions when tapping fab
   var _fabTouched = false;  // flag to prevent hideActions when tapping fab


   function uid() { return 'gra_' + Date.now() + '_' + Math.random().toString(36).slice(2,7); }
   function uid() { return 'gra_' + Date.now() + '_' + Math.random().toString(36).slice(2,7); }
Line 79: Line 79:
       '    <span class="gra-icon gra-icon-search" aria-hidden="true"></span>',
       '    <span class="gra-icon gra-icon-search" aria-hidden="true"></span>',
       '    <span class="gra-fab-btn-label">Search</span>',
       '    <span class="gra-fab-btn-label">Search</span>',
      '  </button>',
      '  <button class="gra-fab-btn gra-fab-btn-dismiss" id="gra-fab-dismiss" type="button" aria-label="Dismiss">',
      '    <span class="gra-icon gra-icon-dismiss" aria-hidden="true"></span>',
      '    <span class="gra-fab-btn-label">Close</span>',
       '  </button>',
       '  </button>',
       '</div>',
       '</div>',
Line 243: Line 247:
     var fabW, fabH, top, left;
     var fabW, fabH, top, left;
     if (_mobile) {
     if (_mobile) {
       fabW = 200; fabH = 48;
       /* Docked as a fixed bar below the reader toolbar.
      top  = rect.top + window.scrollY - fabH - 14;
        All positioning is handled by CSS via .gra-fab-mobile-docked,
      left = rect.left + rect.width / 2 - fabW / 2;
        so it never collides with the native selection menu and never
      top  = clamp(top,  window.scrollY + 8, window.scrollY + window.innerHeight - fabH - 8);
        clips at screen edges or causes horizontal scroll. */
      left = clamp(left, 8, window.innerWidth - fabW - 8);
       $fab.css({ position: '', top: '', left: '', visibility: '' })
       $fab.css({ position: 'absolute', top: top + 'px', left: left + 'px' })
           .addClass('gra-fab-visible gra-fab-mobile-docked');
           .addClass('gra-fab-visible');
       return;
       return;
     }
     }
Line 261: Line 264:
   }
   }


   function hideFab() { $fab.removeClass('gra-fab-visible'); }
   function hideFab() { $fab.removeClass('gra-fab-visible gra-fab-mobile-docked'); }
   function hideActions() { hideFab(); }
   function hideActions() { hideFab(); }


Line 302: Line 305:
     if (currentUserEmail) $fbEmail.val(currentUserEmail);
     if (currentUserEmail) $fbEmail.val(currentUserEmail);
     else $fbEmail.val('');
     else $fbEmail.val('');
     $fbComposer.css({top:'', left:'', transform:''});
     if (!_mobile) $fbComposer.css({top:'', left:'', transform:''});
     $fbComposer.addClass('gra-composer-visible');
     $fbComposer.addClass('gra-composer-visible');
     $backdrop.addClass('gra-backdrop-visible');
     $backdrop.addClass('gra-backdrop-visible');
Line 355: Line 358:
   function openNoteComposer() {
   function openNoteComposer() {
     hideActions();
     hideActions();
     $ntComposer.css({ top: '', left: '', transform: '' });
     if (!_mobile) $ntComposer.css({ top: '', left: '', transform: '' });
     $ntComposer.addClass('gra-composer-visible');
     $ntComposer.addClass('gra-composer-visible');
     $backdrop.addClass('gra-backdrop-visible');
     $backdrop.addClass('gra-backdrop-visible');
Line 395: Line 398:
   function openBookmarkComposer() {
   function openBookmarkComposer() {
     hideActions();
     hideActions();
     $bmComposer.css({ top: '', left: '', transform: '' });
     if (!_mobile) $bmComposer.css({ top: '', left: '', transform: '' });
     $bmComposer.addClass('gra-composer-visible');
     $bmComposer.addClass('gra-composer-visible');
     $backdrop.addClass('gra-backdrop-visible');
     $backdrop.addClass('gra-backdrop-visible');
Line 469: Line 472:
       html += '<div class="gra-note-card" data-gra-id="'+esc(n.id)+'">'
       html += '<div class="gra-note-card" data-gra-id="'+esc(n.id)+'">'
             + '<div class="gra-card-header">'
             + '<div class="gra-card-header">'
             + '<div class="gra-avatar"></div>'
             + '<span class="gra-icon gra-icon-note" aria-hidden="true"></span>'
             + '<div class="gra-card-meta">'
             + '<div class="gra-card-meta">'
             + (n.ts ? '<div class="gra-card-ts">'+esc(fmtTs(n.ts))+'</div>' : '')
             + (n.ts ? '<div class="gra-card-ts">'+esc(fmtTs(n.ts))+'</div>' : '')
Line 527: Line 530:
   function wireEvents() {
   function wireEvents() {


     /* Suppress native context menu inside article content */
     /* Suppress native context menu inside article content (Android/desktop) */
     document.addEventListener('contextmenu', function(e) {
     document.addEventListener('contextmenu', function(e) {
       var tag = e.target.tagName;
       var tag = e.target.tagName;
Line 542: Line 545:
     });
     });


     /* selectionchange — handles both mobile and desktop */
     /* Separate timers so mobile + desktop never clobber each other */
     var _selTimer = null;
     var _selTimer   = null; /* desktop debounce */
    var _mobShowTimer = null; /* mobile show-on-touchend */
 
    /* Mobile: show fab quickly after finger lifts (selection settled).
      180ms feels instant while still letting the range stabilise. */
    document.addEventListener('touchend', function(e) {
      if (!_mobile) return;
      if ($fab[0] && $fab[0].contains(e.target)) return;
      clearTimeout(_mobShowTimer);
      _mobShowTimer = setTimeout(function() {
        var sel = window.getSelection();
        if (!sel || sel.isCollapsed || !sel.toString().trim()) return;
        tryShowActions();
      }, 180);
    }, { passive: true });
 
    /* Mobile: only HIDE the fab when selection is cleared while it's visible.
      (Reposition isn't needed now that the bar is docked, and re-running
      showFab here was causing the lag/flicker.) */
    document.addEventListener('selectionchange', function() {
      if (!_mobile) return;
      if (!$fab.hasClass('gra-fab-visible')) return;
      var sel = window.getSelection();
      if (!sel || sel.isCollapsed || !sel.toString().trim()) {
        clearTimeout(_mobShowTimer);
        hideActions();
      }
    });
 
    /* selectionchange debounced (desktop only) */
     document.addEventListener('selectionchange', function() {
     document.addEventListener('selectionchange', function() {
      if (_mobile) return;
       _selVersion++;
       _selVersion++;
       clearTimeout(_selTimer);
       clearTimeout(_selTimer);
       var v = _selVersion;
       var v = _selVersion;
      /* On mobile fire faster so fab appears before browser popup */
      var delay = _mobile ? 150 : 600;
       _selTimer = setTimeout(function(){
       _selTimer = setTimeout(function(){
         if (v !== _selVersion) return;
         if (v !== _selVersion) return;
         if (_fabSelVer === v) return;
         if (_fabSelVer === v) return;
        /* Only show if there's an actual non-collapsed selection */
        var sel = window.getSelection();
        if (!sel || sel.isCollapsed || !sel.toString().trim()) return;
         tryShowActions();
         tryShowActions();
       }, delay);
       }, 600);
     });
     });
    /* Mobile touchend: re-check after finger lifts (selection may settle) */
    document.addEventListener('touchend', function(e) {
      if (!_mobile) return;
      if ($fab[0] && $fab[0].contains(e.target)) return;
      clearTimeout(_selTimer);
      _selTimer = setTimeout(function() {
        var sel = window.getSelection();
        if (!sel || sel.isCollapsed || !sel.toString().trim()) return;
        tryShowActions();
      }, 200);
    }, { passive: true });


     /* ── KEY FIX: fab touchstart sets flag to prevent hideActions ── */
     /* ── KEY FIX: fab touchstart sets flag to prevent hideActions ── */
Line 629: Line 645:
       else if (q) { $(document).trigger($.Event('keydown', {ctrlKey:true, key:'k', keyCode:75})); }
       else if (q) { $(document).trigger($.Event('keydown', {ctrlKey:true, key:'k', keyCode:75})); }
     });
     });
    /* ── Dismiss button: hide toolbar + clear selection (mobile) ── */
    (function () {
      var dismissEl = document.getElementById('gra-fab-dismiss');
      if (!dismissEl) return;
      function doDismiss(e) {
        e.preventDefault(); e.stopPropagation();
        hideActions();
        _selRange = null; _selText = ''; _selRect = null;
        if (window.getSelection) {
          var s = window.getSelection();
          if (s && s.removeAllRanges) s.removeAllRanges();
        }
      }
      dismissEl.addEventListener('touchend', doDismiss, { passive: false });
      dismissEl.addEventListener('click', doDismiss);
    }());


     /* Feedback composer */
     /* Feedback composer */