Browse Source

implement keyboard-related changes discussed in https://community.tt-rss.org/t/changing-the-amount-of-scroll-by-arrow-key/3452/7

Andrew Dolgov 2 months ago
parent
commit
9ae9302b6b

+ 4 - 6
include/functions.php

@@ -1029,8 +1029,8 @@
 				"prev_feed" => __("Open previous feed"),
 				"next_article_or_scroll" => __("Open next article (in combined mode, scroll down)"),
 				"prev_article_or_scroll" => __("Open previous article (in combined mode, scroll up)"),
-				"next_article_page" => __("Scroll article by one page down"),
-				"prev_article_page" => __("Scroll article by one page up"),
+				"next_headlines_page" => __("Scroll headlines by one page down"),
+				"prev_headlines_page" => __("Scroll headlines by one page up"),
 				"next_article_noscroll" => __("Open next article"),
 				"prev_article_noscroll" => __("Open previous article"),
 				"next_article_noexpand" => __("Move to next article (don't expand)"),
@@ -1104,8 +1104,8 @@
 			"j" => "prev_feed",
 			"n" => "next_article_noscroll",
 			"p" => "prev_article_noscroll",
-			//"(33)|PageUp" => "prev_article_page",
-			//"(34)|PageDown" => "next_article_page",
+			"N" => "article_page_down",
+			"P" => "article_page_up",
 			"*(33)|Shift+PgUp" => "article_page_up",
 			"*(34)|Shift+PgDn" => "article_page_down",
 			"(38)|Up" => "prev_article_or_scroll",
@@ -1123,8 +1123,6 @@
 			"o" => "open_in_new_window",
 			"c p" => "catchup_below",
 			"c n" => "catchup_above",
-			"N" => "article_scroll_down",
-			"P" => "article_scroll_up",
 			"a W" => "toggle_widescreen",
 			"a e" => "toggle_full_text",
 			"e" => "email_article",

+ 27 - 0
js/AppBase.js

@@ -7,6 +7,33 @@ define(["dojo/_base/declare"], function (declare) {
 		hotkey_prefix: 0,
 		hotkey_prefix_pressed: false,
 		hotkey_prefix_timeout: 0,
+		Scrollable: {
+			scrollByPages: function (elem, page_offset, event) {
+				if (!elem) return;
+
+				/* keep a line or so from the previous page  */
+				const offset = (elem.offsetHeight - (page_offset > 0 ? 50 : -50)) * page_offset;
+
+				this.scroll(elem, offset, event);
+			},
+			scroll: function(elem, offset, event) {
+				if (!elem) return;
+
+				if (event && event.repeat) {
+					elem.addClassName("forbid-smooth-scroll");
+					window.clearTimeout(this._scroll_reset_timeout);
+
+					this._scroll_reset_timeout = window.setTimeout(() => {
+						if (elem) elem.removeClassName("forbid-smooth-scroll");
+					}, 250)
+
+				} else {
+					elem.removeClassName("forbid-smooth-scroll");
+				}
+
+				elem.scrollTop += offset;
+			},
+		},
 		constructor: function() {
 			window.onerror = this.Error.onWindowError;
 		},

+ 2 - 21
js/Article.js

@@ -349,29 +349,10 @@ define(["dojo/_base/declare"], function (declare) {
 				return 0;
 		},
 		scrollByPages: function (page_offset, event) {
-			const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
-			const offset = elem.offsetHeight * page_offset * 0.99;
-
-			this.scroll(offset, event);
+			App.Scrollable.scrollByPages($("content-insert"), page_offset, event);
 		},
 		scroll: function (offset, event) {
-
-			const elem = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
-			if (event && event.repeat) {
-				elem.addClassName("forbid-smooth-scroll");
-				window.clearTimeout(this._scroll_reset_timeout);
-
-				this._scroll_reset_timeout = window.setTimeout(() => {
-					if (elem) elem.removeClassName("forbid-smooth-scroll");
-				}, 250)
-
-			} else {
-				elem.removeClassName("forbid-smooth-scroll");
-			}
-
-			elem.scrollTop += offset;
+			App.Scrollable.scroll($("content-insert"), offset, event);
 		},
 		mouseIn: function (id) {
 			this.post_under_pointer = id;

+ 45 - 64
js/Headlines.js

@@ -8,6 +8,7 @@ define(["dojo/_base/declare"], function (declare) {
 		headlines: [],
 		current_first_id: 0,
 		_scroll_reset_timeout: false,
+		line_scroll_offset: 120, /* px */
 		sticky_header_observer: new IntersectionObserver(
 			(entries, observer) => {
 				entries.forEach((entry) => {
@@ -75,7 +76,7 @@ define(["dojo/_base/declare"], function (declare) {
 			else
 				Headlines.syncModified(modified);
 		}),
-		syncModified: function(modified) {
+		syncModified: function (modified) {
 			const ops = {
 				tmark: [],
 				tpub: [],
@@ -88,7 +89,7 @@ define(["dojo/_base/declare"], function (declare) {
 				rescore: {},
 			};
 
-			modified.each(function(m) {
+			modified.each(function (m) {
 				if (m.old.marked != m.new.marked)
 					ops.tmark.push(m.id);
 
@@ -144,26 +145,26 @@ define(["dojo/_base/declare"], function (declare) {
 
 			if (ops.tmark.length != 0)
 				promises.push(xhrPost("backend.php",
-					{ op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2}));
+					{op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2}));
 
 			if (ops.tpub.length != 0)
 				promises.push(xhrPost("backend.php",
-					{ op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2}));
+					{op: "rpc", method: "publishSelected", ids: ops.tpub.toString(), cmode: 2}));
 
 			if (ops.read.length != 0)
 				promises.push(xhrPost("backend.php",
-					{ op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0}));
+					{op: "rpc", method: "catchupSelected", ids: ops.read.toString(), cmode: 0}));
 
 			if (ops.unread.length != 0)
 				promises.push(xhrPost("backend.php",
-					{ op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1}));
+					{op: "rpc", method: "catchupSelected", ids: ops.unread.toString(), cmode: 1}));
 
 			const scores = Object.keys(ops.rescore);
 
 			if (scores.length != 0) {
 				scores.each((score) => {
 					promises.push(xhrPost("backend.php",
-						{ op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score }));
+						{op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score}));
 				});
 			}
 
@@ -282,7 +283,7 @@ define(["dojo/_base/declare"], function (declare) {
 				etop < ctop && ebottom > ctop || ebottom > cbottom && etop < cbottom
 
 		},
-		firstVisible: function() {
+		firstVisible: function () {
 			const rows = $$("#headlines-frame > div[id*=RROW]");
 			const ctr = $("headlines-frame");
 
@@ -332,10 +333,10 @@ define(["dojo/_base/declare"], function (declare) {
 				console.warn("scrollHandler", e);
 			}
 		},
-		objectById: function (id){
+		objectById: function (id) {
 			return this.headlines[id];
 		},
-		setCommonClasses: function() {
+		setCommonClasses: function () {
 			$("headlines-frame").removeClassName("cdm");
 			$("headlines-frame").removeClassName("normal");
 
@@ -348,7 +349,7 @@ define(["dojo/_base/declare"], function (declare) {
 			if (App.isCombinedMode())
 				$("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable");
 		},
-		renderAgain: function() {
+		renderAgain: function () {
 			// TODO: wrap headline elements into a knockoutjs model to prevent all this stuff
 			Headlines.setCommonClasses();
 
@@ -363,23 +364,26 @@ define(["dojo/_base/declare"], function (declare) {
 
 					if (hl.active) {
 						new_row.addClassName("active");
+						Article.unpack(new_row);
 
 						if (App.isCombinedMode())
 							Article.cdmMoveToId(id, {noscroll: true});
 						else
 							Article.view(id);
-
-						Article.unpack(row);
 					}
 
 					if (hl.selected) this.select("all", id);
 				}
 			});
 
-			$$(".cdm .header-sticky-guard").each((e) => { this.sticky_header_observer.observe(e) });
+			$$(".cdm .header-sticky-guard").each((e) => {
+				this.sticky_header_observer.observe(e)
+			});
 
 			if (App.getInitParam("cdm_expanded"))
-				$$("#headlines-frame > div[id*=RROW].cdm").each((e) => { this.unpack_observer.observe(e) });
+				$$("#headlines-frame > div[id*=RROW].cdm").each((e) => {
+					this.unpack_observer.observe(e)
+				});
 
 		},
 		render: function (headlines, hl) {
@@ -527,7 +531,7 @@ define(["dojo/_base/declare"], function (declare) {
 
 			return tmp.firstChild;
 		},
-		updateCurrentUnread: function() {
+		updateCurrentUnread: function () {
 			if ($("feed_current_unread")) {
 				const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat());
 
@@ -689,10 +693,14 @@ define(["dojo/_base/declare"], function (declare) {
 					}
 				}
 
-				$$(".cdm .header-sticky-guard").each((e) => { this.sticky_header_observer.observe(e) });
+				$$(".cdm .header-sticky-guard").each((e) => {
+					this.sticky_header_observer.observe(e)
+				});
 
 				if (App.getInitParam("cdm_expanded"))
-					$$("#headlines-frame > div[id*=RROW].cdm").each((e) => { this.unpack_observer.observe(e) });
+					$$("#headlines-frame > div[id*=RROW].cdm").each((e) => {
+						this.unpack_observer.observe(e)
+					});
 
 			} else {
 				console.error("Invalid object received: " + transport.responseText);
@@ -799,21 +807,20 @@ define(["dojo/_base/declare"], function (declare) {
 			const noexpand = params.noexpand || false;
 			const event = params.event;
 
-			const rows = Headlines.getLoaded();
-
 			let prev_id = false;
 			let next_id = false;
 
 			const active_row = $("RROW-" + Article.getActive());
 
-			if (!active_row) {
+			if (!active_row)
 				Article.setActive(0);
-			}
 
 			if (!Article.getActive() || (active_row && !Headlines.isChildVisible(active_row, $("headlines-frame")))) {
 				next_id = Headlines.firstVisible();
 				prev_id = next_id;
 			} else {
+				const rows = Headlines.getLoaded();
+
 				for (let i = 0; i < rows.length; i++) {
 					if (rows[i] == Article.getActive()) {
 
@@ -831,46 +838,30 @@ define(["dojo/_base/declare"], function (declare) {
 				}
 			}
 
-			console.log("cur: " + Article.getActive() + " next: " + next_id);
+			console.log("cur: " + Article.getActive() + " next: " + next_id + " prev:" + prev_id);
 
 			if (mode === "next") {
-				if (next_id || Article.getActive()) {
+				if (next_id) {
 					if (App.isCombinedMode()) {
-
-						//const row = $("RROW-" + Article.getActive());
-						const ctr = $("headlines-frame");
-
-						if (noscroll) {
-							Article.setActive(next_id);
-							Article.cdmMoveToId(next_id, { event: event, noscroll: noscroll });
-						} else if (next_id) {
-							Article.scroll(ctr.offsetHeight / 2, event);
-						}
-
-					} else if (next_id) {
+						Article.setActive(next_id);
+						Article.cdmMoveToId(next_id, {event: event, noscroll: noscroll});
+					} else {
 						Headlines.correctHeadlinesOffset(next_id);
 						Article.view(next_id, noexpand);
 					}
 				}
-			}
-
-			if (mode === "prev") {
+			} else if (mode === "prev") {
 				if (prev_id || Article.getActive()) {
 					if (App.isCombinedMode()) {
 
 						const row = $("RROW-" + Article.getActive());
-						//const prev_row = $("RROW-" + prev_id);
 						const ctr = $("headlines-frame");
 
-						if (noscroll) {
-							if (row && Math.round(row.offsetTop) < Math.round(ctr.scrollTop)) {
-								Article.cdmMoveToId(Article.getActive(), { force: noscroll, event: event });
-							} else if (prev_id) {
-								Article.setActive(prev_id);
-								Article.cdmMoveToId(prev_id, { force: noscroll, event: event, noscroll: noscroll });
-							}
-						} else {
-							Article.scroll(-ctr.offsetHeight / 2, event);
+						if (row && Math.round(row.offsetTop) < Math.round(ctr.scrollTop)) {
+							Article.cdmMoveToId(Article.getActive(), {force: noscroll, event: event});
+						} else if (prev_id) {
+							Article.setActive(prev_id);
+							Article.cdmMoveToId(prev_id, {force: noscroll, event: event, noscroll: noscroll});
 						}
 
 					} else if (prev_id) {
@@ -1360,21 +1351,11 @@ define(["dojo/_base/declare"], function (declare) {
 
 			}
 		},
-		scrollByPages: function (offset, event) {
-			const elem = $("headlines-frame");
-
-			if (event && event.repeat) {
-				elem.addClassName("forbid-smooth-scroll");
-				window.clearTimeout(this._scroll_reset_timeout);
-
-				this._scroll_reset_timeout = window.setTimeout(() => {
-					if (elem) elem.removeClassName("forbid-smooth-scroll");
-				}, 250)
-			} else {
-				elem.removeClassName("forbid-smooth-scroll");
-			}
-
-			elem.scrollTop += elem.offsetHeight * offset * 0.99;
+		scrollByPages: function (page_offset, event) {
+			App.Scrollable.scrollByPages($("headlines-frame"), page_offset, event);
+		},
+		scroll: function (offset, event) {
+			App.Scrollable.scroll($("headlines-frame"), offset, event);
 		},
 		initHeadlinesMenu: function () {
 			if (!dijit.byId("headlinesMenu")) {

+ 26 - 14
js/tt-rss.js

@@ -285,10 +285,16 @@ require(["dojo/_base/kernel",
 						if (rv) Feeds.open({feed: rv[0], is_cat: rv[1], delayed: true})
 					};
 					this.hotkey_actions["next_article_or_scroll"] = function (event) {
-						Headlines.move('next', {event: event});
+						if (App.isCombinedMode())
+							Headlines.scroll(Headlines.line_scroll_offset, event);
+						else
+							Headlines.move('next', {event: event});
 					};
 					this.hotkey_actions["prev_article_or_scroll"] = function (event) {
-						Headlines.move('prev', {event: event});
+						if (App.isCombinedMode())
+							Headlines.scroll(-Headlines.line_scroll_offset, event);
+						else
+							Headlines.move('prev', {event: event});
 					};
 					this.hotkey_actions["next_article_noscroll"] = function (event) {
 						Headlines.move('next', {noscroll: true, event: event});
@@ -335,28 +341,34 @@ require(["dojo/_base/kernel",
 						Headlines.catchupRelativeTo(0);
 					};
 					this.hotkey_actions["article_scroll_down"] = function (event) {
-						const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
-						if (ctr)
-							Article.scroll(ctr.offsetHeight / 2, event);
+						if (App.isCombinedMode())
+							Headlines.scroll(Headlines.line_scroll_offset, event);
+						else
+							Article.scroll(Headlines.line_scroll_offset, event);
 					};
 					this.hotkey_actions["article_scroll_up"] = function (event) {
-						const ctr = App.isCombinedMode() ? $("headlines-frame") : $("content-insert");
-
-						if (ctr)
-							Article.scroll(-ctr.offsetHeight / 2, event);
+						if (App.isCombinedMode())
+							Headlines.scroll(-Headlines.line_scroll_offset, event);
+						else
+							Article.scroll(-Headlines.line_scroll_offset, event);
 					};
-					this.hotkey_actions["next_article_page"] = function (event) {
+					this.hotkey_actions["next_headlines_page"] = function (event) {
 						Headlines.scrollByPages(1, event);
 					};
-					this.hotkey_actions["prev_article_page"] = function (event) {
+					this.hotkey_actions["prev_headlines_page"] = function (event) {
 						Headlines.scrollByPages(-1, event);
 					};
 					this.hotkey_actions["article_page_down"] = function (event) {
-						Article.scrollByPages(1, event);
+						if (App.isCombinedMode())
+							Headlines.scrollByPages(1, event);
+						else
+							Article.scrollByPages(1, event);
 					};
 					this.hotkey_actions["article_page_up"] = function (event) {
-						Article.scrollByPages(-1, event);
+						if (App.isCombinedMode())
+							Headlines.scrollByPages(-1, event);
+						else
+							Article.scrollByPages(-1, event);
 					};
 					this.hotkey_actions["close_article"] = function () {
 						if (App.isCombinedMode()) {

+ 1 - 1
plugins/hotkeys_noscroll/init.php

@@ -4,7 +4,7 @@ class Hotkeys_Noscroll extends Plugin {
 
 	function about() {
 		return array(1.0,
-			"n/p hotkeys move between articles without scrolling",
+			"n/p (and up/down) hotkeys move between articles without scrolling",
 			"fox");
 	}
 

File diff suppressed because it is too large
+ 0 - 0
themes/compact.css.map


File diff suppressed because it is too large
+ 0 - 0
themes/compact_night.css.map


File diff suppressed because it is too large
+ 0 - 0
themes/light.css.map


File diff suppressed because it is too large
+ 0 - 0
themes/night.css.map


File diff suppressed because it is too large
+ 0 - 0
themes/night_blue.css.map


Some files were not shown because too many files changed in this diff