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; // | 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) { | ||
/* 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: ' | .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">' | ||
+ '< | + '<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: | ||
}); | }); | ||
/* | /* 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; | ||
_selTimer = setTimeout(function(){ | _selTimer = setTimeout(function(){ | ||
if (v !== _selVersion) return; | if (v !== _selVersion) return; | ||
if (_fabSelVer === v) return; | if (_fabSelVer === v) return; | ||
tryShowActions(); | tryShowActions(); | ||
}, | }, 600); | ||
}); | }); | ||
/* ── 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 */ | ||