summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/AppBase.js59
-rw-r--r--js/Article.js142
-rw-r--r--js/ArticleCache.js29
-rw-r--r--js/CommonDialogs.js2
-rw-r--r--js/CommonFilters.js18
-rwxr-xr-xjs/FeedTree.js16
-rw-r--r--js/Feeds.js44
-rwxr-xr-xjs/Headlines.js225
-rw-r--r--js/PluginHost.js7
-rw-r--r--js/PrefFeedTree.js8
-rw-r--r--js/PrefFilterTree.js35
-rw-r--r--js/PrefHelpers.js2
-rw-r--r--js/PrefLabelTree.js26
-rwxr-xr-xjs/common.js57
-rwxr-xr-xjs/prefs.js2
-rw-r--r--js/tt-rss.js18
16 files changed, 441 insertions, 249 deletions
diff --git a/js/AppBase.js b/js/AppBase.js
index 9ab2f507e..4552eea11 100644
--- a/js/AppBase.js
+++ b/js/AppBase.js
@@ -51,8 +51,9 @@ define(["dojo/_base/declare"], function (declare) {
if (dijit.byId("loading_bar"))
dijit.byId("loading_bar").update({progress: loading_progress});
- if (loading_progress >= 90)
- Element.hide("overlay");
+ if (loading_progress >= 90) {
+ $("overlay").hide();
+ }
},
keyeventToAction: function(event) {
@@ -184,10 +185,7 @@ define(["dojo/_base/declare"], function (declare) {
},
handleRpcJson: function(transport) {
- const netalert_dijit = dijit.byId("net-alert");
- let netalert = false;
-
- if (netalert_dijit) netalert = netalert_dijit.domNode;
+ const netalert = $$("#toolbar .net-alert")[0];
try {
const reply = JSON.parse(transport.responseText);
@@ -239,17 +237,15 @@ define(["dojo/_base/declare"], function (declare) {
return reply;
} else {
- if (netalert)
- netalert.show();
- else
- Notify.error("Communication problem with server.");
+ if (netalert) netalert.show();
+
+ Notify.error("Communication problem with server.");
}
} catch (e) {
- if (netalert)
- netalert.show();
- else
- Notify.error("Communication problem with server.");
+ if (netalert) netalert.show();
+
+ Notify.error("Communication problem with server.");
console.error(e);
}
@@ -269,12 +265,10 @@ define(["dojo/_base/declare"], function (declare) {
}
if (k == "update_result") {
- const updatesIcon = dijit.byId("updatesIcon").domNode;
-
if (v) {
- Element.show(updatesIcon);
+ Element.show("updates-available");
} else {
- Element.hide(updatesIcon);
+ Element.hide("updates-available");
}
}
@@ -324,6 +318,12 @@ define(["dojo/_base/declare"], function (declare) {
case "label_base_index":
_label_base_index = parseInt(params[k]);
break;
+ case "cdm_auto_catchup":
+ if (params[k] == 1) {
+ const hl = $("headlines-frame");
+ if (hl) hl.addClassName("auto_catchup");
+ }
+ break;
case "hotkeys":
// filter mnemonic definitions (used for help panel) from hotkeys map
// i.e. *(191)|Ctrl-/ -> *(191)
@@ -352,6 +352,29 @@ define(["dojo/_base/declare"], function (declare) {
this.initSecondStage();
},
+ toggleNightMode: function() {
+ const link = $("theme_css");
+
+ if (link) {
+
+ let user_theme = "";
+ let user_css = "";
+
+ if (link.getAttribute("href").indexOf("themes/night.css") == -1) {
+ user_css = "themes/night.css?" + Date.now();
+ user_theme = "night.css";
+ } else {
+ user_theme = "default.php";
+ user_css = "css/default.css?" + Date.now();
+ }
+
+ fetch(user_css).then(() => {
+ link.setAttribute("href", user_css);
+ xhrPost("backend.php", {op: "rpc", method: "setpref", key: "USER_CSS_THEME", value: user_theme});
+ });
+
+ }
+ },
explainError: function(code) {
return this.displayDlg(__("Error explained"), "explainError", code);
},
diff --git a/js/Article.js b/js/Article.js
index 507560ee4..c3b6766c8 100644
--- a/js/Article.js
+++ b/js/Article.js
@@ -22,13 +22,22 @@ define(["dojo/_base/declare"], function (declare) {
reply.id.each((id) => {
const row = $("RROW-" + id);
+ row.removeClassName("score-low");
+ row.removeClassName("score-high");
+ row.removeClassName("score-half-low");
+ row.removeClassName("score-half-high");
+ row.removeClassName("score-neutral");
+
+ row.addClassName(reply["score_class"]);
+
+
if (row) {
- const pic = row.getElementsByClassName("score-pic")[0];
+ const pic = row.select(".icon-score")[0];
if (pic) {
- pic.src = pic.src.replace(/score_.*?\.png/,
- reply["score_pic"]);
- pic.setAttribute("score", reply["score"]);
+ pic.innerHTML = reply["score_pic"];
+ pic.setAttribute("data-score", reply["score"]);
+ pic.setAttribute("title", reply["score"]);
}
}
});
@@ -41,18 +50,27 @@ define(["dojo/_base/declare"], function (declare) {
}
},
setScore: function (id, pic) {
- const score = pic.getAttribute("score");
+ const row = pic.up("div[id*=RROW]");
+ const score = pic.getAttribute("data-score");
const new_score = prompt(__("Please enter new score for this article:"), score);
- if (new_score != undefined) {
+ if (row && new_score != undefined) {
const query = {op: "article", method: "setScore", id: id, score: new_score};
xhrJson("backend.php", query, (reply) => {
if (reply) {
- pic.src = pic.src.replace(/score_.*?\.png/, reply["score_pic"]);
- pic.setAttribute("score", new_score);
+ pic.innerHTML = reply["score_pic"];
+ pic.setAttribute("data-score", new_score);
pic.setAttribute("title", new_score);
+
+ row.removeClassName("score-low");
+ row.removeClassName("score-high");
+ row.removeClassName("score-half-low");
+ row.removeClassName("score-half-high");
+ row.removeClassName("score-neutral");
+
+ row.addClassName(reply["score_class"]);
}
});
}
@@ -91,10 +109,13 @@ define(["dojo/_base/declare"], function (declare) {
},
openInNewWindow: function (id) {
const w = window.open("");
- w.opener = null;
- w.location = "backend.php?op=article&method=redirect&id=" + id;
- Headlines.toggleUnread(id, 0);
+ if (w) {
+ w.opener = null;
+ w.location = "backend.php?op=article&method=redirect&id=" + id;
+
+ Headlines.toggleUnread(id, 0);
+ }
},
render: function (article) {
App.cleanupMemory("content-insert");
@@ -119,58 +140,65 @@ define(["dojo/_base/declare"], function (declare) {
} catch (e) {
}
},
- view: function (id, noexpand) {
- this.setActive(id);
-
- if (!noexpand) {
- console.log("loading article", id);
-
- const cids = [];
+ formatComments: function(hl) {
+ let comments = "";
- /* only request uncached articles */
+ if (hl.comments) {
+ let comments_msg = __("comments");
- this.getRelativeIds(id).each((n) => {
- if (!ArticleCache.get(n))
- cids.push(n);
- });
-
- const cached_article = ArticleCache.get(id);
-
- if (cached_article) {
- console.log('rendering cached', id);
- this.render(cached_article);
- return false;
+ if (hl.num_comments > 0) {
+ comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments)
}
- xhrPost("backend.php", {op: "article", method: "view", id: id, cids: cids.toString()}, (transport) => {
- try {
- const reply = App.handleRpcJson(transport);
-
- if (reply) {
-
- reply.each(function (article) {
- if (Article.getActive() == article['id']) {
- Article.render(article['content']);
- }
- ArticleCache.set(article['id'], article['content']);
- });
-
- } else {
- console.error("Invalid object received: " + transport.responseText);
-
- Article.render("<div class='whiteBox'>" +
- __('Could not display article (invalid object received - see error console for details)') + "</div>");
- }
-
- //const unread_in_buffer = $$("#headlines-frame > div[id*=RROW][class*=Unread]").length;
- //request_counters(unread_in_buffer == 0);
+ comments = `<a href="${hl.comments}">(${comments_msg})</a>`;
+ }
- Notify.close();
+ return comments;
+ },
+ formatOriginallyFrom: function(hl) {
+ return hl.orig_feed ? `<span>
+ ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${hl.orig_feed[1]}">${hl.orig_feed[0]}</a>
+ </span>` : "";
+ },
+ view: function (id, noexpand) {
+ this.setActive(id);
- } catch (e) {
- App.Error.report(e);
- }
- })
+ if (!noexpand) {
+ const hl = Headlines.objectById(id);
+
+ if (hl) {
+
+ const comments = this.formatComments(hl);
+ const originally_from = this.formatOriginallyFrom(hl);
+
+ const article = `<div class="post post-${hl.id}">
+ <div class="header">
+ <div class="row">
+ <div class="title"><a target="_blank" rel="noopener noreferrer" title="${hl.title}" href="${hl.link}">${hl.title}</a></div>
+ <div class="date">${hl.updated_long}</div>
+ </div>
+ <div class="row">
+ <div class="buttons left">${hl.buttons_left}</div>
+ <div class="comments">${comments}</div>
+ <div class="author">${hl.author}</div>
+ <i class="material-icons">label_outline</i>
+ <span id="ATSTR-${hl.id}">${hl.tags_str}</span>
+ &nbsp;<a title="${__("Edit tags for this article")}" href="#"
+ onclick="Article.editTags(${hl.id})">(+)</a>
+ <div class="buttons right">${hl.buttons}</div>
+ </div>
+ </div>
+ <div id="POSTNOTE-${hl.id}">${hl.note}</div>
+ <div class="content" lang="${hl.lang ? hl.lang : 'en'}">
+ ${originally_from}
+ ${hl.content}
+ ${hl.enclosures}
+ </div>
+ </div>`;
+
+ Headlines.toggleUnread(id, 0);
+ this.render(article);
+ }
}
return false;
diff --git a/js/ArticleCache.js b/js/ArticleCache.js
deleted file mode 100644
index ce34d00d9..000000000
--- a/js/ArticleCache.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict'
-/* global __, ngettext */
-define(["dojo/_base/declare"], function (declare) {
- ArticleCache = {
- has_storage: 'sessionStorage' in window && window['sessionStorage'] !== null,
- set: function (id, obj) {
- if (this.has_storage)
- try {
- sessionStorage["article:" + id] = obj;
- } catch (e) {
- sessionStorage.clear();
- }
- },
- get: function (id) {
- if (this.has_storage)
- return sessionStorage["article:" + id];
- },
- clear: function () {
- if (this.has_storage)
- sessionStorage.clear();
- },
- del: function (id) {
- if (this.has_storage)
- sessionStorage.removeItem("article:" + id);
- },
- }
-
- return ArticleCache;
-});
diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js
index 69a975254..2b7ee8a7f 100644
--- a/js/CommonDialogs.js
+++ b/js/CommonDialogs.js
@@ -179,7 +179,7 @@ define(["dojo/_base/declare"], function (declare) {
title: __("Feeds with update errors"),
style: "width: 600px",
getSelectedFeeds: function () {
- return Tables.getSelected("prefErrorFeedList");
+ return Tables.getSelected("error-feeds-list");
},
removeSelected: function () {
const sel_rows = this.getSelectedFeeds();
diff --git a/js/CommonFilters.js b/js/CommonFilters.js
index 97a676c98..fb2061ea3 100644
--- a/js/CommonFilters.js
+++ b/js/CommonFilters.js
@@ -282,24 +282,10 @@ define(["dojo/_base/declare"], function (declare) {
Filters.editFilterTest(query);
},
selectRules: function (select) {
- $$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
- e.checked = select;
- if (select)
- e.parentNode.addClassName("Selected");
- else
- e.parentNode.removeClassName("Selected");
- });
+ Lists.select("filterDlg_Matches", select);
},
selectActions: function (select) {
- $$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
- e.checked = select;
-
- if (select)
- e.parentNode.addClassName("Selected");
- else
- e.parentNode.removeClassName("Selected");
-
- });
+ Lists.select("filterDlg_Actions", select);
},
editRule: function (e) {
const li = e.parentNode;
diff --git a/js/FeedTree.js b/js/FeedTree.js
index 812b9dd08..9344d70eb 100755
--- a/js/FeedTree.js
+++ b/js/FeedTree.js
@@ -34,17 +34,17 @@ define(["dojo/_base/declare", "dojo/dom-construct", "dijit/Tree", "dijit/Menu"],
const bare_id = parseInt(id.substr(id.indexOf(':')+1));
if (bare_id < _label_base_index) {
- const span = dojo.doc.createElement('span');
- const fg_color = args.item.fg_color[0];
+ const label = dojo.doc.createElement('i');
+ //const fg_color = args.item.fg_color[0];
const bg_color = args.item.bg_color[0];
- span.innerHTML = "&alpha;";
- span.className = 'labelColorIndicator';
- span.setStyle({
- color: fg_color,
- backgroundColor: bg_color});
+ label.className = "material-icons icon icon-label";
+ label.innerHTML = "label";
+ label.setStyle({
+ color: bg_color,
+ });
- domConstruct.place(span, tnode.iconNode, 'only');
+ domConstruct.place(label, tnode.iconNode, 'only');
}
if (id.match("FEED:")) {
diff --git a/js/Feeds.js b/js/Feeds.js
index 70b5176f4..76a6f5971 100644
--- a/js/Feeds.js
+++ b/js/Feeds.js
@@ -213,6 +213,8 @@ define(["dojo/_base/declare"], function (declare) {
App.setLoadingProgress(50);
document.onkeydown = (event) => { return App.hotkeyHandler(event) };
+ window.onresize = () => { Headlines.scrollHandler(); }
+
window.setInterval(() => { Headlines.catchupBatched() }, 10 * 1000);
if (!this.getActive()) {
@@ -338,11 +340,6 @@ define(["dojo/_base/declare"], function (declare) {
if (offset != 0) {
query.skip = offset;
-
- // to prevent duplicate feed titles when showing grouped vfeeds
- if (Headlines.vgroup_last_feed != undefined) {
- query.vgrlf = Headlines.vgroup_last_feed;
- }
} else if (!is_cat && feed == this.getActive() && !params.method) {
query.m = "ForceUpdate";
}
@@ -361,7 +358,7 @@ define(["dojo/_base/declare"], function (declare) {
if (viewfeed_debug) {
window.open("backend.php?" +
dojo.objectToQuery(
- Object.assign({debug: 1, csrf_token: App.getInitParam("csrf_token")}, query)
+ Object.assign({csrf_token: App.getInitParam("csrf_token")}, query)
));
}
@@ -483,40 +480,17 @@ define(["dojo/_base/declare"], function (declare) {
if (App.getInitParam("confirm_feed_catchup") != 1 || confirm(str)) {
- const rows = $$("#headlines-frame > div[id*=RROW][data-orig-feed-id='" + id + "']");
+ const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread][data-orig-feed-id='" + id + "']");
if (rows.length > 0) {
- rows.each(function (row) {
- row.removeClassName("Unread");
-
- if (row.getAttribute("data-article-id") != Article.getActive()) {
- new Effect.Fade(row, {duration: 0.5});
- }
+ for (let i = 0; i < rows.length; i++)
+ Headlines.catchup_id_batch.push(rows[i].getAttribute("data-article-id"));
+ Headlines.catchupBatched(() => {
+ Headlines.updateFloatingTitle(true);
});
-
- const feedTitles = $$("#headlines-frame > div[class='feed-title']");
-
- for (let i = 0; i < feedTitles.length; i++) {
- if (feedTitles[i].getAttribute("data-feed-id") == id) {
-
- if (i < feedTitles.length - 1) {
- new Effect.Fade(feedTitles[i], {duration: 0.5});
- }
-
- break;
- }
- }
-
- Headlines.updateFloatingTitle(true);
}
-
- Notify.progress("Loading, please wait...", true);
-
- xhrPost("backend.php", {op: "rpc", method: "catchupFeed", feed_id: id, is_cat: false}, (transport) => {
- App.handleRpcJson(transport);
- });
}
},
getUnread: function(feed, is_cat) {
@@ -627,7 +601,7 @@ define(["dojo/_base/declare"], function (declare) {
updateRandom: function() {
console.log("in update_random_feed");
- xhrPost("backend.php", {op: "rpc", method: "updateRandom"}, (transport) => {
+ xhrPost("backend.php", {op: "rpc", method: "updaterandomfeed"}, (transport) => {
App.handleRpcJson(transport, true);
});
},
diff --git a/js/Headlines.js b/js/Headlines.js
index e0caddc2a..4524f0556 100755
--- a/js/Headlines.js
+++ b/js/Headlines.js
@@ -4,7 +4,7 @@ define(["dojo/_base/declare"], function (declare) {
Headlines = {
vgroup_last_feed: undefined,
_headlines_scroll_timeout: 0,
- loaded_article_ids: [],
+ headlines: [],
current_first_id: 0,
catchup_id_batch: [],
click: function (event, id, in_body) {
@@ -23,6 +23,8 @@ define(["dojo/_base/declare"], function (declare) {
if (!App.getInitParam("cdm_expanded"))
Article.cdmScrollToId(id);
+ } else if (in_body) {
+ Headlines.toggleUnread(id, 0);
}
return in_body;
@@ -153,9 +155,10 @@ define(["dojo/_base/declare"], function (declare) {
console.warn("scrollHandler", e);
}
},
- updateFloatingTitle: function (unread_only) {
+ 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");
@@ -163,18 +166,18 @@ define(["dojo/_base/declare"], function (declare) {
for (let i = 0; i < elems.length; i++) {
const row = elems[i];
- if (row && row.offsetTop + row.offsetHeight > hf.scrollTop) {
+ if (row && row.offsetTop + row.offsetHeight > hf.scrollTop + safety_offset) {
const header = row.select(".header")[0];
const id = row.getAttribute("data-article-id");
- if (unread_only || id != ft.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 anchor\" onclick=\"Article.cdmScrollToId(" + id + ", true)\">expand_more</i>";
+ ft.select(".dijitCheckBox")[0].outerHTML = "<i class=\"material-icons icon-anchor\" onclick=\"Article.cdmScrollToId(" + id + ", true)\">expand_more</i>";
this.initFloatingMenu();
@@ -185,16 +188,31 @@ define(["dojo/_base/declare"], function (declare) {
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);
}
- ft.style.marginRight = hf.offsetWidth - row.offsetWidth + "px";
+ //ft.style.marginRight = hf.offsetWidth - row.offsetWidth + "px";
- if (header.offsetTop + header.offsetHeight < hf.scrollTop + ft.offsetHeight - 5 &&
+ /* if (header.offsetTop + header.offsetHeight < hf.scrollTop + ft.offsetHeight - 5 &&
row.offsetTop + row.offsetHeight >= hf.scrollTop + ft.offsetHeight - 5)
- new Effect.Appear(ft, {duration: 0.3});
+ Element.show(ft);
+ else
+ Element.hide(ft); */
+
+ if (hf.scrollTop - row.offsetTop <= header.offsetHeight + safety_offset)
+ ft.fade({duration: 0.2});
else
- Element.hide(ft);
+ ft.appear({duration: 0.2});
return;
}
@@ -221,6 +239,141 @@ define(["dojo/_base/declare"], function (declare) {
}
}
},
+ objectById: function (id){
+ return this.headlines[id];
+ },
+ renderHeadline: 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) {
+ 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>`
+
+ const tmp = document.createElement("div");
+ tmp.innerHTML = vgrhdr;
+
+ $("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);
+ const originally_from = Article.formatOriginallyFrom(hl);
+
+ row = `<div class="cdm ${row_class} ${hl.score_class}" id="RROW-${hl.id}" data-article-id="${hl.id}" data-orig-feed-id="${hl.feed_id}"
+ data-content="${escapeHtml(hl.content)}" 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>
+ </div>
+
+ <span onclick="return Headlines.click(event, ${hl.id});" data-article-id="${hl.id}" class="titleWrap hlMenuAttach">
+ <a class="title" title="${hl.title}" target="_blank" rel="noopener noreferrer" href="${hl.link}">
+ ${hl.title}</a>
+ <span class="author">${hl.author}</span>
+ <span class="HLLCTR-${hl.id}">${hl.labels}</span>
+ ${hl.cdm_excerpt ? hl.cdm_excerpt : ""}
+ </span>
+
+ <div class="feed">
+ <a href="#" style="background-color: rgba(${hl.favicon_avg_color_rgba})"
+ 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}" data-score="${hl.score}"
+ onclick="Article.setScore(${hl.id}, this)">${hl.score_pic}</i>
+
+ <span style="cursor : pointer" title="${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>
+ </div>
+ </div>`;
+
+
+ } else {
+ row = `<div class="hl ${row_class} ${hl.score_class}" data-orig-feed-id="${hl.feed_id}" data-article-id="${hl.id}" id="RROW-${hl.id}"
+ 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="${hl.link}">${hl.title} <span class="preview">${hl.content_preview}</span></a>
+ <span class="author">${hl.author}</span>
+ <span class="HLLCTR-${hl.id}">${hl.labels}</span>
+ </span>
+ </div>
+ <span class="feed">
+ <a style="background : rgba(${hl.favicon_avg_color_rgba})" 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}" data-score="${hl.score}"
+ onclick="Article.setScore(${hl.id}, this)">${hl.score_pic}</i>
+ <span onclick="Feeds.open({feed:${hl.feed_id})" style="cursor : pointer" title="${hl.feed_title}">${hl.feed_icon}</span>
+ </div>
+ </div>
+ `;
+ }
+
+ const tmp = document.createElement("div");
+ tmp.innerHTML = row;
+ dojo.parser.parse(tmp);
+
+ PluginHost.run(PluginHost.HOOK_HEADLINE_RENDERED, tmp.firstChild);
+
+ $("headlines-frame").appendChild(tmp.firstChild);
+ },
onLoaded: function (transport, offset) {
const reply = App.handleRpcJson(transport);
@@ -262,19 +415,31 @@ define(["dojo/_base/declare"], function (declare) {
console.log('received', headlines_count, 'headlines, infscroll disabled=', Feeds.infscroll_disabled);
- this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed'];
+ //this.vgroup_last_feed = reply['headlines-info']['vgroup_last_feed'];
this.current_first_id = reply['headlines']['first_id'];
if (offset == 0) {
- this.loaded_article_ids = [];
+ //this.headlines = [];
+ this.vgroup_last_feed = undefined;
dojo.html.set($("toolbar-headlines"),
reply['headlines']['toolbar'],
{parseContent: true});
- $("headlines-frame").innerHTML = '';
+ if (typeof reply['headlines']['content'] == 'string') {
+ $("headlines-frame").innerHTML = reply['headlines']['content'];
+ } else {
+ $("headlines-frame").innerHTML = '';
+
+ for (let i = 0; i < reply['headlines']['content'].length; i++) {
+ const hl = reply['headlines']['content'][i];
+
+ this.renderHeadline(reply['headlines'], hl);
+ this.headlines[parseInt(hl.id)] = hl;
+ }
+ }
- let tmp = document.createElement("div");
+ /* let tmp = document.createElement("div");
tmp.innerHTML = reply['headlines']['content'];
dojo.parser.parse(tmp);
@@ -286,7 +451,7 @@ define(["dojo/_base/declare"], function (declare) {
this.loaded_article_ids.push(row.id);
}
- }
+ } */
let hsp = $("headlines-spacer");
@@ -318,7 +483,7 @@ define(["dojo/_base/declare"], function (declare) {
if (hsp)
c.domNode.removeChild(hsp);
- let tmp = document.createElement("div");
+ /* let tmp = document.createElement("div");
tmp.innerHTML = reply['headlines']['content'];
dojo.parser.parse(tmp);
@@ -330,6 +495,17 @@ define(["dojo/_base/declare"], function (declare) {
this.loaded_article_ids.push(row.id);
}
+ } */
+
+ 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.renderHeadline(reply['headlines'], hl);
+ this.headlines[parseInt(hl.id)] = hl;
+ }
}
if (!hsp) {
@@ -392,10 +568,10 @@ define(["dojo/_base/declare"], function (declare) {
let value = order_by.attr('value');
- if (value == "date_reverse")
- value = "default";
- else
+ if (value != "date_reverse")
value = "date_reverse";
+ else
+ value = "default";
order_by.attr('value', value);
@@ -438,11 +614,10 @@ define(["dojo/_base/declare"], function (declare) {
cmode: cmode, ids: ids.toString()
};
- Notify.progress("Loading, please wait...");
-
xhrPost("backend.php", query, (transport) => {
App.handleRpcJson(transport);
if (callback) callback(transport);
+ Headlines.updateFloatingTitle(true);
});
},
selectionToggleMarked: function (ids) {
@@ -494,10 +669,11 @@ define(["dojo/_base/declare"], function (declare) {
const row = $("RROW-" + id);
if (row) {
-
row.toggleClassName("marked");
query.mark = row.hasClassName("marked") ? 1 : 0;
+ Headlines.updateFloatingTitle(true);
+
if (!client_only)
xhrPost("backend.php", query, (transport) => {
App.handleRpcJson(transport);
@@ -513,6 +689,8 @@ define(["dojo/_base/declare"], function (declare) {
row.toggleClassName("published");
query.pub = row.hasClassName("published") ? 1 : 0;
+ Headlines.updateFloatingTitle(true);
+
if (!client_only)
xhrPost("backend.php", query, (transport) => {
App.handleRpcJson(transport);
@@ -637,6 +815,7 @@ define(["dojo/_base/declare"], function (declare) {
xhrPost("backend.php",
{op: "rpc", method: "catchupSelected", cmode: cmode, ids: id}, (transport) => {
App.handleRpcJson(transport);
+ Headlines.updateFloatingTitle(true);
});
}
},
@@ -834,10 +1013,6 @@ define(["dojo/_base/declare"], function (declare) {
return;
}
- for (let i = 0; i < rows.length; i++) {
- ArticleCache.del(rows[i]);
- }
-
const query = {op: "rpc", method: op, ids: rows.toString()};
xhrPost("backend.php", query, (transport) => {
diff --git a/js/PluginHost.js b/js/PluginHost.js
index 8e5ff32ec..f76b73464 100644
--- a/js/PluginHost.js
+++ b/js/PluginHost.js
@@ -11,6 +11,8 @@ PluginHost = {
HOOK_PARAMS_LOADED: 8,
HOOK_RUNTIME_INFO_LOADED: 9,
HOOK_FLOATING_TITLE: 10,
+ HOOK_INIT_COMPLETE: 11,
+ HOOK_HEADLINE_RENDERED: 12,
hooks: [],
register: function (name, callback) {
if (typeof(this.hooks[name]) == 'undefined')
@@ -22,8 +24,9 @@ PluginHost = {
//console.warn('PluginHost::run ' + name);
if (typeof(this.hooks[name]) != 'undefined')
- for (let i = 0; i < this.hooks[name].length; i++)
- if (!this.hooks[name][i](args)) break;
+ for (let i = 0; i < this.hooks[name].length; i++) {
+ this.hooks[name][i](args);
+ }
}
};
diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js
index 44791ba46..c2c7751bf 100644
--- a/js/PrefFeedTree.js
+++ b/js/PrefFeedTree.js
@@ -76,8 +76,12 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
this.tree.model.store.save();
},
getRowClass: function (item, opened) {
- return (!item.error || item.error == '') ? "dijitTreeRow" :
+ let rc = (!item.error || item.error == '') ? "dijitTreeRow" :
"dijitTreeRow Error";
+
+ if (item.updates_disabled > 0) rc += " UpdatesDisabled";
+
+ return rc;
},
getIconClass: function (item, opened) {
return (!item || this.model.store.getValue(item, 'type') == 'category') ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "feed-icon";
@@ -369,7 +373,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
title: __("Feeds without recent updates"),
style: "width: 600px",
getSelectedFeeds: function () {
- return Tables.getSelected("prefInactiveFeedList");
+ return Tables.getSelected("inactive-feeds-list");
},
removeSelected: function () {
const sel_rows = this.getSelectedFeeds();
diff --git a/js/PrefFilterTree.js b/js/PrefFilterTree.js
index f4fc8ecf2..0a8a2aa43 100644
--- a/js/PrefFilterTree.js
+++ b/js/PrefFilterTree.js
@@ -39,6 +39,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
const feed = this.model.store.getValue(item, 'feed');
const inverse = this.model.store.getValue(item, 'inverse');
+ const last_triggered = this.model.store.getValue(item, 'last_triggered');
if (feed)
label += " (" + __("in") + " " + feed + ")";
@@ -46,18 +47,18 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
if (inverse)
label += " (" + __("Inverse") + ")";
+ if (last_triggered)
+ label += " — " + last_triggered;
+
return label;
},
getIconClass: function (item, opened) {
return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "invisible";
},
- getLabelClass: function (item, opened) {
- const enabled = this.model.store.getValue(item, 'enabled');
- return (enabled != false) ? "dijitTreeLabel labelFixedLength" : "dijitTreeLabel labelFixedLength filterDisabled";
- },
getRowClass: function (item, opened) {
- return (!item.error || item.error == '') ? "dijitTreeRow" :
- "dijitTreeRow Error";
+ const enabled = this.model.store.getValue(item, 'enabled');
+
+ return enabled ? "dijitTreeRow" : "dijitTreeRow filterDisabled";
},
checkItemAcceptance: function(target, source, position) {
const item = dijit.getEnclosingWidget(target).item;
@@ -100,7 +101,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
});
},
joinSelectedFilters: function() {
- const rows = getSelectedFilters();
+ const rows = this.getSelectedFilters();
if (rows.length == 0) {
alert(__("No filters selected."));
@@ -153,24 +154,10 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
Filters.editFilterTest(query);
},
selectRules: function (select) {
- $$("#filterDlg_Matches input[type=checkbox]").each(function (e) {
- e.checked = select;
- if (select)
- e.parentNode.addClassName("Selected");
- else
- e.parentNode.removeClassName("Selected");
- });
+ Lists.select("filterDlg_Matches", select);
},
selectActions: function (select) {
- $$("#filterDlg_Actions input[type=checkbox]").each(function (e) {
- e.checked = select;
-
- if (select)
- e.parentNode.addClassName("Selected");
- else
- e.parentNode.removeClassName("Selected");
-
- });
+ Lists.select("filterDlg_Actions", select);
},
editRule: function (e) {
const li = e.parentNode;
@@ -183,7 +170,7 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], functio
Filters.addFilterAction(li, action);
},
removeFilter: function () {
- const msg = __("FRemove filter?");
+ const msg = __("Remove filter?");
if (confirm(msg)) {
this.hide();
diff --git a/js/PrefHelpers.js b/js/PrefHelpers.js
index 1b23ebea3..a3d122029 100644
--- a/js/PrefHelpers.js
+++ b/js/PrefHelpers.js
@@ -40,7 +40,7 @@ define(["dojo/_base/declare"], function (declare) {
title: __("Settings Profiles"),
style: "width: 600px",
getSelectedProfiles: function () {
- return Tables.getSelected("prefFeedProfileList");
+ return Tables.getSelected("pref-profiles-list");
},
removeSelected: function () {
const sel_rows = this.getSelectedProfiles();
diff --git a/js/PrefLabelTree.js b/js/PrefLabelTree.js
index 45c96af16..988e313b0 100644
--- a/js/PrefLabelTree.js
+++ b/js/PrefLabelTree.js
@@ -18,18 +18,21 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dijit/f
const bare_id = this.model.store.getValue(args.item, 'bare_id');
if (type == 'label') {
- const span = dojo.doc.createElement('span');
- span.innerHTML = '&alpha;';
- span.className = 'labelColorIndicator';
- span.id = 'LICID-' + bare_id;
+ const label = dojo.doc.createElement('i');
+ //const fg_color = args.item.fg_color[0];
+ const bg_color = String(args.item.bg_color);
- span.setStyle({
- color: fg_color,
- backgroundColor: bg_color});
+ label.className = "material-icons icon-label";
+ label.id = 'icon-label-' + String(args.item.bare_id);
+ label.innerHTML = "label";
+ label.setStyle({
+ color: bg_color,
+ });
- tnode._labelIconNode = span;
+ domConstruct.place(label, tnode.iconNode, 'before');
- domConstruct.place(tnode._labelIconNode, tnode.labelNode, 'before');
+ //tnode._labelIconNode = span;
+ //domConstruct.place(tnode._labelIconNode, tnode.labelNode, 'before');
}
return tnode;
@@ -80,11 +83,10 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dijit/f
color = bg;
}
- const e = $("LICID-" + id);
+ const e = $("icon-label-" + id);
if (e) {
- if (fg) e.style.color = fg;
- if (bg) e.style.backgroundColor = bg;
+ if (bg) e.style.color = bg;
}
const query = {
diff --git a/js/common.js b/js/common.js
index 427e3034c..788c159fe 100755
--- a/js/common.js
+++ b/js/common.js
@@ -56,7 +56,23 @@ const Lists = {
if (row)
checked ? row.addClassName("Selected") : row.removeClassName("Selected");
- }
+ },
+ select: function(elemId, selected) {
+ $(elemId).select("li").each((row) => {
+ const checkNode = row.select(".dijitCheckBox,input[type=checkbox]")[0];
+ if (checkNode) {
+ const widget = dijit.getEnclosingWidget(checkNode);
+
+ if (widget) {
+ widget.attr("checked", selected);
+ } else {
+ checkNode.checked = selected;
+ }
+
+ this.onRowChecked(widget);
+ }
+ });
+ },
};
// noinspection JSUnusedGlobalSymbols
@@ -154,8 +170,7 @@ const Notify = {
}
let msgfmt = "<span class=\"msg\">%s</span>".replace("%s", __(msg));
- let icon = false;
-
+ let icon = "";
notify.className = "notify";
@@ -164,23 +179,28 @@ const Notify = {
switch (kind) {
case this.KIND_INFO:
notify.addClassName("notify_info")
- icon = App.getInitParam("icon_information");
+ icon = "notifications";
break;
case this.KIND_ERROR:
notify.addClassName("notify_error");
- icon = App.getInitParam("icon_alert");
+ icon = "error";
break;
case this.KIND_PROGRESS:
notify.addClassName("notify_progress");
icon = App.getInitParam("icon_indicator_white")
break;
+ default:
+ icon = "notifications";
}
- if (icon) msgfmt = "<span><img src=\"%s\"></span>".replace("%s", icon) + msgfmt;
+ if (icon)
+ if (icon.indexOf("data:image") != -1)
+ msgfmt = "<img src=\"%s\">".replace("%s", icon) + msgfmt;
+ else
+ msgfmt = "<i class='material-icons icon-notify'>%s</i>".replace("%s", icon) + msgfmt;
- msgfmt += (" <span><img src=\"%s\" class='close' title=\"" +
- __("Click to close") + "\" onclick=\"Notify.close()\"></span>")
- .replace("%s", App.getInitParam("icon_cross"));
+ msgfmt += "<i class='material-icons icon-close' title=\"" +
+ __("Click to close") + "\" onclick=\"Notify.close()\">close</i>";
notify.innerHTML = msgfmt;
notify.addClassName("visible");
@@ -306,6 +326,21 @@ function popupOpenArticle(id) {
"ttrss_article_popup",
"height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no");
- w.opener = null;
- w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + App.getInitParam("csrf_token");
+ if (w) {
+ w.opener = null;
+ w.location = "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id + "&csrf_token=" + App.getInitParam("csrf_token");
+ }
}
+
+// htmlspecialchars()-alike for headlines data-content attribute
+function escapeHtml(text) {
+ const map = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ };
+
+ return text.replace(/[&<>"']/g, function(m) { return map[m]; });
+} \ No newline at end of file
diff --git a/js/prefs.js b/js/prefs.js
index afbdee0c9..58a59fc24 100755
--- a/js/prefs.js
+++ b/js/prefs.js
@@ -132,6 +132,8 @@ require(["dojo/_base/kernel",
case "help_dialog":
App.helpDialog("main");
return false;
+ case "toggle_night_mode":
+ App.toggleNightMode();
default:
console.log("unhandled action: " + action_name + "; keycode: " + event.which);
}
diff --git a/js/tt-rss.js b/js/tt-rss.js
index 8e5dac811..890e35b55 100644
--- a/js/tt-rss.js
+++ b/js/tt-rss.js
@@ -7,7 +7,6 @@ let Filters;
let Feeds;
let Headlines;
let Article;
-let ArticleCache;
let PluginHost;
const Plugins = {};
@@ -54,7 +53,6 @@ require(["dojo/_base/kernel",
"fox/Feeds",
"fox/Headlines",
"fox/Article",
- "fox/ArticleCache",
"fox/FeedStoreModel",
"fox/FeedTree"], function (dojo, declare, ready, parser, AppBase) {
@@ -119,8 +117,6 @@ require(["dojo/_base/kernel",
}
});
- Cookie.delete("ttrss_test");
-
const toolbar = document.forms["toolbar-main"];
dijit.getEnclosingWidget(toolbar.view_mode).attr('value',
@@ -138,8 +134,6 @@ require(["dojo/_base/kernel",
App.setLoadingProgress(50);
- ArticleCache.clear();
-
this._widescreen_mode = App.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode);
@@ -151,6 +145,9 @@ require(["dojo/_base/kernel",
}
console.log("second stage ok");
+
+ PluginHost.run(PluginHost.HOOK_INIT_COMPLETE, null);
+
},
updateTitle: function() {
let tmp = "Tiny Tiny RSS";
@@ -162,7 +159,6 @@ require(["dojo/_base/kernel",
document.title = tmp;
},
onViewModeChanged: function() {
- ArticleCache.clear();
return Feeds.reloadCurrent('');
},
isCombinedMode: function() {
@@ -184,7 +180,7 @@ require(["dojo/_base/kernel",
}
},
switchPanelMode: function(wide) {
- if (App.isCombinedMode()) return;
+ //if (App.isCombinedMode()) return;
const article_id = Article.getActive();
@@ -470,6 +466,9 @@ require(["dojo/_base/kernel",
Feeds.reloadCurrent();
});
};
+ this.hotkey_actions["toggle_night_mode"] = function () {
+ App.toggleNightMode();
+ };
},
onActionSelected: function(opid) {
switch (opid) {
@@ -535,6 +534,9 @@ require(["dojo/_base/kernel",
alert(__("Widescreen is not available in combined mode."));
}
break;
+ case "qmcToggleNightMode":
+ App.toggleNightMode();
+ break;
case "qmcHKhelp":
App.helpDialog("main");
break;