From db94d94de3b7b978b723453a4f6fde4ffda1be4e Mon Sep 17 00:00:00 2001 From: Mathias Kresin Date: Wed, 14 Jan 2015 19:44:41 +0100 Subject: [PATCH] Improve user notice about failed feeds Show feeds with errors always at the top and highlight them Changes the feed order to: 1. failed 2. enabled 3. disabled Order alphabetical within each group. Show a warning message with a hin to the console if feeds have issues Fixes #300, #303 --- assets/css/app.css | 18 ++++++-- assets/js/all.min.js | 34 +++++++-------- assets/js/feed.js | 6 +-- controllers/feed.php | 25 +---------- locales/cs_CZ/translations.php | 2 +- locales/de_DE/translations.php | 2 +- locales/es_ES/translations.php | 2 +- locales/fr_FR/translations.php | 2 +- locales/it_IT/translations.php | 2 +- locales/pt_BR/translations.php | 2 +- locales/zh_CN/translations.php | 2 +- models/feed.php | 76 ++++++++-------------------------- templates/feed_items.php | 6 +++ templates/feeds.php | 10 ++--- 14 files changed, 70 insertions(+), 119 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index a062f4b..ce003ee 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -449,10 +449,6 @@ nav .active a { padding-top: 30px; } -[data-feed-disabled] * { - color: #aaa; -} - .feed-last-checked { color: brown; font-size: 0.7em; @@ -460,6 +456,11 @@ nav .active a { } .feed-parsing-error { + visibility: hidden; +} + +.items article[data-feed-error] .feed-parsing-error { + visibility: visible; color: #000; font-size: 0.7em; font-weight: normal; @@ -473,6 +474,15 @@ nav .active a { background-color: #fff; } +.items article[data-feed-error] { + background-color: #fcf8e3; + border-color: #fcf8e3; +} + +.items article[data-feed-disabled] * { + color: #aaa; +} + .items h2 { font-size: 100%; margin: 0; diff --git a/assets/js/all.min.js b/assets/js/all.min.js index 0f68281..07564f2 100644 --- a/assets/js/all.min.js +++ b/assets/js/all.min.js @@ -1,21 +1,21 @@ var Miniflux={};Miniflux.App=function(){return{Run:function(){Miniflux.Event.ListenKeyboardEvents();Miniflux.Event.ListenMouseEvents()}}}(); -Miniflux.Feed=function(){var g=[];return{Update:function(e,a){var b=e.querySelector("span.items-count");if(b){var c=e.getAttribute("data-feed-id"),g=e.querySelector("h2:first-of-type");g.className="loading-icon";var h=new XMLHttpRequest;h.onload=function(){g.className="";var c=e.querySelector(".feed-last-checked");c&&(c.innerHTML=c.getAttribute("data-after-update"));if(c=e.querySelector(".feed-parsing-error"))c.innerHTML="";var f=JSON.parse(this.responseText);f.result?b.innerHTML=f.items_count.items_unread+ -"/"+f.items_count.items_total:c&&(c.innerHTML=c.getAttribute("data-after-error"));a&&a(f)};h.open("POST","?action=refresh-feed&feed_id="+c,!0);h.send()}},UpdateAll:function(){var e=Array.prototype.slice.call(document.querySelectorAll("article:not([data-feed-disabled])")),a=setInterval(function(){for(;0g.length;){var b=e.shift();g.push(parseInt(b.getAttribute("data-feed-id")));Miniflux.Feed.Update(b,function(b){b=g.indexOf(b.feed_id);0<=b&&g.splice(b,1);0===e.length&&0===g.length&&(clearInterval(a), -window.location.href="?action=unread")})}},100)}}}(); -Miniflux.Item=function(){function g(a){return item_id=a.getAttribute("data-item-id")}function e(a){if(a&&a.hasAttribute("data-reverse-label")){var b=a.innerHTML;a.innerHTML=a.getAttribute("data-reverse-label");a.setAttribute("data-reverse-label",b)}}function a(a){"mouse"!==Miniflux.Event.lastEventType&&Miniflux.Nav.SelectNextItem();a.parentNode.removeChild(a);k--}function b(){0===k&&window.location.reload();var a=document.getElementById("page-counter");a.textContent=k||"";document.getElementById("nav-counter").textContent= -h||"";switch(document.querySelector("section.page").getAttribute("data-item-page")){case "unread":document.title="Miniflux ("+h+")";break;case "feed-items":document.title="("+k+") "+a.parentNode.firstChild.nodeValue;break;default:document.title=a.parentNode.firstChild.nodeValue+" ("+k+")"}}function c(f){var d=g(f),c=new XMLHttpRequest;c.onload=function(){if(Miniflux.Nav.IsListing()){if(f.getAttribute("data-hide"))a(f);else{f.setAttribute("data-item-status","read");var d=f.querySelector("a.mark"); -e(d);(d=f.querySelector("a.mark"))&&d.setAttribute("data-action","mark-unread")}h--;b()}};c.open("POST","?action=mark-item-read&id="+d,!0);c.send()}function m(f){var d=g(f),c=new XMLHttpRequest;c.onload=function(){if(Miniflux.Nav.IsListing()){if(f.getAttribute("data-hide"))a(f);else{f.setAttribute("data-item-status","unread");var d=f.querySelector("a.mark");e(d);(d=f.querySelector("a.mark"))&&d.setAttribute("data-action","mark-read")}h++;b()}};c.open("POST","?action=mark-item-unread&id="+d,!0);c.send()} -var h=function(){var a=document.getElementById("nav-counter");if(a)return counter=parseInt(a.textContent,10)||0}(),k=function(){var a=document.getElementById("page-counter");if(a)return counter=parseInt(a.textContent,10)||0}();return{MarkAsRead:c,MarkAsUnread:m,MarkAsRemoved:function(f){var d=g(f),c=new XMLHttpRequest;c.onload=function(){Miniflux.Nav.IsListing()&&(a(f),"unread"===f.getAttribute("data-item-status")&&h--,b())};c.open("POST","?action=mark-item-removed&id="+d,!0);c.send()},SwitchBookmark:function(f){var d= -g(f),c="1"===f.getAttribute("data-item-bookmark")?"0":"1",l=new XMLHttpRequest;l.onload=function(){var d=document.querySelector("section.page");if(Miniflux.Nav.IsListing()&&"bookmarks"===d.getAttribute("data-item-page"))a(f),b();else if(f.setAttribute("data-item-bookmark",c),Miniflux.Nav.IsListing())d=f.querySelector("a.bookmark"),e(d);else if((d=f.querySelector("a.bookmark-icon"))&&d.hasAttribute("data-reverse-title")){var g=d.getAttribute("title");d.setAttribute("title",d.getAttribute("data-reverse-title")); -d.setAttribute("data-reverse-title",g)}};l.open("POST","?action=bookmark&id="+d+"&value="+c,!0);l.send()},SwitchStatus:function(a){var d=a.getAttribute("data-item-status");"read"===d?m(a):"unread"===d&&c(a)},Show:function(a){(a=a.querySelector("a.show"))&&a.click()},OpenOriginal:function(a){var d=a.querySelector("a.original");d&&("unread"===a.getAttribute("data-item-status")&&c(a),d.removeAttribute("data-action"),"mouse"!==Miniflux.Event.lastEventType&&d.click())},DownloadContent:function(a){var d= -document.getElementById("download-item");if(d){d.innerHTML=" "+d.getAttribute("data-before-message");d.className="loading-icon";var b=new XMLHttpRequest;b.onload=function(){var a=JSON.parse(b.responseText);d.className="";if(a.result){var c=document.getElementById("item-content");c&&(c.innerHTML=a.content);d.innerHTML=d.getAttribute("data-after-message")}else d.innerHTML=d.getAttribute("data-failure-message")};a=g(a);b.open("POST","?action=download-item&id="+a,!0);b.send()}},MarkListingAsRead:function(a){for(var b= -document.getElementsByTagName("article"),c=[],e=0,h=b.length;ef.length;){var b=e.shift();f.push(parseInt(b.getAttribute("data-feed-id")));Miniflux.Feed.Update(b,function(b){b=f.indexOf(b.feed_id);0<=b&&f.splice(b,1);0===e.length&&0===f.length&&(clearInterval(a),window.location.href="?action=unread")})}}, +100)}}}(); +Miniflux.Item=function(){function f(a){return item_id=a.getAttribute("data-item-id")}function e(a){if(a&&a.hasAttribute("data-reverse-label")){var b=a.innerHTML;a.innerHTML=a.getAttribute("data-reverse-label");a.setAttribute("data-reverse-label",b)}}function a(a){"mouse"!==Miniflux.Event.lastEventType&&Miniflux.Nav.SelectNextItem();a.parentNode.removeChild(a);k--}function b(){0===k&&window.location.reload();var a=document.getElementById("page-counter");a.textContent=k||"";document.getElementById("nav-counter").textContent=h|| +"";switch(document.querySelector("section.page").getAttribute("data-item-page")){case "unread":document.title="Miniflux ("+h+")";break;case "feed-items":document.title="("+k+") "+a.parentNode.firstChild.nodeValue;break;default:document.title=a.parentNode.firstChild.nodeValue+" ("+k+")"}}function c(g){var d=f(g),c=new XMLHttpRequest;c.onload=function(){if(Miniflux.Nav.IsListing()){if(g.getAttribute("data-hide"))a(g);else{g.setAttribute("data-item-status","read");var d=g.querySelector("a.mark");e(d); +(d=g.querySelector("a.mark"))&&d.setAttribute("data-action","mark-unread")}h--;b()}};c.open("POST","?action=mark-item-read&id="+d,!0);c.send()}function m(g){var d=f(g),c=new XMLHttpRequest;c.onload=function(){if(Miniflux.Nav.IsListing()){if(g.getAttribute("data-hide"))a(g);else{g.setAttribute("data-item-status","unread");var d=g.querySelector("a.mark");e(d);(d=g.querySelector("a.mark"))&&d.setAttribute("data-action","mark-read")}h++;b()}};c.open("POST","?action=mark-item-unread&id="+d,!0);c.send()} +var h=function(){var a=document.getElementById("nav-counter");if(a)return counter=parseInt(a.textContent,10)||0}(),k=function(){var a=document.getElementById("page-counter");if(a)return counter=parseInt(a.textContent,10)||0}();return{MarkAsRead:c,MarkAsUnread:m,MarkAsRemoved:function(g){var d=f(g),c=new XMLHttpRequest;c.onload=function(){Miniflux.Nav.IsListing()&&(a(g),"unread"===g.getAttribute("data-item-status")&&h--,b())};c.open("POST","?action=mark-item-removed&id="+d,!0);c.send()},SwitchBookmark:function(g){var d= +f(g),c="1"===g.getAttribute("data-item-bookmark")?"0":"1",l=new XMLHttpRequest;l.onload=function(){var d=document.querySelector("section.page");if(Miniflux.Nav.IsListing()&&"bookmarks"===d.getAttribute("data-item-page"))a(g),b();else if(g.setAttribute("data-item-bookmark",c),Miniflux.Nav.IsListing())d=g.querySelector("a.bookmark"),e(d);else if((d=g.querySelector("a.bookmark-icon"))&&d.hasAttribute("data-reverse-title")){var f=d.getAttribute("title");d.setAttribute("title",d.getAttribute("data-reverse-title")); +d.setAttribute("data-reverse-title",f)}};l.open("POST","?action=bookmark&id="+d+"&value="+c,!0);l.send()},SwitchStatus:function(a){var d=a.getAttribute("data-item-status");"read"===d?m(a):"unread"===d&&c(a)},Show:function(a){(a=a.querySelector("a.show"))&&a.click()},OpenOriginal:function(a){var d=a.querySelector("a.original");d&&("unread"===a.getAttribute("data-item-status")&&c(a),d.removeAttribute("data-action"),"mouse"!==Miniflux.Event.lastEventType&&d.click())},DownloadContent:function(a){var d= +document.getElementById("download-item");if(d){d.innerHTML=" "+d.getAttribute("data-before-message");d.className="loading-icon";var b=new XMLHttpRequest;b.onload=function(){var a=JSON.parse(b.responseText);d.className="";if(a.result){var c=document.getElementById("item-content");c&&(c.innerHTML=a.content);d.innerHTML=d.getAttribute("data-after-message")}else d.innerHTML=d.getAttribute("data-failure-message")};a=f(a);b.open("POST","?action=download-item&id="+a,!0);b.send()}},MarkListingAsRead:function(a){for(var b= +document.getElementsByTagName("article"),c=[],e=0,h=b.length;eb-(a.offsetTop+a.offsetHeight)||b-a.offsetTop>document.documentElement.clientHeight)&&window.scrollTo(0,a.offsetTop-10)}function e(){return document.getElementById("listing")?!0:!1}return{OpenNextPage:function(){var a=document.getElementById("next-page");a&&a.click()},OpenPreviousPage:function(){var a=document.getElementById("previous-page");a&&a.click()},SelectNextItem:function(){var a=document.getElementById("next-item"); -if(a)a.click();else if(e())if(a=document.getElementsByTagName("article"),document.getElementById("current-item"))for(var b=0,c=a.length;bb-(a.offsetTop+a.offsetHeight)||b-a.offsetTop>document.documentElement.clientHeight)&&window.scrollTo(0,a.offsetTop-10)}function e(){return document.getElementById("listing")?!0:!1}return{OpenNextPage:function(){var a=document.getElementById("next-page");a&&a.click()},OpenPreviousPage:function(){var a=document.getElementById("previous-page");a&&a.click()},SelectNextItem:function(){var a=document.getElementById("next-item"); +if(a)a.click();else if(e())if(a=document.getElementsByTagName("article"),document.getElementById("current-item"))for(var b=0,c=a.length;b Model\Feed\get_all_favicons(), 'feeds' => Model\Feed\get_all_item_counts(), 'nothing_to_read' => Request\int_param('nothing_to_read'), 'nb_unread_items' => Model\Item\count_by_status('unread'), + 'nb_failed_feeds' => Model\Feed\count_failed_feeds(), 'menu' => 'feeds', 'title' => t('Subscriptions') ))); @@ -219,7 +198,7 @@ Router\post_action('import', function() { if (Model\Feed\import_opml(Request\file_content('file'))) { Session\flash(t('Your feeds have been imported.')); - Response\redirect('?action=feeds&disable_empty_feeds_check=1'); + Response\redirect('?action=feeds'); } else { diff --git a/locales/cs_CZ/translations.php b/locales/cs_CZ/translations.php index 1b90ccc..94f4911 100644 --- a/locales/cs_CZ/translations.php +++ b/locales/cs_CZ/translations.php @@ -56,7 +56,6 @@ return array( 'download content' => 'stáhnout obsah', 'Help' => 'Nápověda', 'Theme' => 'Vzhled', - 'There are %d empty feeds, there is maybe an error: %s...' => 'K dispozici je %d prázdné kanály, je možná chyba: %s...', 'Items per page' => 'Článků na stránku', 'Previous page' => 'Předchozí stránka', 'Next page' => 'Další stránka', @@ -231,4 +230,5 @@ return array( // 'Avoid mixed content warnings with HTTPS' => '', // 'Download favicons' => '', // 'general' => '', + // 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => '', ); diff --git a/locales/de_DE/translations.php b/locales/de_DE/translations.php index f7b05cf..a8a1098 100644 --- a/locales/de_DE/translations.php +++ b/locales/de_DE/translations.php @@ -56,7 +56,6 @@ return array( 'download content' => 'Inhalt herunterladen', 'Help' => 'Hilfe', 'Theme' => 'Theme', - 'There are %d empty feeds, there is maybe an error: %s...' => 'Es gibt %d leere Feeds, vielleicht gibt es einen Fehler: %s...', 'Items per page' => 'Einträge pro Seite', 'Previous page' => 'vorherige Seite', 'Next page' => 'nächste Seite', @@ -231,4 +230,5 @@ return array( 'Avoid mixed content warnings with HTTPS' => 'Vermeidet Warnungen wegen gemischtem Inhalt bei HTTPS', 'Download favicons' => 'Favicons herunterladen', 'general' => 'allgemein', + 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => 'Fehler bei der letzten Aktualisierung. Aktualisiere den Feed manuell und prüfe die %sKonsole%s anschließend auf Fehler!', ); diff --git a/locales/es_ES/translations.php b/locales/es_ES/translations.php index 7945802..7e865a2 100644 --- a/locales/es_ES/translations.php +++ b/locales/es_ES/translations.php @@ -56,7 +56,6 @@ return array( 'download content' => 'descargar contenido', 'Help' => 'Ayuda', 'Theme' => 'Tema', - 'There are %d empty feeds, there is maybe an error: %s...' => 'Hay %d suscripciones vacías, tal vez haya un problema: %s...', 'Items per page' => 'Ítems por página', 'Previous page' => 'Página anterior', 'Next page' => 'Página siguiente', @@ -231,4 +230,5 @@ return array( // 'Avoid mixed content warnings with HTTPS' => '', // 'Download favicons' => '', // 'general' => '', + // 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => '', ); diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index b2e587f..d75b20f 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -56,7 +56,6 @@ return array( 'download content' => 'télécharger le contenu', 'Help' => 'Aide', 'Theme' => 'Thème', - 'There are %d empty feeds, there is maybe an error: %s...' => 'Il y a %d abonnements vides, il y a peut-être un problème : %s...', 'Items per page' => 'Nombre d\'éléments par page', 'Previous page' => 'Page précédente', 'Next page' => 'Page suivante', @@ -231,4 +230,5 @@ return array( 'Avoid mixed content warnings with HTTPS' => 'Évite les alertes du navigateur web en HTTPS', 'Download favicons' => 'Télécharger les icônes des sites web', 'general' => 'general', + 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!', ); diff --git a/locales/it_IT/translations.php b/locales/it_IT/translations.php index 3660deb..cc02c98 100644 --- a/locales/it_IT/translations.php +++ b/locales/it_IT/translations.php @@ -56,7 +56,6 @@ return array( // 'download content' => '', 'Help' => 'Aiuto', 'Theme' => 'Tema', - // 'There are %d empty feeds, there is maybe an error: %s...' => '', 'Items per page' => 'Articoli per pagina', 'Previous page' => 'Pagina precedente', 'Next page' => 'Pagina successiva', @@ -231,4 +230,5 @@ return array( // 'Avoid mixed content warnings with HTTPS' => '', // 'Download favicons' => '', // 'general' => '', + // 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => '', ); diff --git a/locales/pt_BR/translations.php b/locales/pt_BR/translations.php index 8ec86b5..d72b79d 100644 --- a/locales/pt_BR/translations.php +++ b/locales/pt_BR/translations.php @@ -56,7 +56,6 @@ return array( 'download content' => 'download conteúdo', 'Help' => 'Ajuda', 'Theme' => 'Tema', - 'There are %d empty feeds, there is maybe an error: %s...' => 'Existem %d feeds vazios, talvez haja um erro: %s...', 'Items per page' => 'Itens por página', 'Previous page' => 'Página anterior', 'Next page' => 'Proxima página', @@ -231,4 +230,5 @@ return array( 'Avoid mixed content warnings with HTTPS' => 'Evita alertas de mistura de conteúdo HTTPS', 'Download favicons' => 'Download favicon', // 'general' => '', + // 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => '', ); diff --git a/locales/zh_CN/translations.php b/locales/zh_CN/translations.php index f0dc5f6..8a6b82e 100644 --- a/locales/zh_CN/translations.php +++ b/locales/zh_CN/translations.php @@ -56,7 +56,6 @@ return array( // 'download content' => '', 'Help' => '帮助', // 'Theme' => '', - // 'There are %d empty feeds, there is maybe an error: %s...' => '', 'Items per page' => '每页条目数', 'Previous page' => '上一页', 'Next page' => '下一页', @@ -231,4 +230,5 @@ return array( // 'Avoid mixed content warnings with HTTPS' => '', // 'Download favicons' => '', // 'general' => '', + // 'An error occurred during the last check. Refresh the feed manually and check the %sconsole%s for errors afterwards!' => '', ); diff --git a/models/feed.php b/models/feed.php index f3264bc..c70177a 100644 --- a/models/feed.php +++ b/models/feed.php @@ -293,24 +293,13 @@ function get_ids($limit = LIMIT_ALL) return $query->listing('id', 'id'); } -// Get feeds with no item -function get_all_empty() +// get number of feeds with errors +function count_failed_feeds() { - $feeds = Database::get('db') + return Database::get('db') ->table('feeds') - ->columns('feeds.id', 'feeds.title', 'COUNT(items.id) AS nb_items') - ->join('items', 'feed_id', 'id') - ->isNull('feeds.last_checked') - ->groupBy('feeds.id') - ->findAll(); - - foreach ($feeds as $key => &$feed) { - if ($feed['nb_items'] > 0) { - unset($feeds[$key]); - } - } - - return $feeds; + ->eq('parsing_error', '1') + ->count(); } // Get all feeds @@ -322,53 +311,22 @@ function get_all() ->findAll(); } -// Get all feeds with the number unread/total items +// Get all feeds with the number unread/total items in the order failed, working, disabled function get_all_item_counts() { - $counts = Database::get('db') - ->table('items') - ->columns('feed_id', 'status', 'count(*) as item_count') - ->in('status', array('read', 'unread')) - ->groupBy('feed_id', 'status') - ->findAll(); - - $feeds = Database::get('db') + return Database::get('db') ->table('feeds') - ->asc('title') + ->columns( + 'feeds.*', + 'SUM(CASE WHEN items.status IN ("unread") THEN 1 ELSE 0 END) as "items_unread"', + 'SUM(CASE WHEN items.status IN ("read", "unread") THEN 1 ELSE 0 END) as "items_total"' + ) + ->join('items', 'feed_id', 'id') + ->groupBy('feeds.id') + ->desc('feeds.parsing_error') + ->desc('feeds.enabled') + ->asc('feeds.title') ->findAll(); - - $item_counts = array(); - - foreach ($counts as &$count) { - - if (! isset($item_counts[$count['feed_id']])) { - $item_counts[$count['feed_id']] = array( - 'items_unread' => 0, - 'items_total' => 0, - ); - } - - $item_counts[$count['feed_id']]['items_total'] += $count['item_count']; - - if ($count['status'] === 'unread') { - $item_counts[$count['feed_id']]['items_unread'] = $count['item_count']; - } - } - - foreach ($feeds as &$feed) { - - if (isset($item_counts[$feed['id']])) { - $feed += $item_counts[$feed['id']]; - } - else { - $feed += array( - 'items_unread' => 0, - 'items_total' => 0, - ); - } - } - - return $feeds; } // Get unread/total count for one feed diff --git a/templates/feed_items.php b/templates/feed_items.php index 304991a..1181c00 100644 --- a/templates/feed_items.php +++ b/templates/feed_items.php @@ -19,6 +19,12 @@ + +

+ ','') ?> +

+ +
- + 0): ?> +

','') ?>

+

update your subscriptions?') ?>

-
> +
>

@@ -45,10 +47,8 @@ - - + -