diff options
Diffstat (limited to 'js/Headlines.js')
-rwxr-xr-x | js/Headlines.js | 327 |
1 files changed, 128 insertions, 199 deletions
diff --git a/js/Headlines.js b/js/Headlines.js index 540c400d3..8425dc980 100755 --- a/js/Headlines.js +++ b/js/Headlines.js @@ -8,6 +8,36 @@ define(["dojo/_base/declare"], function (declare) { headlines: [], current_first_id: 0, _scroll_reset_timeout: false, + default_force_previous: false, + default_force_to_top: false, + line_scroll_offset: 120, /* px */ + sticky_header_observer: new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + const header = entry.target.nextElementSibling; + + if (entry.intersectionRatio == 0) { + header.setAttribute("stuck", "1"); + + } else if (entry.intersectionRatio == 1) { + header.removeAttribute("stuck"); + } + + //console.log(entry.target, header, entry.intersectionRatio); + + }); + }, + {threshold: [0, 1], root: document.querySelector("#headlines-frame")} + ), + unpack_observer: new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (entry.intersectionRatio > 0) + Article.unpack(entry.target); + }); + }, + {threshold: [0], root: document.querySelector("#headlines-frame")} + ), row_observer: new MutationObserver((mutations) => { const modified = []; @@ -40,7 +70,6 @@ define(["dojo/_base/declare"], function (declare) { }); Headlines.updateSelectedPrompt(); - Headlines.updateFloatingTitle(true); if ('requestIdleCallback' in window) window.requestIdleCallback(() => { @@ -49,7 +78,7 @@ define(["dojo/_base/declare"], function (declare) { else Headlines.syncModified(modified); }), - syncModified: function(modified) { + syncModified: function (modified) { const ops = { tmark: [], tpub: [], @@ -62,7 +91,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); @@ -118,26 +147,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})); }); } @@ -177,15 +206,24 @@ define(["dojo/_base/declare"], function (declare) { } else if (Article.getActive() != id) { Headlines.select('none'); + + const scroll_position_A = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; + Article.setActive(id); if (App.getInitParam("cdm_expanded")) { + if (!in_body) Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else { - Article.cdmScrollToId(id); + const scroll_position_B = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; + + // this would only work if there's enough space + $("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B; + + Article.cdmMoveToId(id); } } else if (in_body) { @@ -245,36 +283,22 @@ define(["dojo/_base/declare"], function (declare) { Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true}); }, - isChildVisible: function (elem, ctr) { - const ctop = ctr.scrollTop; - const cbottom = ctop + ctr.offsetHeight; - - const etop = elem.offsetTop; - const ebottom = etop + elem.offsetHeight; - - return etop >= ctop && ebottom <= cbottom || - etop < ctop && ebottom > ctop || ebottom > cbottom && etop < cbottom - + isChildVisible: function (elem) { + return App.Scrollable.isChildVisible(elem, $("headlines-frame")); }, - firstVisible: function() { + firstVisible: function () { const rows = $$("#headlines-frame > div[id*=RROW]"); - const ctr = $("headlines-frame"); for (let i = 0; i < rows.length; i++) { const row = rows[i]; - if (this.isChildVisible(row, ctr)) { + if (this.isChildVisible(row)) { return row.getAttribute("data-article-id"); } } }, scrollHandler: function (/*event*/) { try { - Headlines.unpackVisible(); - - if (App.isCombinedMode()) - Headlines.updateFloatingTitle(); - if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { const hsp = $("headlines-spacer"); const container = $("headlines-frame"); @@ -311,82 +335,25 @@ define(["dojo/_base/declare"], function (declare) { console.warn("scrollHandler", e); } }, - updateFloatingTitle: function (status_only) { - if (!App.isCombinedMode()/* || !App.getInitParam("cdm_expanded")*/) return; - - const safety_offset = 120; /* px, needed for firefox */ - const hf = $("headlines-frame"); - const elems = $$("#headlines-frame > div[id*=RROW]"); - const ft = $("floatingTitle"); - - for (let i = 0; i < elems.length; i++) { - const row = elems[i]; - - if (row && row.offsetTop + row.offsetHeight > hf.scrollTop + safety_offset) { - - const header = row.select(".header")[0]; - const id = row.getAttribute("data-article-id"); - - if (status_only || id != ft.getAttribute("data-article-id")) { - if (id != ft.getAttribute("data-article-id")) { - - ft.setAttribute("data-article-id", id); - ft.innerHTML = header.innerHTML; - - ft.select(".dijitCheckBox")[0].outerHTML = "<i class=\"material-icons icon-anchor\" onclick=\"Article.cdmScrollToId(" + id + ", true)\">expand_more</i>"; - - this.initFloatingMenu(); - - } - - if (row.hasClassName("Unread")) - ft.addClassName("Unread"); - else - ft.removeClassName("Unread"); - - if (row.hasClassName("marked")) - ft.addClassName("marked"); - else - ft.removeClassName("marked"); - - if (row.hasClassName("published")) - ft.addClassName("published"); - else - ft.removeClassName("published"); - - PluginHost.run(PluginHost.HOOK_FLOATING_TITLE, row); - } - - if (hf.scrollTop - row.offsetTop <= header.offsetHeight + safety_offset) - ft.fade({duration: 0.2}); - else - ft.appear({duration: 0.2}); - - return; - } - } + objectById: function (id) { + return this.headlines[id]; }, - unpackVisible: function () { - if (!App.isCombinedMode() || !App.getInitParam("cdm_expanded")) return; + setCommonClasses: function () { + $("headlines-frame").removeClassName("cdm"); + $("headlines-frame").removeClassName("normal"); - const rows = $$("#headlines-frame div[id*=RROW][data-content]"); - const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600; + $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; + // for floating title because it's placed outside of headlines-frame + $("main").removeClassName("expandable"); + $("main").removeClassName("expanded"); - if (row.offsetTop <= threshold) { - Article.unpack(row); - } else { - break; - } - } - }, - objectById: function (id){ - return this.headlines[id]; + 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(); $$("#headlines-frame > div[id*=RROW]").each((row) => { const id = row.getAttribute("data-article-id"); @@ -399,19 +366,27 @@ define(["dojo/_base/declare"], function (declare) { if (hl.active) { new_row.addClassName("active"); + Article.unpack(new_row); if (App.isCombinedMode()) - Article.cdmScrollToId(id); + Article.cdmMoveToId(id, {noscroll: true}); else Article.view(id); } if (hl.selected) this.select("all", id); - - Article.unpack(new_row); - } }); + + $$(".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) + }); + }, render: function (headlines, hl) { let row = null; @@ -453,7 +428,7 @@ define(["dojo/_base/declare"], function (declare) { data-article-title="${escapeHtml(hl.title)}" onmouseover="Article.mouseIn(${hl.id})" onmouseout="Article.mouseOut(${hl.id})"> - + <div class="header-sticky-guard"></div> <div class="header"> <div class="left"> <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'> @@ -558,7 +533,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()); @@ -598,34 +573,18 @@ define(["dojo/_base/declare"], function (declare) { Feeds.infscroll_disabled = parseInt(headlines_count) != 30; console.log('infscroll_disabled=', Feeds.infscroll_disabled); - // TODO: the below needs to be applied again when switching expanded/expandable on the fly - // via hotkeys, not just on feed load - - $("headlines-frame").removeClassName("cdm"); - $("headlines-frame").removeClassName("normal"); - - $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); + // also called in renderAgain() after view mode switch + Headlines.setCommonClasses(); $("headlines-frame").setAttribute("is-vfeed", reply['headlines']['is_vfeed'] ? 1 : 0); - // for floating title because it's placed outside of headlines-frame - $("main").removeClassName("expandable"); - $("main").removeClassName("expanded"); - - if (App.isCombinedMode()) - $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable"); - Article.setActive(0); try { $("headlines-frame").removeClassName("smooth-scroll"); $("headlines-frame").scrollTop = 0; $("headlines-frame").addClassName("smooth-scroll"); - - Element.hide("floatingTitle"); - $("floatingTitle").setAttribute("data-article-id", 0); - $("floatingTitle").innerHTML = ""; } catch (e) { console.warn(e); } @@ -736,6 +695,15 @@ define(["dojo/_base/declare"], function (declare) { } } + $$(".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) + }); + } else { console.error("Invalid object received: " + transport.responseText); dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" + @@ -837,33 +805,30 @@ define(["dojo/_base/declare"], function (declare) { move: function (mode, params) { params = params || {}; - const noscroll = params.noscroll || false; - const noexpand = params.noexpand || false; - const event = params.event; - - const rows = Headlines.getLoaded(); + const no_expand = params.no_expand || false; + const force_previous = params.force_previous || this.default_force_previous; + const force_to_top = params.force_to_top || this.default_force_to_top; let prev_id = false; let next_id = false; + let current_id = Article.getActive(); - const active_row = $("RROW-" + Article.getActive()); - - 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; + if (!Headlines.isChildVisible($("RROW-" + current_id))) { + console.log('active article is obscured, resetting to first visible...'); + current_id = Headlines.firstVisible(); + prev_id = current_id; + next_id = current_id; } else { + const rows = Headlines.getLoaded(); + for (let i = 0; i < rows.length; i++) { - if (rows[i] == Article.getActive()) { + if (rows[i] == current_id) { // Account for adjacent identical article ids. if (i > 0) prev_id = rows[i - 1]; for (let j = i + 1; j < rows.length; j++) { - if (rows[j] != Article.getActive()) { + if (rows[j] != current_id) { next_id = rows[j]; break; } @@ -873,51 +838,39 @@ define(["dojo/_base/declare"], function (declare) { } } - console.log("cur: " + Article.getActive() + " next: " + next_id); + console.log("cur: " + current_id + " 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.scroll(ctr.offsetHeight / 2, event); - } else if (next_id) { + window.requestAnimationFrame(() => { Article.setActive(next_id); - Article.cdmScrollToId(next_id, true, event); - } - - } else if (next_id) { - Headlines.correctHeadlinesOffset(next_id); - Article.view(next_id, noexpand); + Article.cdmMoveToId(next_id, {force_to_top: force_to_top}); + }); + } else { + Article.view(next_id, no_expand); } } - } - - if (mode === "prev") { - if (prev_id || Article.getActive()) { + } else if (mode === "prev") { + if (prev_id || current_id) { if (App.isCombinedMode()) { + window.requestAnimationFrame(() => { + const row = $("RROW-" + current_id); + const ctr = $("headlines-frame"); + const delta_px = Math.round(row.offsetTop) - Math.round(ctr.scrollTop); - const row = $("RROW-" + Article.getActive()); - //const prev_row = $("RROW-" + prev_id); - const ctr = $("headlines-frame"); + console.log('moving back, delta_px', delta_px); - if (!noscroll) { - Article.scroll(-ctr.offsetHeight / 2, event); - } else { - if (row && Math.round(row.offsetTop) < Math.round(ctr.scrollTop)) { - Article.cdmScrollToId(Article.getActive(), noscroll, event); + if (!force_previous && row && delta_px < -8) { + Article.setActive(current_id); + Article.cdmMoveToId(current_id, {force_to_top: force_to_top}); } else if (prev_id) { Article.setActive(prev_id); - Article.cdmScrollToId(prev_id, noscroll, event); + Article.cdmMoveToId(prev_id, {force_to_top: force_to_top}); } - } - + }); } else if (prev_id) { - Headlines.correctHeadlinesOffset(prev_id); - Article.view(prev_id, noexpand); + Article.view(prev_id, no_expand); } } } @@ -1254,7 +1207,7 @@ define(["dojo/_base/declare"], function (declare) { eval(elem.value); elem.attr('value', 'false'); }, - correctHeadlinesOffset: function (id) { + scrollToArticleId: function (id) { const container = $("headlines-frame"); const row = $("RROW-" + id); @@ -1274,20 +1227,6 @@ define(["dojo/_base/declare"], function (declare) { container.scrollTop = row.offsetTop + row.offsetHeight - viewport; } }, - initFloatingMenu: function () { - if (!dijit.byId("floatingMenu")) { - - const menu = new dijit.Menu({ - id: "floatingMenu", - selector: ".hlMenuAttach", - targetNodeIds: ["floatingTitle"] - }); - - this.headlinesMenuCommon(menu); - - menu.startup(); - } - }, headlinesMenuCommon: function (menu) { menu.addChild(new dijit.MenuItem({ @@ -1416,21 +1355,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) { + App.Scrollable.scrollByPages($("headlines-frame"), page_offset); + }, + scroll: function (offset) { + App.Scrollable.scroll($("headlines-frame"), offset); }, initHeadlinesMenu: function () { if (!dijit.byId("headlinesMenu")) { |