Quellcode durchsuchen

Add mark-as-unread (#1995)

* Add mark-as-unread

https://github.com/FreshRSS/FreshRSS/issues/1966

* Change sentence

https://github.com/FreshRSS/FreshRSS/pull/1995#discussion_r214515954

* Enable mark-as-unread only when unread articles are shown

In order to prevent erroneous marking-as-unread.
We might find a better logic later.

* Disable instead of hide mark-as-unread option

To make it easier to discover
Alexandre Alapetite vor 7 Jahren
Ursprung
Commit
d3f5bd840d

+ 9 - 9
app/Controllers/entryController.php

@@ -40,6 +40,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 		$get = Minz_Request::param('get');
 		$next_get = Minz_Request::param('nextGet', $get);
 		$id_max = Minz_Request::param('idMax', 0);
+		$is_read = (bool)(Minz_Request::param('is_read', true));
 		FreshRSS_Context::$search = new FreshRSS_BooleanSearch(Minz_Request::param('search', ''));
 
 		FreshRSS_Context::$state = Minz_Request::param('state', 0);
@@ -63,39 +64,38 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 
 			if (!$get) {
 				// No get? Mark all entries as read (from $id_max)
-				$entryDAO->markReadEntries($id_max);
+				$entryDAO->markReadEntries($id_max, $is_read);
 			} else {
 				$type_get = $get[0];
 				$get = substr($get, 2);
 				switch($type_get) {
 				case 'c':
-					$entryDAO->markReadCat($get, $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state);
+					$entryDAO->markReadCat($get, $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
 					break;
 				case 'f':
-					$entryDAO->markReadFeed($get, $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state);
+					$entryDAO->markReadFeed($get, $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
 					break;
 				case 's':
-					$entryDAO->markReadEntries($id_max, true, 0, FreshRSS_Context::$search);
+					$entryDAO->markReadEntries($id_max, true, 0, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
 					break;
 				case 'a':
-					$entryDAO->markReadEntries($id_max, false, 0, FreshRSS_Context::$search, FreshRSS_Context::$state);
+					$entryDAO->markReadEntries($id_max, false, 0, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
 					break;
 				}
 
 				if ($next_get !== 'a') {
 					// Redirect to the correct page (category, feed or starred)
-					// Not "a" because it is the default value if nothing is
-					// given.
+					// Not "a" because it is the default value if nothing is given.
 					$params['get'] = $next_get;
 				}
 			}
 		} else {
-			$is_read = (bool)(Minz_Request::param('is_read', true));
 			$entryDAO->markRead($id, $is_read);
 		}
 
 		if (!$this->ajax) {
-			Minz_Request::good(_t('feedback.sub.feed.marked_read'), array(
+			Minz_Request::good(_t($is_read ? 'feedback.sub.articles.marked_read' : 'feedback.sub.articles.marked_unread'),
+			array(
 				'c' => 'index',
 				'a' => 'index',
 				'params' => $params,

+ 12 - 12
app/Models/EntryDAO.php

@@ -437,7 +437,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @param integer $priorityMin
 	 * @return integer affected rows
 	 */
-	public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0, $filters = null, $state = 0) {
+	public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0, $filters = null, $state = 0, $is_read = true) {
 		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
@@ -445,14 +445,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		}
 
 		$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
-			 . 'SET e.is_read=1 '
-			 . 'WHERE e.is_read=0 AND e.id <= ?';
+			 . 'SET e.is_read=? '
+			 . 'WHERE e.is_read <> ? AND e.id <= ?';
 		if ($onlyFavorites) {
 			$sql .= ' AND e.is_favorite=1';
 		} elseif ($priorityMin >= 0) {
 			$sql .= ' AND f.priority > ' . intval($priorityMin);
 		}
-		$values = array($idMax);
+		$values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax);
 
 		list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state);
 
@@ -480,7 +480,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @param integer $idMax fail safe article ID
 	 * @return integer affected rows
 	 */
-	public function markReadCat($id, $idMax = 0, $filters = null, $state = 0) {
+	public function markReadCat($id, $idMax = 0, $filters = null, $state = 0, $is_read = true) {
 		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
@@ -488,9 +488,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		}
 
 		$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
-			 . 'SET e.is_read=1 '
-			 . 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?';
-		$values = array($id, $idMax);
+			 . 'SET e.is_read=? '
+			 . 'WHERE f.category=? AND e.is_read <> ? AND e.id <= ?';
+		$values = array($is_read ? 1 : 0, $id, $is_read ? 1 : 0, $idMax);
 
 		list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state);
 
@@ -518,7 +518,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @param integer $idMax fail safe article ID
 	 * @return integer affected rows
 	 */
-	public function markReadFeed($id_feed, $idMax = 0, $filters = null, $state = 0) {
+	public function markReadFeed($id_feed, $idMax = 0, $filters = null, $state = 0, $is_read = true) {
 		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
@@ -527,9 +527,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		$this->bd->beginTransaction();
 
 		$sql = 'UPDATE `' . $this->prefix . 'entry` '
-			 . 'SET is_read=1 '
-			 . 'WHERE id_feed=? AND is_read=0 AND id <= ?';
-		$values = array($id_feed, $idMax);
+			 . 'SET is_read=? '
+			 . 'WHERE id_feed=? AND is_read <> ? AND id <= ?';
+		$values = array($is_read ? 1 : 0, $id_feed, $is_read ? 1 : 0, $idMax);
 
 		list($searchValues, $search) = $this->sqlListEntriesWhere('', $filters, $state);
 

+ 4 - 1
app/i18n/cz/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Aktualizovat',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Kategorie %s byla vytvořena.',
 			'deleted' => 'Kategorie byla smazána.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Kanál nelze aktualizovat',
 			'internal_problem' => 'RSS kanál nelze přidat. Pro detaily <a href="%s">zkontrolujte logy FreshRSS</a>.', // @todo
 			'invalid_url' => 'URL <em>%s</em> není platné',
-			'marked_read' => 'Kanály byly označeny jako přečtené',
 			'n_actualized' => '%d kanálů bylo aktualizováno',
 			'n_entries_deleted' => '%d článků bylo smazáno',
 			'no_refresh' => 'Nelze obnovit žádné kanály…',

+ 1 - 0
app/i18n/cz/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Označit vše jako přečtené',
 		'mark_cat_read' => 'Označit kategorii jako přečtenou',
 		'mark_feed_read' => 'Označit kanál jako přečtený',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Nové nejdříve',
 		'non-starred' => 'Zobrazit vše vyjma oblíbených',
 		'normal_view' => 'Normální',

+ 4 - 1
app/i18n/de/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Aktualisieren',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Die Kategorie %s ist erstellt worden.',
 			'deleted' => 'Die Kategorie ist gelöscht worden.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Der Feed kann nicht aktualisiert werden',
 			'internal_problem' => 'Der RSS-Feed konnte nicht hinzugefügt werden. Für Details <a href="%s">prüfen Sie die FreshRSS-Protokolle</a>.', // @todo
 			'invalid_url' => 'Die URL <em>%s</em> ist ungültig',
-			'marked_read' => 'Die Feeds sind als gelesen markiert worden',
 			'n_actualized' => 'Die %d Feeds sind aktualisiert worden',
 			'n_entries_deleted' => 'Die %d Artikel sind gelöscht worden',
 			'no_refresh' => 'Es gibt keinen Feed zum Aktualisieren…',

+ 1 - 0
app/i18n/de/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Alle als gelesen markieren',
 		'mark_cat_read' => 'Kategorie als gelesen markieren',
 		'mark_feed_read' => 'Feed als gelesen markieren',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Neuere zuerst',
 		'non-starred' => 'Alle außer Favoriten zeigen',
 		'normal_view' => 'Normale Ansicht',

+ 4 - 1
app/i18n/en/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Updating',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',
+			'marked_unread' => 'The articles have been marked as unread.',
+		),
 		'category' => array(
 			'created' => 'Category %s has been created.',
 			'deleted' => 'Category has been deleted.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Feed cannot be updated',
 			'internal_problem' => 'The newsfeed could not be added. <a href="%s">Check FreshRSS logs</a> for details. You can try force adding by appending <code>#force_feed</code> to the URL.',
 			'invalid_url' => 'URL <em>%s</em> is invalid',
-			'marked_read' => 'Feeds have been marked as read',
 			'n_actualized' => '%d feeds have been updated',
 			'n_entries_deleted' => '%d articles have been deleted',
 			'no_refresh' => 'There is no feed to refresh…',

+ 1 - 0
app/i18n/en/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Mark all as read',
 		'mark_cat_read' => 'Mark category as read',
 		'mark_feed_read' => 'Mark feed as read',
+		'mark_selection_unread' => 'Mark selection as unread',
 		'newer_first' => 'Newer first',
 		'non-starred' => 'Show non-favourites',
 		'normal_view' => 'Normal view',

+ 4 - 1
app/i18n/es/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Actualización',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Se ha creado la categoría %s.',
 			'deleted' => 'Se ha eliminado la categoría.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'No es posible actualizar la fuente',
 			'internal_problem' => 'No ha sido posible agregar la fuente RSS. <a href="%s">Revisa el registro de FreshRSS </a> para más información.', // @todo
 			'invalid_url' => 'La URL <em>%s</em> es inválida',
-			'marked_read' => 'Fuentes marcadas como leídas',
 			'n_actualized' => 'Se han actualiado %d fuentes',
 			'n_entries_deleted' => 'Se han eliminado %d artículos',
 			'no_refresh' => 'No hay fuente a actualizar…',

+ 1 - 0
app/i18n/es/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Marcar todo como leído',
 		'mark_cat_read' => 'Marcar categoría como leída',
 		'mark_feed_read' => 'Marcar fuente como leída',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Nuevos primero',
 		'non-starred' => 'Mostrar todos menos los favoritos',
 		'normal_view' => 'Vista normal',

+ 4 - 1
app/i18n/fr/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Actualiser',
+		'articles' => array(
+			'marked_read' => 'Les articles sélectionnés ont été marqués comme lus.',
+			'marked_unread' => 'Les articles sélectionnés ont été marqués comme non-lus.',
+		),
 		'category' => array(
 			'created' => 'La catégorie %s a été créée.',
 			'deleted' => 'La catégorie a été supprimée.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Une erreur est survenue',
 			'internal_problem' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails. Vous pouvez essayer de forcer l’ajout par addition de <code>#force_feed</code> à l’URL.',
 			'invalid_url' => 'L’url <em>%s</em> est invalide.',
-			'marked_read' => 'Les flux ont été marqués comme lus.',
 			'n_actualized' => '%d flux ont été mis à jour.',
 			'n_entries_deleted' => '%d articles ont été supprimés.',
 			'no_refresh' => 'Il n’y a aucun flux à actualiser…',

+ 2 - 1
app/i18n/fr/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Tout marquer comme lu',
 		'mark_cat_read' => 'Marquer la catégorie comme lue',
 		'mark_feed_read' => 'Marquer le flux comme lu',
+		'mark_selection_unread' => 'Marquer la sélection comme non-lue',
 		'newer_first' => 'Plus récents en premier',
 		'non-starred' => 'Afficher les non-favoris',
 		'normal_view' => 'Vue normale',
@@ -52,7 +53,7 @@ return array(
 		'starred' => 'Afficher les favoris',
 		'stats' => 'Statistiques',
 		'subscription' => 'Gestion des abonnements',
-		'unread' => 'Afficher les non lus',
+		'unread' => 'Afficher les non-lus',
 	),
 	'share' => 'Partager',
 	'tag' => array(

+ 4 - 1
app/i18n/he/feedback.php

@@ -53,6 +53,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'מימוש',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Category %s has been created.', // @todo
 			'deleted' => 'Category has been deleted.', // @todo
@@ -75,7 +79,6 @@ return array(
 			'error' => 'Feed cannot be updated', // @todo
 			'internal_problem' => 'אין אפשרות להוסיף את ההזנה. <a href="%s">בדקו את הלוגים</a> לפרטים.', // @todo
 			'invalid_url' => 'URL <em>%s</em> אינו תקין',
-			'marked_read' => 'הזנות סומנו כנקראו',
 			'n_actualized' => '%d הזנות עודכנו',
 			'n_entries_deleted' => '%d המאמרים נמחקו',
 			'no_refresh' => 'אין הזנה שניתן לרענן…',

+ 1 - 0
app/i18n/he/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'סימון הכל כנקרא',
 		'mark_cat_read' => 'סימון קטגוריה כנקראה',
 		'mark_feed_read' => 'סימון הזנה כנקראה',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'חדשים בראש',
 		'non-starred' => 'הצגת הכל פרט למועדפים',
 		'normal_view' => 'תצוגה רגילה',

+ 4 - 1
app/i18n/it/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Aggiorna',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Categoria %s creata.',
 			'deleted' => 'Categoria cancellata',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Feed non aggiornato',
 			'internal_problem' => 'RSS feed non aggiunto. <a href="%s">Verifica i logs</a> per dettagli.', // @todo
 			'invalid_url' => 'URL <em>%s</em> non valido',
-			'marked_read' => 'Feeds segnati come letti',
 			'n_actualized' => '%d feeds aggiornati',
 			'n_entries_deleted' => '%d articoli cancellati',
 			'no_refresh' => 'Nessun aggiornamento disponibile…',

+ 1 - 0
app/i18n/it/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Segna tutto come letto',
 		'mark_cat_read' => 'Segna la categoria come letta',
 		'mark_feed_read' => 'Segna il feed come letto',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Mostra prima i recenti',
 		'non-starred' => 'Escludi preferiti',
 		'normal_view' => 'Vista elenco',

+ 4 - 1
app/i18n/kr/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => '피드를 가져오는 중입니다',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => '%s 카테고리가 생성되었습니다.',
 			'deleted' => '카테고리가 삭제되었습니다.',
@@ -74,7 +78,6 @@ return array(
 			'error' => '피드를 변경할 수 없습니다',
 			'internal_problem' => 'RSS 피드를 추가할 수 없습니다. 자세한 내용은 <a href="%s">FreshRSS 로그</a>를 참고하세요.',
 			'invalid_url' => 'URL (<em>%s</em>)이 유효하지 않습니다',
-			'marked_read' => '피드가 읽음으로 표시되었습니다',
 			'n_actualized' => '%d 개의 피드에서 새 글을 가져왔습니다',
 			'n_entries_deleted' => '%d 개의 글을 삭제했습니다',
 			'no_refresh' => '새 글을 가져올 피드가 없습니다…',

+ 1 - 0
app/i18n/kr/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => '모두 읽음으로 표시',
 		'mark_cat_read' => '카테고리를 읽음으로 표시',
 		'mark_feed_read' => '피드를 읽음으로 표시',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => '최근 글 먼저',
 		'non-starred' => '즐겨찾기를 제외하고 표시',
 		'normal_view' => '일반 모드',

+ 4 - 1
app/i18n/nl/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Actualiseren',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Categorie %s is gemaakt.',
 			'deleted' => 'Categorie is verwijderd.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Feed kan niet worden vernieuwd',
 			'internal_problem' => 'De feed kon niet worden toegevoegd. <a href="%s">Controleer de FreshRSS-logbestanden</a> voor details. Toevoegen forceren kan worden geprobeerd door <code>#force_feed</code> aan de URL toe te voegen.',
 			'invalid_url' => 'URL <em>%s</em> is ongeldig',
-			'marked_read' => 'Feeds zijn gemarkeerd als gelezen',
 			'n_actualized' => '%d feeds zijn vernieuwd',
 			'n_entries_deleted' => '%d artikelen zijn verwijderd',
 			'no_refresh' => 'Er is geen feed om te vernieuwen…',

+ 1 - 0
app/i18n/nl/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Markeer alles als gelezen',
 		'mark_cat_read' => 'Markeer categorie als gelezen',
 		'mark_feed_read' => 'Markeer feed als gelezen',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Nieuwste eerst',
 		'non-starred' => 'Laat alles zien behalve favorieten',
 		'normal_view' => 'Normale weergave',

+ 4 - 1
app/i18n/pt-br/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Atualizando',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Categoria %s foi criada.',
 			'deleted' => 'Categoria foi deletada.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'O feed não pode ser atualizado',
 			'internal_problem' => 'O RSS feed não pôde ser adicionado. <a href="%s">Verifique os FreshRSS logs</a> para detalhes.', // @todo
 			'invalid_url' => 'URL <em>%s</em> é inválida',
-			'marked_read' => 'Feeds foram marcados como lidos',
 			'n_actualized' => '%d feeds foram atualizados',
 			'n_entries_deleted' => '%d artigos foram deletados',
 			'no_refresh' => 'Não há feed para atualizar…',

+ 1 - 0
app/i18n/pt-br/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Marcar todos como lidos',
 		'mark_cat_read' => 'Marcar categoria como lida',
 		'mark_feed_read' => 'Marcar feed com lido',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Novos primeiro',
 		'non-starred' => 'Mostrar todos, exceto favoritos',
 		'normal_view' => 'visualização normal',

+ 4 - 1
app/i18n/ru/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Actualise',	//TODO
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Category %s has been created.',	//TODO
 			'deleted' => 'Category has been deleted.',	//TODO
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Feed cannot be updated',	//TODO
 			'internal_problem' => 'The newsfeed could not be added. <a href="%s">Check FreshRSS logs</a> for details. You can try force adding by appending <code>#force_feed</code> to the URL.',	//TODO
 			'invalid_url' => 'URL <em>%s</em> is invalid',	//TODO
-			'marked_read' => 'Feeds have been marked as read',	//TODO
 			'n_actualized' => '%d feeds have been updated',	//TODO
 			'n_entries_deleted' => '%d articles have been deleted',	//TODO
 			'no_refresh' => 'There is no feed to refresh…',	//TODO

+ 1 - 0
app/i18n/ru/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Mark all as read',
 		'mark_cat_read' => 'Mark category as read',
 		'mark_feed_read' => 'Mark feed as read',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Newer first',
 		'non-starred' => 'Show all but favorites',
 		'normal_view' => 'Normal view',

+ 4 - 1
app/i18n/tr/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => 'Güncelleme',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => 'Kategori %s oluşturuldu.',
 			'deleted' => 'Kategori silindi.',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'Akış güncellenemiyor',
 			'internal_problem' => 'RSS akışı eklenemiyor. Detaylar için <a href="%s">FreshRSS log kayıtlarını</a> kontrol edin.', // @todo
 			'invalid_url' => 'URL <em>%s</em> geçersiz',
-			'marked_read' => 'Akışlar okundu olarak işaretlendi',
 			'n_actualized' => '%d akışları güncellendi',
 			'n_entries_deleted' => '%d makaleleri silindi',
 			'no_refresh' => 'Yenilenecek akış yok…',

+ 1 - 0
app/i18n/tr/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => 'Hepsini okundu olarak işaretle',
 		'mark_cat_read' => 'Kategoriyi okundu olarak işaretle',
 		'mark_feed_read' => 'Akışı okundu olarak işaretle',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => 'Önce yeniler',
 		'non-starred' => 'Favori dışındakileri göster',
 		'normal_view' => 'Normal görünüm',

+ 4 - 1
app/i18n/zh-cn/feedback.php

@@ -52,6 +52,10 @@ return array(
 	),
 	'sub' => array(
 		'actualize' => '获取',
+		'articles' => array(
+			'marked_read' => 'The selected articles have been marked as read.',	//TODO
+			'marked_unread' => 'The articles have been marked as unread.',	//TODO
+		),
 		'category' => array(
 			'created' => '分类 %s 已创建。',
 			'deleted' => '分类已删除。',
@@ -74,7 +78,6 @@ return array(
 			'error' => 'RSS 源更新失败',
 			'internal_problem' => 'RSS 源添加失败。<a href="%s">检查 FreshRSS 日志</a> 查看详情。', // @todo
 			'invalid_url' => 'URL <em>%s</em> 无效',
-			'marked_read' => 'RSS 源已被设为已读',
 			'n_actualized' => '%d 个 RSS 源已更新',
 			'n_entries_deleted' => '%d 篇文章已删除',
 			'no_refresh' => '没有可刷新的 RSS 源…',

+ 1 - 0
app/i18n/zh-cn/index.php

@@ -40,6 +40,7 @@ return array(
 		'mark_all_read' => '全部设为已读',
 		'mark_cat_read' => '此分类设为已读',
 		'mark_feed_read' => '此源设为已读',
+		'mark_selection_unread' => 'Mark selection as unread',	//TODO
 		'newer_first' => '由新到旧',
 		'non-starred' => '显示未收藏',
 		'normal_view' => '普通视图',

+ 16 - 3
app/layout/nav_menu.phtml

@@ -62,9 +62,10 @@
 	<?php
 		$get = FreshRSS_Context::currentGet();
 		$string_mark = _t('index.menu.mark_all_read');
-		if ($get[0] == 'f') {
+		$string_unmark = _t('index.menu.mark_selection_unread');
+		if ($get[0] === 'f') {
 			$string_mark = _t('index.menu.mark_feed_read');
-		} elseif ($get[0] == 'c') {
+		} elseif ($get[0] === 'c') {
 			$string_mark = _t('index.menu.mark_cat_read');
 		}
 
@@ -77,8 +78,12 @@
 				'idMax' => FreshRSS_Context::$id_max,
 				'search' => htmlspecialchars_decode(FreshRSS_Context::$search, ENT_QUOTES),
 				'state' => FreshRSS_Context::$state,
-			)
+			),
 		);
+
+		$mark_unread_url = $mark_read_url;
+		$mark_unread_url['params']['is_read'] = false;
+		$mark_unread_url['params']['nextGet'] = $get;
 	?>
 
 	<div class="stick" id="nav_menu_read_all">
@@ -110,6 +115,7 @@
 	$mark_before_today['params']['idMax'] = $today . '000000';
 	$mark_before_one_week = $mark_read_url;
 	$mark_before_one_week['params']['idMax'] = ($today - 604800) . '000000';
+	$mark_unread_enabled = FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ) or !FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ);
 ?>
 				<li class="item">
 					<button class="as-link <?php echo $confirm; ?>"
@@ -123,6 +129,13 @@
 					        formaction="<?php echo Minz_Url::display($mark_before_one_week); ?>"
 					        type="submit"><?php echo _t('index.menu.before_one_week'); ?></button>
 				</li>
+				<li class="separator"></li>
+				<li class="item">
+					<button class="as-link <?php echo $mark_unread_enabled ? $confirm : '" disabled="disabled'; ?>"
+					        form="mark-read-menu"
+					        formaction="<?php echo Minz_Url::display($mark_unread_url); ?>"
+					        type="submit"><?php echo $string_unmark; ?></button>
+				</li>
 			</ul>
 		</div>
 		</form>

+ 3 - 0
p/themes/BlueLagoon/BlueLagoon.css

@@ -115,6 +115,9 @@ form th {
 }
 
 /*=== Buttons */
+button.as-link[disabled] {
+	color:#555 !important;
+}
 
 .dropdown-menu .input select, .dropdown-menu .input input {
     background:#444;

+ 3 - 0
p/themes/Dark/dark.css

@@ -113,6 +113,9 @@ form th {
 }
 
 /*=== Buttons */
+button.as-link[disabled] {
+	color:#445 !important;
+}
 .stick {
 	vertical-align: middle;
 	font-size: 0;

+ 3 - 0
p/themes/Screwdriver/screwdriver.css

@@ -115,6 +115,9 @@ form th {
 }
 
 /*=== Buttons */
+button.as-link[disabled] {
+	color:#555 !important;
+}
 
 .dropdown-menu .input select, .dropdown-menu .input input {
     background:#444;

+ 3 - 0
p/themes/base-theme/template.css

@@ -118,6 +118,9 @@ button.as-link:active {
 	font-size: 1.1em;
 	text-align: left;
 }
+button.as-link[disabled] {
+	color:#DDD !important;
+}
 
 /*=== Tables */
 table {