'use strict';
/* global __, ngettext, Article, App */
/* global dojo, dijit, PluginHost, Notify, xhr, 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.closest('.cdm').querySelector(".header");
if (entry.isIntersecting) {
header.removeAttribute("data-is-stuck");
} else {
header.setAttribute("data-is-stuck", "true");
}
//console.log(entry.target, entry.intersectionRatio, entry.isIntersecting, entry.boundingClientRect.top);
});
},
{threshold: [0, 1], root: document.querySelector("#headlines-frame")}
),
sticky_content_observer: new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
const header = entry.target.closest('.cdm').querySelector(".header");
header.style.position = entry.isIntersecting ? "sticky" : "unset";
//console.log(entry.target, entry.intersectionRatio, entry.isIntersecting, entry.boundingClientRect.top);
});
},
{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});
}
}
}
});
PluginHost.run(PluginHost.HOOK_HEADLINE_MUTATIONS, mutations);
Headlines.updateSelectedPrompt();
window.requestIdleCallback(() => {
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(xhr.post("backend.php",
{op: "rpc", method: "markSelected", "ids[]": ops.tmark, cmode: 2}));
if (ops.tpub.length != 0)
promises.push(xhr.post("backend.php",
{op: "rpc", method: "publishSelected", "ids[]": ops.tpub, cmode: 2}));
if (ops.read.length != 0)
promises.push(xhr.post("backend.php",
{op: "rpc", method: "catchupSelected", "ids[]": ops.read, cmode: 0}));
if (ops.unread.length != 0)
promises.push(xhr.post("backend.php",
{op: "rpc", method: "catchupSelected", "ids[]": ops.unread, cmode: 1}));
const scores = Object.keys(ops.rescore);
if (scores.length != 0) {
scores.forEach((score) => {
promises.push(xhr.post("backend.php",
{op: "article", method: "setScore", "ids[]": ops.rescore[score].toString(), score: score}));
});
}
Promise.allSettled(promises).then((results) => {
let feeds = [];
let labels = [];
results.forEach((res) => {
if (res) {
try {
const obj = JSON.parse(res.value);
if (obj.feeds)
feeds = feeds.concat(obj.feeds);
if (obj.labels)
labels = labels.concat(obj.labels);
} catch (e) {
console.warn(e, res);
}
}
});
if (feeds.length > 0) {
console.log('requesting counters for', feeds, labels);
Feeds.requestCounters(feeds, labels);
}
PluginHost.run(PluginHost.HOOK_HEADLINE_MUTATIONS_SYNCED, results);
});
},
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 = dijit.byId("toolbar-main").getValues().view_mode;
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;
}
}
}
PluginHost.run(PluginHost.HOOK_HEADLINES_SCROLL_HANDLER);
} catch (e) {
console.warn("scrollHandler", e);
}
},
objectById: function (id) {
return this.headlines[id];
},
setCommonClasses: function (headlines_count) {
const container = App.byId("headlines-frame");
container.removeClassName("cdm");
container.removeClassName("normal");
container.addClassName(App.isCombinedMode() ? "cdm" : "normal");
container.setAttribute("data-enable-grid", App.getInitParam("cdm_enable_grid") ? "true" : "false");
container.setAttribute("data-headlines-count", parseInt(headlines_count));
container.setAttribute("data-is-cdm", App.isCombinedMode() ? "true" : "false");
container.setAttribute("data-is-cdm-expanded", App.getInitParam("cdm_expanded"));
// 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(this.headlines.filter((h) => h.id).length);
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)
});
App.findAll(".cdm .content").forEach((e) => {
this.sticky_content_observer.observe(e)
});
if (App.getInitParam("cdm_expanded"))
App.findAll("#headlines-frame > div[id*=RROW].cdm").forEach((e) => {
this.unpack_observer.observe(e)
});
dijit.byId('main').resize();
PluginHost.run(PluginHost.HOOK_HEADLINES_RENDERED);
},
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 = `