ソースを参照

Enhance content path feature (#2778)

- Add a maintenance section to be able to clear cache and force reload a feed.
- Add an icon next to path field to show a pop-up with the result of the content path.

Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Co-authored-by: Marien Fressinaud <dev@marienfressinaud.fr>
Julien-Pierre Avérous 6 年 前
コミット
d30ac40772

+ 141 - 0
app/Controllers/feedController.php

@@ -647,6 +647,147 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		}
 	}
 
+	/**
+	 * This action force clears the cache of a feed.
+	 *
+	 * Parameters are:
+	 *   - id (mandatory - no default): Feed ID
+	 *
+	 */
+	public function clearCacheAction() {
+		//Get Feed.
+		$id = Minz_Request::param('id');
+
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$feed = $feedDAO->searchById($id);
+
+		if (!$feed) {
+			Minz_Request::bad(_t('feedback.sub.feed.not_found'), array());
+			return;
+		}
+
+		$feed->clearCache();
+
+		Minz_Request::good(_t('feedback.sub.feed.cache_cleared', $feed->name()), array(
+			'params' => array('get' => 'f_' . $feed->id())
+		));
+	}
+
+	/**
+	 * This action forces reloading the articles of a feed.
+	 *
+	 * Parameters are:
+	 *   - id (mandatory - no default): Feed ID
+	 *
+	 */
+	public function reloadAction() {
+		@set_time_limit(300);
+
+		//Get Feed ID.
+		$feed_id = Minz_Request::param('id');
+
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$entryDAO = FreshRSS_Factory::createEntryDao();
+
+		$feed = $feedDAO->searchById($feed_id);
+
+		if (!$feed) {
+			Minz_Request::bad(_t('feedback.sub.feed.not_found'), array());
+			return;
+		}
+
+		//Re-fetch articles as if the feed was new.
+		self::actualizeFeed($feed_id, null, false, null, true);
+
+		//Extract all feed entries from database, load complete content and store them back in database.
+		$entries = $entryDAO->listWhere('f', $feed_id, FreshRSS_Entry::STATE_ALL, 'DESC', 0);
+
+		$entryDAO->beginTransaction();
+
+		foreach ($entries as $entry) {
+			$entry->loadCompleteContent(true);
+			$entryDAO->updateEntry($entry->toArray());
+		}
+
+		$entryDAO->commit();
+
+		//Give feedback to user.
+		Minz_Request::good(_t('feedback.sub.feed.reloaded', $feed->name()), array(
+			'params' => array('get' => 'f_' . $feed->id())
+		));
+	}
+
+	/**
+	 * This action creates a preview of a content-selector.
+	 *
+	 * Parameters are:
+	 *   - id (mandatory - no default): Feed ID
+	 *   - selector (mandatory - no default): Selector to preview
+	 *
+	 */
+	public function contentSelectorPreviewAction() {
+
+		//Configure.
+		$this->view->fatalError = '';
+		$this->view->selectorSuccess = false;
+		$this->view->htmlContent = '';
+
+		$this->view->_layout(false);
+
+		$this->_csp([
+			'default-src' => "'self'",
+			'frame-src' => '*',
+			'img-src' => '* data:',
+			'media-src' => '*',
+		]);
+
+		//Get parameters.
+		$feed_id = Minz_Request::param('id');
+		$content_selector = trim(Minz_Request::param('selector'));
+
+		if (!$content_selector) {
+			$this->view->fatalError = _t('feedback.sub.feed.selector_preview.selector_empty');
+			return;
+		}
+
+		//Check Feed ID validity.
+		$entryDAO = FreshRSS_Factory::createEntryDao();
+		$entries = $entryDAO->listWhere('f', $feed_id);
+
+		if (empty($entries)) {
+			$this->view->fatalError = _t('feedback.sub.feed.selector_preview.no_entries');
+			return;
+		}
+
+		//Get feed & entry.
+		$entry = $entries[0];
+		$feed = $entry->feed(true);
+
+		if (!$feed) {
+			$this->view->fatalError = _t('feedback.sub.feed.selector_preview.no_feed');
+			return;
+		}
+
+		//Fetch & select content.
+		try {
+			$fullContent = FreshRSS_Entry::getContentByParsing(
+				htmlspecialchars_decode($entry->link(), ENT_QUOTES),
+				$content_selector,
+				$feed->attributes()
+			);
+
+			if ($fullContent != '') {
+				$this->view->selectorSuccess = true;
+				$this->view->htmlContent = $fullContent;
+			} else {
+				$this->view->selectorSuccess = false;
+				$this->view->htmlContent = $entry->content();
+			}
+		} catch (Exception $e) {
+			$this->view->fatalError = _t('feedback.sub.feed.selector_preview.http_error');
+		}
+	}
+
 	/**
 	 * This method update TTL values for feeds if needed.
 	 * It changes the old default value (-2) to the new default value (0).

+ 4 - 4
app/Models/Entry.php

@@ -330,7 +330,7 @@ class FreshRSS_Entry extends Minz_Model {
 		}
 	}
 
-	private static function get_content_by_parsing($url, $path, $attributes = array()) {
+	public static function getContentByParsing($url, $path, $attributes = array()) {
 		require_once(LIB_PATH . '/lib_phpQuery.php');
 		$system_conf = Minz_Configuration::get('system');
 		$limits = $system_conf->limits;
@@ -387,7 +387,7 @@ class FreshRSS_Entry extends Minz_Model {
 		}
 	}
 
-	public function loadCompleteContent() {
+	public function loadCompleteContent($force = false) {
 		// Gestion du contenu
 		// On cherche à récupérer les articles en entier... même si le flux ne le propose pas
 		$feed = $this->feed(true);
@@ -395,13 +395,13 @@ class FreshRSS_Entry extends Minz_Model {
 			$entryDAO = FreshRSS_Factory::createEntryDao();
 			$entry = $entryDAO->searchByGuid($this->feedId, $this->guid);
 
-			if ($entry) {
+			if ($entry && !$force) {
 				// l'article existe déjà en BDD, en se contente de recharger ce contenu
 				$this->content = $entry->content();
 			} else {
 				try {
 					// l'article n'est pas en BDD, on va le chercher sur le site
-					$fullContent = self::get_content_by_parsing(
+					$fullContent = self::getContentByParsing(
 						htmlspecialchars_decode($this->link(), ENT_QUOTES),
 						$feed->pathEntries(),
 						$feed->attributes()

+ 1 - 0
app/Models/Themes.php

@@ -86,6 +86,7 @@ class FreshRSS_Themes extends Minz_Model {
 			'key' => '⚿',
 			'label' => '🏷️',
 			'link' => '↗',
+			'look' => '👁',
 			'login' => '🔒',
 			'logout' => '🔓',
 			'next' => '⏩',

+ 10 - 0
app/i18n/cz/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS kanály byly aktualizovány',
 			'added' => 'RSS kanál <em>%s</em> byl přidán',
 			'already_subscribed' => 'Již jste přihlášen k odběru <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Kanál byl smazán',
 			'error' => 'Kanál nelze aktualizovat',
 			'internal_problem' => 'RSS kanál nelze přidat. Pro detaily <a href="%s">zkontrolujte logy FreshRSS</a>.',
 			'invalid_url' => 'URL <em>%s</em> není platné',
 			'not_added' => '<em>%s</em> nemůže být přidán',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Nelze obnovit žádné kanály…',
 			'n_actualized' => '%d kanálů bylo aktualizováno',
 			'n_entries_deleted' => '%d článků bylo smazáno',
 			'over_max' => 'Dosáhl jste maximálního počtu kanálů (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Kanál byl aktualizován',
 		),
 		'purge_completed' => 'Vyprázdněno (smazáno %d článků)',

+ 11 - 0
app/i18n/cz/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informace',
 		'keep_min' => 'Zachovat tento minimální počet článků',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do <em>%s</em>.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'Nejsou označeny žádné kanály.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/de/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'Die RSS-Feeds sind aktualisiert worden',
 			'added' => 'Der RSS-Feed <em>%s</em> ist hinzugefügt worden',
 			'already_subscribed' => 'Sie haben <em>%s</em> bereits abonniert',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Der Feed ist gelöscht worden',
 			'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>.',
 			'invalid_url' => 'Die URL <em>%s</em> ist ungültig',
 			'not_added' => '<em>%s</em> konnte nicht hinzugefügt werden',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Es gibt keinen Feed zum Aktualisieren…',
 			'n_actualized' => 'Die %d Feeds sind aktualisiert worden',
 			'n_entries_deleted' => 'Die %d Artikel sind gelöscht worden',
 			'over_max' => 'Sie haben Ihre Feeds-Limite erreicht (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Der Feed ist aktualisiert worden',
 		),
 		'purge_completed' => 'Bereinigung abgeschlossen (%d Artikel gelöscht)',

+ 11 - 0
app/i18n/de/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Information',	// TODO - Translation
 		'keep_min' => 'Minimale Anzahl an Artikeln, die behalten wird',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie <em>%s</em> eingefügt.',
 		'mute' => 'Stumm schalten',
 		'no_selected' => 'Kein Feed ausgewählt.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Zeige in eigener Kategorie',
 			'_' => 'Sichtbarkeit',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Alle Feeds zeigen',
 			'error' => 'Nur Feeds mit Fehlern zeigen',

+ 10 - 0
app/i18n/en/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS feeds have been updated',
 			'added' => 'RSS feed <em>%s</em> has been added',
 			'already_subscribed' => 'You have already subscribed to <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',
 			'deleted' => 'Feed has been deleted',
 			'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',
 			'not_added' => '<em>%s</em> could not be added',
+			'not_found' => 'Feed cannot be found',
 			'no_refresh' => 'There is no feed to refresh…',
 			'n_actualized' => '%d feeds have been updated',
 			'n_entries_deleted' => '%d articles have been deleted',
 			'over_max' => 'You have reached your limit of feeds (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',
+				'no_entries' => 'There are no articles in this feed. You need at least one article to create a preview.',
+				'no_feed' => 'Internal error (feed can\'t be found).',
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',
+			),
 			'updated' => 'Feed has been updated',
 		),
 		'purge_completed' => 'Purge completed (%d articles deleted)',

+ 12 - 1
app/i18n/en/sub.php

@@ -34,7 +34,7 @@ return array(
 		),
 		'clear_cache' => 'Always clear cache',
 		'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',
-		'css_path' => 'Articles CSS path on original website',
+		'css_path' => 'Article CSS selector on original website',
 		'description' => 'Description',
 		'empty' => 'This feed is empty. Please verify that it is still maintained.',
 		'error' => 'This feed has encountered a problem. Please verify that it is always reachable then update it.',
@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Information',
 		'keep_min' => 'Minimum number of articles to keep',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',
+			'clear_cache_help' => 'Clear the cache for this feed.',
+			'reload_articles' => 'Reload articles',
+			'reload_articles_help' => 'Reload articles and fetch complete content if a selector is defined.',
+			'title' => 'Maintenance',
+		),
 		'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',
 		'mute' => 'mute',
 		'no_selected' => 'No feed selected.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',
 			'_' => 'Visibility',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source code',
+			'show_rendered' => 'Show content',
+		),
 		'show' => array(
 			'all' => 'Show all feeds',
 			'error' => 'Show only feeds with error',

+ 10 - 0
app/i18n/es/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'Las fuentes RSS se han actualizado',
 			'added' => 'Fuente RSS agregada <em>%s</em>',
 			'already_subscribed' => 'Ya estás suscrito a <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Fuente eliminada',
 			'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.',
 			'invalid_url' => 'La URL <em>%s</em> es inválida',
 			'not_added' => '<em>%s</em> no ha podido se añadida',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'No hay fuente a actualizar…',
 			'n_actualized' => 'Se han actualiado %d fuentes',
 			'n_entries_deleted' => 'Se han eliminado %d artículos',
 			'over_max' => 'Has alcanzado tu límite de fuentes (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Fuente actualizada',
 		),
 		'purge_completed' => 'Limpieza completada (se han eliminado %d artículos)',

+ 11 - 0
app/i18n/es/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Información',
 		'keep_min' => 'Número mínimo de artículos a conservar',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría <em>%s</em>.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'No hay funentes seleccionadas.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/fr/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'Les flux ont été mis à jour.',
 			'added' => 'Le flux <em>%s</em> a bien été ajouté.',
 			'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>',
+			'cache_cleared' => 'Le cache de <em>%s</em> a été vidée.',
 			'deleted' => 'Le flux a été supprimé.',
 			'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.',
 			'not_added' => '<em>%s</em> n’a pas pu être ajouté.',
+			'not_found' => 'Le flux n\'a pas pu être trouvé.',
 			'no_refresh' => 'Il n’y a aucun flux à actualiser…',
 			'n_actualized' => '%d flux ont été mis à jour.',
 			'n_entries_deleted' => '%d articles ont été supprimés.',
 			'over_max' => 'Vous avez atteint votre limite de flux (%d)',
+			'reloaded' => '<em>%s</em> a été rechargé.',
+			'selector_preview' => array(
+				'http_error' => 'Échec lors du chargement du contenu du site web.',
+				'no_entries' => 'Il n\'y a pas d\'articles dans ce flux. Vous devez avoir au moins un article pour générer une prévisualisation.',
+				'no_feed' => 'Erreur interne (le flux n\'a pas pu être trouvé).',
+				'no_result' => 'Le sélecteur n\'a produit aucune concordance. Dans ces circonstances, le texte original du flux sera affiché.',
+				'selector_empty' => 'Le sélecteur est vide. Vous devez en définir un pour générer une prévisualisation.',
+			),
 			'updated' => 'Le flux a été mis à jour',
 		),
 		'purge_completed' => 'Purge effectuée (%d articles supprimés).',

+ 11 - 0
app/i18n/fr/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informations',
 		'keep_min' => 'Nombre minimum d’articles à conserver',
+		'maintenance' => array(
+			'clear_cache' => 'Vider le cache',
+			'clear_cache_help' => 'Supprime le cache de ce flux.',
+			'reload_articles' => 'Recharger les articles',
+			'reload_articles_help' => 'Recharge les articles et récupère le contenu complet si un sélecteur est défini.',
+			'title' => 'Maintenance',
+		),
 		'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans <em>%s</em>.',
 		'mute' => 'muet',
 		'no_selected' => 'Aucun flux sélectionné.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Afficher dans sa catégorie',
 			'_' => 'Visibilité',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Afficher le code source',
+			'show_rendered' => 'Afficher le contenu',
+		),
 		'show' => array(
 			'all' => 'Montrer tous les flux',
 			'error' => 'Montrer seulement les flux en erreur',

+ 10 - 0
app/i18n/he/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'הזנות RSS עודכנו',
 			'added' => 'RSS הזנת <em>%s</em> נוספה',
 			'already_subscribed' => 'אתה כבר רשום ל <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'ההזנה נמחקה',
 			'error' => 'Feed cannot be updated',	// TODO - Translation
 			'internal_problem' => 'אין אפשרות להוסיף את ההזנה. <a href="%s">בדקו את הלוגים</a> לפרטים.',
 			'invalid_url' => 'URL <em>%s</em> אינו תקין',
 			'not_added' => '<em>%s</em> אין אפשרות להוסיף את',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'אין הזנה שניתן לרענן…',
 			'n_actualized' => '%d הזנות עודכנו',
 			'n_entries_deleted' => '%d המאמרים נמחקו',
 			'over_max' => 'You have reached your limit of feeds (%d)',	// TODO - Translation
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'ההזנה התעדכנה',
 		),
 		'purge_completed' => 'הניקוי הושלם (%d מאמרים נמחקו)',

+ 11 - 0
app/i18n/he/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'מידע',
 		'keep_min' => 'מסםר מינימלי של מאמרים לשמור',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת	<em>%s</em>.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'אף הזנה לא נבחרה.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/it/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS feeds aggiornati',
 			'added' => 'RSS feed <em>%s</em> aggiunti',
 			'already_subscribed' => 'Hai già sottoscritto <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Feed cancellato',
 			'error' => 'Feed non aggiornato',
 			'internal_problem' => 'RSS feed non aggiunto. <a href="%s">Verifica i logs</a> per dettagli.',
 			'invalid_url' => 'URL <em>%s</em> non valido',
 			'not_added' => '<em>%s</em> non può essere aggiunto',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Nessun aggiornamento disponibile…',
 			'n_actualized' => '%d feeds aggiornati',
 			'n_entries_deleted' => '%d articoli cancellati',
 			'over_max' => 'Hai raggiunto il numero limite di feed (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Feed aggiornato',
 		),
 		'purge_completed' => 'Svecchiamento completato (%d articoli cancellati)',

+ 11 - 0
app/i18n/it/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informazioni',
 		'keep_min' => 'Numero minimo di articoli da mantenere',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come <em>%s</em>.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'Nessun feed selezionato.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/kr/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS 피드에서 새 글을 가져왔습니다',
 			'added' => '<em>%s</em> 피드가 추가되었습니다',
 			'already_subscribed' => '이미 <em>%s</em> 피드를 구독 중입니다',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => '피드가 삭제되었습니다',
 			'error' => '피드를 변경할 수 없습니다',
 			'internal_problem' => 'RSS 피드를 추가할 수 없습니다. 자세한 내용은 <a href="%s">FreshRSS 로그</a>를 참고하세요.',
 			'invalid_url' => 'URL (<em>%s</em>)이 유효하지 않습니다',
 			'not_added' => '<em>%s</em> 피드를 추가할 수 없습니다',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => '새 글을 가져올 피드가 없습니다…',
 			'n_actualized' => '%d 개의 피드에서 새 글을 가져왔습니다',
 			'n_entries_deleted' => '%d 개의 글을 삭제했습니다',
 			'over_max' => '피드 개수 제한에 다다랐습니다 (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => '피드가 변경되었습니다',
 		),
 		'purge_completed' => '삭제 완료 (%d 개의 글을 삭제했습니다)',

+ 11 - 0
app/i18n/kr/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => '정보',
 		'keep_min' => '최소 유지 글 개수',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 <em>%s</em> 아래로 분류됩니다.',
 		'mute' => '무기한 새로고침 금지',
 		'no_selected' => '선택된 피드가 없습니다.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => '피드가 속한 카테고리에만 표시하기',
 			'_' => '표시',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => '모든 피드 보기',
 			'error' => '오류가 발생한 피드만 보기',

+ 10 - 0
app/i18n/nl/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS-feeds vernieuwd',
 			'added' => 'RSS feed <em>%s</em> toegevoegd',
 			'already_subscribed' => 'Al geabonneerd op <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Feed verwijderd',
 			'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',
 			'not_added' => '<em>%s</em> kon niet worden toegevoegd',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Er is geen feed om te vernieuwen…',
 			'n_actualized' => '%d feeds zijn vernieuwd',
 			'n_entries_deleted' => '%d artikelen zijn verwijderd',
 			'over_max' => 'Maximum aantal feeds bereikt (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Feed is vernieuwd',
 		),
 		'purge_completed' => 'Opschonen klaar (%d artikelen verwijderd)',

+ 11 - 0
app/i18n/nl/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informatie',
 		'keep_min' => 'Minimum aantal artikelen om te houden',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder <em>%s</em>.',
 		'mute' => 'demp',
 		'no_selected' => 'Geen feed geselecteerd.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Toon in categorie',
 			'_' => 'Zichtbaarheid',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Alle feeds tonen',
 			'error' => 'Alleen feeds met een foutmelding tonen',

+ 10 - 0
app/i18n/oc/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'Los fluxes son estats actualizats',
 			'added' => 'Lo flux RSS <em>%s</em> es ajustat',
 			'already_subscribed' => 'Seguissètz ja <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Lo flux es suprimit',
 			'error' => 'Error en actualizar',
 			'internal_problem' => 'Lo flux pòt pas èsser ajustat. <a href="%s">Consultatz los jornals d’audit de FreshRSS</a> per ne saber mai. Podètz forçar l’apondon en ajustant <code>#force_feed</code> a l’URL.',
 			'invalid_url' => 'L\'URL <em>%s</em> es invalida',
 			'not_added' => '<em>%s</em> a pas pogut èsser ajustat',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'I a pas cap de flux d’actualizar…',
 			'n_actualized' => '%s fluxes son estats actualizats',
 			'n_entries_deleted' => '%d articles son estats suprimits',
 			'over_max' => 'Avètz atengut vòstra limita de fluxes (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Lo flux es actualizat',
 		),
 		'purge_completed' => 'Purga realizada (%s articles suprimits)',

+ 11 - 0
app/i18n/oc/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informacions',
 		'keep_min' => 'Nombre minimum d’articles de servar',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins <em>%s</em>.',
 		'mute' => 'mut',
 		'no_selected' => 'Cap de flux pas seleccionat.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Mostar dins sa categoria',
 			'_' => 'Visibilitat',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Mostrar totes los fluxes',
 			'error' => 'Mostrar pas que los fluxes amb errors',

+ 10 - 0
app/i18n/pt-br/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS feeds foi atualizado',
 			'added' => 'RSS feed <em>%s</em> foi adicionado',
 			'already_subscribed' => 'Você já está inscrito no <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'o Feed foi deletado',
 			'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.',
 			'invalid_url' => 'URL <em>%s</em> é inválida',
 			'not_added' => '<em>%s</em> não pode ser atualizado',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Não há feed para atualizar…',
 			'n_actualized' => '%d feeds foram atualizados',
 			'n_entries_deleted' => '%d artigos foram deletados',
 			'over_max' => 'Você atingiu seu limite de feeds (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Feed foram atualizados',
 		),
 		'purge_completed' => 'Limpeza completa (%d artigos deletados)',

+ 11 - 0
app/i18n/pt-br/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informações',
 		'keep_min' => 'Número mínimo de artigos para manter',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como <em>%s</em>.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'Nenhum feed selecionado.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/ru/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS feeds have been updated',	// TODO - Translation
 			'added' => 'RSS feed <em>%s</em> has been added',	// TODO - Translation
 			'already_subscribed' => 'You have already subscribed to <em>%s</em>',	// TODO - Translation
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Feed has been deleted',	// TODO - Translation
 			'error' => 'Feed cannot be updated',	// TODO - Translation
 			'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 - Translation
 			'invalid_url' => 'URL <em>%s</em> is invalid',	// TODO - Translation
 			'not_added' => '<em>%s</em> could not be added',	// TODO - Translation
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'There is no feed to refresh…',	// TODO - Translation
 			'n_actualized' => '%d feeds have been updated',	// TODO - Translation
 			'n_entries_deleted' => '%d articles have been deleted',	// TODO - Translation
 			'over_max' => 'You have reached your limit of feeds (%d)',	// TODO - Translation
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Feed has been updated',	// TODO - Translation
 		),
 		'purge_completed' => 'Purge completed (%d articles deleted)',	// TODO - Translation

+ 12 - 1
app/i18n/ru/sub.php

@@ -34,7 +34,7 @@ return array(
 		),
 		'clear_cache' => 'Always clear cache',	// TODO - Translation
 		'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',	// TODO - Translation
-		'css_path' => 'Articles CSS path on original website',	// TODO - Translation
+		'css_path' => 'Article CSS selector on original website',	// TODO - Translation
 		'description' => 'Description',	// TODO - Translation
 		'empty' => 'This feed is empty. Please verify that it is still maintained.',	// TODO - Translation
 		'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Information',	// TODO - Translation
 		'keep_min' => 'Minimum number of articles to keep',	// TODO - Translation
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',	// TODO - Translation
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'No feed selected.',	// TODO - Translation
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/sk/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS kanál bol aktualizovaný',
 			'added' => 'RSS kanál <em>%s</em> bol pridaný',
 			'already_subscribed' => 'Tento RSS kanál už odoberáte: <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Kanál bol vymazaný',
 			'error' => 'Kanál sa nepodarilo aktualizovať',
 			'internal_problem' => 'Kanál sa nepodarilo pridať. <a href="%s">Prečítajte si záznamy FreshRSS</a>, ak chcete poznať podrobnosti. Skúste pridať kanál pomocou <code>#force_feed</code> v odkaze (URL).',
 			'invalid_url' => 'Odkaz <em>%s</em> je neplatný',
 			'not_added' => 'Kanál <em>%s</em> sa nepodarilo pridať',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Žiadny kanál sa neaktualizoval…',
 			'n_actualized' => 'Počet aktualizovaných kanálov: %d',
 			'n_entries_deleted' => 'Počet vymazaných článkov: %d',
 			'over_max' => 'Dosiahli ste limit počtu kanálov (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Kanál bol aktualizovaný',
 		),
 		'purge_completed' => 'Čistenie ukončené. Počet vymazaných článkov: %d',

+ 11 - 0
app/i18n/sk/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Informácia',
 		'keep_min' => 'Minimálny počet článkov na uchovanie',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Keď vymažete kategóriu, jej kanály sa automaticky zaradia pod <em>%s</em>.',
 		'mute' => 'stíšiť',
 		'no_selected' => 'Nevybrali ste kanál.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Zobraziť vo svojej kategórii',
 			'_' => 'Viditeľnosť',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Zobraziť všetky kanály',
 			'error' => 'Zobraziť iba kanály s chybou',

+ 10 - 0
app/i18n/tr/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => 'RSS akışları güncellendi',
 			'added' => '<em>%s</em> RSS akışı eklendi',
 			'already_subscribed' => '<em>%s</em> için zaten aboneliğiniz bulunmakta',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => 'Akış silindi',
 			'error' => 'Akış güncellenemiyor',
 			'internal_problem' => 'RSS akışı eklenemiyor. Detaylar için <a href="%s">FreshRSS log kayıtlarını</a> kontrol edin.',
 			'invalid_url' => 'URL <em>%s</em> geçersiz',
 			'not_added' => '<em>%s</em> eklenemedi',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => 'Yenilenecek akış yok…',
 			'n_actualized' => '%d akışları güncellendi',
 			'n_entries_deleted' => '%d makaleleri silindi',
 			'over_max' => 'Akış limitini aştınız (%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => 'Akış güncellendi',
 		),
 		'purge_completed' => 'Temizleme tamamlandı (%d makale silindi)',

+ 11 - 0
app/i18n/tr/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => 'Bilgi',
 		'keep_min' => 'En az tutulacak makale sayısı',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar <em>%s</em> içerisine yerleşir.',
 		'mute' => 'mute',	// TODO - Translation
 		'no_selected' => 'Hiçbir akış seçilmedi.',
@@ -54,6 +61,10 @@ return array(
 			'normal' => 'Show in its category',	// TODO - Translation
 			'_' => 'Visibility',	// TODO - Translation
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => 'Show all feeds',	// TODO - Translation
 			'error' => 'Show only feeds with error',	// TODO - Translation

+ 10 - 0
app/i18n/zh-cn/feedback.php

@@ -84,15 +84,25 @@ return array(
 			'actualizeds' => '已更新订阅源',
 			'added' => '订阅源 <em>%s</em> 已添加',
 			'already_subscribed' => '你已订阅 <em>%s</em>',
+			'cache_cleared' => '<em>%s</em> cache has been cleared',	// TODO - Translation
 			'deleted' => '已删除订阅源',
 			'error' => '订阅源更新失败',
 			'internal_problem' => '订阅源添加失败。<a href="%s">检查 FreshRSS 日志</a> 查看详情。你可以在地址链接后附加 <code>#force_feed</code> 从而尝试强制添加。',
 			'invalid_url' => '地址链接 <em>%s</em> 无效',
 			'not_added' => '<em>%s</em> 添加失败',
+			'not_found' => 'Feed cannot be found',	// TODO - Translation
 			'no_refresh' => '没有可刷新的订阅源…',
 			'n_actualized' => '已更新 %d 个订阅源',
 			'n_entries_deleted' => '已删除 %d 篇文章',
 			'over_max' => '你已达到订阅源数上限(%d)',
+			'reloaded' => '<em>%s</em> has been reloaded',	// TODO - Translation
+			'selector_preview' => array(
+				'http_error' => 'Failed to load website content.',	// TODO - Translation
+				'no_entries' => 'There is no entries in your feed. You need at least one entry to create a preview.',	// TODO - Translation
+				'no_feed' => 'Internal error (no feed to entry).',	// TODO - Translation
+				'no_result' => 'The selector didn\'t match anything. As a fallback the original feed text will be displayed instead.',	// TODO - Translation
+				'selector_empty' => 'The selector is empty. You need to define one to create a preview.',	// TODO - Translation
+			),
 			'updated' => '已更新订阅源',
 		),
 		'purge_completed' => '清除完成(已删除 %d 篇文章)',

+ 11 - 0
app/i18n/zh-cn/sub.php

@@ -44,6 +44,13 @@ return array(
 		),
 		'information' => '信息',
 		'keep_min' => '至少保存的文章数',
+		'maintenance' => array(
+			'clear_cache' => 'Clear cache',	// TODO - Translation
+			'clear_cache_help' => 'Clear the cache of this feed on disk',	// TODO - Translation
+			'reload_articles' => 'Reload articles',	// TODO - Translation
+			'reload_articles_help' => 'Reload articles and fetch complete content',	// TODO - Translation
+			'title' => 'Maintenance',	// TODO - Translation
+		),
 		'moved_category_deleted' => '删除分类时,其中的订阅源会自动归类到 <em>%s</em>',
 		'mute' => '暂停',
 		'no_selected' => '未选择订阅源',
@@ -54,6 +61,10 @@ return array(
 			'normal' => '在分类中显示',
 			'_' => '可见性',
 		),
+		'selector_preview' => array(
+			'show_raw' => 'Show source',	// TODO - Translation
+			'show_rendered' => 'Show content',	// TODO - Translation
+		),
 		'show' => array(
 			'all' => '显示所有订阅源',
 			'error' => '仅显示有错误的订阅源',

+ 13 - 0
app/layout/layout.phtml

@@ -66,5 +66,18 @@
 	<span class="msg"><?= $msg ?></span>
 	<a class="close" href=""><?= _i('close') ?></a>
 </div>
+
+<!-- Popup-->
+<div id="popup">
+	<div id="popup-content">
+		<div id="popup-close" class="popup-row">&times;</div>
+		<div id="popup-txt" class="popup-row"></div>
+		<div id="popup-iframe-container" class="popup-row">
+			<div id="popup-iframe-sub">
+				<iframe id="popup-iframe" frameborder="0"></iframe>
+			</div>
+		</div>
+	</div>
+</div>
 	</body>
 </html>

+ 36 - 0
app/views/feed/contentSelectorPreview.phtml

@@ -0,0 +1,36 @@
+<?php FreshRSS::preLayout(); ?>
+<!DOCTYPE html>
+<html class="preview_background" lang="<?= FreshRSS_Context::$user_conf->language ?>" xml:lang="<?= FreshRSS_Context::$user_conf->language ?>">
+	<head>
+		<?= self::headStyle() ?>
+		<script src="<?= Minz_Url::display('/scripts/preview.js?' . @filemtime(PUBLIC_PATH . '/scripts/preview.js')) ?>"></script>
+	</head>
+	<body class="preview_background">
+		<?php if ($this->fatalError != '') { ?>
+			<p class="alert alert-warn"><?= $this->fatalError ?></p>
+		<?php } else { ?>
+			<?php if ($this->selectorSuccess === false) { ?>
+				<p class="alert alert-warn">
+					<?= _t('feedback.sub.feed.selector_preview.no_result') ?>
+				</p>
+			<?php } ?>
+
+			<div class="preview_controls">
+				<label for="freshrss_rendered">
+					<input type="radio" id="freshrss_rendered" name="freshrss_type" checked="checked" />
+					<?= _t('sub.feed.selector_preview.show_rendered') ?>
+				</label>
+
+				<label for="freshrss_raw">
+					<input type="radio" id="freshrss_raw" name="freshrss_type" />
+					<?= _t('sub.feed.selector_preview.show_raw') ?>
+				</label>
+			</div>
+
+			<div class="content large">
+				<div dir="auto" id="freshrss_rendered_view"><?= $this->htmlContent ?></div>
+				<pre id="freshrss_raw_view" hidden="hidden"> <?= htmlspecialchars($this->htmlContent) ?></pre>
+			</div>
+		<?php } ?>
+	</body>
+</html>

+ 20 - 1
app/views/helpers/feed/update.phtml

@@ -274,7 +274,10 @@
 		<div class="form-group">
 			<label class="group-name" for="path_entries"><?= _t('sub.feed.css_path') ?></label>
 			<div class="group-controls">
-				<input type="text" name="path_entries" id="path_entries" class="extend" value="<?= $this->feed->pathEntries() ?>" placeholder="<?= _t('gen.short.blank_to_disable') ?>" />
+				<div class="stick">
+					<input type="text" name="path_entries" id="path_entries" class="extend" value="<?= $this->feed->pathEntries() ?>" placeholder="<?= _t('gen.short.blank_to_disable') ?>" />
+					<a id="popup-preview-selector" class="btn" href="<?= _url('feed', 'contentSelectorPreview', 'id', $this->feed->id(), 'selector', 'selector-token') ?>"><?= _i('look') ?></a>
+				</div>
 				<?= _i('help') ?> <?= _t('sub.feed.css_help') ?>
 			</div>
 		</div>
@@ -354,5 +357,21 @@
 				<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
 			</div>
 		</div>
+
+		<legend><?= _t('sub.feed.maintenance.title') ?></legend>
+		<div class="form-group">
+			<div class="group-controls">
+				<a class="btn btn-important" href="<?= _url('feed', 'clearCache', 'id', $this->feed->id()) ?>">
+					<?= _t('sub.feed.maintenance.clear_cache') ?>
+				</a>
+				<?= _i('help') ?> <?= _t('sub.feed.maintenance.clear_cache_help') ?>
+			</div>
+			<div class="group-controls">
+				<a class="btn btn-important" href="<?= _url('feed', 'reload', 'id', $this->feed->id()) ?>">
+					<?= _t('sub.feed.maintenance.reload_articles') ?>
+				</a>
+				<?= _i('help') ?> <?= _t('sub.feed.maintenance.reload_articles_help') ?>
+			</div>
+		</div>
 	</form>
 </div>

+ 20 - 1
p/scripts/extra.js

@@ -1,8 +1,25 @@
 // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
 "use strict";
-/* globals context, openNotification, xmlHttpRequestJson */
+/* globals context, openNotification, openPopupWithSource, xmlHttpRequestJson */
 /* jshint esversion:6, strict:global */
 
+function fix_popup_preview_selector() {
+	const link = document.getElementById('popup-preview-selector');
+
+	if (!link) {
+		return;
+	}
+
+	link.addEventListener('click', function (ev) {
+		const selector_entries = document.getElementById('path_entries').value;
+		const href = link.href.replace('selector-token', encodeURIComponent(selector_entries));
+
+		openPopupWithSource(href);
+
+		ev.preventDefault();
+	});
+}
+
 //<crypto form (Web login)>
 function poormanSalt() {	//If crypto.getRandomValues is not available
 	const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz';
@@ -176,6 +193,7 @@ function init_slider_observers() {
 							slider.classList.add('active');
 							closer.classList.add('active');
 							context.ajax_loading = false;
+							fix_popup_preview_selector();
 						};
 					req.send();
 					return false;
@@ -240,6 +258,7 @@ function init_extra() {
 	init_select_observers();
 	init_slider_observers();
 	init_configuration_alert();
+	fix_popup_preview_selector();
 }
 
 if (document.readyState && document.readyState !== 'loading') {

+ 67 - 0
p/scripts/main.js

@@ -1215,6 +1215,72 @@ function init_notifications() {
 }
 // </notification>
 
+// <popup>
+let popup = null,
+	popup_iframe_container = null,
+	popup_iframe = null,
+	popup_txt = null,
+	popup_working = false;
+
+function openPopupWithMessage(msg) {
+	if (popup_working === true) {
+		return false;
+	}
+
+	popup_working = true;
+
+	popup_txt.innerHTML = msg;
+
+	popup_txt.style.display = 'table-row';
+	popup.style.display = 'block';
+}
+
+function openPopupWithSource(source) {
+	if (popup_working === true) {
+		return false;
+	}
+
+	popup_working = true;
+
+	popup_iframe.src = source;
+
+	popup_iframe_container.style.display = 'table-row';
+	popup.style.display = 'block';
+}
+
+function closePopup() {
+	popup.style.display = 'none';
+	popup_iframe_container.style.display = 'none';
+	popup_txt.style.display = 'none';
+
+	popup_iframe.src = 'about:blank';
+
+	popup_working = false;
+}
+
+function init_popup() {
+	//Fetch elements.
+	popup = document.getElementById('popup');
+
+	popup_iframe_container = document.getElementById('popup-iframe-container');
+	popup_iframe = document.getElementById('popup-iframe');
+
+	popup_txt = document.getElementById('popup-txt');
+
+	//Configure close button.
+	document.getElementById('popup-close').addEventListener('click', function (ev) {
+  		closePopup();
+  	});
+
+  	//Configure close-on-click.
+	window.addEventListener('click', function (ev) {
+		if (ev.target == popup) {
+			closePopup();
+		}
+	});
+}
+// </popup>
+
 // <notifs html5>
 var notifs_html5_permission = 'denied';
 
@@ -1483,6 +1549,7 @@ function init_beforeDOM() {
 
 function init_afterDOM() {
 	init_notifications();
+	init_popup();
 	init_confirm_action();
 	const stream = document.getElementById('stream');
 	if (stream) {

+ 43 - 0
p/scripts/preview.js

@@ -0,0 +1,43 @@
+// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
+"use strict";
+/* jshint esversion:6, strict:global */
+
+let rendered_node = null,
+	rendered_view = null,
+	raw_node = null,
+	raw_view = null;
+
+
+function update_ui() {
+	if (rendered_node.checked && !raw_node.checked) {
+		rendered_view.removeAttribute('hidden');
+		raw_view.setAttribute('hidden', true);
+	} else if (!rendered_node.checked && raw_node.checked) {
+		rendered_view.setAttribute('hidden', true);
+		raw_view.removeAttribute('hidden');
+	}
+}
+
+function init_afterDOM() {
+	rendered_node = document.getElementById('freshrss_rendered');
+	rendered_view = document.getElementById('freshrss_rendered_view');
+
+	raw_node = document.getElementById('freshrss_raw');
+	raw_view = document.getElementById('freshrss_raw_view');
+
+	rendered_node.addEventListener('click', update_ui);
+	raw_node.addEventListener('click', update_ui);
+}
+
+
+if (document.readyState && document.readyState !== 'loading') {
+	init_afterDOM();
+} else {
+	document.addEventListener('DOMContentLoaded', function () {
+		if (window.console) {
+			console.log('FreshRSS waiting for DOMContentLoaded…');
+		}
+		init_afterDOM();
+	}, false);
+}
+// @license-end

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

@@ -772,6 +772,73 @@ br {
 	vertical-align: middle;
 }
 
+/*=== Popup */
+#popup {
+	display: none;
+	position: fixed;
+	z-index: 1;
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 100%;
+	overflow: auto;
+	background-color: #eee;
+	background-color: rgba(0,0,0,0.4);
+}
+
+#popup-content {
+	margin: 5rem auto;
+	display: table;
+	width: 80%;
+	height: 80%;
+	overflow: hidden;
+	background-color: #fafafa;
+	border-radius: .25rem;
+	box-shadow: 0 0 1px #737373, 1px 2px 3px #4a4a4f;
+}
+
+.popup-row {
+	display: table-row;
+	width: 100%;
+}
+
+#popup-close {
+	float: right;
+	width: 27px;
+	height: 27px;
+	padding-bottom: 5px;
+	color: #aaa;
+	font-size: 28px;
+	font-weight: bold;
+}
+
+#popup-close:hover,
+#popup-close:focus {
+	color: #000;
+	text-decoration: none;
+	cursor: pointer;
+}
+
+#popup-txt {
+	display: none;
+	height: 100%;
+}
+
+#popup-iframe-container {
+	display: none;
+	height: 100%;
+}
+
+#popup-iframe-sub {
+	padding: 10px;
+	height: 100%;
+}
+
+#popup-iframe {
+	width: 100%;
+	height: 100%;
+}
+
 /*=== Navigation menu (for articles) */
 #nav_entries {
 	background: #fff;
@@ -1214,3 +1281,32 @@ pre.enclosure-description {
 		font-style: italic;
 	}
 }
+
+/*=== PREVIEW */
+/*===========*/
+.preview_controls {
+	margin-left: auto;
+	margin-right: auto;
+	padding: 1rem;
+	max-width: 1000px;
+	text-align: center;
+	background-color: #eee;
+	border: 1px solid #e0e0e0;
+	border-radius: .25rem;
+}
+
+.preview_controls label {
+	display: inline;
+}
+
+.preview_controls label input[type="radio"] {
+	margin-top: -4px;
+}
+
+.preview_controls label + label {
+	margin-left: 1rem;
+}
+
+.preview_background {
+	background-color: transparent;
+}

+ 18 - 0
p/themes/icons/look.svg

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(0.0764824,0,0,0.0764824,-0.812646,-0.015801)">
+        <g transform="matrix(1,0,0,1,-8.5,-25)">
+            <path d="M20.165,126.756C19.688,128.746 19.721,130.785 20.207,132.866C45.104,175.078 81.07,202.183 123.5,202.183C157.853,202.183 191.961,185.861 227.057,133.589C227.806,131.634 227.838,128.051 227.005,126.1C191.909,73.828 157.853,57.429 123.5,57.429C81.07,57.429 45.062,84.543 20.165,126.756Z" style="fill:rgb(102,102,102);"/>
+        </g>
+        <g transform="matrix(1.04264,0,0,1.04264,-18.5327,-41.2106)">
+            <circle cx="128.071" cy="140.428" r="50.694" style="fill:white;"/>
+        </g>
+        <g transform="matrix(0.620569,0,0,0.620569,35.523,18.0607)">
+            <circle cx="128.071" cy="140.428" r="50.694" style="fill:rgb(102,102,102);"/>
+        </g>
+        <g transform="matrix(0.216239,0,0,0.216239,87.306,74.8402)">
+            <circle cx="128.071" cy="140.428" r="50.694" style="fill:white;"/>
+        </g>
+    </g>
+</svg>