diff options
Diffstat (limited to 'js')
| -rw-r--r-- | js/AppBase.js | 59 | ||||
| -rw-r--r-- | js/Article.js | 142 | ||||
| -rw-r--r-- | js/ArticleCache.js | 29 | ||||
| -rw-r--r-- | js/CommonDialogs.js | 2 | ||||
| -rw-r--r-- | js/CommonFilters.js | 18 | ||||
| -rwxr-xr-x | js/FeedTree.js | 16 | ||||
| -rw-r--r-- | js/Feeds.js | 44 | ||||
| -rwxr-xr-x | js/Headlines.js | 225 | ||||
| -rw-r--r-- | js/PluginHost.js | 7 | ||||
| -rw-r--r-- | js/PrefFeedTree.js | 8 | ||||
| -rw-r--r-- | js/PrefFilterTree.js | 35 | ||||
| -rw-r--r-- | js/PrefHelpers.js | 2 | ||||
| -rw-r--r-- | js/PrefLabelTree.js | 26 | ||||
| -rwxr-xr-x | js/common.js | 57 | ||||
| -rwxr-xr-x | js/prefs.js | 2 | ||||
| -rw-r--r-- | js/tt-rss.js | 18 |
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> + <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 = "α"; - 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 = 'α'; - 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + 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; |