Add double tap detection for next/previous page navigation
This commit is contained in:
parent
22b68eefd8
commit
fc473f1d11
3 changed files with 40 additions and 8 deletions
|
@ -12,7 +12,7 @@ static findParent(element,selector){for(;element&&element!==document;element=ele
|
|||
return null;}
|
||||
static hasPassiveEventListenerOption(){var passiveSupported=false;try{var options=Object.defineProperty({},"passive",{get:function(){passiveSupported=true;}});window.addEventListener("test",options,options);window.removeEventListener("test",options,options);}catch(err){passiveSupported=false;}
|
||||
return passiveSupported;}}
|
||||
class TouchHandler{constructor(){this.reset();}
|
||||
class TouchHandler{constructor(navHandler){this.navHandler=navHandler;this.reset();}
|
||||
reset(){this.touch={start:{x:-1,y:-1},move:{x:-1,y:-1},element:null};}
|
||||
calculateDistance(){if(this.touch.start.x>=-1&&this.touch.move.x>=-1){let horizontalDistance=Math.abs(this.touch.move.x-this.touch.start.x);let verticalDistance=Math.abs(this.touch.move.y-this.touch.start.y);if(horizontalDistance>30&&verticalDistance<70){return this.touch.move.x-this.touch.start.x;}}
|
||||
return 0;}
|
||||
|
@ -26,7 +26,7 @@ onTouchEnd(event){if(event.touches===undefined){return;}
|
|||
if(this.touch.element!==null){let distance=Math.abs(this.calculateDistance());if(distance>75){EntryHandler.toggleEntryStatus(this.touch.element);}
|
||||
this.touch.element.style.opacity=1;this.touch.element.style.transform="none";}
|
||||
this.reset();}
|
||||
listen(){let elements=document.querySelectorAll(".touch-item");let hasPassiveOption=DomHelper.hasPassiveEventListenerOption();elements.forEach((element)=>{element.addEventListener("touchstart",(e)=>this.onTouchStart(e),hasPassiveOption?{passive:true}:false);element.addEventListener("touchmove",(e)=>this.onTouchMove(e),hasPassiveOption?{passive:false}:false);element.addEventListener("touchend",(e)=>this.onTouchEnd(e),hasPassiveOption?{passive:true}:false);element.addEventListener("touchcancel",()=>this.reset(),hasPassiveOption?{passive:true}:false);});}}
|
||||
listen(){let elements=document.querySelectorAll(".touch-item");let hasPassiveOption=DomHelper.hasPassiveEventListenerOption();elements.forEach((element)=>{element.addEventListener("touchstart",(e)=>this.onTouchStart(e),hasPassiveOption?{passive:true}:false);element.addEventListener("touchmove",(e)=>this.onTouchMove(e),hasPassiveOption?{passive:false}:false);element.addEventListener("touchend",(e)=>this.onTouchEnd(e),hasPassiveOption?{passive:true}:false);element.addEventListener("touchcancel",()=>this.reset(),hasPassiveOption?{passive:true}:false);});let entryContentElement=document.querySelector(".entry-content");if(entryContentElement){let doubleTapTimers={previous:null,next:null};const detectDoubleTap=(doubleTapTimer,event)=>{const timer=doubleTapTimers[doubleTapTimer];if(timer===null){doubleTapTimers[doubleTapTimer]=setTimeout(()=>{doubleTapTimers[doubleTapTimer]=null;},200);}else{event.preventDefault();this.navHandler.goToPage(doubleTapTimer);}};entryContentElement.addEventListener("touchend",(e)=>{if(e.changedTouches[0].clientX>=(entryContentElement.offsetWidth/2)){detectDoubleTap("next",e);}else{detectDoubleTap("previous",e);}},hasPassiveOption?{passive:false}:false);entryContentElement.addEventListener("touchmove",(e)=>{Object.keys(doubleTapTimers).forEach(timer=>doubleTapTimers[timer]=null);});}}}
|
||||
class KeyboardHandler{constructor(){this.queue=[];this.shortcuts={};}
|
||||
on(combination,callback){this.shortcuts[combination]=callback;}
|
||||
listen(){document.onkeydown=(event)=>{if(this.isEventIgnored(event)||this.isModifierKeyDown(event)){return;}
|
||||
|
@ -98,13 +98,13 @@ break;}}}
|
|||
isEntry(){return document.querySelector("section.entry")!==null;}
|
||||
isListView(){return document.querySelector(".items")!==null;}}
|
||||
class LinkStateHandler{static flip(element){let labelElement=document.createElement("span");labelElement.className="link-flipped-state";labelElement.appendChild(document.createTextNode(element.dataset.labelNewState));element.parentNode.appendChild(labelElement);element.parentNode.removeChild(element);}}
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g b",()=>navHandler.goToPage("starred"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToFeedOrFeeds());keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.on("f",()=>navHandler.toggleBookmark());keyboardHandler.on("?",()=>navHandler.showKeyboardShortcuts());keyboardHandler.on("#",()=>navHandler.unsubscribeFromFeed());keyboardHandler.on("/",(e)=>navHandler.setFocusToSearchInput(e));keyboardHandler.on("Escape",()=>ModalHandler.close());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-toggle-bookmark]",(event)=>{EntryHandler.toggleBookmark(event.target);});mouseHandler.onClick("a[data-toggle-status]",(event)=>{let currentItem=DomHelper.findParent(event.target,"entry");if(!currentItem){currentItem=DomHelper.findParent(event.target,"item");}
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g b",()=>navHandler.goToPage("starred"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToFeedOrFeeds());keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.on("f",()=>navHandler.toggleBookmark());keyboardHandler.on("?",()=>navHandler.showKeyboardShortcuts());keyboardHandler.on("#",()=>navHandler.unsubscribeFromFeed());keyboardHandler.on("/",(e)=>navHandler.setFocusToSearchInput(e));keyboardHandler.on("Escape",()=>ModalHandler.close());keyboardHandler.listen();let touchHandler=new TouchHandler(navHandler);touchHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-toggle-bookmark]",(event)=>{EntryHandler.toggleBookmark(event.target);});mouseHandler.onClick("a[data-toggle-status]",(event)=>{let currentItem=DomHelper.findParent(event.target,"entry");if(!currentItem){currentItem=DomHelper.findParent(event.target,"item");}
|
||||
if(currentItem){EntryHandler.toggleEntryStatus(currentItem);}});mouseHandler.onClick("a[data-fetch-content-entry]",(event)=>{EntryHandler.fetchOriginalContent(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});mouseHandler.onClick("a[data-action=search]",(event)=>{navHandler.setFocusToSearchInput(event);});mouseHandler.onClick("a[data-link-state=flip]",(event)=>{LinkStateHandler.flip(event.target);},true);if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}
|
||||
if("serviceWorker"in navigator){let scriptElement=document.getElementById("service-worker-script");if(scriptElement){navigator.serviceWorker.register(scriptElement.src);}}});})();`,
|
||||
"sw": `'use strict';self.addEventListener("fetch",(event)=>{if(event.request.url.includes("/feed/icon/")){event.respondWith(caches.open("feed_icons").then((cache)=>{return cache.match(event.request).then((response)=>{return response||fetch(event.request).then((response)=>{cache.put(event.request,response.clone());return response;});});}));}});`,
|
||||
}
|
||||
|
||||
var JavascriptsChecksums = map[string]string{
|
||||
"app": "966a3deb03c0cd0dfe1411958786b0eab7ec6f4fe3b008c0cfd4cd859f03ce3e",
|
||||
"app": "41518498a6efcdb4dc72abdbe0dc44d93abbe244b1f50f2a89dee51e7fbee673",
|
||||
"sw": "55fffa223919cc18572788fb9c62fccf92166c0eb5d3a1d6f91c31f24d020be9",
|
||||
}
|
||||
|
|
6
ui/static/js/bootstrap.js
vendored
6
ui/static/js/bootstrap.js
vendored
|
@ -1,9 +1,6 @@
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
FormHandler.handleSubmitButtons();
|
||||
|
||||
let touchHandler = new TouchHandler();
|
||||
touchHandler.listen();
|
||||
|
||||
let navHandler = new NavHandler();
|
||||
let keyboardHandler = new KeyboardHandler();
|
||||
keyboardHandler.on("g u", () => navHandler.goToPage("unread"));
|
||||
|
@ -33,6 +30,9 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
keyboardHandler.on("Escape", () => ModalHandler.close());
|
||||
keyboardHandler.listen();
|
||||
|
||||
let touchHandler = new TouchHandler(navHandler);
|
||||
touchHandler.listen();
|
||||
|
||||
let mouseHandler = new MouseHandler();
|
||||
mouseHandler.onClick("a[data-save-entry]", (event) => {
|
||||
EntryHandler.saveEntry(event.target);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class TouchHandler {
|
||||
constructor() {
|
||||
constructor(navHandler) {
|
||||
this.navHandler = navHandler;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
|
@ -93,5 +94,36 @@ class TouchHandler {
|
|||
element.addEventListener("touchend", (e) => this.onTouchEnd(e), hasPassiveOption ? { passive: true } : false);
|
||||
element.addEventListener("touchcancel", () => this.reset(), hasPassiveOption ? { passive: true } : false);
|
||||
});
|
||||
|
||||
let entryContentElement = document.querySelector(".entry-content");
|
||||
if (entryContentElement) {
|
||||
let doubleTapTimers = {
|
||||
previous: null,
|
||||
next: null
|
||||
};
|
||||
|
||||
const detectDoubleTap = (doubleTapTimer, event) => {
|
||||
const timer = doubleTapTimers[doubleTapTimer];
|
||||
if (timer === null) {
|
||||
doubleTapTimers[doubleTapTimer] = setTimeout(() => {
|
||||
doubleTapTimers[doubleTapTimer] = null;
|
||||
}, 200);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.navHandler.goToPage(doubleTapTimer);
|
||||
}
|
||||
};
|
||||
|
||||
entryContentElement.addEventListener("touchend", (e) => {
|
||||
if (e.changedTouches[0].clientX >= (entryContentElement.offsetWidth / 2)) {
|
||||
detectDoubleTap("next", e);
|
||||
} else {
|
||||
detectDoubleTap("previous", e);
|
||||
}
|
||||
}, hasPassiveOption ? { passive: false } : false);
|
||||
entryContentElement.addEventListener("touchmove", (e) => {
|
||||
Object.keys(doubleTapTimers).forEach(timer => doubleTapTimers[timer] = null);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue