'use strict'; /* global __, ngettext, Article, App */ /* global xhrPost, dojo, dijit, PluginHost, Notify, $$, Feeds */ /* global CommonDialogs */ const Headlines = { vgroup_last_feed: undefined, _headlines_scroll_timeout: 0, _observer_counters_timeout: 0, 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 = []; mutations.forEach((m) => { if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) { const row = m.target; const id = row.getAttribute("data-article-id"); if (Headlines.headlines[id]) { const hl = Headlines.headlines[id]; if (hl) { const hl_old = {...{}, ...hl}; hl.unread = row.hasClassName("Unread"); hl.marked = row.hasClassName("marked"); hl.published = row.hasClassName("published"); // not sent by backend hl.selected = row.hasClassName("Selected"); hl.active = row.hasClassName("active"); hl.score = row.getAttribute("data-score"); modified.push({id: hl.id, new: hl, old: hl_old, row: row}); } } } }); Headlines.updateSelectedPrompt(); if ('requestIdleCallback' in window) window.requestIdleCallback(() => { Headlines.syncModified(modified); }); else Headlines.syncModified(modified); }), syncModified: function (modified) { const ops = { tmark: [], tpub: [], read: [], unread: [], select: [], deselect: [], activate: [], deactivate: [], rescore: {}, }; modified.forEach(function (m) { if (m.old.marked != m.new.marked) ops.tmark.push(m.id); if (m.old.published != m.new.published) ops.tpub.push(m.id); if (m.old.unread != m.new.unread) m.new.unread ? ops.unread.push(m.id) : ops.read.push(m.id); if (m.old.selected != m.new.selected) m.new.selected ? ops.select.push(m.row) : ops.deselect.push(m.row); if (m.old.active != m.new.active) m.new.active ? ops.activate.push(m.row) : ops.deactivate.push(m.row); if (m.old.score != m.new.score) { const score = m.new.score; ops.rescore[score] = ops.rescore[score] || []; ops.rescore[score].push(m.id); } }); ops.select.forEach((row) => { const cb = dijit.getEnclosingWidget(row.querySelector(".rchk")); if (cb) cb.attr('checked', true); }); ops.deselect.forEach((row) => { const cb = dijit.getEnclosingWidget(row.querySelector(".rchk")); if (cb && !row.hasClassName("active")) cb.attr('checked', false); }); ops.activate.forEach((row) => { const cb = dijit.getEnclosingWidget(row.querySelector(".rchk")); if (cb) cb.attr('checked', true); }); ops.deactivate.forEach((row) => { const cb = dijit.getEnclosingWidget(row.querySelector(".rchk")); if (cb && !row.hasClassName("Selected")) cb.attr('checked', false); }); const promises = []; if (ops.tmark.length != 0) promises.push(xhrPost("backend.php", {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})); if (ops.read.length != 0) promises.push(xhrPost("backend.php", {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})); const scores = Object.keys(ops.rescore); if (scores.length != 0) { scores.forEach((score) => { promises.push(xhrPost("backend.php", {op: "article", method: "setScore", id: ops.rescore[score].toString(), score: score})); }); } if (promises.length > 0) Promise.all([promises]).then(() => { window.clearTimeout(this._observer_counters_timeout); this._observer_counters_timeout = setTimeout(() => { Feeds.requestCounters(true); }, 1000); }); }, click: function (event, id, in_body) { in_body = in_body || false; if (event.shiftKey && Article.getActive()) { Headlines.select('none'); const ids = Headlines.getRange(Article.getActive(), id); console.log(Article.getActive(), id, ids); for (let i = 0; i < ids.length; i++) Headlines.select('all', ids[i]); } else if (event.ctrlKey) { Headlines.select('invert', id); } else { // eslint-disable-next-line no-lonely-if if (App.isCombinedMode()) { if (event.altKey && !in_body) { Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else if (Article.getActive() != id) { Headlines.select('none'); const scroll_position_A = App.byId("RROW-" + id).offsetTop - App.byId("headlines-frame").scrollTop; Article.setActive(id); if (App.getInitParam("cdm_expanded")) { if (!in_body) Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else { const scroll_position_B = App.byId("RROW-" + id).offsetTop - App.byId("headlines-frame").scrollTop; // this would only work if there's enough space App.byId("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B; Article.cdmMoveToId(id); } } else if (in_body) { Headlines.toggleUnread(id, 0); } else { /* !in body */ Article.openInNewWindow(id); } return in_body; } else { // eslint-disable-next-line no-lonely-if if (event.altKey) { Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else { Headlines.select('none'); Article.view(id); } } } return false; }, initScrollHandler: function () { App.byId("headlines-frame").onscroll = (event) => { clearTimeout(this._headlines_scroll_timeout); this._headlines_scroll_timeout = window.setTimeout(function () { //console.log('done scrolling', event); Headlines.scrollHandler(event); }, 50); } }, loadMore: function () { const view_mode = document.forms["toolbar-main"].view_mode.value; const unread_in_buffer = App.findAll("#headlines-frame > div[id*=RROW][class*=Unread]").length; const num_all = App.findAll("#headlines-frame > div[id*=RROW]").length; const num_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat()); // TODO implement marked & published let offset = num_all; switch (view_mode) { case "marked": case "published": console.warn("loadMore: ", view_mode, "not implemented"); break; case "unread": offset = unread_in_buffer; break; case "adaptive": if (!(Feeds.getActive() == -1 && !Feeds.activeIsCat())) offset = num_unread > 0 ? unread_in_buffer : num_all; break; } console.log("loadMore, offset=", offset); Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true}); }, isChildVisible: function (elem) { return App.Scrollable.isChildVisible(elem, App.byId("headlines-frame")); }, firstVisible: function () { const rows = App.findAll("#headlines-frame > div[id*=RROW]"); for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (this.isChildVisible(row)) { return row.getAttribute("data-article-id"); } } }, unpackVisible: function(container) { const rows = App.findAll("#headlines-frame > div[id*=RROW][data-content].cdm"); for (let i = 0; i < rows.length; i++) { if (App.Scrollable.isChildVisible(rows[i], container)) { console.log('force unpacking:', rows[i].getAttribute('id')); Article.unpack(rows[i]); } } }, scrollHandler: function (/*event*/) { try { if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { const hsp = App.byId("headlines-spacer"); const container = App.byId("headlines-frame"); if (hsp && hsp.previousSibling) { const last_row = hsp.previousSibling; // invoke lazy load if last article in buffer is nearly visible OR is active if (Article.getActive() == last_row.getAttribute("data-article-id") || last_row.offsetTop - 250 <= container.scrollTop + container.offsetHeight) { hsp.innerHTML = " " + __("Loading, please wait...") + ""; Headlines.loadMore(); return; } } } if (App.isCombinedMode() && App.getInitParam("cdm_expanded")) { const container = App.byId("headlines-frame") /* don't do anything until there was some scrolling */ if (container.scrollTop > 0) Headlines.unpackVisible(container); } if (App.getInitParam("cdm_auto_catchup")) { const rows = App.findAll("#headlines-frame > div[id*=RROW][class*=Unread]"); for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (App.byId("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) { row.removeClassName("Unread"); } else { break; } } } } catch (e) { console.warn("scrollHandler", e); } }, objectById: function (id) { return this.headlines[id]; }, setCommonClasses: function () { App.byId("headlines-frame").removeClassName("cdm"); App.byId("headlines-frame").removeClassName("normal"); App.byId("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); // for floating title because it's placed outside of headlines-frame App.byId("main").removeClassName("expandable"); App.byId("main").removeClassName("expanded"); if (App.isCombinedMode()) App.byId("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable"); }, renderAgain: function () { // TODO: wrap headline elements into a knockoutjs model to prevent all this stuff Headlines.setCommonClasses(); App.findAll("#headlines-frame > div[id*=RROW]").forEach((row) => { const id = row.getAttribute("data-article-id"); const hl = this.headlines[id]; if (hl) { const new_row = this.render({}, hl); row.parentNode.replaceChild(new_row, row); if (hl.active) { new_row.addClassName("active"); Article.unpack(new_row); if (App.isCombinedMode()) Article.cdmMoveToId(id, {noscroll: true}); else Article.view(id); } if (hl.selected) this.select("all", id); } }); App.findAll(".cdm .header-sticky-guard").forEach((e) => { this.sticky_header_observer.observe(e) }); if (App.getInitParam("cdm_expanded")) App.findAll("#headlines-frame > div[id*=RROW].cdm").forEach((e) => { this.unpack_observer.observe(e) }); }, render: function (headlines, hl) { let row = null; let row_class = ""; if (hl.marked) row_class += " marked"; if (hl.published) row_class += " published"; if (hl.unread) row_class += " Unread"; if (headlines.vfeed_group_enabled) row_class += " vgrlf"; if (headlines.vfeed_group_enabled && hl.feed_title && this.vgroup_last_feed != hl.feed_id) { const vgrhdr = `
` const tmp = document.createElement("div"); tmp.innerHTML = vgrhdr; App.byId("headlines-frame").appendChild(tmp.firstChild); this.vgroup_last_feed = hl.feed_id; } if (App.isCombinedMode()) { row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable"; const comments = Article.formatComments(hl); row = `