diff options
Diffstat (limited to 'js/Headlines.js')
-rwxr-xr-x | js/Headlines.js | 2334 |
1 files changed, 1132 insertions, 1202 deletions
diff --git a/js/Headlines.js b/js/Headlines.js index 540c400d3..b98098c33 100755 --- a/js/Headlines.js +++ b/js/Headlines.js @@ -1,944 +1,760 @@ 'use strict'; -/* global __, ngettext */ -define(["dojo/_base/declare"], function (declare) { - Headlines = { - vgroup_last_feed: undefined, - _headlines_scroll_timeout: 0, - _observer_counters_timeout: 0, - headlines: [], - current_first_id: 0, - _scroll_reset_timeout: false, - row_observer: new MutationObserver((mutations) => { - const modified = []; - mutations.each((m) => { - if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) { +/* 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); - const row = m.target; - const id = row.getAttribute("data-article-id"); + }); + }, + {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 = []; - if (Headlines.headlines[id]) { - const hl = Headlines.headlines[id]; + mutations.each((m) => { + if (m.type == 'attributes' && ['class', 'data-score'].indexOf(m.attributeName) != -1) { - if (hl) { - const hl_old = Object.extend({}, hl); + const row = m.target; + const id = row.getAttribute("data-article-id"); - hl.unread = row.hasClassName("Unread"); - hl.marked = row.hasClassName("marked"); - hl.published = row.hasClassName("published"); + if (Headlines.headlines[id]) { + const hl = Headlines.headlines[id]; - // not sent by backend - hl.selected = row.hasClassName("Selected"); - hl.active = row.hasClassName("active"); + if (hl) { + const hl_old = Object.extend({}, hl); - hl.score = row.getAttribute("data-score"); + hl.unread = row.hasClassName("Unread"); + hl.marked = row.hasClassName("marked"); + hl.published = row.hasClassName("published"); - modified.push({id: hl.id, new: hl, old: hl_old, row: row}); - } + // 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(); - Headlines.updateFloatingTitle(true); + Headlines.updateSelectedPrompt(); - if ('requestIdleCallback' in window) - window.requestIdleCallback(() => { - Headlines.syncModified(modified); - }); - else + if ('requestIdleCallback' in window) + window.requestIdleCallback(() => { Headlines.syncModified(modified); - }), - syncModified: function(modified) { - const ops = { - tmark: [], - tpub: [], - read: [], - unread: [], - select: [], - deselect: [], - activate: [], - deactivate: [], - rescore: {}, - }; - - modified.each(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); - } }); + else + Headlines.syncModified(modified); + }), + syncModified: function (modified) { + const ops = { + tmark: [], + tpub: [], + read: [], + unread: [], + select: [], + deselect: [], + activate: [], + deactivate: [], + rescore: {}, + }; + + modified.each(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.each((row) => { - const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); + ops.select.each((row) => { + const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); - if (cb) - cb.attr('checked', true); - }); + if (cb) + cb.attr('checked', true); + }); - ops.deselect.each((row) => { - const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); + ops.deselect.each((row) => { + const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); - if (cb && !row.hasClassName("active")) - cb.attr('checked', false); - }); + if (cb && !row.hasClassName("active")) + cb.attr('checked', false); + }); - ops.activate.each((row) => { - const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); + ops.activate.each((row) => { + const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); - if (cb) - cb.attr('checked', true); - }); + if (cb) + cb.attr('checked', true); + }); - ops.deactivate.each((row) => { - const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); + ops.deactivate.each((row) => { + const cb = dijit.getEnclosingWidget(row.select(".rchk")[0]); - if (cb && !row.hasClassName("Selected")) - cb.attr('checked', false); - }); + if (cb && !row.hasClassName("Selected")) + cb.attr('checked', false); + }); - const promises = []; + const promises = []; - if (ops.tmark.length != 0) - promises.push(xhrPost("backend.php", - { op: "rpc", method: "markSelected", ids: ops.tmark.toString(), cmode: 2})); + 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.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.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})); + 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); + 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 })); - }); - } + if (scores.length != 0) { + scores.each((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); + if (promises.length > 0) + Promise.all([promises]).then(() => { + window.clearTimeout(this._observer_counters_timeout); - this._observer_counters_timeout = setTimeout(() => { - Feeds.requestCounters(true); - }, 1000); - }); + this._observer_counters_timeout = setTimeout(() => { + Feeds.requestCounters(true); + }, 1000); + }); - }, - click: function (event, id, in_body) { - in_body = in_body || false; + }, + click: function (event, id, in_body) { + in_body = in_body || false; - if (event.shiftKey && Article.getActive()) { - Headlines.select('none'); + if (event.shiftKey && Article.getActive()) { + Headlines.select('none'); - const ids = Headlines.getRange(Article.getActive(), id); + const ids = Headlines.getRange(Article.getActive(), id); - console.log(Article.getActive(), id, ids); + console.log(Article.getActive(), id, ids); - for (let i = 0; i < ids.length; i++) - Headlines.select('all', ids[i]); + for (let i = 0; i < ids.length; i++) + Headlines.select('all', ids[i]); - } else if (event.ctrlKey) { - Headlines.select('invert', id); - } else { - if (App.isCombinedMode()) { + } else if (event.ctrlKey) { + Headlines.select('invert', id); + } else { + if (App.isCombinedMode()) { - if (event.altKey && !in_body) { + if (event.altKey && !in_body) { - Article.openInNewWindow(id); - Headlines.toggleUnread(id, 0); + Article.openInNewWindow(id); + Headlines.toggleUnread(id, 0); - } else if (Article.getActive() != id) { + } else if (Article.getActive() != id) { - Headlines.select('none'); - Article.setActive(id); + Headlines.select('none'); - if (App.getInitParam("cdm_expanded")) { - if (!in_body) - Article.openInNewWindow(id); + const scroll_position_A = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; - Headlines.toggleUnread(id, 0); - } else { - Article.cdmScrollToId(id); - } + Article.setActive(id); - } else if (in_body) { - Headlines.toggleUnread(id, 0); - } else { /* !in body */ - Article.openInNewWindow(id); - } + if (App.getInitParam("cdm_expanded")) { + + if (!in_body) + Article.openInNewWindow(id); - return in_body; - } else { - if (event.altKey) { - Article.openInNewWindow(id); Headlines.toggleUnread(id, 0); } else { - Headlines.select('none'); - Article.view(id); - } - } - } - - return false; - }, - initScrollHandler: function () { - $("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 = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length; - const num_all = $$("#headlines-frame > div[id*=RROW]").length; - const num_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat()); + const scroll_position_B = $("RROW-" + id).offsetTop - $("headlines-frame").scrollTop; - // TODO implement marked & published + // this would only work if there's enough space + $("headlines-frame").scrollTop -= scroll_position_A-scroll_position_B; - 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, 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 - - }, - firstVisible: function() { - const rows = $$("#headlines-frame > div[id*=RROW]"); - const ctr = $("headlines-frame"); + Article.cdmMoveToId(id); + } - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; + } else if (in_body) { + Headlines.toggleUnread(id, 0); + } else { /* !in body */ + Article.openInNewWindow(id); + } - if (this.isChildVisible(row, ctr)) { - return row.getAttribute("data-article-id"); + return in_body; + } else { + if (event.altKey) { + Article.openInNewWindow(id); + Headlines.toggleUnread(id, 0); + } else { + Headlines.select('none'); + Article.view(id); } } - }, - scrollHandler: function (/*event*/) { - try { - Headlines.unpackVisible(); + } - if (App.isCombinedMode()) - Headlines.updateFloatingTitle(); + return false; + }, + initScrollHandler: function () { + $("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 = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length; + const num_all = $$("#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; + } - if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { - const hsp = $("headlines-spacer"); - const container = $("headlines-frame"); + console.log("loadMore, offset=", offset); - if (hsp && hsp.previousSibling) { - const last_row = hsp.previousSibling; + Feeds.open({feed: Feeds.getActive(), is_cat: Feeds.activeIsCat(), offset: offset, append: true}); + }, + isChildVisible: function (elem) { + return App.Scrollable.isChildVisible(elem, $("headlines-frame")); + }, + firstVisible: function () { + const rows = $$("#headlines-frame > div[id*=RROW]"); - // 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 = "<span class='loading'><img src='images/indicator_tiny.gif'> " + - __("Loading, please wait...") + "</span>"; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; - Headlines.loadMore(); - return; - } + if (this.isChildVisible(row)) { + return row.getAttribute("data-article-id"); + } + } + }, + scrollHandler: function (/*event*/) { + try { + if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { + const hsp = $("headlines-spacer"); + const container = $("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 = "<span class='loading'><img src='images/indicator_tiny.gif'> " + + __("Loading, please wait...") + "</span>"; + + Headlines.loadMore(); + return; } } + } - if (App.getInitParam("cdm_auto_catchup")) { + if (App.getInitParam("cdm_auto_catchup")) { - let rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]"); + const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]"); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; - if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) { - row.removeClassName("Unread"); - } else { - break; - } + if ($("headlines-frame").scrollTop > (row.offsetTop + row.offsetHeight / 2)) { + row.removeClassName("Unread"); + } else { + break; } } - } catch (e) { - 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); - } + } catch (e) { + console.warn("scrollHandler", e); + } + }, + objectById: function (id) { + return this.headlines[id]; + }, + setCommonClasses: function () { + $("headlines-frame").removeClassName("cdm"); + $("headlines-frame").removeClassName("normal"); + + $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); + + // 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"); + }, + 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"); + 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 (hf.scrollTop - row.offsetTop <= header.offsetHeight + safety_offset) - ft.fade({duration: 0.2}); + if (App.isCombinedMode()) + Article.cdmMoveToId(id, {noscroll: true}); else - ft.appear({duration: 0.2}); - - return; + Article.view(id); } + + if (hl.selected) this.select("all", id); } - }, - unpackVisible: function () { - if (!App.isCombinedMode() || !App.getInitParam("cdm_expanded")) return; + }); - const rows = $$("#headlines-frame div[id*=RROW][data-content]"); - const threshold = $("headlines-frame").scrollTop + $("headlines-frame").offsetHeight + 600; + $$(".cdm .header-sticky-guard").each((e) => { + this.sticky_header_observer.observe(e) + }); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; + if (App.getInitParam("cdm_expanded")) + $$("#headlines-frame > div[id*=RROW].cdm").each((e) => { + this.unpack_observer.observe(e) + }); - if (row.offsetTop <= threshold) { - Article.unpack(row); - } else { - break; - } - } - }, - objectById: function (id){ - return this.headlines[id]; - }, - renderAgain: function() { - // TODO: wrap headline elements into a knockoutjs model to prevent all this stuff + }, + render: function (headlines, hl) { + let row = null; - $$("#headlines-frame > div[id*=RROW]").each((row) => { - const id = row.getAttribute("data-article-id"); - const hl = this.headlines[id]; + let row_class = ""; - if (hl) { - const new_row = this.render({}, hl); + 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"; - row.parentNode.replaceChild(new_row, row); + if (headlines.vfeed_group_enabled && hl.feed_title && this.vgroup_last_feed != hl.feed_id) { + const vgrhdr = `<div data-feed-id='${hl.feed_id}' class='feed-title'> + <div style='float : right'>${hl.feed_icon}</div> + <a class="title" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title} + <a class="catchup" title="${__('mark feed as read')}" onclick="Feeds.catchupFeedInGroup(${hl.feed_id})" href="#"><i class="icon-done material-icons">done_all</i></a> + </div>` - if (hl.active) { - new_row.addClassName("active"); - - if (App.isCombinedMode()) - Article.cdmScrollToId(id); - else - Article.view(id); - } + const tmp = document.createElement("div"); + tmp.innerHTML = vgrhdr; - if (hl.selected) this.select("all", id); + $("headlines-frame").appendChild(tmp.firstChild); - Article.unpack(new_row); + this.vgroup_last_feed = hl.feed_id; + } - } - }); - }, - render: function (headlines, hl) { - let row = null; + if (App.isCombinedMode()) { + row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable"; + + const comments = Article.formatComments(hl); + const originally_from = Article.formatOriginallyFrom(hl); + + row = `<div class="cdm ${row_class} ${Article.getScoreClass(hl.score)}" + id="RROW-${hl.id}" + data-article-id="${hl.id}" + data-orig-feed-id="${hl.feed_id}" + data-content="${App.escapeHtml(hl.content)}" + data-score="${hl.score}" + data-article-title="${App.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'> + <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i> + <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i> + </div> - let row_class = ""; + <span onclick="return Headlines.click(event, ${hl.id});" data-article-id="${hl.id}" class="titleWrap hlMenuAttach"> + <a class="title" title="${App.escapeHtml(hl.title)}" target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.link)}"> + ${hl.title}</a> + <span class="author">${hl.author}</span> + ${hl.labels} + ${hl.cdm_excerpt ? hl.cdm_excerpt : ""} + </span> + + <div class="feed"> + <a href="#" style="background-color: ${hl.feed_bg_color}" + onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a> + </div> - 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"; + <span class="updated" title="${hl.imported}">${hl.updated}</span> - if (headlines.vfeed_group_enabled && hl.feed_title && this.vgroup_last_feed != hl.feed_id) { - let vgrhdr = `<div data-feed-id='${hl.feed_id}' class='feed-title'> - <div style='float : right'>${hl.feed_icon}</div> - <a class="title" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title} - <a class="catchup" title="${__('mark feed as read')}" onclick="Feeds.catchupFeedInGroup(${hl.feed_id})" href="#"><i class="icon-done material-icons">done_all</i></a> - </div>` + <div class="right"> + <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i> - const tmp = document.createElement("div"); - tmp.innerHTML = vgrhdr; + <span style="cursor : pointer" title="${App.escapeHtml(hl.feed_title)}" onclick="Feeds.open({feed:${hl.feed_id}})"> + ${hl.feed_icon}</span> + </div> - $("headlines-frame").appendChild(tmp.firstChild); + </div> - this.vgroup_last_feed = hl.feed_id; - } + <div class="content" onclick="return Headlines.click(event, ${hl.id}, true);"> + <div id="POSTNOTE-${hl.id}">${hl.note}</div> + <div class="content-inner" lang="${hl.lang ? hl.lang : 'en'}"> + <img src="${App.getInitParam('icon_indicator_white')}"> + </div> + <div class="intermediate"> + ${hl.enclosures} + </div> + <div class="footer" onclick="event.stopPropagation()"> - if (App.isCombinedMode()) { - row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable"; - - const comments = Article.formatComments(hl); - const originally_from = Article.formatOriginallyFrom(hl); - - row = `<div class="cdm ${row_class} ${Article.getScoreClass(hl.score)}" - id="RROW-${hl.id}" - data-article-id="${hl.id}" - data-orig-feed-id="${hl.feed_id}" - data-content="${escapeHtml(hl.content)}" - data-score="${hl.score}" - data-article-title="${escapeHtml(hl.title)}" - onmouseover="Article.mouseIn(${hl.id})" - onmouseout="Article.mouseOut(${hl.id})"> - - <div class="header"> <div class="left"> - <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'> - <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i> - <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i> + ${hl.buttons_left} + <i class="material-icons">label_outline</i> + <span id="ATSTR-${hl.id}">${hl.tags_str}</span> + <a title="${__("Edit tags for this article")}" href="#" + onclick="Article.editTags(${hl.id})">(+)</a> + ${comments} </div> - - <span onclick="return Headlines.click(event, ${hl.id});" data-article-id="${hl.id}" class="titleWrap hlMenuAttach"> - <a class="title" title="${escapeHtml(hl.title)}" target="_blank" rel="noopener noreferrer" href="${escapeHtml(hl.link)}"> - ${hl.title}</a> - <span class="author">${hl.author}</span> - ${hl.labels} - ${hl.cdm_excerpt ? hl.cdm_excerpt : ""} - </span> - - <div class="feed"> - <a href="#" style="background-color: ${hl.feed_bg_color}" - onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a> - </div> - - <span class="updated" title="${hl.imported}">${hl.updated}</span> - - <div class="right"> - <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i> - - <span style="cursor : pointer" title="${escapeHtml(hl.feed_title)}" onclick="Feeds.open({feed:${hl.feed_id}})"> - ${hl.feed_icon}</span> - </div> - - </div> - - <div class="content" onclick="return Headlines.click(event, ${hl.id}, true);"> - <div id="POSTNOTE-${hl.id}">${hl.note}</div> - <div class="content-inner" lang="${hl.lang ? hl.lang : 'en'}"> - <img src="${App.getInitParam('icon_indicator_white')}"> - </div> - <div class="intermediate"> - ${hl.enclosures} - </div> - <div class="footer" onclick="event.stopPropagation()"> - - <div class="left"> - ${hl.buttons_left} - <i class="material-icons">label_outline</i> - <span id="ATSTR-${hl.id}">${hl.tags_str}</span> - <a title="${__("Edit tags for this article")}" href="#" - onclick="Article.editTags(${hl.id})">(+)</a> - ${comments} - </div> - - <div class="right"> - ${originally_from} - ${hl.buttons} - </div> + + <div class="right"> + ${originally_from} + ${hl.buttons} </div> </div> - </div>`; - - - } else { - row = `<div class="hl ${row_class} ${Article.getScoreClass(hl.score)}" - id="RROW-${hl.id}" - data-orig-feed-id="${hl.feed_id}" - data-article-id="${hl.id}" - data-score="${hl.score}" - data-article-title="${escapeHtml(hl.title)}" - onmouseover="Article.mouseIn(${hl.id})" - onmouseout="Article.mouseOut(${hl.id})"> - <div class="left"> - <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'> - <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i> - <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i> - </div> - <div onclick="return Headlines.click(event, ${hl.id})" class="title"> - <span data-article-id="${hl.id}" class="hl-content hlMenuAttach"> - <a class="title" href="${escapeHtml(hl.link)}">${hl.title} <span class="preview">${hl.content_preview}</span></a> - <span class="author">${hl.author}</span> - ${hl.labels} + </div> + </div>`; + + + } else { + row = `<div class="hl ${row_class} ${Article.getScoreClass(hl.score)}" + id="RROW-${hl.id}" + data-orig-feed-id="${hl.feed_id}" + data-article-id="${hl.id}" + data-score="${hl.score}" + data-article-title="${App.escapeHtml(hl.title)}" + onmouseover="Article.mouseIn(${hl.id})" + onmouseout="Article.mouseOut(${hl.id})"> + <div class="left"> + <input dojoType="dijit.form.CheckBox" type="checkbox" onclick="Headlines.onRowChecked(this)" class='rchk'> + <i class="marked-pic marked-${hl.id} material-icons" onclick="Headlines.toggleMark(${hl.id})">star</i> + <i class="pub-pic pub-${hl.id} material-icons" onclick="Headlines.togglePub(${hl.id})">rss_feed</i> + </div> + <div onclick="return Headlines.click(event, ${hl.id})" class="title"> + <span data-article-id="${hl.id}" class="hl-content hlMenuAttach"> + <a class="title" href="${App.escapeHtml(hl.link)}">${hl.title} <span class="preview">${hl.content_preview}</span></a> + <span class="author">${hl.author}</span> + ${hl.labels} + </span> + </div> + <span class="feed"> + <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a> </span> - </div> - <span class="feed"> - <a style="background : ${hl.feed_bg_color}" href="#" onclick="Feeds.open({feed:${hl.feed_id}})">${hl.feed_title}</a> - </span> - <div title="${hl.imported}"> - <span class="updated">${hl.updated}</span> - </div> - <div class="right"> - <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i> - <span onclick="Feeds.open({feed:${hl.feed_id}})" style="cursor : pointer" title="${escapeHtml(hl.feed_title)}">${hl.feed_icon}</span> - </div> - </div> - `; - } + <div title="${hl.imported}"> + <span class="updated">${hl.updated}</span> + </div> + <div class="right"> + <i class="material-icons icon-score" title="${hl.score}" onclick="Article.setScore(${hl.id}, this)">${Article.getScorePic(hl.score)}</i> + <span onclick="Feeds.open({feed:${hl.feed_id}})" style="cursor : pointer" title="${App.escapeHtml(hl.feed_title)}">${hl.feed_icon}</span> + </div> + </div> + `; + } - const tmp = document.createElement("div"); - tmp.innerHTML = row; - dojo.parser.parse(tmp); + const tmp = document.createElement("div"); + tmp.innerHTML = row; + dojo.parser.parse(tmp); - this.row_observer.observe(tmp.firstChild, {attributes: true}); + this.row_observer.observe(tmp.firstChild, {attributes: true}); - PluginHost.run(PluginHost.HOOK_HEADLINE_RENDERED, tmp.firstChild); + PluginHost.run(PluginHost.HOOK_HEADLINE_RENDERED, tmp.firstChild); - return tmp.firstChild; - }, - updateCurrentUnread: function() { - if ($("feed_current_unread")) { - const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat()); + return tmp.firstChild; + }, + updateCurrentUnread: function () { + if ($("feed_current_unread")) { + const feed_unread = Feeds.getUnread(Feeds.getActive(), Feeds.activeIsCat()); - if (feed_unread > 0 && !Element.visible("feeds-holder")) { - $("feed_current_unread").innerText = feed_unread; - Element.show("feed_current_unread"); - } else { - Element.hide("feed_current_unread"); - } + if (feed_unread > 0 && !Element.visible("feeds-holder")) { + $("feed_current_unread").innerText = feed_unread; + Element.show("feed_current_unread"); + } else { + Element.hide("feed_current_unread"); } - }, - onLoaded: function (transport, offset, append) { - const reply = App.handleRpcJson(transport); - - console.log("Headlines.onLoaded: offset=", offset, "append=", append); - - let is_cat = false; - let feed_id = false; - - if (reply) { - - is_cat = reply['headlines']['is_cat']; - feed_id = reply['headlines']['id']; - Feeds.last_search_query = reply['headlines']['search_query']; - - if (feed_id != -7 && (feed_id != Feeds.getActive() || is_cat != Feeds.activeIsCat())) - return; - - const headlines_count = reply['headlines-info']['count']; - - //this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed']; - this.current_first_id = reply['headlines']['first_id']; - - console.log('received', headlines_count, 'headlines'); - - if (!append) { - 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 + } + }, + onLoaded: function (transport, offset, append) { + const reply = App.handleRpcJson(transport); - $("headlines-frame").removeClassName("cdm"); - $("headlines-frame").removeClassName("normal"); + console.log("Headlines.onLoaded: offset=", offset, "append=", append); - $("headlines-frame").addClassName(App.isCombinedMode() ? "cdm" : "normal"); + let is_cat = false; + let feed_id = false; - $("headlines-frame").setAttribute("is-vfeed", - reply['headlines']['is_vfeed'] ? 1 : 0); + if (reply) { - // for floating title because it's placed outside of headlines-frame - $("main").removeClassName("expandable"); - $("main").removeClassName("expanded"); + is_cat = reply['headlines']['is_cat']; + feed_id = reply['headlines']['id']; + Feeds.last_search_query = reply['headlines']['search_query']; - if (App.isCombinedMode()) - $("main").addClassName(App.getInitParam("cdm_expanded") ? " expanded" : " expandable"); + if (feed_id != -7 && (feed_id != Feeds.getActive() || is_cat != Feeds.activeIsCat())) + return; - Article.setActive(0); + const headlines_count = reply['headlines-info']['count']; - try { - $("headlines-frame").removeClassName("smooth-scroll"); - $("headlines-frame").scrollTop = 0; - $("headlines-frame").addClassName("smooth-scroll"); + //this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed']; + this.current_first_id = reply['headlines']['first_id']; - Element.hide("floatingTitle"); - $("floatingTitle").setAttribute("data-article-id", 0); - $("floatingTitle").innerHTML = ""; - } catch (e) { - console.warn(e); - } + console.log('received', headlines_count, 'headlines'); - this.headlines = []; - this.vgroup_last_feed = undefined; + if (!append) { + Feeds.infscroll_disabled = parseInt(headlines_count) != 30; + console.log('infscroll_disabled=', Feeds.infscroll_disabled); - dojo.html.set($("toolbar-headlines"), - reply['headlines']['toolbar'], - {parseContent: true}); + // also called in renderAgain() after view mode switch + Headlines.setCommonClasses(); - if (typeof reply['headlines']['content'] == 'string') { - $("headlines-frame").innerHTML = reply['headlines']['content']; - } else { - $("headlines-frame").innerHTML = ''; + $("headlines-frame").setAttribute("is-vfeed", + reply['headlines']['is_vfeed'] ? 1 : 0); - for (let i = 0; i < reply['headlines']['content'].length; i++) { - const hl = reply['headlines']['content'][i]; - - $("headlines-frame").appendChild(this.render(reply['headlines'], hl)); + Article.setActive(0); - this.headlines[parseInt(hl.id)] = hl; - } - } + try { + $("headlines-frame").removeClassName("smooth-scroll"); + $("headlines-frame").scrollTop = 0; + $("headlines-frame").addClassName("smooth-scroll"); + } catch (e) { + console.warn(e); + } - let hsp = $("headlines-spacer"); + this.headlines = []; + this.vgroup_last_feed = undefined; - if (!hsp) { - hsp = document.createElement("div"); - hsp.id = "headlines-spacer"; - } + dojo.html.set($("toolbar-headlines"), + reply['headlines']['toolbar'], + {parseContent: true}); - dijit.byId('headlines-frame').domNode.appendChild(hsp); + if (typeof reply['headlines']['content'] == 'string') { + $("headlines-frame").innerHTML = reply['headlines']['content']; + } else { + $("headlines-frame").innerHTML = ''; - this.initHeadlinesMenu(); + for (let i = 0; i < reply['headlines']['content'].length; i++) { + const hl = reply['headlines']['content'][i]; - if (Feeds.infscroll_disabled) - hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + - __("Click to open next unread feed.") + "</a>"; + $("headlines-frame").appendChild(this.render(reply['headlines'], hl)); - if (Feeds._search_query) { - $("feed_title").innerHTML += "<span id='cancel_search'>" + - " (<a href='#' onclick='Feeds.cancelSearch()'>" + __("Cancel search") + "</a>)" + - "</span>"; + this.headlines[parseInt(hl.id)] = hl; } + } - Headlines.updateCurrentUnread(); - - } else if (headlines_count > 0 && feed_id == Feeds.getActive() && is_cat == Feeds.activeIsCat()) { - const c = dijit.byId("headlines-frame"); - - let hsp = $("headlines-spacer"); + let hsp = $("headlines-spacer"); - if (hsp) - c.domNode.removeChild(hsp); + if (!hsp) { + hsp = document.createElement("div"); + hsp.id = "headlines-spacer"; + } - let headlines_appended = 0; + dijit.byId('headlines-frame').domNode.appendChild(hsp); - if (typeof reply['headlines']['content'] == 'string') { - $("headlines-frame").innerHTML = reply['headlines']['content']; - } else { - for (let i = 0; i < reply['headlines']['content'].length; i++) { - const hl = reply['headlines']['content'][i]; + this.initHeadlinesMenu(); - if (!this.headlines[parseInt(hl.id)]) { - $("headlines-frame").appendChild(this.render(reply['headlines'], hl)); + if (Feeds.infscroll_disabled) + hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + + __("Click to open next unread feed.") + "</a>"; - this.headlines[parseInt(hl.id)] = hl; - ++headlines_appended; - } - } - } - - Feeds.infscroll_disabled = headlines_appended == 0; + if (Feeds._search_query) { + $("feed_title").innerHTML += "<span id='cancel_search'>" + + " (<a href='#' onclick='Feeds.cancelSearch()'>" + __("Cancel search") + "</a>)" + + "</span>"; + } - console.log('appended', headlines_appended, 'headlines, infscroll_disabled=', Feeds.infscroll_disabled); + Headlines.updateCurrentUnread(); - if (!hsp) { - hsp = document.createElement("div"); - hsp.id = "headlines-spacer"; - } + } else if (headlines_count > 0 && feed_id == Feeds.getActive() && is_cat == Feeds.activeIsCat()) { + const c = dijit.byId("headlines-frame"); - c.domNode.appendChild(hsp); + let hsp = $("headlines-spacer"); - this.initHeadlinesMenu(); + if (hsp) + c.domNode.removeChild(hsp); - if (Feeds.infscroll_disabled) { - hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + - __("Click to open next unread feed.") + "</a>"; - } + let headlines_appended = 0; + if (typeof reply['headlines']['content'] == 'string') { + $("headlines-frame").innerHTML = reply['headlines']['content']; } else { - Feeds.infscroll_disabled = true; - const first_id_changed = reply['headlines']['first_id_changed']; + for (let i = 0; i < reply['headlines']['content'].length; i++) { + const hl = reply['headlines']['content'][i]; - console.log("no headlines received, infscroll_disabled=", Feeds.infscroll_disabled, 'first_id_changed=', first_id_changed); - - let hsp = $("headlines-spacer"); + if (!this.headlines[parseInt(hl.id)]) { + $("headlines-frame").appendChild(this.render(reply['headlines'], hl)); - if (hsp) { - if (first_id_changed) { - hsp.innerHTML = "<a href='#' onclick='Feeds.reloadCurrent()'>" + - __("New articles found, reload feed to continue.") + "</a>"; - } else { - hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + - __("Click to open next unread feed.") + "</a>"; + this.headlines[parseInt(hl.id)] = hl; + ++headlines_appended; } } } - } else { - console.error("Invalid object received: " + transport.responseText); - dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" + - __('Could not update headlines (invalid object received - see error console for details)') + - "</div>"); - } - - Feeds.infscroll_in_progress = 0; + Feeds.infscroll_disabled = headlines_appended == 0; - // this is used to auto-catchup articles if needed after infscroll request has finished, - // unpack visible articles, fill buffer more, etc - this.scrollHandler(); + console.log('appended', headlines_appended, 'headlines, infscroll_disabled=', Feeds.infscroll_disabled); - Notify.close(); - }, - reverse: function () { - const toolbar = document.forms["toolbar-main"]; - const order_by = dijit.getEnclosingWidget(toolbar.order_by); - - let value = order_by.attr('value'); - - if (value != "date_reverse") - value = "date_reverse"; - else - value = "default"; + if (!hsp) { + hsp = document.createElement("div"); + hsp.id = "headlines-spacer"; + } - order_by.attr('value', value); + c.domNode.appendChild(hsp); - Feeds.reloadCurrent(); - }, - selectionToggleUnread: function (params) { - params = params || {}; + this.initHeadlinesMenu(); - const cmode = params.cmode != undefined ? params.cmode : 2; - const no_error = params.no_error || false; - const ids = params.ids || Headlines.getSelected(); + if (Feeds.infscroll_disabled) { + hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + + __("Click to open next unread feed.") + "</a>"; + } - if (ids.length == 0) { - if (!no_error) - alert(__("No articles selected.")); + } else { + Feeds.infscroll_disabled = true; + const first_id_changed = reply['headlines']['first_id_changed']; - return; - } + console.log("no headlines received, infscroll_disabled=", Feeds.infscroll_disabled, 'first_id_changed=', first_id_changed); - ids.each((id) => { - const row = $("RROW-" + id); + const hsp = $("headlines-spacer"); - if (row) { - switch (cmode) { - case 0: - row.removeClassName("Unread"); - break; - case 1: - row.addClassName("Unread"); - break; - case 2: - row.toggleClassName("Unread"); + if (hsp) { + if (first_id_changed) { + hsp.innerHTML = "<a href='#' onclick='Feeds.reloadCurrent()'>" + + __("New articles found, reload feed to continue.") + "</a>"; + } else { + hsp.innerHTML = "<a href='#' onclick='Feeds.openNextUnread()'>" + + __("Click to open next unread feed.") + "</a>"; } } - }); - }, - selectionToggleMarked: function (ids) { - ids = ids || Headlines.getSelected(); - - if (ids.length == 0) { - alert(__("No articles selected.")); - return; } - ids.each((id) => { - this.toggleMark(id); + $$(".cdm .header-sticky-guard").each((e) => { + this.sticky_header_observer.observe(e) }); - }, - selectionTogglePublished: function (ids) { - ids = ids || Headlines.getSelected(); - - if (ids.length == 0) { - alert(__("No articles selected.")); - return; - } - - ids.each((id) => { - this.togglePub(id); - }); - }, - toggleMark: function (id) { - const row = $("RROW-" + id); - - if (row) - row.toggleClassName("marked"); - - }, - togglePub: function (id) { - const row = $("RROW-" + id); - - if (row) - row.toggleClassName("published"); - }, - move: function (mode, params) { - params = params || {}; - - const noscroll = params.noscroll || false; - 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) { - Article.setActive(0); - } - - if (!Article.getActive() || (active_row && !Headlines.isChildVisible(active_row, $("headlines-frame")))) { - next_id = Headlines.firstVisible(); - prev_id = next_id; - } else { - for (let i = 0; i < rows.length; i++) { - if (rows[i] == Article.getActive()) { - - // Account for adjacent identical article ids. - if (i > 0) prev_id = rows[i - 1]; + if (App.getInitParam("cdm_expanded")) + $$("#headlines-frame > div[id*=RROW].cdm").each((e) => { + this.unpack_observer.observe(e) + }); - for (let j = i + 1; j < rows.length; j++) { - if (rows[j] != Article.getActive()) { - next_id = rows[j]; - break; - } - } - break; - } - } - } + } else { + console.error("Invalid object received: " + transport.responseText); + dijit.byId("headlines-frame").attr('content', "<div class='whiteBox'>" + + __('Could not update headlines (invalid object received - see error console for details)') + + "</div>"); + } - console.log("cur: " + Article.getActive() + " next: " + next_id); + Feeds.infscroll_in_progress = 0; - if (mode === "next") { - if (next_id || Article.getActive()) { - if (App.isCombinedMode()) { + // this is used to auto-catchup articles if needed after infscroll request has finished, + // unpack visible articles, fill buffer more, etc + this.scrollHandler(); - //const row = $("RROW-" + Article.getActive()); - const ctr = $("headlines-frame"); + Notify.close(); + }, + reverse: function () { + const toolbar = document.forms["toolbar-main"]; + const order_by = dijit.getEnclosingWidget(toolbar.order_by); - if (!noscroll) { - Article.scroll(ctr.offsetHeight / 2, event); - } else if (next_id) { - Article.setActive(next_id); - Article.cdmScrollToId(next_id, true, event); - } + let value = order_by.attr('value'); - } else if (next_id) { - Headlines.correctHeadlinesOffset(next_id); - Article.view(next_id, noexpand); - } - } - } + if (value != "date_reverse") + value = "date_reverse"; + else + value = "default"; - if (mode === "prev") { - if (prev_id || Article.getActive()) { - if (App.isCombinedMode()) { + order_by.attr('value', value); - const row = $("RROW-" + Article.getActive()); - //const prev_row = $("RROW-" + prev_id); - const ctr = $("headlines-frame"); + Feeds.reloadCurrent(); + }, + selectionToggleUnread: function (params) { + params = params || {}; - 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); - } else if (prev_id) { - Article.setActive(prev_id); - Article.cdmScrollToId(prev_id, noscroll, event); - } - } + const cmode = params.cmode != undefined ? params.cmode : 2; + const no_error = params.no_error || false; + const ids = params.ids || Headlines.getSelected(); - } else if (prev_id) { - Headlines.correctHeadlinesOffset(prev_id); - Article.view(prev_id, noexpand); - } - } - } - }, - updateSelectedPrompt: function () { - const count = Headlines.getSelected().length; - const elem = $("selected_prompt"); + if (ids.length == 0) { + if (!no_error) + alert(__("No articles selected.")); - if (elem) { - elem.innerHTML = ngettext("%d article selected", - "%d articles selected", count).replace("%d", count); + return; + } - count > 0 ? Element.show(elem) : Element.hide(elem); - } - }, - toggleUnread: function (id, cmode) { + ids.each((id) => { const row = $("RROW-" + id); if (row) { - if (typeof cmode == "undefined") cmode = 2; - switch (cmode) { case 0: row.removeClassName("Unread"); @@ -948,554 +764,668 @@ define(["dojo/_base/declare"], function (declare) { break; case 2: row.toggleClassName("Unread"); - break; } } - }, - selectionRemoveLabel: function (id, ids) { - if (!ids) ids = Headlines.getSelected(); + }); + }, + selectionToggleMarked: function (ids) { + ids = ids || Headlines.getSelected(); + + if (ids.length == 0) { + alert(__("No articles selected.")); + return; + } - if (ids.length == 0) { - alert(__("No articles selected.")); - return; - } + ids.each((id) => { + this.toggleMark(id); + }); + }, + selectionTogglePublished: function (ids) { + ids = ids || Headlines.getSelected(); - const query = { - op: "article", method: "removeFromLabel", - ids: ids.toString(), lid: id - }; + if (ids.length == 0) { + alert(__("No articles selected.")); + return; + } - xhrPost("backend.php", query, (transport) => { - App.handleRpcJson(transport); - this.onLabelsUpdated(transport); - }); - }, - selectionAssignLabel: function (id, ids) { - if (!ids) ids = Headlines.getSelected(); + ids.each((id) => { + this.togglePub(id); + }); + }, + toggleMark: function (id) { + const row = $("RROW-" + id); + + if (row) + row.toggleClassName("marked"); + + }, + togglePub: function (id) { + const row = $("RROW-" + id); + + if (row) + row.toggleClassName("published"); + }, + move: function (mode, params) { + params = params || {}; + + 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(); + + 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(); - if (ids.length == 0) { - alert(__("No articles selected.")); - return; - } + for (let i = 0; i < rows.length; i++) { + if (rows[i] == current_id) { - const query = { - op: "article", method: "assignToLabel", - ids: ids.toString(), lid: id - }; + // Account for adjacent identical article ids. + if (i > 0) prev_id = rows[i - 1]; - xhrPost("backend.php", query, (transport) => { - App.handleRpcJson(transport); - this.onLabelsUpdated(transport); - }); - }, - deleteSelection: function () { - const rows = Headlines.getSelected(); + for (let j = i + 1; j < rows.length; j++) { + if (rows[j] != current_id) { + next_id = rows[j]; + break; + } + } + break; + } + } + } - if (rows.length == 0) { - alert(__("No articles selected.")); - return; + console.log("cur: " + current_id + " next: " + next_id + " prev:" + prev_id); + + if (mode === "next") { + if (next_id) { + if (App.isCombinedMode()) { + window.requestAnimationFrame(() => { + Article.setActive(next_id); + Article.cdmMoveToId(next_id, {force_to_top: force_to_top}); + }); + } else { + Article.view(next_id, no_expand); + } } + } 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 fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); - let str; + console.log('moving back, delta_px', delta_px); - if (Feeds.getActive() != 0) { - str = ngettext("Delete %d selected article in %s?", "Delete %d selected articles in %s?", rows.length); - } else { - str = ngettext("Delete %d selected article?", "Delete %d selected articles?", rows.length); + 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.cdmMoveToId(prev_id, {force_to_top: force_to_top}); + } + }); + } else if (prev_id) { + Article.view(prev_id, no_expand); + } } + } + }, + updateSelectedPrompt: function () { + const count = Headlines.getSelected().length; + const elem = $("selected_prompt"); - str = str.replace("%d", rows.length); - str = str.replace("%s", fn); + if (elem) { + elem.innerHTML = ngettext("%d article selected", + "%d articles selected", count).replace("%d", count); - if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { - return; + count > 0 ? Element.show(elem) : Element.hide(elem); + } + }, + toggleUnread: function (id, cmode) { + const row = $("RROW-" + id); + + if (row) { + if (typeof cmode == "undefined") cmode = 2; + + switch (cmode) { + case 0: + row.removeClassName("Unread"); + break; + case 1: + row.addClassName("Unread"); + break; + case 2: + row.toggleClassName("Unread"); + break; } + } + }, + selectionRemoveLabel: function (id, ids) { + if (!ids) ids = Headlines.getSelected(); - const query = {op: "rpc", method: "delete", ids: rows.toString()}; + if (ids.length == 0) { + alert(__("No articles selected.")); + return; + } - xhrPost("backend.php", query, (transport) => { - App.handleRpcJson(transport); - Feeds.reloadCurrent(); - }); - }, - getSelected: function () { - const rv = []; + const query = { + op: "article", method: "removeFromLabel", + ids: ids.toString(), lid: id + }; + + xhrPost("backend.php", query, (transport) => { + App.handleRpcJson(transport); + this.onLabelsUpdated(transport); + }); + }, + selectionAssignLabel: function (id, ids) { + if (!ids) ids = Headlines.getSelected(); + + if (ids.length == 0) { + alert(__("No articles selected.")); + return; + } - $$("#headlines-frame > div[id*=RROW][class*=Selected]").each( - function (child) { - rv.push(child.getAttribute("data-article-id")); - }); + const query = { + op: "article", method: "assignToLabel", + ids: ids.toString(), lid: id + }; + + xhrPost("backend.php", query, (transport) => { + App.handleRpcJson(transport); + this.onLabelsUpdated(transport); + }); + }, + deleteSelection: function () { + const rows = Headlines.getSelected(); + + if (rows.length == 0) { + alert(__("No articles selected.")); + return; + } - // consider active article a honorary member of selected articles - if (Article.getActive()) - rv.push(Article.getActive()); + const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); + let str; - return rv.uniq(); - }, - getLoaded: function () { - const rv = []; + if (Feeds.getActive() != 0) { + str = ngettext("Delete %d selected article in %s?", "Delete %d selected articles in %s?", rows.length); + } else { + str = ngettext("Delete %d selected article?", "Delete %d selected articles?", rows.length); + } - const children = $$("#headlines-frame > div[id*=RROW-]"); + str = str.replace("%d", rows.length); + str = str.replace("%s", fn); - children.each(function (child) { - if (Element.visible(child)) { - rv.push(child.getAttribute("data-article-id")); - } + if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { + return; + } + + const query = {op: "rpc", method: "delete", ids: rows.toString()}; + + xhrPost("backend.php", query, (transport) => { + App.handleRpcJson(transport); + Feeds.reloadCurrent(); + }); + }, + getSelected: function () { + const rv = []; + + $$("#headlines-frame > div[id*=RROW][class*=Selected]").each( + function (child) { + rv.push(child.getAttribute("data-article-id")); }); - return rv; - }, - onRowChecked: function (elem) { - const row = elem.domNode.up("div[id*=RROW]"); + // consider active article a honorary member of selected articles + if (Article.getActive()) + rv.push(Article.getActive()); - // do not allow unchecking active article checkbox - if (row.hasClassName("active")) { - elem.attr("checked", 1); - return; - } + return rv.uniq(); + }, + getLoaded: function () { + const rv = []; - if (elem.attr("checked")) { - row.addClassName("Selected"); - } else { - row.removeClassName("Selected"); - } - }, - getRange: function (start, stop) { - if (start == stop) - return [start]; + const children = $$("#headlines-frame > div[id*=RROW-]"); - const rows = $$("#headlines-frame > div[id*=RROW]"); - const results = []; - let collecting = false; + children.each(function (child) { + if (Element.visible(child)) { + rv.push(child.getAttribute("data-article-id")); + } + }); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - const id = row.getAttribute('data-article-id'); + return rv; + }, + onRowChecked: function (elem) { + const row = elem.domNode.up("div[id*=RROW]"); - if (id == start || id == stop) { - if (!collecting) { - collecting = true; - } else { - results.push(id); - break; - } - } + // do not allow unchecking active article checkbox + if (row.hasClassName("active")) { + elem.attr("checked", 1); + return; + } - if (collecting) + if (elem.attr("checked")) { + row.addClassName("Selected"); + } else { + row.removeClassName("Selected"); + } + }, + getRange: function (start, stop) { + if (start == stop) + return [start]; + + const rows = $$("#headlines-frame > div[id*=RROW]"); + const results = []; + let collecting = false; + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + const id = row.getAttribute('data-article-id'); + + if (id == start || id == stop) { + if (!collecting) { + collecting = true; + } else { results.push(id); + break; + } } - return results; - }, - select: function (mode, articleId) { - // mode = all,none,unread,invert,marked,published - let query = "#headlines-frame > div[id*=RROW]"; + if (collecting) + results.push(id); + } - if (articleId) query += "[data-article-id=" + articleId + "]"; + return results; + }, + select: function (mode, articleId) { + // mode = all,none,unread,invert,marked,published + let query = "#headlines-frame > div[id*=RROW]"; + + if (articleId) query += "[data-article-id=" + articleId + "]"; + + switch (mode) { + case "none": + case "all": + case "invert": + break; + case "marked": + query += "[class*=marked]"; + break; + case "published": + query += "[class*=published]"; + break; + case "unread": + query += "[class*=Unread]"; + break; + default: + console.warn("select: unknown mode", mode); + } + + const rows = $$(query); + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; switch (mode) { case "none": - case "all": - case "invert": + row.removeClassName("Selected"); break; - case "marked": - query += "[class*=marked]"; - break; - case "published": - query += "[class*=published]"; - break; - case "unread": - query += "[class*=Unread]"; + case "invert": + row.toggleClassName("Selected"); break; default: - console.warn("select: unknown mode", mode); - } - - const rows = $$(query); - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - - switch (mode) { - case "none": - row.removeClassName("Selected"); - break; - case "invert": - row.toggleClassName("Selected"); - break; - default: - row.addClassName("Selected"); - } - } - }, - archiveSelection: function () { - const rows = Headlines.getSelected(); - - if (rows.length == 0) { - alert(__("No articles selected.")); - return; + row.addClassName("Selected"); } + } + }, + archiveSelection: function () { + const rows = Headlines.getSelected(); - const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); - let str; - let op; + if (rows.length == 0) { + alert(__("No articles selected.")); + return; + } - if (Feeds.getActive() != 0) { - str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length); - op = "archive"; - } else { - str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length); - str += " " + __("Please note that unstarred articles might get purged on next feed update."); + const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); + let str; + let op; - op = "unarchive"; - } + if (Feeds.getActive() != 0) { + str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length); + op = "archive"; + } else { + str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length); + str += " " + __("Please note that unstarred articles might get purged on next feed update."); - str = str.replace("%d", rows.length); - str = str.replace("%s", fn); + op = "unarchive"; + } - if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { - return; - } + str = str.replace("%d", rows.length); + str = str.replace("%s", fn); - const query = {op: "rpc", method: op, ids: rows.toString()}; + if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { + return; + } - xhrPost("backend.php", query, (transport) => { - App.handleRpcJson(transport); - Feeds.reloadCurrent(); - }); - }, - catchupSelection: function () { - const rows = Headlines.getSelected(); + const query = {op: "rpc", method: op, ids: rows.toString()}; - if (rows.length == 0) { - alert(__("No articles selected.")); - return; - } + xhrPost("backend.php", query, (transport) => { + App.handleRpcJson(transport); + Feeds.reloadCurrent(); + }); + }, + catchupSelection: function () { + const rows = Headlines.getSelected(); + + if (rows.length == 0) { + alert(__("No articles selected.")); + return; + } - const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); + const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); - let str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length); + let str = ngettext("Mark %d selected article in %s as read?", "Mark %d selected articles in %s as read?", rows.length); - str = str.replace("%d", rows.length); - str = str.replace("%s", fn); + str = str.replace("%d", rows.length); + str = str.replace("%s", fn); - if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { - return; - } + if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { + return; + } - Headlines.selectionToggleUnread({ids: rows, cmode: 0}); - }, - catchupRelativeTo: function (below, id) { + Headlines.selectionToggleUnread({ids: rows, cmode: 0}); + }, + catchupRelativeTo: function (below, id) { - if (!id) id = Article.getActive(); + if (!id) id = Article.getActive(); - if (!id) { - alert(__("No article is selected.")); - return; - } + if (!id) { + alert(__("No article is selected.")); + return; + } - const visible_ids = this.getLoaded(); + const visible_ids = this.getLoaded(); - const ids_to_mark = []; + const ids_to_mark = []; - if (!below) { - for (let i = 0; i < visible_ids.length; i++) { - if (visible_ids[i] != id) { - const e = $("RROW-" + visible_ids[i]); + if (!below) { + for (let i = 0; i < visible_ids.length; i++) { + if (visible_ids[i] != id) { + const e = $("RROW-" + visible_ids[i]); - if (e && e.hasClassName("Unread")) { - ids_to_mark.push(visible_ids[i]); - } - } else { - break; + if (e && e.hasClassName("Unread")) { + ids_to_mark.push(visible_ids[i]); } + } else { + break; } - } else { - for (let i = visible_ids.length - 1; i >= 0; i--) { - if (visible_ids[i] != id) { - const e = $("RROW-" + visible_ids[i]); + } + } else { + for (let i = visible_ids.length - 1; i >= 0; i--) { + if (visible_ids[i] != id) { + const e = $("RROW-" + visible_ids[i]); - if (e && e.hasClassName("Unread")) { - ids_to_mark.push(visible_ids[i]); - } - } else { - break; + if (e && e.hasClassName("Unread")) { + ids_to_mark.push(visible_ids[i]); } + } else { + break; } } + } - if (ids_to_mark.length == 0) { - alert(__("No articles found to mark")); - } else { - const msg = ngettext("Mark %d article as read?", "Mark %d articles as read?", ids_to_mark.length).replace("%d", ids_to_mark.length); + if (ids_to_mark.length == 0) { + alert(__("No articles found to mark")); + } else { + const msg = ngettext("Mark %d article as read?", "Mark %d articles as read?", ids_to_mark.length).replace("%d", ids_to_mark.length); - if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) { + if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(msg)) { - for (var i = 0; i < ids_to_mark.length; i++) { - var e = $("RROW-" + ids_to_mark[i]); - e.removeClassName("Unread"); - } + for (let i = 0; i < ids_to_mark.length; i++) { + const e = $("RROW-" + ids_to_mark[i]); + e.removeClassName("Unread"); } } - }, - onLabelsUpdated: function (transport) { - const data = JSON.parse(transport.responseText); - - if (data) { - data['info-for-headlines'].each(function (elem) { - $$(".HLLCTR-" + elem.id).each(function (ctr) { - ctr.innerHTML = elem.labels; - }); + } + }, + onLabelsUpdated: function (transport) { + const data = JSON.parse(transport.responseText); + + if (data) { + data['info-for-headlines'].each(function (elem) { + $$(".HLLCTR-" + elem.id).each(function (ctr) { + ctr.innerHTML = elem.labels; }); + }); + } + }, + onActionChanged: function (elem) { + // eslint-disable-next-line no-eval + eval(elem.value); + elem.attr('value', 'false'); + }, + scrollToArticleId: function (id) { + const container = $("headlines-frame"); + const row = $("RROW-" + id); + + if (!container || !row) return; + + const viewport = container.offsetHeight; + + const rel_offset_top = row.offsetTop - container.scrollTop; + const rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop; + + //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom); + //console.log("Vport: " + viewport); + + if (rel_offset_top <= 0 || rel_offset_top > viewport) { + container.scrollTop = row.offsetTop; + } else if (rel_offset_bottom > viewport) { + container.scrollTop = row.offsetTop + row.offsetHeight - viewport; + } + }, + headlinesMenuCommon: function (menu) { + + menu.addChild(new dijit.MenuItem({ + label: __("Open original article"), + onClick: function (/* event */) { + Article.openInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id")); } - }, - onActionChanged: function (elem) { - eval(elem.value); - elem.attr('value', 'false'); - }, - correctHeadlinesOffset: function (id) { - const container = $("headlines-frame"); - const row = $("RROW-" + id); + })); - if (!container || !row) return; + menu.addChild(new dijit.MenuItem({ + label: __("Display article URL"), + onClick: function (/* event */) { + Article.displayUrl(this.getParent().currentTarget.getAttribute("data-article-id")); + } + })); - const viewport = container.offsetHeight; + menu.addChild(new dijit.MenuSeparator()); - const rel_offset_top = row.offsetTop - container.scrollTop; - const rel_offset_bottom = row.offsetTop + row.offsetHeight - container.scrollTop; + menu.addChild(new dijit.MenuItem({ + label: __("Toggle unread"), + onClick: function () { - //console.log("Rtop: " + rel_offset_top + " Rbtm: " + rel_offset_bottom); - //console.log("Vport: " + viewport); + let ids = Headlines.getSelected(); + // cast to string + const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; + ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - if (rel_offset_top <= 0 || rel_offset_top > viewport) { - container.scrollTop = row.offsetTop; - } else if (rel_offset_bottom > viewport) { - container.scrollTop = row.offsetTop + row.offsetHeight - viewport; + Headlines.selectionToggleUnread({ids: ids, no_error: 1}); } - }, - initFloatingMenu: function () { - if (!dijit.byId("floatingMenu")) { + })); - const menu = new dijit.Menu({ - id: "floatingMenu", - selector: ".hlMenuAttach", - targetNodeIds: ["floatingTitle"] - }); - - this.headlinesMenuCommon(menu); + menu.addChild(new dijit.MenuItem({ + label: __("Toggle starred"), + onClick: function () { + let ids = Headlines.getSelected(); + // cast to string + const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; + ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - menu.startup(); + Headlines.selectionToggleMarked(ids); } - }, - headlinesMenuCommon: function (menu) { + })); - menu.addChild(new dijit.MenuItem({ - label: __("Open original article"), - onClick: function (event) { - Article.openInNewWindow(this.getParent().currentTarget.getAttribute("data-article-id")); - } - })); - - menu.addChild(new dijit.MenuItem({ - label: __("Display article URL"), - onClick: function (event) { - Article.displayUrl(this.getParent().currentTarget.getAttribute("data-article-id")); - } - })); - - menu.addChild(new dijit.MenuSeparator()); + menu.addChild(new dijit.MenuItem({ + label: __("Toggle published"), + onClick: function () { + let ids = Headlines.getSelected(); + // cast to string + const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; + ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - menu.addChild(new dijit.MenuItem({ - label: __("Toggle unread"), - onClick: function () { + Headlines.selectionTogglePublished(ids); + } + })); - let ids = Headlines.getSelected(); - // cast to string - const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; - ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; + menu.addChild(new dijit.MenuSeparator()); - Headlines.selectionToggleUnread({ids: ids, no_error: 1}); - } - })); + menu.addChild(new dijit.MenuItem({ + label: __("Mark above as read"), + onClick: function () { + Headlines.catchupRelativeTo(0, this.getParent().currentTarget.getAttribute("data-article-id")); + } + })); - menu.addChild(new dijit.MenuItem({ - label: __("Toggle starred"), - onClick: function () { - let ids = Headlines.getSelected(); - // cast to string - const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; - ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; + menu.addChild(new dijit.MenuItem({ + label: __("Mark below as read"), + onClick: function () { + Headlines.catchupRelativeTo(1, this.getParent().currentTarget.getAttribute("data-article-id")); + } + })); - Headlines.selectionToggleMarked(ids); - } - })); - menu.addChild(new dijit.MenuItem({ - label: __("Toggle published"), - onClick: function () { - let ids = Headlines.getSelected(); - // cast to string - const id = (this.getParent().currentTarget.getAttribute("data-article-id")) + ""; - ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; + const labels = App.getInitParam("labels"); - Headlines.selectionTogglePublished(ids); - } - })); + if (labels && labels.length) { menu.addChild(new dijit.MenuSeparator()); - menu.addChild(new dijit.MenuItem({ - label: __("Mark above as read"), - onClick: function () { - Headlines.catchupRelativeTo(0, this.getParent().currentTarget.getAttribute("data-article-id")); - } - })); + const labelAddMenu = new dijit.Menu({ownerMenu: menu}); + const labelDelMenu = new dijit.Menu({ownerMenu: menu}); - menu.addChild(new dijit.MenuItem({ - label: __("Mark below as read"), - onClick: function () { - Headlines.catchupRelativeTo(1, this.getParent().currentTarget.getAttribute("data-article-id")); - } - })); + labels.each(function (label) { + const bare_id = label.id; + const name = label.caption; + labelAddMenu.addChild(new dijit.MenuItem({ + label: name, + labelId: bare_id, + onClick: function () { - const labels = App.getInitParam("labels"); - - if (labels && labels.length) { - - menu.addChild(new dijit.MenuSeparator()); - - const labelAddMenu = new dijit.Menu({ownerMenu: menu}); - const labelDelMenu = new dijit.Menu({ownerMenu: menu}); - - labels.each(function (label) { - const bare_id = label.id; - const name = label.caption; - - labelAddMenu.addChild(new dijit.MenuItem({ - label: name, - labelId: bare_id, - onClick: function () { - - let ids = Headlines.getSelected(); - // cast to string - const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + ""; - - ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - - Headlines.selectionAssignLabel(this.labelId, ids); - } - })); + let ids = Headlines.getSelected(); + // cast to string + const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + ""; - labelDelMenu.addChild(new dijit.MenuItem({ - label: name, - labelId: bare_id, - onClick: function () { - let ids = Headlines.getSelected(); - // cast to string - const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + ""; + ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; + Headlines.selectionAssignLabel(this.labelId, ids); + } + })); - Headlines.selectionRemoveLabel(this.labelId, ids); - } - })); + labelDelMenu.addChild(new dijit.MenuItem({ + label: name, + labelId: bare_id, + onClick: function () { + let ids = Headlines.getSelected(); + // cast to string + const id = (this.getParent().ownerMenu.currentTarget.getAttribute("data-article-id")) + ""; - }); + ids = ids.length != 0 && ids.indexOf(id) != -1 ? ids : [id]; - menu.addChild(new dijit.PopupMenuItem({ - label: __("Assign label"), - popup: labelAddMenu - })); - - menu.addChild(new dijit.PopupMenuItem({ - label: __("Remove label"), - popup: labelDelMenu + Headlines.selectionRemoveLabel(this.labelId, ids); + } })); - } - }, - 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"); - } + menu.addChild(new dijit.PopupMenuItem({ + label: __("Assign label"), + popup: labelAddMenu + })); - elem.scrollTop += elem.offsetHeight * offset * 0.99; - }, - initHeadlinesMenu: function () { - if (!dijit.byId("headlinesMenu")) { + menu.addChild(new dijit.PopupMenuItem({ + label: __("Remove label"), + popup: labelDelMenu + })); - const menu = new dijit.Menu({ - id: "headlinesMenu", - targetNodeIds: ["headlines-frame"], - selector: ".hlMenuAttach" - }); + } + }, + 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")) { + + const menu = new dijit.Menu({ + id: "headlinesMenu", + targetNodeIds: ["headlines-frame"], + selector: ".hlMenuAttach" + }); - this.headlinesMenuCommon(menu); + this.headlinesMenuCommon(menu); - menu.startup(); - } + menu.startup(); + } - /* vgroup feed title menu */ + /* vgroup feed title menu */ - if (!dijit.byId("headlinesFeedTitleMenu")) { + if (!dijit.byId("headlinesFeedTitleMenu")) { - const menu = new dijit.Menu({ - id: "headlinesFeedTitleMenu", - targetNodeIds: ["headlines-frame"], - selector: "div.cdmFeedTitle" - }); + const menu = new dijit.Menu({ + id: "headlinesFeedTitleMenu", + targetNodeIds: ["headlines-frame"], + selector: "div.cdmFeedTitle" + }); - menu.addChild(new dijit.MenuItem({ - label: __("Select articles in group"), - onClick: function (event) { - Headlines.select("all", - "#headlines-frame > div[id*=RROW]" + - "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']"); + menu.addChild(new dijit.MenuItem({ + label: __("Select articles in group"), + onClick: function (/* event */) { + Headlines.select("all", + "#headlines-frame > div[id*=RROW]" + + "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']"); - } - })); + } + })); - menu.addChild(new dijit.MenuItem({ - label: __("Mark group as read"), - onClick: function () { - Headlines.select("none"); - Headlines.select("all", - "#headlines-frame > div[id*=RROW]" + - "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']"); + menu.addChild(new dijit.MenuItem({ + label: __("Mark group as read"), + onClick: function () { + Headlines.select("none"); + Headlines.select("all", + "#headlines-frame > div[id*=RROW]" + + "[data-orig-feed-id='" + this.getParent().currentTarget.getAttribute("data-feed-id") + "']"); - Headlines.catchupSelection(); - } - })); + Headlines.catchupSelection(); + } + })); - menu.addChild(new dijit.MenuItem({ - label: __("Mark feed as read"), - onClick: function () { - Feeds.catchupFeedInGroup(this.getParent().currentTarget.getAttribute("data-feed-id")); - } - })); + menu.addChild(new dijit.MenuItem({ + label: __("Mark feed as read"), + onClick: function () { + Feeds.catchupFeedInGroup(this.getParent().currentTarget.getAttribute("data-feed-id")); + } + })); - menu.addChild(new dijit.MenuItem({ - label: __("Edit feed"), - onClick: function () { - CommonDialogs.editFeed(this.getParent().currentTarget.getAttribute("data-feed-id")); - } - })); + menu.addChild(new dijit.MenuItem({ + label: __("Edit feed"), + onClick: function () { + CommonDialogs.editFeed(this.getParent().currentTarget.getAttribute("data-feed-id")); + } + })); - menu.startup(); - } + menu.startup(); } } - - return Headlines; -}); +} |