diff options
-rw-r--r-- | CONTRIBUTING.md | 27 | ||||
-rwxr-xr-x | classes/article.php | 2 | ||||
-rw-r--r-- | classes/diskcache.php | 2 | ||||
-rwxr-xr-x | classes/feeds.php | 78 | ||||
-rwxr-xr-x | classes/handler/public.php | 19 | ||||
-rwxr-xr-x | classes/pluginhost.php | 12 | ||||
-rwxr-xr-x | classes/pref/feeds.php | 53 | ||||
-rw-r--r-- | classes/pref/prefs.php | 2 | ||||
-rwxr-xr-x | classes/rpc.php | 175 | ||||
-rwxr-xr-x | classes/rssutils.php | 15 | ||||
-rw-r--r-- | classes/userhelper.php | 4 | ||||
-rw-r--r-- | js/Article.js | 7 | ||||
-rw-r--r-- | js/CommonDialogs.js | 125 | ||||
-rwxr-xr-x | js/Headlines.js | 61 | ||||
-rw-r--r-- | plugins/af_psql_trgm/init.php | 78 | ||||
-rwxr-xr-x | plugins/af_redditimgur/init.php | 573 | ||||
-rw-r--r-- | plugins/scored_oldest_first/init.php | 35 | ||||
-rw-r--r-- | themes/compact.css | 6 | ||||
-rw-r--r-- | themes/compact_night.css | 32 | ||||
-rw-r--r-- | themes/light.css | 6 | ||||
-rw-r--r-- | themes/light/tt-rss.less | 1 | ||||
-rw-r--r-- | themes/light/utility.less | 4 | ||||
-rw-r--r-- | themes/light/zoom.less | 1 | ||||
-rw-r--r-- | themes/night.css | 32 | ||||
-rw-r--r-- | themes/night_base.less | 23 | ||||
-rw-r--r-- | themes/night_blue.css | 32 |
26 files changed, 608 insertions, 797 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2e97cffbb..091e735a0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,33 +1,20 @@ ## Contributing code the right way -*(or: how I learned to post merge requests without crying myself to sleep)* +TLDR: it works like Github. -New user accounts on Gogs are not allowed to fork repositories because people -use development accounts to spam. To get initial fork access, do the following: +1. Register on the [development website](https://git.tt-rss.org); +2. Fork the repository you're interested in; +3. Do the needful; +4. File a PR against master branch; -1. Register on the forums and on [Gogs](https://git.tt-rss.org); -2. Create a thread describing your proposed changes in [Development subforum](https://community.tt-rss.org/c/tiny-tiny-rss/development) -while including your Gogs username; -3. You'll be given proper access and will be able to fork repositories and file PRs, etc; - -If you already have a fully functional Gogs account it works pretty much like Github: - -1. Fork the repository you're interested in; -2. Do the needful; -3. File a pull request with your changes against master branch; - -That's it. If you have any other questions, see this [forum thread](https://discourse.tt-rss.org/t/how-to-contribute-code-via-pull-requests-on-git-tt-rss-org/1850). - -If you don't want to deal with the above, you can also clone one of the repositories -locally, do the needful, and post resulting patches on the [forums](https://community.tt-rss.org/c/tiny-tiny-rss/development). +If you have any other questions, see this [forum thread](https://discourse.tt-rss.org/t/how-to-contribute-code-via-pull-requests-on-git-tt-rss-org/1850). Please don't inline patches in forum posts, attach files instead (``.patch`` or ``.diff`` file extensions should work). ## Contributing translations -Believe it or not, people also spam using Weblate. Therefore, there's some minor -jumping through hoops involved here: +Believe it or not, people also spam using Weblate. Therefore, some minor jumping through hoops is involved here: 1. Register on [Weblate](https://weblate.tt-rss.org/) / forums; 2. Post in the [Weblate discussion thread](https://community.tt-rss.org/t/easier-translations-with-weblate/1680) on the forum, ask to be added to a project diff --git a/classes/article.php b/classes/article.php index 5527b7253..3a58f4576 100755 --- a/classes/article.php +++ b/classes/article.php @@ -29,7 +29,7 @@ class Article extends Handler_Protected { if (!$content) { $pluginhost = new PluginHost(); $pluginhost->load_all(PluginHost::KIND_ALL, $owner_uid); - $pluginhost->load_data(); + //$pluginhost->load_data(); foreach ($pluginhost->get_hooks(PluginHost::HOOK_GET_FULL_TEXT) as $p) { $extracted_content = $p->hook_get_full_text($url); diff --git a/classes/diskcache.php b/classes/diskcache.php index c56dc6f14..daa171bf6 100644 --- a/classes/diskcache.php +++ b/classes/diskcache.php @@ -395,7 +395,7 @@ class DiskCache { $tmppluginhost = new PluginHost(); $tmppluginhost->load(PLUGINS, PluginHost::KIND_SYSTEM); - $tmppluginhost->load_data(); + //$tmppluginhost->load_data(); foreach ($tmppluginhost->get_hooks(PluginHost::HOOK_SEND_LOCAL_FILE) as $plugin) { if ($plugin->hook_send_local_file($filename)) return true; diff --git a/classes/feeds.php b/classes/feeds.php index 744c463af..2015f2435 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -61,54 +61,42 @@ class Feeds extends Handler_Protected { $reply .= "<span class=\"right\">"; $reply .= "<span id='selected_prompt'></span>"; $reply .= " "; - $reply .= "<select dojoType=\"fox.form.Select\" - onchange=\"Headlines.onActionChanged(this)\">"; - $reply .= "<option value=\"0\" disabled='1'>".__('Select...')."</option>"; - - $reply .= "<option value=\"Headlines.select('all')\">".__('All')."</option>"; - $reply .= "<option value=\"Headlines.select('unread')\">".__('Unread')."</option>"; - $reply .= "<option value=\"Headlines.select('invert')\">".__('Invert')."</option>"; - $reply .= "<option value=\"Headlines.select('none')\">".__('None')."</option>"; - - $reply .= "<option value=\"0\" disabled=\"1\">".__('Selection toggle:')."</option>"; - - $reply .= "<option value=\"Headlines.selectionToggleUnread()\">".__('Unread')."</option> - <option value=\"Headlines.selectionToggleMarked()\">".__('Starred')."</option> - <option value=\"Headlines.selectionTogglePublished()\">".__('Published')."</option>"; - - $reply .= "<option value=\"0\" disabled=\"1\">".__('Selection:')."</option>"; - - $reply .= "<option value=\"Headlines.catchupSelection()\">".__('Mark as read')."</option>"; - $reply .= "<option value=\"Article.selectionSetScore()\">".__('Set score')."</option>"; - - if ($feed_id == 0 && !$is_cat) { - $reply .= "<option value=\"Headlines.archiveSelection()\">".__('Move back')."</option>"; - $reply .= "<option value=\"Headlines.deleteSelection()\">".__('Delete')."</option>"; - } else { - $reply .= "<option value=\"Headlines.archiveSelection()\">".__('Archive')."</option>"; - } + $reply .= "<div dojoType='fox.form.DropDownButton' title='".__('Select articles')."'> + <span>".__("Select...")."</span> + <div dojoType='dijit.Menu' style='display: none;'> + <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"all\")'>".__('All')."</div> + <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"unread\")'>".__('Unread')."</div> + <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"invert\")'>".__('Invert')."</div> + <div dojoType='dijit.MenuItem' onclick='Headlines.select(\"none\")'>".__('None')."</div> + <div dojoType='dijit.MenuSeparator'></div> + <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleUnread()'>".__('Toggle unread')."</div> + <div dojoType='dijit.MenuItem' onclick='Headlines.selectionToggleMarked()'>".__('Toggle starred')."</div> + <div dojoType='dijit.MenuItem' onclick='Headlines.selectionTogglePublished()'>".__('Toggle published')."</div> + <div dojoType='dijit.MenuSeparator'></div> + <div dojoType='dijit.MenuItem' onclick='Headlines.catchupSelection()'>".__('Mark as read')."</div> + <div dojoType='dijit.MenuItem' onclick='Article.selectionSetScore()'>".__('Set score')."</div>"; if (PluginHost::getInstance()->get_plugin("mail")) { - $reply .= "<option value=\"Plugins.Mail.send()\">".__('Forward by email'). - "</option>"; + $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mail.send()'>".__('Forward by email')."</div>"; } if (PluginHost::getInstance()->get_plugin("mailto")) { - $reply .= "<option value=\"Plugins.Mailto.send()\">".__('Forward by email'). - "</option>"; + $reply .= "<div dojoType='dijit.MenuItem' value='Plugins.Mailto.send()'>".__('Forward by email')."</div>"; } - $reply .= "<option value=\"0\" disabled=\"1\">".__('Feed:')."</option>"; + foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM) as $p) { + $reply .= $p->hook_headline_toolbar_select_menu_item($feed_id, $is_cat); + } - //$reply .= "<option value=\"catchupPage()\">".__('Mark as read')."</option>"; - - $reply .= "<option value=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">". - __('Show as feed')."</option>"; + if ($feed_id == 0 && !$is_cat) { + $reply .= "<div dojoType='dijit.MenuSeparator'></div> + <div dojoType='dijit.MenuItem' class='text-error' onclick='Headlines.deleteSelection()'>".__('Delete permanently')."</div>"; + } - $reply .= "</select>"; + $reply .= "</div>"; /* menu */ - //$reply .= "</h2"; + $reply .= "</div>"; /* dropdown */ foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_HEADLINE_TOOLBAR_BUTTON) as $p) { $reply .= $p->hook_headline_toolbar_button($feed_id, $is_cat); @@ -362,19 +350,6 @@ class Feeds extends Handler_Protected { $this->mark_timestamp(" enclosures"); - 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"] ]; - } - } - - $this->mark_timestamp(" orig-feed-id"); - $line["updated_long"] = TimeHelper::make_local_datetime($line["updated"],true); $line["updated"] = TimeHelper::make_local_datetime($line["updated"], false, false, false, true); @@ -1852,7 +1827,7 @@ class Feeds extends Handler_Protected { uuid, lang, hide_images, - unread,feed_id,marked,published,link,last_read,orig_feed_id, + unread,feed_id,marked,published,link,last_read, last_marked, last_published, $vfeed_query_part $content_query_part @@ -1896,7 +1871,6 @@ class Feeds extends Handler_Protected { updated, unread, feed_id, - orig_feed_id, marked, published, num_comments, diff --git a/classes/handler/public.php b/classes/handler/public.php index 86a82cc61..fdf55b1d2 100755 --- a/classes/handler/public.php +++ b/classes/handler/public.php @@ -45,7 +45,7 @@ class Handler_Public extends Handler { $tmppluginhost = new PluginHost(); $tmppluginhost->load(PLUGINS, PluginHost::KIND_ALL); $tmppluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); - $tmppluginhost->load_data(); + //$tmppluginhost->load_data(); $handler = $tmppluginhost->get_feed_handler( PluginHost::feed_to_pfeed_id($feed)); @@ -82,13 +82,14 @@ class Handler_Public extends Handler { while ($line = $result->fetch()) { $line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content"]), 100, '...')); + $line["tags"] = Article::get_article_tags($line["id"], $owner_uid); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { $line = $p->hook_query_headlines($line); } foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_EXPORT_FEED) as $p) { - $line = $p->hook_article_export_feed($line, $feed, $is_cat); + $line = $p->hook_article_export_feed($line, $feed, $is_cat, $owner_uid); } $tpl->setVariable('ARTICLE_ID', @@ -121,9 +122,7 @@ class Handler_Public extends Handler { $tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url'] ? $line["site_url"] : get_self_url_prefix()), true); $tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title'] ? $line['feed_title'] : $feed_title), true); - $tags = Article::get_article_tags($line["id"], $owner_uid); - - foreach ($tags as $tag) { + foreach ($line["tags"] as $tag) { $tpl->setVariable('ARTICLE_CATEGORY', htmlspecialchars($tag), true); $tpl->addBlock('category'); } @@ -181,6 +180,7 @@ class Handler_Public extends Handler { while ($line = $result->fetch()) { $line["content_preview"] = Sanitizer::sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...')); + $line["tags"] = Article::get_article_tags($line["id"], $owner_uid); foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) { $line = $p->hook_query_headlines($line, 100); @@ -202,12 +202,10 @@ class Handler_Public extends Handler { if ($line['note']) $article['note'] = $line['note']; if ($article['author']) $article['author'] = $line['author']; - $tags = Article::get_article_tags($line["id"], $owner_uid); - - if (count($tags) > 0) { + if (count($line["tags"]) > 0) { $article['tags'] = array(); - foreach ($tags as $tag) { + foreach ($line["tags"] as $tag) { array_push($article['tags'], $tag); } } @@ -330,7 +328,6 @@ class Handler_Public extends Handler { tag_cache, author, guid, - orig_feed_id, note FROM ttrss_entries,ttrss_user_entries WHERE id = ? AND ref_id = id AND owner_uid = ?"); @@ -1248,7 +1245,7 @@ class Handler_Public extends Handler { $method = clean($_REQUEST["pmethod"]); $host->load($plugin_name, PluginHost::KIND_USER, 0); - $host->load_data(); + //$host->load_data(); $plugin = $host->get_plugin($plugin_name); diff --git a/classes/pluginhost.php b/classes/pluginhost.php index 3ff658918..08871af51 100755 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -14,6 +14,7 @@ class PluginHost { private $plugin_actions = array(); private $owner_uid; private $last_registered; + private $data_loaded; private static $instance; const API_VERSION = 2; @@ -68,6 +69,7 @@ class PluginHost { const HOOK_ENCLOSURE_IMPORTED = 45; const HOOK_HEADLINES_CUSTOM_SORT_MAP = 46; const HOOK_HEADLINES_CUSTOM_SORT_OVERRIDE = 47; + const HOOK_HEADLINE_TOOLBAR_SELECT_MENU_ITEM = 48; const KIND_ALL = 1; const KIND_SYSTEM = 2; @@ -268,6 +270,8 @@ class PluginHost { } } } + + $this->load_data(); } function is_system($plugin) { @@ -352,8 +356,8 @@ class PluginHost { } } - function load_data() { - if ($this->owner_uid) { + private function load_data() { + if ($this->owner_uid && !$this->data_loaded && get_schema_version() > 100) { $sth = $this->pdo->prepare("SELECT name, content FROM ttrss_plugin_storage WHERE owner_uid = ?"); $sth->execute([$this->owner_uid]); @@ -361,6 +365,8 @@ class PluginHost { while ($line = $sth->fetch()) { $this->storage[$line["name"]] = unserialize($line["content"]); } + + $this->data_loaded = true; } } @@ -411,6 +417,8 @@ class PluginHost { function get($sender, $name, $default_value = false) { $idx = get_class($sender); + $this->load_data(); + if (isset($this->storage[$idx][$name])) { return $this->storage[$idx][$name]; } else { diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index e1e88ddc0..88c5b7f0e 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1607,54 +1607,23 @@ class Pref_Feeds extends Handler_Protected { /* save starred articles in Archived feed */ - /* prepare feed if necessary */ + $sth = $pdo->prepare("UPDATE ttrss_user_entries SET + feed_id = NULL, orig_feed_id = NULL + WHERE feed_id = ? AND marked = true AND owner_uid = ?"); - $sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ? - AND owner_uid = ?"); $sth->execute([$id, $owner_uid]); - if ($row = $sth->fetch()) { - $feed_url = $row["feed_url"]; + /* Remove access key for the feed */ - $sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds - WHERE feed_url = ? AND owner_uid = ?"); - $sth->execute([$feed_url, $owner_uid]); - - if ($row = $sth->fetch()) { - $archive_id = $row["id"]; - } else { - $res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds"); - $row = $res->fetch(); - - $new_feed_id = (int)$row['id'] + 1; - - $sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds - (id, owner_uid, title, feed_url, site_url, created) - SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds - WHERE id = ?"); - $sth->execute([$new_feed_id, $id]); - - $archive_id = $new_feed_id; - } - - $sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL, - orig_feed_id = ? WHERE feed_id = ? AND - marked = true AND owner_uid = ?"); - - $sth->execute([$archive_id, $id, $owner_uid]); - - /* Remove access key for the feed */ - - $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE - feed_id = ? AND owner_uid = ?"); - $sth->execute([$id, $owner_uid]); + $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE + feed_id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); - /* remove the feed */ + /* remove the feed */ - $sth = $pdo->prepare("DELETE FROM ttrss_feeds - WHERE id = ? AND owner_uid = ?"); - $sth->execute([$id, $owner_uid]); - } + $sth = $pdo->prepare("DELETE FROM ttrss_feeds + WHERE id = ? AND owner_uid = ?"); + $sth->execute([$id, $owner_uid]); $pdo->commit(); diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index bce3c171b..55a15efb8 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -911,7 +911,7 @@ class Pref_Prefs extends Handler_Protected { $tmppluginhost = new PluginHost(); $tmppluginhost->load_all($tmppluginhost::KIND_ALL, $_SESSION["uid"], true); - $tmppluginhost->load_data(true); + //$tmppluginhost->load_data(true); foreach ($tmppluginhost->get_plugins() as $name => $plugin) { $about = $plugin->about(); diff --git a/classes/rpc.php b/classes/rpc.php index 6b41a51b8..0e881b3ce 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -80,20 +80,6 @@ class RPC extends Handler_Protected { } } - // Silent - function remarchive() { - $ids = explode(",", clean($_REQUEST["ids"])); - - $sth = $this->pdo->prepare("DELETE FROM ttrss_archived_feeds WHERE - (SELECT COUNT(*) FROM ttrss_user_entries - WHERE orig_feed_id = :id) = 0 AND - id = :id AND owner_uid = :uid"); - - foreach ($ids as $id) { - $sth->execute([":id" => $id, ":uid" => $_SESSION['uid']]); - } - } - function addfeed() { $feed = clean($_REQUEST['feed']); $cat = clean($_REQUEST['cat']); @@ -150,113 +136,6 @@ class RPC extends Handler_Protected { print json_encode(array("message" => "UPDATE_COUNTERS")); } - function unarchive() { - $ids = explode(",", clean($_REQUEST["ids"])); - - foreach ($ids as $id) { - $this->pdo->beginTransaction(); - - $sth = $this->pdo->prepare("SELECT feed_url,site_url,title FROM ttrss_archived_feeds - WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = :id - AND owner_uid = :uid) AND owner_uid = :uid"); - $sth->execute([":uid" => $_SESSION['uid'], ":id" => $id]); - - if ($row = $sth->fetch()) { - $feed_url = $row['feed_url']; - $site_url = $row['site_url']; - $title = $row['title']; - - $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? - AND owner_uid = ?"); - $sth->execute([$feed_url, $_SESSION['uid']]); - - if ($row = $sth->fetch()) { - $feed_id = $row["id"]; - } else { - if (!$title) $title = '[Unknown]'; - - $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds - (owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method) - VALUES (?, ?, ?, ?, NULL, '', '', 0)"); - $sth->execute([$_SESSION['uid'], $feed_url, $site_url, $title]); - - $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? - AND owner_uid = ?"); - $sth->execute([$feed_url, $_SESSION['uid']]); - - if ($row = $sth->fetch()) { - $feed_id = $row['id']; - } - } - - if ($feed_id) { - $sth = $this->pdo->prepare("UPDATE ttrss_user_entries - SET feed_id = ?, orig_feed_id = NULL - WHERE ref_id = ? AND owner_uid = ?"); - $sth->execute([$feed_id, $id, $_SESSION['uid']]); - } - } - - $this->pdo->commit(); - } - - print json_encode(array("message" => "UPDATE_COUNTERS")); - } - - function archive() { - $ids = explode(",", clean($_REQUEST["ids"])); - - foreach ($ids as $id) { - $this->archive_article($id, $_SESSION["uid"]); - } - - print json_encode(array("message" => "UPDATE_COUNTERS")); - } - - private function archive_article($id, $owner_uid) { - $this->pdo->beginTransaction(); - - if (!$owner_uid) $owner_uid = $_SESSION['uid']; - - $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries - WHERE ref_id = ? AND owner_uid = ?"); - $sth->execute([$id, $owner_uid]); - - if ($row = $sth->fetch()) { - - /* prepare the archived table */ - - $feed_id = (int) $row['feed_id']; - - if ($feed_id) { - $sth = $this->pdo->prepare("SELECT id FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $sth->execute([$feed_id, $owner_uid]); - - if ($row = $sth->fetch()) { - $new_feed_id = $row['id']; - } else { - $row = $this->pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds")->fetch(); - $new_feed_id = (int)$row['id'] + 1; - - $sth = $this->pdo->prepare("INSERT INTO ttrss_archived_feeds - (id, owner_uid, title, feed_url, site_url, created) - SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds - WHERE id = ?"); - - $sth->execute([$new_feed_id, $feed_id]); - } - - $sth = $this->pdo->prepare("UPDATE ttrss_user_entries - SET orig_feed_id = ?, feed_id = NULL - WHERE ref_id = ? AND owner_uid = ?"); - $sth->execute([$new_feed_id, $id, $owner_uid]); - } - } - - $this->pdo->commit(); - } - function publ() { $pub = clean($_REQUEST["pub"]); $id = clean($_REQUEST["id"]); @@ -347,60 +226,6 @@ class RPC extends Handler_Protected { print "</ul>"; } - // Silent - function massSubscribe() { - - $payload = json_decode(clean($_REQUEST["payload"]), false); - $mode = clean($_REQUEST["mode"]); - - if (!$payload || !is_array($payload)) return; - - if ($mode == 1) { - foreach ($payload as $feed) { - - $title = $feed[0]; - $feed_url = $feed[1]; - - $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE - feed_url = ? AND owner_uid = ?"); - $sth->execute([$feed_url, $_SESSION['uid']]); - - if (!$sth->fetch()) { - $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds - (owner_uid,feed_url,title,cat_id,site_url) - VALUES (?, ?, ?, NULL, '')"); - - $sth->execute([$_SESSION['uid'], $feed_url, $title]); - } - } - } else if ($mode == 2) { - // feed archive - foreach ($payload as $id) { - $sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds - WHERE id = ? AND owner_uid = ?"); - $sth->execute([$id, $_SESSION['uid']]); - - if ($row = $sth->fetch()) { - $site_url = $row['site_url']; - $feed_url = $row['feed_url']; - $title = $row['title']; - - $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE - feed_url = ? AND owner_uid = ?"); - $sth->execute([$feed_url, $_SESSION['uid']]); - - if (!$sth->fetch()) { - $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds - (owner_uid,feed_url,title,cat_id,site_url) - VALUES (?, ?, ?, NULL, ?)"); - - $sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]); - } - } - } - } - } - function catchupFeed() { $feed_id = clean($_REQUEST['feed_id']); $is_cat = clean($_REQUEST['is_cat']) == "true"; diff --git a/classes/rssutils.php b/classes/rssutils.php index 857bc2948..96f7b7c36 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -276,7 +276,7 @@ class RSSUtils { $pluginhost->load(PLUGINS, PluginHost::KIND_ALL); $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); - $pluginhost->load_data(); + //$pluginhost->load_data(); $basic_info = array(); foreach ($pluginhost->get_hooks(PluginHost::HOOK_FEED_BASIC_INFO) as $plugin) { @@ -403,7 +403,7 @@ class RSSUtils { $pluginhost->load(PLUGINS, PluginHost::KIND_ALL); $pluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid); - $pluginhost->load_data(); + //$pluginhost->load_data(); $rss_hash = false; @@ -1379,18 +1379,11 @@ class RSSUtils { } } + // deprecated; table not used static function expire_feed_archive() { - Debug::log("Removing old archived feeds..."); - $pdo = Db::pdo(); - if (DB_TYPE == "pgsql") { - $pdo->query("DELETE FROM ttrss_archived_feeds - WHERE created < NOW() - INTERVAL '1 month'"); - } else { - $pdo->query("DELETE FROM ttrss_archived_feeds - WHERE created < DATE_SUB(NOW(), INTERVAL 1 MONTH)"); - } + $pdo->query("DELETE FROM ttrss_archived_feeds"); } static function expire_lock_files() { diff --git a/classes/userhelper.php b/classes/userhelper.php index 6a80aed2b..76bb338d4 100644 --- a/classes/userhelper.php +++ b/classes/userhelper.php @@ -80,9 +80,9 @@ class UserHelper { $pluginhost->load($plugins, PluginHost::KIND_USER, $owner_uid); - if (get_schema_version() > 100) { + /*if (get_schema_version() > 100) { $pluginhost->load_data(); - } + }*/ } } diff --git a/js/Article.js b/js/Article.js index 587243a01..a075e321f 100644 --- a/js/Article.js +++ b/js/Article.js @@ -175,11 +175,6 @@ const Article = { return comments; }, - formatOriginallyFrom: function(hl) { - return hl.orig_feed ? `<span> - ${__('Originally from:')} <a target="_blank" rel="noopener noreferrer" href="${App.escapeHtml(hl.orig_feed[1])}">${hl.orig_feed[0]}</a> - </span>` : ""; - }, unpack: function(row) { if (row.hasAttribute("data-content")) { console.log("unpacking: " + row.id); @@ -220,7 +215,6 @@ const Article = { if (hl) { const comments = this.formatComments(hl); - const originally_from = this.formatOriginallyFrom(hl); const article = `<div class="post post-${hl.id}" data-article-id="${hl.id}"> <div class="header"> @@ -243,7 +237,6 @@ const Article = { </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> diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js index c59ef9b25..b344967a7 100644 --- a/js/CommonDialogs.js +++ b/js/CommonDialogs.js @@ -241,131 +241,6 @@ const CommonDialogs = { dialog.show(); }, - feedBrowser: function() { - const query = {op: "feeds", method: "feedBrowser"}; - - if (dijit.byId("feedAddDlg")) - dijit.byId("feedAddDlg").hide(); - - if (dijit.byId("feedBrowserDlg")) - dijit.byId("feedBrowserDlg").destroyRecursive(); - - // noinspection JSUnusedGlobalSymbols - const dialog = new dijit.Dialog({ - id: "feedBrowserDlg", - title: __("More Feeds"), - style: "width: 600px", - getSelectedFeedIds: function () { - const list = $$("#browseFeedList li[id*=FBROW]"); - const selected = []; - - list.each(function (child) { - const id = child.id.replace("FBROW-", ""); - - if (child.hasClassName('Selected')) { - selected.push(id); - } - }); - - return selected; - }, - getSelectedFeeds: function () { - const list = $$("#browseFeedList li.Selected"); - const selected = []; - - list.each(function (child) { - const title = child.getElementsBySelector("span.fb_feedTitle")[0].innerHTML; - const url = child.getElementsBySelector("a.fb_feedUrl")[0].href; - - selected.push([title, url]); - - }); - - return selected; - }, - - subscribe: function () { - const mode = this.attr('value').mode; - let selected = []; - - if (mode == "1") - selected = this.getSelectedFeeds(); - else - selected = this.getSelectedFeedIds(); - - if (selected.length > 0) { - dijit.byId("feedBrowserDlg").hide(); - - Notify.progress("Loading, please wait...", true); - - const query = { - op: "rpc", method: "massSubscribe", - payload: JSON.stringify(selected), mode: mode - }; - - xhrPost("backend.php", query, () => { - Notify.close(); - - if (App.isPrefs()) - dijit.byId("feedTree").reload(); - else - Feeds.reload(); - }); - - } else { - alert(__("No feeds selected.")); - } - - }, - update: function () { - Element.show('feed_browser_spinner'); - - xhrPost("backend.php", dialog.attr("value"), (transport) => { - Notify.close(); - - Element.hide('feed_browser_spinner'); - - const reply = JSON.parse(transport.responseText); - const mode = reply['mode']; - - if ($("browseFeedList") && reply['content']) { - $("browseFeedList").innerHTML = reply['content']; - } - - dojo.parser.parse("browseFeedList"); - - if (mode == 2) { - Element.show(dijit.byId('feed_archive_remove').domNode); - } else { - Element.hide(dijit.byId('feed_archive_remove').domNode); - } - }); - }, - removeFromArchive: function () { - const selected = this.getSelectedFeedIds(); - - if (selected.length > 0) { - if (confirm(__("Remove selected feeds from the archive? Feeds with stored articles will not be removed."))) { - Element.show('feed_browser_spinner'); - - const query = {op: "rpc", method: "remarchive", ids: selected.toString()}; - - xhrPost("backend.php", query, () => { - dialog.update(); - }); - } - } - }, - execute: function () { - if (this.validate()) { - this.subscribe(); - } - }, - href: "backend.php?" + dojo.objectToQuery(query) - }); - - dialog.show(); - }, addLabel: function(select, callback) { const caption = prompt(__("Please enter label caption:"), ""); diff --git a/js/Headlines.js b/js/Headlines.js index b98098c33..3b4498430 100755 --- a/js/Headlines.js +++ b/js/Headlines.js @@ -300,6 +300,16 @@ const Headlines = { } } }, + unpackVisible: function(container) { + const rows = $$("#headlines-frame > div[id*=RROW][data-content].cdm"); + + for (let i = 0; i < rows.length; i++) { + if (App.Scrollable.isChildVisible(rows[i], container)) { + console.log('force unpacking:', rows[i].getAttribute('id')); + Article.unpack(rows[i]); + } + } + }, scrollHandler: function (/*event*/) { try { if (!Feeds.infscroll_disabled && !Feeds.infscroll_in_progress) { @@ -320,6 +330,14 @@ const Headlines = { } } + if (App.isCombinedMode() && App.getInitParam("cdm_expanded")) { + const container = $("headlines-frame") + + /* don't do anything until there was some scrolling */ + if (container.scrollTop > 0) + Headlines.unpackVisible(container); + } + if (App.getInitParam("cdm_auto_catchup")) { const rows = $$("#headlines-frame > div[id*=RROW][class*=Unread]"); @@ -420,7 +438,6 @@ const Headlines = { 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} ${Article.getScoreClass(hl.score)}" id="RROW-${hl.id}" @@ -483,7 +500,6 @@ const Headlines = { </div> <div class="right"> - ${originally_from} ${hl.buttons} </div> </div> @@ -1085,42 +1101,6 @@ const Headlines = { } } }, - archiveSelection: function () { - const rows = Headlines.getSelected(); - - if (rows.length == 0) { - alert(__("No articles selected.")); - return; - } - - const fn = Feeds.getName(Feeds.getActive(), Feeds.activeIsCat()); - let str; - let op; - - if (Feeds.getActive() != 0) { - str = ngettext("Archive %d selected article in %s?", "Archive %d selected articles in %s?", rows.length); - op = "archive"; - } else { - str = ngettext("Move %d archived article back?", "Move %d archived articles back?", rows.length); - str += " " + __("Please note that unstarred articles might get purged on next feed update."); - - op = "unarchive"; - } - - str = str.replace("%d", rows.length); - str = str.replace("%s", fn); - - if (App.getInitParam("confirm_feed_catchup") && !confirm(str)) { - return; - } - - const query = {op: "rpc", method: op, ids: rows.toString()}; - - xhrPost("backend.php", query, (transport) => { - App.handleRpcJson(transport); - Feeds.reloadCurrent(); - }); - }, catchupSelection: function () { const rows = Headlines.getSelected(); @@ -1206,11 +1186,6 @@ const Headlines = { }); } }, - onActionChanged: function (elem) { - // eslint-disable-next-line no-eval - eval(elem.value); - elem.attr('value', 'false'); - }, scrollToArticleId: function (id) { const container = $("headlines-frame"); const row = $("RROW-" + id); diff --git a/plugins/af_psql_trgm/init.php b/plugins/af_psql_trgm/init.php index 20e3981ce..a38386d5b 100644 --- a/plugins/af_psql_trgm/init.php +++ b/plugins/af_psql_trgm/init.php @@ -3,6 +3,8 @@ class Af_Psql_Trgm extends Plugin { /* @var PluginHost $host */ private $host; + private $default_similarity = 0.75; + private $default_min_length = 32; function about() { return array(1.0, @@ -37,7 +39,6 @@ class Af_Psql_Trgm extends Plugin { $host->add_hook($host::HOOK_PREFS_EDIT_FEED, $this); $host->add_hook($host::HOOK_PREFS_SAVE_FEED, $this); $host->add_hook($host::HOOK_ARTICLE_BUTTON, $this); - } function get_js() { @@ -124,7 +125,7 @@ class Af_Psql_Trgm extends Plugin { if ($args != "prefFeeds") return; print "<div dojoType=\"dijit.layout.AccordionPane\" - title=\"<i class='material-icons'>extension</i> ".__('Mark similar articles as read')."\">"; + title=\"<i class='material-icons'>extension</i> ".__('Mark similar articles as read (af_psql_trgm)')."\">"; if (DB_TYPE != "pgsql") { print_error("Database type not supported."); @@ -132,17 +133,14 @@ class Af_Psql_Trgm extends Plugin { $res = $this->pdo->query("select 'similarity'::regproc"); - if (!$res->fetch()) { + if (!$res || !$res->fetch()) { print_error("pg_trgm extension not found."); } - $similarity = $this->host->get($this, "similarity"); - $min_title_length = $this->host->get($this, "min_title_length"); + $similarity = $this->host->get($this, "similarity", $this->default_similarity); + $min_title_length = $this->host->get($this, "min_title_length", $this->default_min_length); $enable_globally = $this->host->get($this, "enable_globally"); - if (!$similarity) $similarity = '0.75'; - if (!$min_title_length) $min_title_length = '32'; - print "<form dojoType=\"dijit.form.Form\">"; print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\"> @@ -197,10 +195,10 @@ class Af_Psql_Trgm extends Plugin { print_button("submit", __("Save"), "class='alt-primary'"); print "</form>"; - $enabled_feeds = $this->host->get($this, "enabled_feeds"); - if (!array($enabled_feeds)) $enabled_feeds = array(); + /* cleanup */ + $enabled_feeds = $this->filter_unknown_feeds( + $this->get_stored_array("enabled_feeds")); - $enabled_feeds = $this->filter_unknown_feeds($enabled_feeds); $this->host->set($this, "enabled_feeds", $enabled_feeds); if (count($enabled_feeds) > 0) { @@ -221,14 +219,11 @@ class Af_Psql_Trgm extends Plugin { } function hook_prefs_edit_feed($feed_id) { - print "<header>".__("Similarity (pg_trgm)")."</header>"; + print "<header>".__("Similarity (af_psql_trgm)")."</header>"; print "<section>"; - $enabled_feeds = $this->host->get($this, "enabled_feeds"); - if (!array($enabled_feeds)) $enabled_feeds = array(); - - $key = array_search($feed_id, $enabled_feeds); - $checked = $key !== false ? "checked" : ""; + $enabled_feeds = $this->get_stored_array("enabled_feeds"); + $checked = in_array($feed_id, $enabled_feeds) ? "checked" : ""; print "<fieldset>"; @@ -241,8 +236,7 @@ class Af_Psql_Trgm extends Plugin { } function hook_prefs_save_feed($feed_id) { - $enabled_feeds = $this->host->get($this, "enabled_feeds"); - if (!is_array($enabled_feeds)) $enabled_feeds = array(); + $enabled_feeds = $this->get_stored_array("enabled_feeds"); $enable = checkbox_to_sql_bool($_POST["trgm_similarity_enabled"]); $key = array_search($feed_id, $enabled_feeds); @@ -265,29 +259,39 @@ class Af_Psql_Trgm extends Plugin { if (DB_TYPE != "pgsql") return $article; $res = $this->pdo->query("select 'similarity'::regproc"); - if (!$res->fetch()) return $article; + if (!$res || !$res->fetch()) return $article; $enable_globally = $this->host->get($this, "enable_globally"); - if (!$enable_globally) { - $enabled_feeds = $this->host->get($this, "enabled_feeds"); - $key = array_search($article["feed"]["id"], $enabled_feeds); - if ($key === false) return $article; + if (!$enable_globally && + !in_array($article["feed"]["id"], + $this->get_stored_array("enabled_feeds"))) { + + return $article; } - $similarity = (float) $this->host->get($this, "similarity"); - if ($similarity < 0.01) return $article; + $similarity = (float) $this->host->get($this, "similarity", $this->default_similarity); - $min_title_length = (int) $this->host->get($this, "min_title_length"); - if (mb_strlen($article["title"]) < $min_title_length) return $article; + if ($similarity < 0.01) { + Debug::log("af_psql_trgm: similarity is set too low ($similarity)", Debug::$LOG_EXTENDED); + return $article; + } + + $min_title_length = (int) $this->host->get($this, "min_title_length", $this->default_min_length); + + if (mb_strlen($article["title"]) < $min_title_length) { + Debug::log("af_psql_trgm: article title is too short (min: $min_title_length)", Debug::$LOG_EXTENDED); + return $article; + } $owner_uid = $article["owner_uid"]; $entry_guid = $article["guid_hashed"]; $title_escaped = $article["title"]; // trgm does not return similarity=1 for completely equal strings + // this seems to be no longer the case (fixed in upstream?) - $sth = $this->pdo->prepare("SELECT COUNT(id) AS nequal + /* $sth = $this->pdo->prepare("SELECT COUNT(id) AS nequal FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND date_entered >= NOW() - interval '3 days' AND title = ? AND @@ -303,7 +307,7 @@ class Af_Psql_Trgm extends Plugin { if ($nequal != 0) { $article["force_catchup"] = true; return $article; - } + } */ $sth = $this->pdo->prepare("SELECT MAX(SIMILARITY(title, ?)) AS ms FROM ttrss_entries, ttrss_user_entries WHERE ref_id = id AND @@ -315,14 +319,15 @@ class Af_Psql_Trgm extends Plugin { $row = $sth->fetch(); $similarity_result = $row['ms']; - Debug::log("af_psql_trgm: similarity result: $similarity_result", Debug::$LOG_EXTENDED); + Debug::log("af_psql_trgm: similarity result for $title_escaped: $similarity_result", Debug::$LOG_EXTENDED); if ($similarity_result >= $similarity) { + Debug::log("af_psql_trgm: marking article as read ($similarity_result >= $similarity)", Debug::$LOG_EXTENDED); + $article["force_catchup"] = true; } return $article; - } function api_version() { @@ -345,4 +350,13 @@ class Af_Psql_Trgm extends Plugin { return $tmp; } + private function get_stored_array($name) { + $tmp = $this->host->get($this, $name); + + if (!is_array($tmp)) $tmp = []; + + return $tmp; + } + + } diff --git a/plugins/af_redditimgur/init.php b/plugins/af_redditimgur/init.php index 8d34bb556..6eb530e27 100755 --- a/plugins/af_redditimgur/init.php +++ b/plugins/af_redditimgur/init.php @@ -4,6 +4,8 @@ class Af_RedditImgur extends Plugin { /* @var PluginHost $host */ private $host; private $domain_blacklist = [ "github.com" ]; + private $dump_json_data = false; + private $fallback_preview_urls = []; function about() { return array(1.0, @@ -96,315 +98,414 @@ class Af_RedditImgur extends Plugin { echo __("Configuration saved"); } - /** - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - private function inline_stuff($article, &$doc, $xpath) { + private function process_post_media($data, $doc, $xpath, $anchor) { + $found = 0; - $entries = $xpath->query('(//a[@href]|//img[@src])'); - $img_entries = $xpath->query("(//img[@src])"); + if (is_array($data["media_metadata"])) { + foreach ($data["media_metadata"] as $media) { + $media_url = htmlspecialchars_decode($media["s"]["u"]); - $found = false; - //$debug = 1; + Debug::log("found media_metadata (gallery): $media_url", Debug::$LOG_VERBOSE); - foreach ($entries as $entry) { - if ($entry->hasAttribute("href") && strpos($entry->getAttribute("href"), "reddit.com") === false) { + if ($media_url) { + $this->handle_as_image($doc, $anchor, $media_url); + $found = 1; + } + } + } - if ($this->is_blacklisted($entry->getAttribute("href"))) - continue; + // v.redd.it - see below + /* if (is_array($data["media"])) { + foreach ($data["media"] as $media) { + if (isset($media["fallback_url"])) { + $stream_url = $media["fallback_url"]; - Debug::log("processing href: " . $entry->getAttribute("href"), Debug::$LOG_VERBOSE); + if (isset($data["preview"]["images"][0]["source"])) + $poster_url = $data["preview"]["images"][0]["source"]["url"]; + else + $poster_url = ""; - $matches = array(); + Debug::log("found stream fallback_url: $stream_url / poster $poster_url", Debug::$LOG_VERBOSE); - if (!$found && preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry->getAttribute("href"), $matches)) { - Debug::log("handling as twitter: " . $matches[1] . " " . $matches[2], Debug::$LOG_VERBOSE); + $this->handle_as_video($doc, $anchor, $stream_url, $poster_url); + } - $oembed_result = UrlHelper::fetch("https://publish.twitter.com/oembed?url=" . urlencode($entry->getAttribute("href"))); + $found = 1; + } + } */ - if ($oembed_result) { - $oembed_result = json_decode($oembed_result, true); + if (!$found && $data["post_hint"] == "hosted:video") { + $media_url = $data["url"]; - if ($oembed_result && isset($oembed_result["html"])) { + if (isset($data["preview"]["images"][0]["source"])) + $poster_url = htmlspecialchars_decode($data["preview"]["images"][0]["source"]["url"]); + else + $poster_url = ""; - $tmp = new DOMDocument(); - if (@$tmp->loadHTML('<?xml encoding="utf-8" ?>' . $oembed_result["html"])) { - $p = $doc->createElement("p"); + Debug::log("found hosted video url: $media_url / poster $poster_url, looking up fallback url...", Debug::$LOG_VERBOSE); - $p->appendChild($doc->importNode( - $tmp->getElementsByTagName("blockquote")->item(0), TRUE)); + $fallback_url = $data["media"]["reddit_video"]["fallback_url"]; - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($p, $entry); - $entry->parentNode->insertBefore($br, $entry); + if ($fallback_url) { + Debug::log("found video fallback_url: $fallback_url", Debug::$LOG_VERBOSE); + $this->handle_as_video($doc, $anchor, $fallback_url, $poster_url); - $found = 1; - } + $found = 1; + } + } + + if (!$found && $data["post_hint"] == "video") { + $media_url = $data["url"]; + + if (isset($data["preview"]["images"][0]["source"])) + $poster_url = htmlspecialchars_decode($data["preview"]["images"][0]["source"]["url"]); + else + $poster_url = ""; + + Debug::log("found video url: $media_url / poster $poster_url", Debug::$LOG_VERBOSE); + $this->handle_as_video($doc, $anchor, $media_url, $poster_url); + + $found = 1; + } + + if (!$found && $data["post_hint"] == "image") { + $media_url = $data["url"]; + + Debug::log("found image url: $media_url", Debug::$LOG_VERBOSE); + $this->handle_as_image($doc, $anchor, $media_url); + + $found = 1; + } + + if (!$found && is_array($data["preview"]["images"])) { + foreach ($data["preview"]["images"] as $img) { + if (isset($img["source"]["url"])) { + $media_url = htmlspecialchars_decode($img["source"]["url"]); + $target_url = $data["url"]; + + if ($media_url) { + if ($data["post_hint"] == "self") { + Debug::log("found preview image url: $media_url (link: $target_url)", Debug::$LOG_VERBOSE); + $this->handle_as_image($doc, $anchor, $media_url, $target_url); + + $found = 1; + } else { // gonna use this later if nothing is found using generic link processing + Debug::log("found fallback preview image url: $media_url (link: $target_url);", Debug::$LOG_VERBOSE); + array_push($this->fallback_preview_urls, $media_url); } } } + } + } - if (!$found && preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry->getAttribute("href"), $matches)) { - $entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]); - } + return $found; + } - if (!$found && preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry->getAttribute("href"), $matches)) { + private function inline_stuff($article, &$doc, $xpath) { - Debug::log("Handling as Gfycat", Debug::$LOG_VERBOSE); + $found = false; - $source_stream = 'https://giant.gfycat.com/' . $matches[2] . '.mp4'; - $poster_url = 'https://thumbs.gfycat.com/' . $matches[2] . '-mobile.jpg'; + // embed before reddit <table> post layout + $anchor = $xpath->query('//body/*')->item(0); - $content_type = $this->get_content_type($source_stream); + // deal with json-provided media content first + if ($article["link"] && $anchor) { + Debug::log("JSON: requesting from URL: " . $article["link"] . "/.json", Debug::$LOG_VERBOSE); - if (strpos($content_type, "video/") !== false) { - $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - $found = 1; - } - } + $tmp = UrlHelper::fetch($article["link"] . "/.json"); - if (!$found && preg_match("/https?:\/\/v\.redd\.it\/(.*)$/i", $entry->getAttribute("href"), $matches)) { + $this->fallback_preview_urls = []; - Debug::log("Handling as reddit inline video", Debug::$LOG_VERBOSE); + if ($tmp && $anchor) { + $json = json_decode($tmp, true); - $img = $img_entries->item(0); + if ($json) { + Debug::log("JSON: processing media elements...", Debug::$LOG_EXTENDED); - if ($img) { - $poster_url = $img->getAttribute("src"); - } else { - $poster_url = false; - } + if ($this->dump_json_data) print_r($json); - // Get original article URL from v.redd.it redirects - $source_article_url = $this->get_location($matches[0]); - Debug::log("Resolved ".$matches[0]." to ".$source_article_url, Debug::$LOG_VERBOSE); + foreach ($json as $listing) { + foreach ($listing["data"]["children"] as $child) { - $source_stream = false; + $data = $child["data"]; - if ($source_article_url) { - $j = json_decode(UrlHelper::fetch($source_article_url.".json"), true); + if (is_array($data["crosspost_parent_list"])) { + Debug::log("JSON: processing child crosspost_parent_list", Debug::$LOG_EXTENDED); + + foreach ($data["crosspost_parent_list"] as $parent) { + if ($this->process_post_media($parent, $doc, $xpath, $anchor)) { + $found = 1; - if ($j) { - foreach ($j as $listing) { - foreach ($listing["data"]["children"] as $child) { - if ($child["data"]["url"] == $matches[0]) { - try { - $source_stream = $child["data"]["media"]["reddit_video"]["fallback_url"]; - } - catch (Exception $e) { - } break 2; } } } + + Debug::log("JSON: processing child data element...", Debug::$LOG_EXTENDED); + + if (!$found && $this->process_post_media($data, $doc, $xpath, $anchor)) { + $found = 1; + + break 2; + } } } + } else { + Debug::log("JSON: failed to parse received data.", Debug::$LOG_EXTENDED); + } + } else { + if (!$tmp) { + global $fetch_last_error; + Debug::log("JSON: failed to fetch post:" . $fetch_last_error, Debug::$LOG_EXTENDED); + } + } + } else if (!$anchor) { + Debug::log("JSON: anchor element not found, unable to embed", Debug::$LOG_EXTENDED); + } - if (!$source_stream) { - $source_stream = "https://v.redd.it/" . $matches[1] . "/DASH_600_K"; - } + if ($found) { + Debug::log("JSON: found media data, skipping further processing of content", Debug::$LOG_VERBOSE); + $this->remove_post_thumbnail($doc, $xpath); + return true; + } - $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - $found = 1; - } + $entries = $xpath->query('//a[@href]'); - if (!$found && preg_match("/https?:\/\/(www\.)?streamable.com\//i", $entry->getAttribute("href"))) { + foreach ($entries as $entry) { + $entry_href = $entry->getAttribute("href"); - Debug::log("Handling as Streamable", Debug::$LOG_VERBOSE); + $matches = []; - $tmp = UrlHelper::fetch($entry->getAttribute("href")); + /* skip links going back to reddit (and any other blacklisted stuff) */ + if (!$found && $this->is_blacklisted($entry_href, ["reddit.com"])) { + Debug::log("BODY: domain of $entry_href is blacklisted, skipping", Debug::$LOG_EXTENDED); + continue; + } - if ($tmp) { - $tmpdoc = new DOMDocument(); + Debug::log("BODY: processing URL: " . $entry_href, Debug::$LOG_VERBOSE); - if (@$tmpdoc->loadHTML($tmp)) { - $tmpxpath = new DOMXPath($tmpdoc); + if (!$found && preg_match("/^https?:\/\/twitter.com\/(.*?)\/status\/(.*)/", $entry_href, $matches)) { + Debug::log("handling as twitter: " . $matches[1] . " " . $matches[2], Debug::$LOG_VERBOSE); - $source_node = $tmpxpath->query("//video[contains(@class,'video-player-tag')]//source[contains(@src, '.mp4')]")->item(0); - $poster_node = $tmpxpath->query("//video[contains(@class,'video-player-tag') and @poster]")->item(0); + $oembed_result = UrlHelper::fetch("https://publish.twitter.com/oembed?url=" . urlencode($entry_href)); - if ($source_node && $poster_node) { - $source_stream = $source_node->getAttribute("src"); - $poster_url = $poster_node->getAttribute("poster"); + if ($oembed_result) { + $oembed_result = json_decode($oembed_result, true); - $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - $found = 1; - } + if ($oembed_result && isset($oembed_result["html"])) { + + $tmp = new DOMDocument(); + if (@$tmp->loadHTML('<?xml encoding="utf-8" ?>' . $oembed_result["html"])) { + $p = $doc->createElement("p"); + + $p->appendChild($doc->importNode( + $tmp->getElementsByTagName("blockquote")->item(0), TRUE)); + + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($p, $entry); + $entry->parentNode->insertBefore($br, $entry); + + $found = 1; } } } + } - // imgur .gif -> .gifv - if (!$found && preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry->getAttribute("href"))) { - Debug::log("Handling as imgur gif (->gifv)", Debug::$LOG_VERBOSE); + if (!$found && preg_match("/\.gfycat.com\/([a-z]+)?(\.[a-z]+)$/i", $entry_href, $matches)) { + $entry->setAttribute("href", "http://www.gfycat.com/".$matches[1]); + } - $entry->setAttribute("href", - str_replace(".gif", ".gifv", $entry->getAttribute("href"))); - } + if (!$found && preg_match("/https?:\/\/(www\.)?gfycat.com\/([a-z]+)$/i", $entry_href, $matches)) { - if (!$found && preg_match("/\.(gifv|mp4)$/i", $entry->getAttribute("href"))) { - Debug::log("Handling as imgur gifv", Debug::$LOG_VERBOSE); + Debug::log("Handling as Gfycat", Debug::$LOG_VERBOSE); - $source_stream = str_replace(".gifv", ".mp4", $entry->getAttribute("href")); + $source_stream = 'https://giant.gfycat.com/' . $matches[2] . '.mp4'; + $poster_url = 'https://thumbs.gfycat.com/' . $matches[2] . '-mobile.jpg'; - if (strpos($source_stream, "imgur.com") !== false) - $poster_url = str_replace(".mp4", "h.jpg", $source_stream); + $content_type = $this->get_content_type($source_stream); + if (strpos($content_type, "video/") !== false) { $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - - $found = true; + $found = 1; } + } - $matches = array(); - if (!$found && (preg_match("/youtube\.com\/v\/([\w-]+)/", $entry->getAttribute("href"), $matches) || - preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry->getAttribute("href"), $matches) || - preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry->getAttribute("href"), $matches) || - preg_match("/\/\/youtu.be\/([\w-]+)/", $entry->getAttribute("href"), $matches))) { + // imgur .gif -> .gifv + if (!$found && preg_match("/i\.imgur\.com\/(.*?)\.gif$/i", $entry_href)) { + Debug::log("Handling as imgur gif (->gifv)", Debug::$LOG_VERBOSE); - $vid_id = $matches[1]; + $entry->setAttribute("href", + str_replace(".gif", ".gifv", $entry_href)); + } - Debug::log("Handling as youtube: $vid_id", Debug::$LOG_VERBOSE); + if (!$found && preg_match("/\.(gifv|mp4)$/i", $entry_href)) { + Debug::log("Handling as imgur gifv", Debug::$LOG_VERBOSE); - $iframe = $doc->createElement("iframe"); - $iframe->setAttribute("class", "youtube-player"); - $iframe->setAttribute("type", "text/html"); - $iframe->setAttribute("width", "640"); - $iframe->setAttribute("height", "385"); - $iframe->setAttribute("src", "https://www.youtube.com/embed/$vid_id"); - $iframe->setAttribute("allowfullscreen", "1"); - $iframe->setAttribute("frameborder", "0"); + $source_stream = str_replace(".gifv", ".mp4", $entry_href); - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($iframe, $entry); - $entry->parentNode->insertBefore($br, $entry); + if (strpos($source_stream, "imgur.com") !== false) + $poster_url = str_replace(".mp4", "h.jpg", $source_stream); - $found = true; - } + $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - if (!$found && (preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?[$\?]/i", $entry->getAttribute("href")) || - mb_strpos($entry->getAttribute("href"), "i.reddituploads.com") !== false || - mb_strpos($this->get_content_type($entry->getAttribute("href")), "image/") !== false)) { + $found = true; + } - Debug::log("Handling as a picture", Debug::$LOG_VERBOSE); + $matches = array(); + if (!$found && (preg_match("/youtube\.com\/v\/([\w-]+)/", $entry_href, $matches) || + preg_match("/youtube\.com\/.*?[\&\?]v=([\w-]+)/", $entry_href, $matches) || + preg_match("/youtube\.com\/embed\/([\w-]+)/", $entry_href, $matches) || + preg_match("/youtube\.com\/watch\?v=([\w-]+)/", $entry_href, $matches) || + preg_match("/\/\/youtu.be\/([\w-]+)/", $entry_href, $matches))) { - $img = $doc->createElement('img'); - $img->setAttribute("src", $entry->getAttribute("href")); + $vid_id = $matches[1]; - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($img, $entry); - $entry->parentNode->insertBefore($br, $entry); + Debug::log("Handling as youtube: $vid_id", Debug::$LOG_VERBOSE); - $found = true; - } + $iframe = $doc->createElement("iframe"); + $iframe->setAttribute("class", "youtube-player"); + $iframe->setAttribute("type", "text/html"); + $iframe->setAttribute("width", "640"); + $iframe->setAttribute("height", "385"); + $iframe->setAttribute("src", "https://www.youtube.com/embed/$vid_id"); + $iframe->setAttribute("allowfullscreen", "1"); + $iframe->setAttribute("frameborder", "0"); - // imgur via link rel="image_src" href="..." - if (!$found && preg_match("/imgur/", $entry->getAttribute("href"))) { + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($iframe, $entry); + $entry->parentNode->insertBefore($br, $entry); - Debug::log("handling as imgur page/whatever", Debug::$LOG_VERBOSE); + $found = true; + } - $content = UrlHelper::fetch(["url" => $entry->getAttribute("href"), - "http_accept" => "text/*"]); + if (!$found && (preg_match("/\.(jpg|jpeg|gif|png)(\?[0-9][0-9]*)?[$\?]/i", $entry_href) || + /* mb_strpos($entry_href, "i.reddituploads.com") !== false || */ + mb_strpos($this->get_content_type($entry_href), "image/") !== false)) { - if ($content) { - $cdoc = new DOMDocument(); + Debug::log("Handling as a picture", Debug::$LOG_VERBOSE); - if (@$cdoc->loadHTML($content)) { - $cxpath = new DOMXPath($cdoc); + $img = $doc->createElement('img'); + $img->setAttribute("src", $entry_href); - $rel_image = $cxpath->query("//link[@rel='image_src']")->item(0); + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($img, $entry); + $entry->parentNode->insertBefore($br, $entry); - if ($rel_image) { + $found = true; + } - $img = $doc->createElement('img'); - $img->setAttribute("src", $rel_image->getAttribute("href")); + // imgur via link rel="image_src" href="..." + if (!$found && preg_match("/imgur/", $entry_href)) { - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($img, $entry); - $entry->parentNode->insertBefore($br, $entry); + Debug::log("handling as imgur page/whatever", Debug::$LOG_VERBOSE); - $found = true; - } + $content = UrlHelper::fetch(["url" => $entry_href, + "http_accept" => "text/*"]); + + if ($content) { + $cdoc = new DOMDocument(); + + if (@$cdoc->loadHTML($content)) { + $cxpath = new DOMXPath($cdoc); + + $rel_image = $cxpath->query("//link[@rel='image_src']")->item(0); + + if ($rel_image) { + + $img = $doc->createElement('img'); + $img->setAttribute("src", $rel_image->getAttribute("href")); + + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($img, $entry); + $entry->parentNode->insertBefore($br, $entry); + + $found = true; } } } + } - // wtf is this even - if (!$found && preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry->getAttribute("href"), $matches)) { - $img_id = $matches[1]; + // wtf is this even + if (!$found && preg_match("/^https?:\/\/gyazo\.com\/([^\.\/]+$)/", $entry_href, $matches)) { + $img_id = $matches[1]; - Debug::log("handling as gyazo: $img_id", Debug::$LOG_VERBOSE); + Debug::log("handling as gyazo: $img_id", Debug::$LOG_VERBOSE); - $img = $doc->createElement('img'); - $img->setAttribute("src", "https://i.gyazo.com/$img_id.jpg"); + $img = $doc->createElement('img'); + $img->setAttribute("src", "https://i.gyazo.com/$img_id.jpg"); - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($img, $entry); - $entry->parentNode->insertBefore($br, $entry); + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($img, $entry); + $entry->parentNode->insertBefore($br, $entry); - $found = true; - } - - // let's try meta properties - if (!$found) { - Debug::log("looking for meta og:image", Debug::$LOG_VERBOSE); + $found = true; + } - $content = UrlHelper::fetch(["url" => $entry->getAttribute("href"), - "http_accept" => "text/*"]); + // let's try meta properties + if (!$found) { + Debug::log("looking for meta og:image", Debug::$LOG_VERBOSE); - if ($content) { - $cdoc = new DOMDocument(); + $content = UrlHelper::fetch(["url" => $entry_href, + "http_accept" => "text/*"]); - if (@$cdoc->loadHTML($content)) { - $cxpath = new DOMXPath($cdoc); + if ($content) { + $cdoc = new DOMDocument(); - $og_image = $cxpath->query("//meta[@property='og:image']")->item(0); - $og_video = $cxpath->query("//meta[@property='og:video']")->item(0); + if (@$cdoc->loadHTML($content)) { + $cxpath = new DOMXPath($cdoc); - if ($og_video) { + $og_image = $cxpath->query("//meta[@property='og:image']")->item(0); + $og_video = $cxpath->query("//meta[@property='og:video']")->item(0); - $source_stream = $og_video->getAttribute("content"); + if ($og_video) { - if ($source_stream) { + $source_stream = $og_video->getAttribute("content"); - if ($og_image) { - $poster_url = $og_image->getAttribute("content"); - } else { - $poster_url = false; - } + if ($source_stream) { - $this->handle_as_video($doc, $entry, $source_stream, $poster_url); - $found = true; + if ($og_image) { + $poster_url = $og_image->getAttribute("content"); + } else { + $poster_url = false; } - } else if ($og_image) { + $this->handle_as_video($doc, $entry, $source_stream, $poster_url); + $found = true; + } - $og_src = $og_image->getAttribute("content"); + } else if ($og_image) { - if ($og_src) { - $img = $doc->createElement('img'); - $img->setAttribute("src", $og_src); + $og_src = $og_image->getAttribute("content"); - $br = $doc->createElement('br'); - $entry->parentNode->insertBefore($img, $entry); - $entry->parentNode->insertBefore($br, $entry); + if ($og_src) { + $img = $doc->createElement('img'); + $img->setAttribute("src", $og_src); - $found = true; - } + $br = $doc->createElement('br'); + $entry->parentNode->insertBefore($img, $entry); + $entry->parentNode->insertBefore($br, $entry); + + $found = true; } } } } - } + } - // remove tiny thumbnails - if ($entry->hasAttribute("src")) { - if ($entry->parentNode && $entry->parentNode->parentNode) { - $entry->parentNode->parentNode->removeChild($entry->parentNode); - } + if (!$found && $anchor && count($this->fallback_preview_urls) > 0) { + Debug::log("JSON: processing fallback preview urls...", Debug::$LOG_VERBOSE); + + foreach ($this->fallback_preview_urls as $media_url) { + $this->handle_as_image($doc, $anchor, $media_url); + + $found = 1; } } + if ($found) + $this->remove_post_thumbnail($doc, $xpath); + return $found; } @@ -470,6 +571,32 @@ class Af_RedditImgur extends Plugin { return 2; } + private function remove_post_thumbnail($doc, $xpath) { + $thumb = $xpath->query("//td/a/img[@src]")->item(0); + + if ($thumb) + $thumb->parentNode->parentNode->removeChild($thumb->parentNode); + } + + private function handle_as_image($doc, $entry, $image_url, $link_url = false) { + $img = $doc->createElement("img"); + $img->setAttribute("src", $image_url); + + $p = $doc->createElement("p"); + + if ($link_url) { + $a = $doc->createElement("a"); + $a->setAttribute("href", $link_url); + + $a->appendChild($img); + $p->appendChild($a); + } else { + $p->appendChild($img); + } + + $entry->parentNode->insertBefore($p, $entry); + } + private function handle_as_video($doc, $entry, $source_stream, $poster_url = false) { Debug::log("handle_as_video: $source_stream", Debug::$LOG_VERBOSE); @@ -499,31 +626,63 @@ class Af_RedditImgur extends Plugin { } function testurl() { - $url = htmlspecialchars($_REQUEST["url"]); + + $url = clean($_POST["url"]); + $article_url = clean($_POST["article_url"]); + + $this->dump_json_data = true; + + if (!$url && !$article_url) { + header("Content-type: text/html"); + ?> + <style type="text/css"> + fieldset { border : 0; } + label { display : inline-block; min-width : 120px; } + </style> + <form action="backend.php?op=pluginhandler&method=testurl&plugin=af_redditimgur" method="post"> + <fieldset> + <label>URL:</label> + <input name="url" size="100" value="<?php echo htmlspecialchars($url) ?>"></input> + </fieldset> + <fieldset> + <label>Article URL:</label> + <input name="article_url" size="100" value="<?php echo htmlspecialchars($article_url) ?>"></input> + </fieldset> + <fieldset> + <button type="submit">Test</button> + </fieldset> + </form> + <?php + return; + } header("Content-type: text/plain"); - print "URL: $url\n"; + Debug::set_enabled(true); + Debug::set_loglevel(Debug::$LOG_EXTENDED); + + Debug::log("URL: $url", Debug::$LOG_VERBOSE); $doc = new DOMDocument(); - @$doc->loadHTML("<html><body><a href=\"$url\">[link]</a></body>"); + @$doc->loadHTML("<html><body><table><tr><td><a href=\"$url\">[link]</a></td></tr></table></body>"); $xpath = new DOMXPath($doc); - $found = $this->inline_stuff([], $doc, $xpath); + $found = $this->inline_stuff(["link" => $article_url], $doc, $xpath); - print "Inline result: $found\n"; + Debug::log("Inline result: $found", Debug::$LOG_VERBOSE); if (!$found) { - print "\nReadability result:\n"; + Debug::log("Readability result:", Debug::$LOG_VERBOSE); $article = $this->readability([], $url, $doc, $xpath); print_r($article); } else { - print "\nResulting HTML:\n"; + Debug::log("Resulting HTML:", Debug::$LOG_VERBOSE); print $doc->saveHTML(); } + } private function get_header($url, $header, $useragent = SELF_USER_AGENT) { @@ -588,10 +747,10 @@ class Af_RedditImgur extends Plugin { return $article; } - private function is_blacklisted($src) { + private function is_blacklisted($src, $also_blacklist = []) { $src_domain = parse_url($src, PHP_URL_HOST); - foreach ($this->domain_blacklist as $domain) { + foreach (array_merge($this->domain_blacklist, $also_blacklist) as $domain) { if (strstr($src_domain, $domain) !== false) { return true; } diff --git a/plugins/scored_oldest_first/init.php b/plugins/scored_oldest_first/init.php new file mode 100644 index 000000000..fc3b1f8db --- /dev/null +++ b/plugins/scored_oldest_first/init.php @@ -0,0 +1,35 @@ +<?php +class Scored_Oldest_First extends Plugin { + + function init($host) { + $host->add_hook($host::HOOK_HEADLINES_CUSTOM_SORT_MAP, $this); + $host->add_hook($host::HOOK_HEADLINES_CUSTOM_SORT_OVERRIDE, $this); + } + + function hook_headlines_custom_sort_map() { + return [ + "dates_reverse_scored" => "Oldest first (with score)" + ]; + } + + function hook_headlines_custom_sort_override($order) { + if ($order == "dates_reverse_scored") { + return [ "score DESC, updated", true ]; + } else { + return [ "", false ]; + } + } + + function about() { + return array(1.0, + "Consider article score while sorting by oldest first", + "fox", + false, + ""); + } + + function api_version() { + return 2; + } + +} diff --git a/themes/compact.css b/themes/compact.css index c2828c587..9e936befa 100644 --- a/themes/compact.css +++ b/themes/compact.css @@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.ttrss_main div.prefHelp { color: #555; @@ -1646,6 +1647,10 @@ body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin: 2em; } +body.ttrss_utility.installer pre, +body.ttrss_utility.feed_debugger pre { + white-space: pre-wrap; +} body.ttrss_utility.share_popup { margin: 0; padding: 0; @@ -1880,6 +1885,7 @@ body.ttrss_zoom div.post div.content pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } /* rules specific to compact.css */ body.ttrss_main.ttrss_index.flat #feedTree.dijitTree .dijitTreeLabel { diff --git a/themes/compact_night.css b/themes/compact_night.css index 6666787b0..0e56dc5cc 100644 --- a/themes/compact_night.css +++ b/themes/compact_night.css @@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.ttrss_main div.prefHelp { color: #ccc; @@ -1800,6 +1801,10 @@ body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin: 2em; } +body.ttrss_utility.installer pre, +body.ttrss_utility.feed_debugger pre { + white-space: pre-wrap; +} body.ttrss_utility.share_popup { margin: 0; padding: 0; @@ -1871,6 +1876,7 @@ body.ttrss_zoom div.post div.content pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.flat.ttrss_main.ttrss_prefs #main, body.flat.ttrss_main.ttrss_prefs #footer { @@ -1886,23 +1892,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp { body.flat.ttrss_main.ttrss_prefs hr { border-color: #666; } -body.flat.ttrss_main { - /* - .post .content img, - .cdm .content-inner img, - .post .content video, - .cdm .content-inner video { - transition : opacity 0.2s linear, filter 0.2s linear; - } - - .post .content img:not(:hover), - .cdm .content-inner img:not(:hover), - .post .content video:not(:hover), - .cdm .content-inner video:not(:hover) { - opacity : 0.5; - filter: grayscale(80%); - } */ -} body.flat.ttrss_main img[src*='indicator_white.gif'] { filter: invert(1); } @@ -2027,6 +2016,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode { background-color: #222; } +body.flat.ttrss_main .dijitSplitterHover { + background-color: rgba(255, 255, 255, 0.25); +} body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before { color: #999999; background: #222; @@ -2080,6 +2072,12 @@ body.flat.ttrss_main ul#filterDlg_Actions { background: #222; border-color: #666; } +body.flat.ttrss_main .post .content img, +body.flat.ttrss_main .cdm .content-inner img, +body.flat.ttrss_main .post .content video, +body.flat.ttrss_main .cdm .content-inner video { + filter: saturate(0.9) brightness(0.8); +} body.flat.ttrss_main .article-note { background: #b87d2c; border-color: #b87d2c; diff --git a/themes/light.css b/themes/light.css index d2f5aa4ee..8467c346f 100644 --- a/themes/light.css +++ b/themes/light.css @@ -343,6 +343,7 @@ body.ttrss_main .dijitContentPane pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.ttrss_main div.prefHelp { color: #555; @@ -1646,6 +1647,10 @@ body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin: 2em; } +body.ttrss_utility.installer pre, +body.ttrss_utility.feed_debugger pre { + white-space: pre-wrap; +} body.ttrss_utility.share_popup { margin: 0; padding: 0; @@ -1880,4 +1885,5 @@ body.ttrss_zoom div.post div.content pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } diff --git a/themes/light/tt-rss.less b/themes/light/tt-rss.less index 5a0f8542d..835585fe6 100644 --- a/themes/light/tt-rss.less +++ b/themes/light/tt-rss.less @@ -394,6 +394,7 @@ body.ttrss_main { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } div.prefHelp { diff --git a/themes/light/utility.less b/themes/light/utility.less index 087c4ced3..1c30c77ab 100644 --- a/themes/light/utility.less +++ b/themes/light/utility.less @@ -101,6 +101,10 @@ body.ttrss_utility.ttrss_login { body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin : 2em; + + pre { + white-space : pre-wrap; + } } body.ttrss_utility.share_popup { diff --git a/themes/light/zoom.less b/themes/light/zoom.less index e06939ac2..e599bc447 100644 --- a/themes/light/zoom.less +++ b/themes/light/zoom.less @@ -64,6 +64,7 @@ body.ttrss_zoom { display : block; max-width : 98%; overflow : auto; + white-space: pre-wrap; } } } diff --git a/themes/night.css b/themes/night.css index face01932..86e8f0a0f 100644 --- a/themes/night.css +++ b/themes/night.css @@ -344,6 +344,7 @@ body.ttrss_main .dijitContentPane pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.ttrss_main div.prefHelp { color: #ccc; @@ -1801,6 +1802,10 @@ body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin: 2em; } +body.ttrss_utility.installer pre, +body.ttrss_utility.feed_debugger pre { + white-space: pre-wrap; +} body.ttrss_utility.share_popup { margin: 0; padding: 0; @@ -1872,6 +1877,7 @@ body.ttrss_zoom div.post div.content pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.flat.ttrss_main.ttrss_prefs #main, body.flat.ttrss_main.ttrss_prefs #footer { @@ -1887,23 +1893,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp { body.flat.ttrss_main.ttrss_prefs hr { border-color: #666; } -body.flat.ttrss_main { - /* - .post .content img, - .cdm .content-inner img, - .post .content video, - .cdm .content-inner video { - transition : opacity 0.2s linear, filter 0.2s linear; - } - - .post .content img:not(:hover), - .cdm .content-inner img:not(:hover), - .post .content video:not(:hover), - .cdm .content-inner video:not(:hover) { - opacity : 0.5; - filter: grayscale(80%); - } */ -} body.flat.ttrss_main img[src*='indicator_white.gif'] { filter: invert(1); } @@ -2028,6 +2017,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode { background-color: #222; } +body.flat.ttrss_main .dijitSplitterHover { + background-color: rgba(255, 255, 255, 0.25); +} body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before { color: #999999; background: #222; @@ -2081,6 +2073,12 @@ body.flat.ttrss_main ul#filterDlg_Actions { background: #222; border-color: #666; } +body.flat.ttrss_main .post .content img, +body.flat.ttrss_main .cdm .content-inner img, +body.flat.ttrss_main .post .content video, +body.flat.ttrss_main .cdm .content-inner video { + filter: saturate(0.9) brightness(0.8); +} body.flat.ttrss_main .article-note { background: #b87d2c; border-color: #b87d2c; diff --git a/themes/night_base.less b/themes/night_base.less index 0dff7d191..76ea04a7f 100644 --- a/themes/night_base.less +++ b/themes/night_base.less @@ -200,6 +200,10 @@ body.flat.ttrss_main { } } + .dijitSplitterHover { + background-color : rgba(255, 255, 255, 0.25); + } + .dijitCheckBox:not(.dijitChecked)::before { color : @fg-text-muted; background: @color-panel-bg; @@ -267,22 +271,13 @@ body.flat.ttrss_main { border-color : @border-light; } - /* - .post .content img, - .cdm .content-inner img, - .post .content video, - .cdm .content-inner video { - transition : opacity 0.2s linear, filter 0.2s linear; + .post .content, + .cdm .content-inner { + img, video { + filter: saturate(0.9) brightness(0.8); + } } - .post .content img:not(:hover), - .cdm .content-inner img:not(:hover), - .post .content video:not(:hover), - .cdm .content-inner video:not(:hover) { - opacity : 0.5; - filter: grayscale(80%); - } */ - .article-note { background : @color-accent; border-color : @color-accent; diff --git a/themes/night_blue.css b/themes/night_blue.css index d5cda927f..51c12baa3 100644 --- a/themes/night_blue.css +++ b/themes/night_blue.css @@ -344,6 +344,7 @@ body.ttrss_main .dijitContentPane pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.ttrss_main div.prefHelp { color: #ccc; @@ -1801,6 +1802,10 @@ body.ttrss_utility.installer, body.ttrss_utility.feed_debugger { margin: 2em; } +body.ttrss_utility.installer pre, +body.ttrss_utility.feed_debugger pre { + white-space: pre-wrap; +} body.ttrss_utility.share_popup { margin: 0; padding: 0; @@ -1872,6 +1877,7 @@ body.ttrss_zoom div.post div.content pre { display: block; max-width: 98%; overflow: auto; + white-space: pre-wrap; } body.flat.ttrss_main.ttrss_prefs #main, body.flat.ttrss_main.ttrss_prefs #footer { @@ -1887,23 +1893,6 @@ body.flat.ttrss_main.ttrss_prefs div.prefHelp { body.flat.ttrss_main.ttrss_prefs hr { border-color: #666; } -body.flat.ttrss_main { - /* - .post .content img, - .cdm .content-inner img, - .post .content video, - .cdm .content-inner video { - transition : opacity 0.2s linear, filter 0.2s linear; - } - - .post .content img:not(:hover), - .cdm .content-inner img:not(:hover), - .post .content video:not(:hover), - .cdm .content-inner video:not(:hover) { - opacity : 0.5; - filter: grayscale(80%); - } */ -} body.flat.ttrss_main img[src*='indicator_white.gif'] { filter: invert(1); } @@ -2028,6 +2017,9 @@ body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonCon body.flat.ttrss_main .dijitToolbar .dijitSelect:not(.dijitHover) .dijitButtonNode { background-color: #222; } +body.flat.ttrss_main .dijitSplitterHover { + background-color: rgba(255, 255, 255, 0.25); +} body.flat.ttrss_main .dijitCheckBox:not(.dijitChecked)::before { color: #999999; background: #222; @@ -2081,6 +2073,12 @@ body.flat.ttrss_main ul#filterDlg_Actions { background: #222; border-color: #666; } +body.flat.ttrss_main .post .content img, +body.flat.ttrss_main .cdm .content-inner img, +body.flat.ttrss_main .post .content video, +body.flat.ttrss_main .cdm .content-inner video { + filter: saturate(0.9) brightness(0.8); +} body.flat.ttrss_main .article-note { background: #257aa7; border-color: #257aa7; |