render enclosures on the client
This commit is contained in:
parent
916c21fe60
commit
82adb01307
|
@ -311,8 +311,6 @@ class API extends Handler {
|
|||
|
||||
while ($line = $sth->fetch()) {
|
||||
|
||||
$attachments = Article::get_article_enclosures($line['id']);
|
||||
|
||||
$article = array(
|
||||
"id" => $line["id"],
|
||||
"guid" => $line["guid"],
|
||||
|
@ -326,7 +324,7 @@ class API extends Handler {
|
|||
"author" => $line["author"],
|
||||
"updated" => (int) strtotime($line["updated"]),
|
||||
"feed_id" => $line["feed_id"],
|
||||
"attachments" => $attachments,
|
||||
"attachments" => Article::get_enclosures($line['id']),
|
||||
"score" => (int)$line["score"],
|
||||
"feed_title" => $line["feed_title"],
|
||||
"note" => $line["note"],
|
||||
|
@ -736,7 +734,7 @@ class API extends Handler {
|
|||
"tags" => $tags,
|
||||
);
|
||||
|
||||
$enclosures = Article::get_article_enclosures($line['id']);
|
||||
$enclosures = Article::get_enclosures($line['id']);
|
||||
|
||||
if ($include_attachments)
|
||||
$headline_row['attachments'] = $enclosures;
|
||||
|
|
|
@ -352,145 +352,62 @@ class Article extends Handler_Protected {
|
|||
}
|
||||
}
|
||||
|
||||
static function format_article_enclosures($id, $always_display_enclosures,
|
||||
$article_content, $hide_images = false) {
|
||||
static function format_enclosures($id,
|
||||
$always_display_enclosures,
|
||||
$article_content,
|
||||
$hide_images = false) {
|
||||
|
||||
$result = self::get_article_enclosures($id);
|
||||
$rv = '';
|
||||
$enclosures = self::get_enclosures($id);
|
||||
$rv = [];
|
||||
$enclosures_formatted = "";
|
||||
|
||||
/*foreach ($enclosures as &$enc) {
|
||||
array_push($enclosures, [
|
||||
"type" => $enc["content_type"],
|
||||
"filename" => basename($enc["content_url"]),
|
||||
"url" => $enc["content_url"],
|
||||
"title" => $enc["title"],
|
||||
"width" => (int) $enc["width"],
|
||||
"height" => (int) $enc["height"]
|
||||
]);
|
||||
}*/
|
||||
|
||||
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_FORMAT_ENCLOSURES,
|
||||
function ($result) use (&$rv) {
|
||||
function ($result) use (&$enclosures_formatted, &$enclosures) {
|
||||
if (is_array($result)) {
|
||||
$rv = $result[0];
|
||||
$result = $result[1];
|
||||
$enclosures_formatted = $result[0];
|
||||
$enclosures = $result[1];
|
||||
} else {
|
||||
$rv = $result;
|
||||
$enclosures_formatted = $result;
|
||||
}
|
||||
},
|
||||
$rv, $result, $id, $always_display_enclosures, $article_content, $hide_images);
|
||||
$enclosures_formatted, $enclosures, $id, $always_display_enclosures, $article_content, $hide_images);
|
||||
|
||||
if ($rv === '' && !empty($result)) {
|
||||
$entries_html = array();
|
||||
$entries = array();
|
||||
$entries_inline = array();
|
||||
if (!empty($enclosures_formatted)) {
|
||||
$rv['formatted'] = $enclosures_formatted;
|
||||
return $rv;
|
||||
}
|
||||
|
||||
foreach ($result as $line) {
|
||||
$rv['can_inline'] = isset($_SESSION["uid"]) &&
|
||||
empty($_SESSION["bw_limit"]) &&
|
||||
!get_pref("STRIP_IMAGES") &&
|
||||
($always_display_enclosures || !preg_match("/<img/i", $article_content));
|
||||
|
||||
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_ENCLOSURE_ENTRY,
|
||||
function($result) use (&$line) {
|
||||
$line = $result;
|
||||
},
|
||||
$line, $id);
|
||||
$rv['inline_text_only'] = $hide_images && $rv['can_inline'];
|
||||
|
||||
$url = $line["content_url"];
|
||||
$ctype = $line["content_type"];
|
||||
$title = $line["title"];
|
||||
$width = $line["width"];
|
||||
$height = $line["height"];
|
||||
$rv['entries'] = [];
|
||||
|
||||
if (!$ctype) $ctype = __("unknown type");
|
||||
foreach ($enclosures as $enc) {
|
||||
|
||||
//$filename = substr($url, strrpos($url, "/")+1);
|
||||
$filename = basename($url);
|
||||
// this is highly approximate
|
||||
$enc["filename"] = basename($enc["content_url"]);
|
||||
|
||||
$player = format_inline_player($url, $ctype);
|
||||
|
||||
if ($player) array_push($entries_inline, $player);
|
||||
|
||||
# $entry .= " <a target=\"_blank\" href=\"" . htmlspecialchars($url) . "\" rel=\"noopener noreferrer\">" .
|
||||
# $filename . " (" . $ctype . ")" . "</a>";
|
||||
|
||||
$entry = "<div onclick=\"Article.popupOpenUrl('".htmlspecialchars($url)."')\"
|
||||
dojoType=\"dijit.MenuItem\">$filename ($ctype)</div>";
|
||||
|
||||
array_push($entries_html, $entry);
|
||||
|
||||
$entry = array();
|
||||
|
||||
$entry["type"] = $ctype;
|
||||
$entry["filename"] = $filename;
|
||||
$entry["url"] = $url;
|
||||
$entry["title"] = $title;
|
||||
$entry["width"] = $width;
|
||||
$entry["height"] = $height;
|
||||
|
||||
array_push($entries, $entry);
|
||||
}
|
||||
|
||||
if ($_SESSION['uid'] && !get_pref("STRIP_IMAGES") && !$_SESSION["bw_limit"]) {
|
||||
if ($always_display_enclosures ||
|
||||
!preg_match("/<img/i", $article_content)) {
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
|
||||
$retval = null;
|
||||
|
||||
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_RENDER_ENCLOSURE,
|
||||
function($result) use (&$retval) {
|
||||
$retval = $result;
|
||||
},
|
||||
$entry, $hide_images);
|
||||
|
||||
if (!empty($retval)) {
|
||||
$rv .= $retval;
|
||||
} else {
|
||||
|
||||
if (preg_match("/image/", $entry["type"])) {
|
||||
|
||||
if (!$hide_images) {
|
||||
$encsize = '';
|
||||
if ($entry['height'] > 0)
|
||||
$encsize .= ' height="' . intval($entry['height']) . '"';
|
||||
if ($entry['width'] > 0)
|
||||
$encsize .= ' width="' . intval($entry['width']) . '"';
|
||||
$rv .= "<p><img loading=\"lazy\"
|
||||
alt=\"".htmlspecialchars($entry["filename"])."\"
|
||||
src=\"" .htmlspecialchars($entry["url"]) . "\"
|
||||
" . $encsize . " /></p>";
|
||||
} else {
|
||||
$rv .= "<p><a target=\"_blank\" rel=\"noopener noreferrer\"
|
||||
href=\"".htmlspecialchars($entry["url"])."\"
|
||||
>" .htmlspecialchars($entry["url"]) . "</a></p>";
|
||||
}
|
||||
|
||||
if ($entry['title']) {
|
||||
$rv.= "<div class=\"enclosure_title\">${entry['title']}</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($entries_inline) > 0) {
|
||||
//$rv .= "<hr clear='both'/>";
|
||||
foreach ($entries_inline as $entry) { $rv .= $entry; };
|
||||
$rv .= "<br clear='both'/>";
|
||||
}
|
||||
|
||||
$rv .= "<div class=\"attachments\" dojoType=\"fox.form.DropDownButton\">".
|
||||
"<span>" . __('Attachments')."</span>";
|
||||
|
||||
$rv .= "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry["title"])
|
||||
$title = " — " . truncate_string($entry["title"], 30);
|
||||
else
|
||||
$title = "";
|
||||
|
||||
if ($entry["filename"])
|
||||
$filename = truncate_middle(htmlspecialchars($entry["filename"]), 60);
|
||||
else
|
||||
$filename = "";
|
||||
|
||||
$rv .= "<div onclick='Article.popupOpenUrl(\"".htmlspecialchars($entry["url"])."\")'
|
||||
dojoType=\"dijit.MenuItem\">".$filename . $title."</div>";
|
||||
|
||||
};
|
||||
|
||||
$rv .= "</div>";
|
||||
$rv .= "</div>";
|
||||
PluginHost::getInstance()->chain_hooks_callback(PluginHost::HOOK_ENCLOSURE_ENTRY,
|
||||
function ($result) use (&$enc) {
|
||||
$enc = $result;
|
||||
},
|
||||
$enc, $id);
|
||||
array_push($rv['entries'], $enc);
|
||||
}
|
||||
|
||||
return $rv;
|
||||
|
@ -613,7 +530,7 @@ class Article extends Handler_Protected {
|
|||
}
|
||||
}
|
||||
|
||||
static function get_article_enclosures($id) {
|
||||
static function get_enclosures($id) {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
||||
|
@ -625,7 +542,7 @@ class Article extends Handler_Protected {
|
|||
|
||||
$cache = new DiskCache("images");
|
||||
|
||||
while ($line = $sth->fetch()) {
|
||||
while ($line = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
if ($cache->exists(sha1($line["content_url"]))) {
|
||||
$line["content_url"] = $cache->getUrl(sha1($line["content_url"]));
|
||||
|
|
|
@ -283,8 +283,10 @@ class Feeds extends Handler_Protected {
|
|||
|
||||
$this->mark_timestamp(" pre-enclosures");
|
||||
|
||||
$line["enclosures"] = Article::format_article_enclosures($id, $line["always_display_enclosures"],
|
||||
$line["content"], $line["hide_images"]);
|
||||
$line["enclosures"] = Article::format_enclosures($id,
|
||||
$line["always_display_enclosures"],
|
||||
$line["content"],
|
||||
$line["hide_images"]);
|
||||
|
||||
$this->mark_timestamp(" enclosures");
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class Handler_Public extends Handler {
|
|||
$tpl->addBlock('category');
|
||||
}
|
||||
|
||||
$enclosures = Article::get_article_enclosures($line["id"]);
|
||||
$enclosures = Article::get_enclosures($line["id"]);
|
||||
|
||||
if (count($enclosures) > 0) {
|
||||
foreach ($enclosures as $e) {
|
||||
|
@ -218,7 +218,7 @@ class Handler_Public extends Handler {
|
|||
}
|
||||
}
|
||||
|
||||
$enclosures = Article::get_article_enclosures($line["id"]);
|
||||
$enclosures = Article::get_enclosures($line["id"]);
|
||||
|
||||
if (count($enclosures) > 0) {
|
||||
$article['enclosures'] = array();
|
||||
|
@ -356,9 +356,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
$line['content'] = DiskCache::rewriteUrls($line['content']);
|
||||
|
||||
$enclosures = Article::get_article_enclosures($line["id"]);
|
||||
|
||||
header("Content-Type: text/html");
|
||||
header("Content-Type: text/html");
|
||||
|
||||
$rv .= "<!DOCTYPE html>
|
||||
<html><head>
|
||||
|
@ -392,6 +390,7 @@ class Handler_Public extends Handler {
|
|||
|
||||
$rv .= "</head>";
|
||||
|
||||
$enclosures = Article::get_enclosures($line["id"]);
|
||||
list ($og_image, $og_stream) = Article::get_article_image($enclosures, $line['content'], $line["site_url"]);
|
||||
|
||||
if ($og_image) {
|
||||
|
@ -436,10 +435,10 @@ class Handler_Public extends Handler {
|
|||
|
||||
$rv .= $line["content"];
|
||||
|
||||
$rv .= Article::format_article_enclosures($id,
|
||||
/* $rv .= Article::format_article_enclosures($id,
|
||||
$line["always_display_enclosures"],
|
||||
$line["content"],
|
||||
$line["hide_images"]);
|
||||
$line["hide_images"]); */
|
||||
|
||||
$rv .= "</div>"; # content
|
||||
|
||||
|
|
|
@ -192,9 +192,7 @@ class RPC extends Handler_Protected {
|
|||
}
|
||||
|
||||
function sanityCheck() {
|
||||
$_SESSION["hasAudio"] = clean($_REQUEST["hasAudio"]) === "true";
|
||||
$_SESSION["hasSandbox"] = clean($_REQUEST["hasSandbox"]) === "true";
|
||||
$_SESSION["hasMp3"] = clean($_REQUEST["hasMp3"]) === "true";
|
||||
$_SESSION["clientTzOffset"] = clean($_REQUEST["clientTzOffset"]);
|
||||
|
||||
$reply = array();
|
||||
|
|
|
@ -298,37 +298,6 @@ function print_error($msg) {
|
|||
return print format_error($msg);
|
||||
}
|
||||
|
||||
function format_inline_player($url, $ctype) {
|
||||
|
||||
$entry = "";
|
||||
|
||||
$url = htmlspecialchars($url);
|
||||
|
||||
if (strpos($ctype, "audio/") === 0) {
|
||||
|
||||
$entry .= "<div class='inline-player'>";
|
||||
|
||||
if ($_SESSION["hasAudio"] && (strpos($ctype, "ogg") !== false ||
|
||||
$_SESSION["hasMp3"])) {
|
||||
|
||||
$entry .= "<audio preload=\"none\" controls>
|
||||
<source type=\"$ctype\" src=\"$url\"/>
|
||||
</audio> ";
|
||||
|
||||
}
|
||||
|
||||
if ($entry) $entry .= "<a target=\"_blank\" rel=\"noopener noreferrer\"
|
||||
href=\"$url\">" . basename($url) . "</a>";
|
||||
|
||||
$entry .= "</div>";
|
||||
|
||||
return $entry;
|
||||
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function print_label_select($name, $value, $attributes = "") {
|
||||
|
||||
$pdo = Db::pdo();
|
||||
|
|
18
js/App.js
18
js/App.js
|
@ -586,6 +586,10 @@ const App = {
|
|||
isPrefs() {
|
||||
return this.is_prefs;
|
||||
},
|
||||
audioCanPlay: function(ctype) {
|
||||
const a = document.createElement('audio');
|
||||
return a.canPlayType(ctype);
|
||||
},
|
||||
init: function(parser, is_prefs) {
|
||||
this.is_prefs = is_prefs;
|
||||
window.onerror = this.Error.onWindowError;
|
||||
|
@ -604,17 +608,11 @@ const App = {
|
|||
this.initHotkeyActions();
|
||||
this.enableCsrfSupport();
|
||||
|
||||
const a = document.createElement('audio');
|
||||
const hasAudio = !!a.canPlayType;
|
||||
const hasSandbox = "sandbox" in document.createElement("iframe");
|
||||
const hasMp3 = !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
|
||||
const clientTzOffset = new Date().getTimezoneOffset() * 60;
|
||||
|
||||
const params = {
|
||||
op: "rpc", method: "sanityCheck", hasAudio: hasAudio,
|
||||
hasMp3: hasMp3,
|
||||
clientTzOffset: clientTzOffset,
|
||||
hasSandbox: hasSandbox
|
||||
op: "rpc",
|
||||
method: "sanityCheck",
|
||||
clientTzOffset: new Date().getTimezoneOffset() * 60,
|
||||
hasSandbox: "sandbox" in document.createElement("iframe")
|
||||
};
|
||||
|
||||
xhrPost("backend.php", params, (transport) => {
|
||||
|
|
|
@ -140,6 +140,62 @@ const Article = {
|
|||
|
||||
Headlines.toggleUnread(id, 0);
|
||||
},
|
||||
renderEnclosures: function (enclosures) {
|
||||
|
||||
// enclosure list was handled by backend (HOOK_FORMAT_ENCLOSURES)
|
||||
if (enclosures.formatted)
|
||||
return enclosures.formatted;
|
||||
|
||||
return `
|
||||
${enclosures.can_inline ?
|
||||
`<div class='attachments-inline'>
|
||||
${enclosures.entries.map((enc) => {
|
||||
if (!enclosures.inline_text_only) {
|
||||
if (enc.content_type && enc.content_type.indexOf("image/") != -1) {
|
||||
return `<p>
|
||||
<img loading="lazy"
|
||||
width="${enc.width ? enc.width : ''}"
|
||||
height="${enc.height ? enc.height : ''}"
|
||||
src="${App.escapeHtml(enc.content_url)}"
|
||||
title="${App.escapeHtml(enc.title ? enc.title : enc.content_url)}"/>
|
||||
</p>`
|
||||
} else if (enc.content_type && enc.content_type.indexOf("audio/") != -1 && App.audioCanPlay(enc.content_type)) {
|
||||
return `<p class='inline-player' title="${App.escapeHtml(enc.content_url)}">
|
||||
<audio preload="none" controls="controls">
|
||||
<source type="${App.escapeHtml(enc.content_type)}" src="${App.escapeHtml(enc.content_url)}"/>
|
||||
</audio>
|
||||
</p>
|
||||
`;
|
||||
} else {
|
||||
return `<p>
|
||||
<a target="_blank" href="${App.escapeHtml(enc.content_url)}"
|
||||
title="${App.escapeHtml(enc.title ? enc.title : enc.content_url)}"
|
||||
rel="noopener noreferrer">${App.escapeHtml(enc.content_url)}</a>
|
||||
</p>`
|
||||
}
|
||||
} else {
|
||||
return `<p>
|
||||
<a target="_blank" href="${App.escapeHtml(enc.content_url)}"
|
||||
title="${App.escapeHtml(enc.title ? enc.title : enc.content_url)}"
|
||||
rel="noopener noreferrer">${App.escapeHtml(enc.content_url)}</a>
|
||||
</p>`
|
||||
}
|
||||
}).join("")}
|
||||
</div>` : ''}
|
||||
${enclosures.entries.length > 0 ?
|
||||
`<div class="attachments" dojoType="fox.form.DropDownButton">
|
||||
<span>${__('Attachments')}</span>
|
||||
<div dojoType="dijit.Menu" style="display: none">
|
||||
${enclosures.entries.map((enc) => `
|
||||
<div onclick='Article.popupOpenUrl("${App.escapeHtml(enc.content_url)}")'
|
||||
title="${App.escapeHtml(enc.title ? enc.title : enc.content_url)}" dojoType="dijit.MenuItem">
|
||||
${enc.title ? enc.title : enc.filename}
|
||||
</div>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
`
|
||||
},
|
||||
render: function (article) {
|
||||
App.cleanupMemory("content-insert");
|
||||
|
||||
|
@ -241,7 +297,7 @@ const Article = {
|
|||
<div id="POSTNOTE-${hl.id}">${hl.note}</div>
|
||||
<div class="content" lang="${hl.lang ? hl.lang : 'en'}">
|
||||
${hl.content}
|
||||
${hl.enclosures}
|
||||
${Article.renderEnclosures(hl.enclosures)}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
|
|
@ -488,7 +488,7 @@ const Headlines = {
|
|||
<img src="${App.getInitParam('icon_indicator_white')}">
|
||||
</div>
|
||||
<div class="intermediate">
|
||||
${hl.enclosures}
|
||||
${Article.renderEnclosures(hl.enclosures)}
|
||||
</div>
|
||||
<div class="footer" onclick="event.stopPropagation()">
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* global Plugins, __, require, PluginHost */
|
||||
|
||||
const _shorten_expanded_threshold = 1.5; //window heights
|
||||
|
||||
Plugins.Shorten_Expanded = {
|
||||
|
@ -22,26 +24,23 @@ require(['dojo/_base/kernel', 'dojo/ready'], function (dojo, ready) {
|
|||
window.setTimeout(function() {
|
||||
if (row) {
|
||||
|
||||
const c_inner = row.select(".content-inner")[0];
|
||||
const c_inter = row.select(".intermediate")[0];
|
||||
const content = row.querySelector(".content-inner");
|
||||
const attachments = row.querySelector(".attachments-inline");
|
||||
|
||||
if (c_inner && c_inter &&
|
||||
if (content && attachments &&
|
||||
row.offsetHeight >= _shorten_expanded_threshold * window.innerHeight) {
|
||||
|
||||
let tmp = document.createElement("div");
|
||||
|
||||
c_inter.select("> *:not([class*='attachments'])").each(function(p) {
|
||||
p.parentNode.removeChild(p);
|
||||
tmp.appendChild(p);
|
||||
});
|
||||
|
||||
c_inner.innerHTML = `<div class="content-shrink-wrap">
|
||||
${c_inner.innerHTML}
|
||||
${tmp.innerHTML}</div>
|
||||
content.innerHTML = `
|
||||
<div class="content-shrink-wrap">
|
||||
${content.innerHTML}
|
||||
${attachments.innerHTML}
|
||||
</div>
|
||||
<button dojoType="dijit.form.Button" class="alt-info expand-prompt" onclick="return Plugins.Shorten_Expanded.expand('${row.id}')" href="#">
|
||||
${__("Click to expand article")}</button>`;
|
||||
|
||||
dojo.parser.parse(c_inner);
|
||||
attachments.innerHTML = "";
|
||||
|
||||
dojo.parser.parse(content);
|
||||
}
|
||||
}
|
||||
}, 150);
|
||||
|
|
Loading…
Reference in New Issue