render article on the client using headlines data

This commit is contained in:
Andrew Dolgov 2018-12-08 09:32:14 +03:00
parent 41e967136f
commit bd66a9ef28
8 changed files with 135 additions and 156 deletions

View File

@ -27,6 +27,7 @@ class Article extends Handler_Protected {
} }
} }
/*
function view() { function view() {
$id = clean($_REQUEST["id"]); $id = clean($_REQUEST["id"]);
$cids = explode(",", clean($_REQUEST["cids"])); $cids = explode(",", clean($_REQUEST["cids"]));
@ -63,8 +64,9 @@ class Article extends Handler_Protected {
} }
print json_encode($articles); print json_encode($articles);
} } */
/*
private function catchupArticleById($id, $cmode) { private function catchupArticleById($id, $cmode) {
if ($cmode == 0) { if ($cmode == 0) {
@ -86,6 +88,7 @@ class Article extends Handler_Protected {
$feed_id = $this->getArticleFeed($id); $feed_id = $this->getArticleFeed($id);
CCache::update($feed_id, $_SESSION["uid"]); CCache::update($feed_id, $_SESSION["uid"]);
} }
*/
static function create_published_article($title, $url, $content, $labels_str, static function create_published_article($title, $url, $content, $labels_str,
$owner_uid) { $owner_uid) {

View File

@ -285,8 +285,6 @@ class Feeds extends Handler_Protected {
if (!$line["feed_title"]) $line["feed_title"] = ""; if (!$line["feed_title"]) $line["feed_title"] = "";
if (get_pref('COMBINED_DISPLAY_MODE')) {
$line["buttons_left"] = ""; $line["buttons_left"] = "";
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) { foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
$line["buttons_left"] .= $p->hook_article_left_button($line); $line["buttons_left"] .= $p->hook_article_left_button($line);
@ -305,7 +303,6 @@ class Feeds extends Handler_Protected {
} }
$line['content'] = rewrite_cached_urls($line['content']); $line['content'] = rewrite_cached_urls($line['content']);
$line["content"] = htmlspecialchars($line["content"]);
if ($line['note']) if ($line['note'])
$line['note'] = Article::format_article_note($id, $line['note']); $line['note'] = Article::format_article_note($id, $line['note']);
@ -335,10 +332,11 @@ class Feeds extends Handler_Protected {
$line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ]; $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["updated"] = make_local_datetime($line["updated"], false, false, false, true);
$line['imported'] = T_sprintf("Imported at %s", $line['imported'] = T_sprintf("Imported at %s",
make_local_datetime($line["date_entered"], false)); make_local_datetime($line["date_entered"], false));

View File

@ -137,58 +137,65 @@ define(["dojo/_base/declare"], function (declare) {
} catch (e) { } catch (e) {
} }
}, },
formatComments: function(hl) {
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>`;
}
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) { view: function (id, noexpand) {
this.setActive(id); this.setActive(id);
if (!noexpand) { if (!noexpand) {
console.log("loading article", id); const hl = Headlines.objectById(id);
const cids = []; if (hl) {
/* only request uncached articles */ const comments = this.formatComments(hl);
const originally_from = this.formatOriginallyFrom(hl);
this.getRelativeIds(id).each((n) => { const article = `<div class="post post-${hl.id}">
if (!ArticleCache.get(n)) <div class="header">
cids.push(n); <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>`;
const cached_article = ArticleCache.get(id); Headlines.toggleUnread(id, 0);
this.render(article);
if (cached_article) {
console.log('rendering cached', id);
this.render(cached_article);
return false;
} }
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);
Notify.close();
} catch (e) {
App.Error.report(e);
}
})
} }
return false; return false;

View File

@ -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;
});

View File

@ -4,7 +4,7 @@ define(["dojo/_base/declare"], function (declare) {
Headlines = { Headlines = {
vgroup_last_feed: undefined, vgroup_last_feed: undefined,
_headlines_scroll_timeout: 0, _headlines_scroll_timeout: 0,
loaded_article_ids: [], headlines: [],
current_first_id: 0, current_first_id: 0,
catchup_id_batch: [], catchup_id_batch: [],
click: function (event, id, in_body) { 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) { renderHeadline: function (headlines, hl) {
let row = null; let row = null;
@ -266,24 +269,11 @@ define(["dojo/_base/declare"], function (declare) {
if (App.isCombinedMode()) { if (App.isCombinedMode()) {
row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable"; row_class += App.getInitParam("cdm_expanded") ? " expanded" : " expandable";
let originally_from = hl.orig_feed ? `<span> const comments = Article.formatComments(hl);
${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${hl.orig_feed[1]}">${hl.orig_feed[0]}</a> const originally_from = Article.formatOriginallyFrom(hl);
</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>`;
}
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}" 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="header">
<div class="left"> <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 class="content" onclick="return Headlines.click(event, ${hl.id}, true);">
<div id="POSTNOTE-${hl.id}">${hl.note}</div> <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"> <div class="intermediate">
${hl.enclosures} ${hl.enclosures}
</div> </div>
@ -329,7 +321,7 @@ define(["dojo/_base/declare"], function (declare) {
${hl.buttons_left} ${hl.buttons_left}
<i class="material-icons">label_outline</i> <i class="material-icons">label_outline</i>
<span id="ATSTR-${hl.id}">${hl.tags_str}</span> <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> onclick="Article.editTags(${hl.id})">(+)</a>
${comments} ${comments}
</div> </div>
@ -426,7 +418,7 @@ define(["dojo/_base/declare"], function (declare) {
this.current_first_id = reply['headlines']['first_id']; this.current_first_id = reply['headlines']['first_id'];
if (offset == 0) { if (offset == 0) {
this.loaded_article_ids = []; //this.headlines = [];
this.vgroup_last_feed = undefined; this.vgroup_last_feed = undefined;
dojo.html.set($("toolbar-headlines"), dojo.html.set($("toolbar-headlines"),
@ -439,7 +431,10 @@ define(["dojo/_base/declare"], function (declare) {
$("headlines-frame").innerHTML = ''; $("headlines-frame").innerHTML = '';
for (let i = 0; i < reply['headlines']['content'].length; i++) { 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']; $("headlines-frame").innerHTML = reply['headlines']['content'];
} else { } else {
for (let i = 0; i < reply['headlines']['content'].length; i++) { 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; return;
} }
for (let i = 0; i < rows.length; i++) {
ArticleCache.del(rows[i]);
}
const query = {op: "rpc", method: op, ids: rows.toString()}; const query = {op: "rpc", method: op, ids: rows.toString()};
xhrPost("backend.php", query, (transport) => { xhrPost("backend.php", query, (transport) => {

View File

@ -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"); 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]; });
}

View File

@ -7,7 +7,6 @@ let Filters;
let Feeds; let Feeds;
let Headlines; let Headlines;
let Article; let Article;
let ArticleCache;
let PluginHost; let PluginHost;
const Plugins = {}; const Plugins = {};
@ -54,7 +53,6 @@ require(["dojo/_base/kernel",
"fox/Feeds", "fox/Feeds",
"fox/Headlines", "fox/Headlines",
"fox/Article", "fox/Article",
"fox/ArticleCache",
"fox/FeedStoreModel", "fox/FeedStoreModel",
"fox/FeedTree"], function (dojo, declare, ready, parser, AppBase) { "fox/FeedTree"], function (dojo, declare, ready, parser, AppBase) {
@ -138,8 +136,6 @@ require(["dojo/_base/kernel",
App.setLoadingProgress(50); App.setLoadingProgress(50);
ArticleCache.clear();
this._widescreen_mode = App.getInitParam("widescreen"); this._widescreen_mode = App.getInitParam("widescreen");
this.switchPanelMode(this._widescreen_mode); this.switchPanelMode(this._widescreen_mode);
@ -162,7 +158,6 @@ require(["dojo/_base/kernel",
document.title = tmp; document.title = tmp;
}, },
onViewModeChanged: function() { onViewModeChanged: function() {
ArticleCache.clear();
return Feeds.reloadCurrent(''); return Feeds.reloadCurrent('');
}, },
isCombinedMode: function() { isCombinedMode: function() {

View File

@ -18,8 +18,6 @@ Plugins.Note = {
dialog.hide(); dialog.hide();
if (reply) { if (reply) {
ArticleCache.del(id);
const elem = $("POSTNOTE-" + id); const elem = $("POSTNOTE-" + id);
if (elem) { if (elem) {