summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/Article.js99
-rw-r--r--js/ArticleCache.js29
-rw-r--r--js/Feeds.js7
-rwxr-xr-xjs/Headlines.js175
-rwxr-xr-xjs/common.js19
-rw-r--r--js/tt-rss.js7
6 files changed, 236 insertions, 100 deletions
diff --git a/js/Article.js b/js/Article.js
index 46769223e..b91501faf 100644
--- a/js/Article.js
+++ b/js/Article.js
@@ -137,58 +137,65 @@ define(["dojo/_base/declare"], function (declare) {
} catch (e) {
}
},
- view: function (id, noexpand) {
- this.setActive(id);
-
- if (!noexpand) {
- console.log("loading article", id);
+ formatComments: function(hl) {
+ let comments = "";
- const cids = [];
-
- /* only request uncached articles */
-
- this.getRelativeIds(id).each((n) => {
- if (!ArticleCache.get(n))
- cids.push(n);
- });
+ if (hl.comments) {
+ let comments_msg = __("comments");
- 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/Feeds.js b/js/Feeds.js
index 5f7765119..52c1476f7 100644
--- a/js/Feeds.js
+++ b/js/Feeds.js
@@ -340,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";
}
@@ -363,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)
));
}
diff --git a/js/Headlines.js b/js/Headlines.js
index 2b33ed396..0c8ce5abf 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) {
@@ -239,6 +239,140 @@ 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 && 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" onclick="Feeds.catchupFeedInGroup(${hl.feed_id})" href="#">${__('mark feed as read')}</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>
+ `;
+ }
+
+ if (row != null) {
+ const tmp = document.createElement("div");
+ tmp.innerHTML = row;
+ dojo.parser.parse(tmp);
+
+ $("headlines-frame").appendChild(tmp.firstChild);
+ }
+ },
onLoaded: function (transport, offset) {
const reply = App.handleRpcJson(transport);
@@ -280,19 +414,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);
@@ -304,7 +450,7 @@ define(["dojo/_base/declare"], function (declare) {
this.loaded_article_ids.push(row.id);
}
- }
+ } */
let hsp = $("headlines-spacer");
@@ -336,7 +482,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);
@@ -348,6 +494,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) {
@@ -855,10 +1012,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/common.js b/js/common.js
index 5b2812be6..788c159fe 100755
--- a/js/common.js
+++ b/js/common.js
@@ -326,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/tt-rss.js b/js/tt-rss.js
index 8e5dac811..402160e02 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) {
@@ -138,8 +136,6 @@ require(["dojo/_base/kernel",
App.setLoadingProgress(50);
- ArticleCache.clear();
-
this._widescreen_mode = App.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode);
@@ -162,7 +158,6 @@ require(["dojo/_base/kernel",
document.title = tmp;
},
onViewModeChanged: function() {
- ArticleCache.clear();
return Feeds.reloadCurrent('');
},
isCombinedMode: function() {
@@ -184,7 +179,7 @@ require(["dojo/_base/kernel",
}
},
switchPanelMode: function(wide) {
- if (App.isCombinedMode()) return;
+ //if (App.isCombinedMode()) return;
const article_id = Article.getActive();