render article on the client using headlines data
This commit is contained in:
parent
41e967136f
commit
bd66a9ef28
|
@ -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) {
|
||||
|
|
|
@ -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"] = "";
|
||||
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"]);
|
||||
|
||||
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"]);
|
||||
|
||||
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('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"]);
|
||||
|
||||
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']]);
|
||||
|
||||
if ($tmp_line = $ofgh->fetch()) {
|
||||
$line["orig_feed"] = [ $tmp_line["title"], $tmp_line["site_url"], $tmp_line["feed_url"] ];
|
||||
}
|
||||
}
|
||||
$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["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);
|
||||
}
|
||||
|
||||
$line['content'] = rewrite_cached_urls($line['content']);
|
||||
|
||||
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('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"]);
|
||||
|
||||
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']]);
|
||||
|
||||
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));
|
||||
|
||||
|
|
|
@ -137,58 +137,65 @@ define(["dojo/_base/declare"], function (declare) {
|
|||
} 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) {
|
||||
this.setActive(id);
|
||||
|
||||
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) => {
|
||||
if (!ArticleCache.get(n))
|
||||
cids.push(n);
|
||||
});
|
||||
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>`;
|
||||
|
||||
const cached_article = ArticleCache.get(id);
|
||||
|
||||
if (cached_article) {
|
||||
console.log('rendering cached', id);
|
||||
this.render(cached_article);
|
||||
return false;
|
||||
Headlines.toggleUnread(id, 0);
|
||||
this.render(article);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -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) => {
|
||||
|
|
13
js/common.js
13
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]; });
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -18,8 +18,6 @@ Plugins.Note = {
|
|||
dialog.hide();
|
||||
|
||||
if (reply) {
|
||||
ArticleCache.del(id);
|
||||
|
||||
const elem = $("POSTNOTE-" + id);
|
||||
|
||||
if (elem) {
|
||||
|
|
Loading…
Reference in New Issue