diff options
author | Andrew Dolgov <[email protected]> | 2018-12-08 09:32:14 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2018-12-08 09:32:14 +0300 |
commit | bd66a9ef28ddf25e014e852e5ee770868f619aaa (patch) | |
tree | 1e101b88ff13c1aaeaaa743a1c582a828327a90c | |
parent | 41e967136f6391ff6d7a7c20af47dc434d853099 (diff) |
render article on the client using headlines data
-rwxr-xr-x | classes/article.php | 5 | ||||
-rwxr-xr-x | classes/feeds.php | 78 | ||||
-rw-r--r-- | js/Article.js | 99 | ||||
-rw-r--r-- | js/ArticleCache.js | 29 | ||||
-rwxr-xr-x | js/Headlines.js | 46 | ||||
-rwxr-xr-x | js/common.js | 13 | ||||
-rw-r--r-- | js/tt-rss.js | 5 | ||||
-rw-r--r-- | plugins/note/note.js | 2 |
8 files changed, 128 insertions, 149 deletions
diff --git a/classes/article.php b/classes/article.php index 2624a3d67..3ddf66d5c 100755 --- a/classes/article.php +++ b/classes/article.php @@ -27,6 +27,7 @@ class Article extends Handler_Protected { } } + /* function view() { $id = clean($_REQUEST["id"]); $cids = explode(",", clean($_REQUEST["cids"])); @@ -63,8 +64,9 @@ class Article extends Handler_Protected { } print json_encode($articles); - } + } */ + /* private function catchupArticleById($id, $cmode) { if ($cmode == 0) { @@ -86,6 +88,7 @@ class Article extends Handler_Protected { $feed_id = $this->getArticleFeed($id); CCache::update($feed_id, $_SESSION["uid"]); } + */ static function create_published_article($title, $url, $content, $labels_str, $owner_uid) { diff --git a/classes/feeds.php b/classes/feeds.php index a5810cdaa..6b499e65c 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -285,60 +285,58 @@ class Feeds extends Handler_Protected { if (!$line["feed_title"]) $line["feed_title"] = ""; - if (get_pref('COMBINED_DISPLAY_MODE')) { - - $line["buttons_left"] = ""; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { - $line["buttons_left"] .= $p->hook_article_left_button($line); - } + $line["buttons_left"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { + $line["buttons_left"] .= $p->hook_article_left_button($line); + } - $line["buttons"] = ""; - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { - $line["buttons"] .= $p->hook_article_button($line); - } + $line["buttons"] = ""; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) { + $line["buttons"] .= $p->hook_article_button($line); + } - $line["content"] = sanitize($line["content"], - $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); + $line["content"] = sanitize($line["content"], + $line['hide_images'], false, $line["site_url"], $highlight_words, $line["id"]); - foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { - $line = $p->hook_render_article_cdm($line); - } + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_CDM) as $p) { + $line = $p->hook_render_article_cdm($line); + } - $line['content'] = rewrite_cached_urls($line['content']); - $line["content"] = htmlspecialchars($line["content"]); + $line['content'] = rewrite_cached_urls($line['content']); - if ($line['note']) - $line['note'] = Article::format_article_note($id, $line['note']); - else - $line['note'] = ""; + if ($line['note']) + $line['note'] = Article::format_article_note($id, $line['note']); + else + $line['note'] = ""; - if (!get_pref("CDM_EXPANDED")) { - $line["cdm_excerpt"] = "<span class='collapse'> - <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' - title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; + if (!get_pref("CDM_EXPANDED")) { + $line["cdm_excerpt"] = "<span class='collapse'> + <i class='material-icons' onclick='return Article.cdmUnsetActive(event)' + title=\"" . __("Collapse article") . "\">remove_circle</i></span>"; - if (get_pref('SHOW_CONTENT_PREVIEW')) { - $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; - } - } + if (get_pref('SHOW_CONTENT_PREVIEW')) { + $line["cdm_excerpt"] .= "<span class='excerpt'>" . $line["content_preview"] . "</span>"; + } + } - $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], - $line["content"], $line["hide_images"]); + $line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"], + $line["content"], $line["hide_images"]); - if ($line["orig_feed_id"]) { + if ($line["orig_feed_id"]) { - $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); + $ofgh = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds + WHERE id = ? AND owner_uid = ?"); + $ofgh->execute([$line["orig_feed_id"], $_SESSION['uid']]); - if ($tmp_line = $ofgh->fetch()) { - $line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; - } - } + if ($tmp_line = $ofgh->fetch()) { + $line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; + } } - + $line["updated_long"] = make_local_datetime($line["updated"],true); $line["updated"] = make_local_datetime($line["updated"], false, false, false, true); + + $line['imported'] = T_sprintf("Imported at %s", make_local_datetime($line["date_entered"], false)); 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> + <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/Headlines.js b/js/Headlines.js index e2f75297d..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,9 @@ define(["dojo/_base/declare"], function (declare) { } } }, + objectById: function (id){ + return this.headlines[id]; + }, renderHeadline: function (headlines, hl) { let row = null; @@ -266,24 +269,11 @@ define(["dojo/_base/declare"], function (declare) { if (App.isCombinedMode()) { row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable"; - let originally_from = hl.orig_feed ? `<span> - ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${hl.orig_feed[1]}">${hl.orig_feed[0]}</a> - </span>` : ""; - - let comments = ""; - - if (hl.comments) { - let comments_msg = __("comments"); - - if (hl.num_comments > 0) { - comments_msg = hl.num_comments + " " + ngettext("comment", "comments", hl.num_comments) - } - - comments = `<a href="${hl.comments}">(${comments_msg})</a>`; - } + 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="${hl.content}" onmouseover="Article.mouseIn(${hl.id})" onmouseout="Article.mouseOut(${hl.id})"> + data-content="${escapeHtml(hl.content)}" onmouseover="Article.mouseIn(${hl.id})" onmouseout="Article.mouseOut(${hl.id})"> <div class="header"> <div class="left"> @@ -319,7 +309,9 @@ define(["dojo/_base/declare"], function (declare) { <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'}"></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> @@ -329,7 +321,7 @@ define(["dojo/_base/declare"], function (declare) { ${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="#" + <a title="${__("Edit tags for this article")}" href="#" onclick="Article.editTags(${hl.id})">(+)</a> ${comments} </div> @@ -426,7 +418,7 @@ define(["dojo/_base/declare"], function (declare) { 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"), @@ -439,7 +431,10 @@ define(["dojo/_base/declare"], function (declare) { $("headlines-frame").innerHTML = ''; for (let i = 0; i < reply['headlines']['content'].length; i++) { - this.renderHeadline(reply['headlines'], reply['headlines']['content'][i]); + const hl = reply['headlines']['content'][i]; + + this.renderHeadline(reply['headlines'], hl); + this.headlines[parseInt(hl.id)] = hl; } } @@ -505,7 +500,10 @@ define(["dojo/_base/declare"], function (declare) { $("headlines-frame").innerHTML = reply['headlines']['content']; } else { for (let i = 0; i < reply['headlines']['content'].length; i++) { - this.renderHeadline(reply['headlines'], reply['headlines']['content'][i]); + const hl = reply['headlines']['content'][i]; + + this.renderHeadline(reply['headlines'], hl); + this.headlines[parseInt(hl.id)] = hl; } } @@ -1014,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 00bac636b..788c159fe 100755 --- a/js/common.js +++ b/js/common.js @@ -331,3 +331,16 @@ function popupOpenArticle(id) { 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/tt-rss.js b/js/tt-rss.js index 8e5dac811..2f04fb201 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() { diff --git a/plugins/note/note.js b/plugins/note/note.js index 7ea76798c..0c811000d 100644 --- a/plugins/note/note.js +++ b/plugins/note/note.js @@ -18,8 +18,6 @@ Plugins.Note = { dialog.hide(); if (reply) { - ArticleCache.del(id); - const elem = $("POSTNOTE-" + id); if (elem) { |