Browse Source

Merge branch '646-new-cat-system' into dev

Marien Fressinaud 11 years ago
parent
commit
f1a5a174ea

+ 187 - 0
app/Controllers/categoryController.php

@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * Controller to handle actions relative to categories.
+ * User needs to be connected.
+ */
+class FreshRSS_category_Controller extends Minz_ActionController {
+	/**
+	 * This action is called before every other action in that class. It is
+	 * the common boiler plate for every action. It is triggered by the
+	 * underlying framework.
+	 *
+	 */
+	public function firstAction() {
+		if (!$this->view->loginOk) {
+			Minz_Error::error(
+				403,
+				array('error' => array(_t('access_denied')))
+			);
+		}
+
+		$catDAO = new FreshRSS_CategoryDAO();
+		$catDAO->checkDefault();
+	}
+
+	/**
+	 * This action creates a new category.
+	 *
+	 * Request parameter is:
+	 *   - new-category
+	 */
+	public function createAction() {
+		$catDAO = new FreshRSS_CategoryDAO();
+		$url_redirect = array('c' => 'subscription', 'a' => 'index');
+
+		if (Minz_Request::isPost()) {
+			invalidateHttpCache();
+
+			$cat_name = Minz_Request::param('new-category');
+			if (!$cat_name) {
+				Minz_Request::bad(_t('category_no_name'), $url_redirect);
+			}
+
+			$cat = new FreshRSS_Category($cat_name);
+
+			if ($catDAO->searchByName($cat->name()) != null) {
+				Minz_Request::bad(_t('category_name_exists'), $url_redirect);
+			}
+
+			$values = array(
+				'id' => $cat->id(),
+				'name' => $cat->name(),
+			);
+
+			if ($catDAO->addCategory($values)) {
+				Minz_Request::good(_t('category_created', $cat->name()), $url_redirect);
+			} else {
+				Minz_Request::bad(_t('error_occurred'), $url_redirect);
+			}
+		}
+
+		Minz_Request::forward($url_redirect, true);
+	}
+
+	/**
+	 * This action updates the given category.
+	 *
+	 * Request parameters are:
+	 *   - id
+	 *   - name
+	 */
+	public function updateAction() {
+		$catDAO = new FreshRSS_CategoryDAO();
+		$url_redirect = array('c' => 'subscription', 'a' => 'index');
+
+		if (Minz_Request::isPost()) {
+			invalidateHttpCache();
+
+			$id = Minz_Request::param('id');
+			$name = Minz_Request::param('name', '');
+			if (strlen($name) <= 0) {
+				Minz_Request::bad(_t('category_no_name'), $url_redirect);
+			}
+
+			if ($catDAO->searchById($id) == null) {
+				Minz_Request::bad(_t('category_not_exist'), $url_redirect);
+			}
+
+			$cat = new FreshRSS_Category($name);
+			$values = array(
+				'name' => $cat->name(),
+			);
+
+			if ($catDAO->updateCategory($id, $values)) {
+				Minz_Request::good(_t('category_updated'), $url_redirect);
+			} else {
+				Minz_Request::bad(_t('error_occurred'), $url_redirect);
+			}
+		}
+
+		Minz_Request::forward($url_redirect, true);
+	}
+
+	/**
+	 * This action deletes a category.
+	 * Feeds in the given category are moved in the default category.
+	 * Related user queries are deleted too.
+	 *
+	 * Request parameter is:
+	 *   - id (of a category)
+	 */
+	public function deleteAction() {
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$catDAO = new FreshRSS_CategoryDAO();
+		$default_category = $catDAO->getDefault();
+		$url_redirect = array('c' => 'subscription', 'a' => 'index');
+
+		if (Minz_Request::isPost()) {
+			invalidateHttpCache();
+
+			$id = Minz_Request::param('id');
+			if (!$id) {
+				Minz_Request::bad(_t('category_no_id'), $url_redirect);
+			}
+
+			if ($id === $default_category->id()) {
+				Minz_Request::bad(_t('category_not_delete_default'), $url_redirect);
+			}
+
+			if ($feedDAO->changeCategory($id, $default_category->id()) === false) {
+				Minz_Request::bad(_t('error_occurred'), $url_redirect);
+			}
+
+			if ($catDAO->deleteCategory($id) === false) {
+				Minz_Request::bad(_t('error_occurred'), $url_redirect);
+			}
+
+			// Remove related queries.
+			$this->view->conf->remove_query_by_get('c_' . $id);
+			$this->view->conf->save();
+
+			Minz_Request::good(_t('category_deleted'), $url_redirect);
+		}
+
+		Minz_Request::forward($url_redirect, true);
+	}
+
+	/**
+	 * This action deletes all the feeds relative to a given category.
+	 * Feed-related queries are deleted.
+	 *
+	 * Request parameter is:
+	 *   - id (of a category)
+	 */
+	public function emptyAction() {
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$url_redirect = array('c' => 'subscription', 'a' => 'index');
+
+		if (Minz_Request::isPost()) {
+			invalidateHttpCache();
+
+			$id = Minz_Request::param('id');
+			if (!$id) {
+				Minz_Request::bad(_t('category_no_id'), $url_redirect);
+			}
+
+			// List feeds to remove then related user queries.
+			$feeds = $feedDAO->listByCategory($id);
+
+			if ($feedDAO->deleteFeedByCategory($id)) {
+				// TODO: Delete old favicons
+
+				// Remove related queries
+				foreach ($feeds as $feed) {
+					$this->view->conf->remove_query_by_get('f_' . $feed->id());
+				}
+				$this->view->conf->save();
+
+				Minz_Request::good(_t('category_emptied'), $url_redirect);
+			} else {
+				Minz_Request::bad(_t('error_occurred'), $url_redirect);
+			}
+		}
+
+		Minz_Request::forward($url_redirect, true);
+	}
+}

+ 0 - 162
app/Controllers/configureController.php

@@ -8,9 +8,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 * This action is called before every other action in that class. It is
 	 * the common boiler plate for every action. It is triggered by the
 	 * underlying framework.
-	 *
-	 * @todo see if the category default configuration is needed here or if
-	 *       we can move it to the categorize action
 	 */
 	public function firstAction() {
 		if (!$this->view->loginOk) {
@@ -19,165 +16,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 				array('error' => array(_t('access_denied')))
 			);
 		}
-
-		$catDAO = new FreshRSS_CategoryDAO();
-		$catDAO->checkDefault();
-	}
-
-	/**
-	 * This action handles the category configuration page
-	 *
-	 * It displays the category configuration page.
-	 * If this action is reached through a POST request, it loops through
-	 * every category to check for modification then add a new category if
-	 * needed then sends a notification to the user.
-	 * If a category name is emptied, the category is deleted and all
-	 * related feeds are moved to the default category. Related user queries
-	 * are deleted too.
-	 * If a category name is changed, it is updated.
-	 */
-	public function categorizeAction() {
-		$feedDAO = FreshRSS_Factory::createFeedDao();
-		$catDAO = new FreshRSS_CategoryDAO();
-		$defaultCategory = $catDAO->getDefault();
-		$defaultId = $defaultCategory->id();
-
-		if (Minz_Request::isPost()) {
-			$cats = Minz_Request::param('categories', array());
-			$ids = Minz_Request::param('ids', array());
-			$newCat = trim(Minz_Request::param('new_category', ''));
-
-			foreach ($cats as $key => $name) {
-				if (strlen($name) > 0) {
-					$cat = new FreshRSS_Category($name);
-					$values = array(
-						'name' => $cat->name(),
-					);
-					$catDAO->updateCategory($ids[$key], $values);
-				} elseif ($ids[$key] != $defaultId) {
-					$feedDAO->changeCategory($ids[$key], $defaultId);
-					$catDAO->deleteCategory($ids[$key]);
-
-					// Remove related queries.
-					$this->view->conf->remove_query_by_get('c_' . $ids[$key]);
-					$this->view->conf->save();
-				}
-			}
-
-			if ($newCat != '') {
-				$cat = new FreshRSS_Category($newCat);
-				$values = array(
-					'id' => $cat->id(),
-					'name' => $cat->name(),
-				);
-
-				if ($catDAO->searchByName($newCat) == null) {
-					$catDAO->addCategory($values);
-				}
-			}
-			invalidateHttpCache();
-
-			Minz_Request::good(_t('categories_updated'),
-			                   array('c' => 'configure', 'a' => 'categorize'));
-		}
-
-		$this->view->categories = $catDAO->listCategories(false);
-		$this->view->defaultCategory = $catDAO->getDefault();
-		$this->view->feeds = $feedDAO->listFeeds();
-
-		Minz_View::prependTitle(_t('categories_management') . ' · ');
-	}
-
-	/**
-	 * This action handles the feed configuration page.
-	 *
-	 * It displays the feed configuration page.
-	 * If this action is reached through a POST request, it stores all new
-	 * configuraiton values then sends a notification to the user.
-	 *
-	 * The options available on the page are:
-	 *   - name
-	 *   - description
-	 *   - website URL
-	 *   - feed URL
-	 *   - category id (default: default category id)
-	 *   - CSS path to article on website
-	 *   - display in main stream (default: 0)
-	 *   - HTTP authentication
-	 *   - number of article to retain (default: -2)
-	 *   - refresh frequency (default: -2)
-	 * Default values are empty strings unless specified.
-	 */
-	public function feedAction() {
-		$catDAO = new FreshRSS_CategoryDAO();
-		$this->view->categories = $catDAO->listCategories(false);
-
-		$feedDAO = FreshRSS_Factory::createFeedDao();
-		$this->view->feeds = $feedDAO->listFeeds();
-
-		$id = Minz_Request::param('id');
-		if ($id == false && !empty($this->view->feeds)) {
-			$id = current($this->view->feeds)->id();
-		}
-
-		$this->view->flux = false;
-		if ($id != false) {
-			$this->view->flux = $this->view->feeds[$id];
-
-			if (!$this->view->flux) {
-				Minz_Error::error(
-					404,
-					array('error' => array(_t('page_not_found')))
-				);
-			} else {
-				if (Minz_Request::isPost() && $this->view->flux) {
-					$user = Minz_Request::param('http_user', '');
-					$pass = Minz_Request::param('http_pass', '');
-
-					$httpAuth = '';
-					if ($user != '' || $pass != '') {
-						$httpAuth = $user . ':' . $pass;
-					}
-
-					$cat = intval(Minz_Request::param('category', 0));
-
-					$values = array(
-						'name' => Minz_Request::param('name', ''),
-						'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
-						'website' => Minz_Request::param('website', ''),
-						'url' => Minz_Request::param('url', ''),
-						'category' => $cat,
-						'pathEntries' => Minz_Request::param('path_entries', ''),
-						'priority' => intval(Minz_Request::param('priority', 0)),
-						'httpAuth' => $httpAuth,
-						'keep_history' => intval(Minz_Request::param('keep_history', -2)),
-						'ttl' => intval(Minz_Request::param('ttl', -2)),
-					);
-
-					if ($feedDAO->updateFeed($id, $values)) {
-						$this->view->flux->_category($cat);
-						$this->view->flux->faviconPrepare();
-						$notif = array(
-							'type' => 'good',
-							'content' => _t('feed_updated')
-						);
-					} else {
-						$notif = array(
-							'type' => 'bad',
-							'content' => _t('error_occurred_update')
-						);
-					}
-					invalidateHttpCache();
-
-					Minz_Session::_param('notification', $notif);
-					Minz_Request::forward(array('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
-				}
-
-				Minz_View::prependTitle(_t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · ');
-			}
-		} else {
-			Minz_View::prependTitle(_t('rss_feed_management') . ' · ');
-		}
 	}
 
 	/**

+ 28 - 52
app/Controllers/feedController.php

@@ -26,8 +26,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 
 		if ($url === false) {
 			Minz_Request::forward(array(
-				'c' => 'configure',
-				'a' => 'feed'
+				'c' => 'subscription',
+				'a' => 'index'
 			), true);
 		}
 
@@ -166,7 +166,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				$feedDAO->rollBack ();
 			}
 
-			Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
+			Minz_Request::forward (array ('c' => 'subscription', 'a' => 'index', 'params' => $params), true);
 		} else {
 
 			// GET request so we must ask confirmation to user
@@ -193,8 +193,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				Minz_Session::_param('notification', $notif);
 
 				Minz_Request::forward(array(
-					'c' => 'configure',
-					'a' => 'feed',
+					'c' => 'subscription',
+					'a' => 'index',
 					'params' => array(
 						'id' => $feed->id()
 					)
@@ -214,7 +214,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			);
 			Minz_Session::_param ('notification', $notif);
 			invalidateHttpCache();
-			Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
+			Minz_Request::forward (array ('c' => 'subscription',
+			                              'a' => 'index',
+			                              'params' => array('id' => $id)), true);
 		}
 	}
 
@@ -376,62 +378,36 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		}
 	}
 
-	public function deleteAction () {
-		if (Minz_Request::isPost ()) {
-			$type = Minz_Request::param ('type', 'feed');
-			$id = Minz_Request::param ('id');
-
+	public function deleteAction() {
+		if (Minz_Request::isPost()) {
+			$id = Minz_Request::param('id');
 			$feedDAO = FreshRSS_Factory::createFeedDao();
-			if ($type == 'category') {
-				// List feeds to remove then related user queries.
-				$feeds = $feedDAO->listByCategory($id);
-
-				if ($feedDAO->deleteFeedByCategory ($id)) {
-					// Remove related queries
-					foreach ($feeds as $feed) {
-						$this->view->conf->remove_query_by_get('f_' . $feed->id());
-					}
-					$this->view->conf->save();
 
-					$notif = array (
-						'type' => 'good',
-						'content' => Minz_Translate::t ('category_emptied')
-					);
-					//TODO: Delete old favicons
-				} else {
-					$notif = array (
-						'type' => 'bad',
-						'content' => Minz_Translate::t ('error_occured')
-					);
-				}
-			} else {
-				if ($feedDAO->deleteFeed ($id)) {
-					// Remove related queries
-					$this->view->conf->remove_query_by_get('f_' . $id);
-					$this->view->conf->save();
+			if ($feedDAO->deleteFeed($id)) {
+				// TODO: Delete old favicon
 
-					$notif = array (
-						'type' => 'good',
-						'content' => Minz_Translate::t ('feed_deleted')
-					);
-					//TODO: Delete old favicon
-				} else {
-					$notif = array (
-						'type' => 'bad',
-						'content' => Minz_Translate::t ('error_occured')
-					);
-				}
+				// Remove related queries
+				$this->view->conf->remove_query_by_get('f_' . $id);
+				$this->view->conf->save();
+
+				$notif = array(
+					'type' => 'good',
+					'content' => _t('feed_deleted')
+				);
+			} else {
+				$notif = array(
+					'type' => 'bad',
+					'content' => _t('error_occured')
+				);
 			}
 
-			Minz_Session::_param ('notification', $notif);
+			Minz_Session::_param('notification', $notif);
 
 			$redirect_url = Minz_Request::param('r', false, true);
 			if ($redirect_url) {
 				Minz_Request::forward($redirect_url);
-			} elseif ($type == 'category') {
-				Minz_Request::forward(array ('c' => 'configure', 'a' => 'categorize'), true);
 			} else {
-				Minz_Request::forward(array ('c' => 'configure', 'a' => 'feed'), true);
+				Minz_Request::forward(array('c' => 'subscription', 'a' => 'index'), true);
 			}
 		}
 	}

+ 119 - 0
app/Controllers/subscriptionController.php

@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * Controller to handle subscription actions.
+ */
+class FreshRSS_subscription_Controller extends Minz_ActionController {
+	/**
+	 * This action is called before every other action in that class. It is
+	 * the common boiler plate for every action. It is triggered by the
+	 * underlying framework.
+	 */
+	public function firstAction() {
+		if (!$this->view->loginOk) {
+			Minz_Error::error(
+				403,
+				array('error' => array(_t('access_denied')))
+			);
+		}
+
+		$catDAO = new FreshRSS_CategoryDAO();
+
+		$catDAO->checkDefault();
+		$this->view->categories = $catDAO->listCategories(false);
+		$this->view->default_category = $catDAO->getDefault();
+	}
+
+	/**
+	 * This action handles the main subscription page
+	 *
+	 * It displays categories and associated feeds.
+	 */
+	public function indexAction() {
+		Minz_View::prependTitle(_t('subscription_management') . ' · ');
+
+		$id = Minz_Request::param('id');
+		if ($id !== false) {
+			$feedDAO = FreshRSS_Factory::createFeedDao();
+			$this->view->feed = $feedDAO->searchById($id);
+		}
+	}
+
+	/**
+	 * This action handles the feed configuration page.
+	 *
+	 * It displays the feed configuration page.
+	 * If this action is reached through a POST request, it stores all new
+	 * configuraiton values then sends a notification to the user.
+	 *
+	 * The options available on the page are:
+	 *   - name
+	 *   - description
+	 *   - website URL
+	 *   - feed URL
+	 *   - category id (default: default category id)
+	 *   - CSS path to article on website
+	 *   - display in main stream (default: 0)
+	 *   - HTTP authentication
+	 *   - number of article to retain (default: -2)
+	 *   - refresh frequency (default: -2)
+	 * Default values are empty strings unless specified.
+	 */
+	public function feedAction() {
+		if (Minz_Request::param('ajax')) {
+			$this->view->_useLayout(false);
+		}
+
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$this->view->feeds = $feedDAO->listFeeds();
+
+		$id = Minz_Request::param('id');
+		if ($id === false || !isset($this->view->feeds[$id])) {
+			Minz_Error::error(
+				404,
+				array('error' => array(_t('page_not_found')))
+			);
+			return;
+		}
+
+		$this->view->feed = $this->view->feeds[$id];
+
+		Minz_View::prependTitle(_t('rss_feed_management') . ' · ' . $this->view->feed->name() . ' · ');
+
+		if (Minz_Request::isPost()) {
+			$user = Minz_Request::param('http_user', '');
+			$pass = Minz_Request::param('http_pass', '');
+
+			$httpAuth = '';
+			if ($user != '' || $pass != '') {
+				$httpAuth = $user . ':' . $pass;
+			}
+
+			$cat = intval(Minz_Request::param('category', 0));
+
+			$values = array(
+				'name' => Minz_Request::param('name', ''),
+				'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
+				'website' => Minz_Request::param('website', ''),
+				'url' => Minz_Request::param('url', ''),
+				'category' => $cat,
+				'pathEntries' => Minz_Request::param('path_entries', ''),
+				'priority' => intval(Minz_Request::param('priority', 0)),
+				'httpAuth' => $httpAuth,
+				'keep_history' => intval(Minz_Request::param('keep_history', -2)),
+				'ttl' => intval(Minz_Request::param('ttl', -2)),
+			);
+
+			invalidateHttpCache();
+
+			if ($feedDAO->updateFeed($id, $values)) {
+				$this->view->feed->_category($cat);
+				$this->view->feed->faviconPrepare();
+
+				Minz_Request::good(_t('feed_updated'), array('c' => 'subscription', 'params' => array('id' => $id)));
+			} else {
+				Minz_Request::bad(_t('error_occurred_update'), array('c' => 'subscription'));
+			}
+		}
+	}
+}

+ 1 - 1
app/Models/Category.php

@@ -61,7 +61,7 @@ class FreshRSS_Category extends Minz_Model {
 		$this->id = $value;
 	}
 	public function _name ($value) {
-		$this->name = $value;
+		$this->name = substr(trim($value), 0, 255);
 	}
 	public function _feeds ($values) {
 		if (!is_array ($values)) {

+ 1 - 0
app/Models/Themes.php

@@ -82,6 +82,7 @@ class FreshRSS_Themes extends Minz_Model {
 			'favorite' => '★',
 			'help' => 'ⓘ',
 			'icon' => '⊚',
+			'import' => '⤓',
 			'key' => '⚿',
 			'link' => '↗',
 			'login' => '🔒',

+ 0 - 75
app/layout/aside_feed.phtml

@@ -1,75 +0,0 @@
-<ul class="nav nav-list aside aside_feed">
-	<li class="nav-header"><?php echo Minz_Translate::t ('your_rss_feeds'); ?></li>
-
-	<li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>" autocomplete="off">
-		<div class="stick">
-			<input type="url" name="url_rss" placeholder="<?php echo Minz_Translate::t ('add_rss_feed'); ?>" />
-			<div class="dropdown">
-				<div id="dropdown-cat" class="dropdown-target"></div>
-
-				<a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo FreshRSS_Themes::icon('down'); ?></a>
-				<ul class="dropdown-menu">
-					<li class="dropdown-close"><a href="#close">❌</a></li>
-
-					<li class="dropdown-header"><?php echo Minz_Translate::t ('category'); ?></li>
-
-					<li class="input">
-						<select name="category" id="category">
-						<?php foreach ($this->categories as $cat) { ?>
-						<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id () == 1 ? ' selected="selected"' : ''; ?>>
-							<?php echo $cat->name (); ?>
-						</option>
-						<?php } ?>
-						<option value="nc"><?php echo Minz_Translate::t ('new_category'); ?></option>
-						</select>
-					</li>
-
-					<li class="input" style="display:none">
-						<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
-					</li>
-
-					<li class="separator"></li>
-
-					<li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li>
-					<li class="input">
-						<input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" />
-					</li>
-					<li class="input">
-						<input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" />
-					</li>
-				</ul>
-			</div>
-			<button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('add'); ?></button>
-		</div>
-	</form></li>
-
-	<li class="item">
-		<a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&amp;url_rss='+encodeURIComponent(url), '_blank');})();">
-			<?php echo Minz_Translate::t('bookmark'); ?>
-		</a>
-	</li>
-
-	<li class="item<?php echo Minz_Request::controllerName () == 'importExport' ? ' active' : ''; ?>">
-		<a href="<?php echo _url ('importExport', 'index'); ?>"><?php echo Minz_Translate::t ('import_export'); ?></a>
-	</li>
-
-	<li class="item<?php echo Minz_Request::actionName () == 'categorize' ? ' active' : ''; ?>">
-		<a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Minz_Translate::t ('categories_management'); ?></a>
-	</li>
-
-	<li class="separator"></li>
-
-	<?php if (!empty ($this->feeds)) { ?>
-	<?php foreach ($this->feeds as $feed) { ?>
-	<?php $nbEntries = $feed->nbEntries (); ?>
-	<li class="item<?php echo (isset($this->flux) && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
-		<a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>">
-			<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
-			<?php echo $feed->name (); ?>
-		</a>
-	</li>
-	<?php } ?>
-	<?php } else { ?>
-	<li class="item disable"><?php echo Minz_Translate::t ('no_rss_feed'); ?></li>
-	<?php } ?>
-</ul>

+ 3 - 3
app/layout/aside_flux.phtml

@@ -7,8 +7,8 @@
 
 		<li>
 			<div class="stick configure-feeds">
-				<a class="btn btn-important" href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('subscription_management'); ?></a>
-				<a class="btn btn-important" href="<?php echo _url('configure', 'categorize'); ?>" title="<?php echo _t('categories_management'); ?>"><?php echo _i('category-white'); ?></a>
+				<a class="btn btn-important" href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('subscription_management'); ?></a>
+				<a class="btn btn-important" href="<?php echo _url('importExport', 'index'); ?>"><?php echo _i('import'); ?></a>
 			</div>
 		</li>
 		<?php } elseif (Minz_Configuration::needsLogin()) { ?>
@@ -89,7 +89,7 @@
 		<li class="item"><a target="_blank" href="http://example.net/"><?php echo _t('see_website'); ?></a></li>
 		<?php if ($this->loginOk) { ?>
 		<li class="separator"></li>
-		<li class="item"><a href="<?php echo _url('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo _t('administration'); ?></a></li>
+		<li class="item"><a href="<?php echo _url('subscription', 'index', 'id', '!!!!!!'); ?>"><?php echo _t('administration'); ?></a></li>
 		<li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo _t('actualize'); ?></a></li>
 		<li class="item">
 			<?php $confirm = $this->conf->reading_confirm ? 'confirm' : ''; ?>

+ 17 - 0
app/layout/aside_subscription.phtml

@@ -0,0 +1,17 @@
+<ul class="nav nav-list aside aside_feed">
+	<li class="nav-header"><?php echo _t('subscription_management'); ?></li>
+
+	<li class="item<?php echo Minz_Request::controllerName() == 'subscription' ? ' active' : ''; ?>">
+		<a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('subscription_management'); ?></a>
+	</li>
+
+	<li class="item<?php echo Minz_Request::controllerName() == 'importExport' ? ' active' : ''; ?>">
+		<a href="<?php echo _url('importExport', 'index'); ?>"><?php echo _t('import_export'); ?></a>
+	</li>
+
+	<li class="item">
+		<a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&amp;url_rss='+encodeURIComponent(url), '_blank');})();">
+			<?php echo _t('bookmark'); ?>
+		</a>
+	</li>
+</ul>

+ 0 - 55
app/views/configure/categorize.phtml

@@ -1,55 +0,0 @@
-<?php $this->partial ('aside_feed'); ?>
-
-<div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
-
-	<form method="post" action="<?php echo _url ('configure', 'categorize'); ?>">
-		<legend><?php echo Minz_Translate::t ('categories_management'); ?></legend>
-
-		<p class="alert alert-warn"><?php echo Minz_Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p>
-
-		<?php $i = 0; foreach ($this->categories as $cat) { $i++; ?>
-		<div class="form-group">
-			<label class="group-name" for="cat_<?php echo $cat->id (); ?>">
-				<?php echo Minz_Translate::t ('category_number', $i); ?>
-			</label>
-			<div class="group-controls">
-				<div class="stick">
-					<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
-
-					<?php if ($cat->nbFeed () > 0) { ?>
-					<a class="btn" href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id ()); ?>">
-						<?php echo _i('link'); ?>
-					</a>
-					<button formaction="<?php echo _url('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"
-					        class="btn btn-attention confirm"
-					        data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>"
-					        type="submit"><?php echo _t('ask_empty'); ?></button>
-					<?php } ?>
-				</div>
-				(<?php echo Minz_Translate::t ('number_feeds', $cat->nbFeed ()); ?>)
-
-				<?php if ($cat->id () === $this->defaultCategory->id ()) { ?>
-				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('can_not_be_deleted'); ?>
-				<?php } ?>
-
-				<input type="hidden" name="ids[]" value="<?php echo $cat->id (); ?>" />
-			</div>
-		</div>
-		<?php } ?>
-
-		<div class="form-group">
-			<label class="group-name" for="new_category"><?php echo Minz_Translate::t ('add_category'); ?></label>
-			<div class="group-controls">
-				<input type="text" id="new_category" name="new_category" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
-			</div>
-		</div>
-
-		<div class="form-group form-actions">
-			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
-			</div>
-		</div>
-	</form>
-</div>

+ 25 - 32
app/views/configure/feed.phtml → app/views/helpers/feed/update.phtml

@@ -1,40 +1,37 @@
-<?php $this->partial ('aside_feed'); ?>
-
-<?php if ($this->flux) { ?>
 <div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Minz_Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Minz_Translate::t ('filter'); ?></a>
+	<!-- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Minz_Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->feed->id ()); ?>"><?php echo Minz_Translate::t ('filter'); ?></a> -->
 
-	<h1><?php echo $this->flux->name (); ?></h1>
-	<?php echo $this->flux->description (); ?>
+	<h1><?php echo $this->feed->name (); ?></h1>
+	<?php echo $this->feed->description (); ?>
 
-	<?php $nbEntries = $this->flux->nbEntries (); ?>
+	<?php $nbEntries = $this->feed->nbEntries (); ?>
 
-	<?php if ($this->flux->inError ()) { ?>
+	<?php if ($this->feed->inError ()) { ?>
 	<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p>
 	<?php } elseif ($nbEntries === 0) { ?>
 	<p class="alert alert-warn"><?php echo Minz_Translate::t ('feed_empty'); ?></p>
 	<?php } ?>
 
-	<form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off">
+	<form method="post" action="<?php echo _url ('subscription', 'feed', 'id', $this->feed->id ()); ?>" autocomplete="off">
 		<legend><?php echo Minz_Translate::t ('informations'); ?></legend>
 		<div class="form-group">
 			<label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="name" id="name" class="extend" value="<?php echo $this->flux->name () ; ?>" />
+				<input type="text" name="name" id="name" class="extend" value="<?php echo $this->feed->name () ; ?>" />
 			</div>
 		</div>
 		<div class="form-group">
 			<label class="group-name" for="description"><?php echo Minz_Translate::t ('feed_description'); ?></label>
 			<div class="group-controls">
-				<textarea name="description" id="description"><?php echo htmlspecialchars($this->flux->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea>
+				<textarea name="description" id="description"><?php echo htmlspecialchars($this->feed->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea>
 			</div>
 		</div>
 		<div class="form-group">
 			<label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label>
 			<div class="group-controls">
 				<div class="stick">
-					<input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" />
-					<a class="btn" target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
+					<input type="text" name="website" id="website" class="extend" value="<?php echo $this->feed->website (); ?>" />
+					<a class="btn" target="_blank" href="<?php echo $this->feed->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
 				</div>
 			</div>
 		</div>
@@ -42,11 +39,11 @@
 			<label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label>
 			<div class="group-controls">
 				<div class="stick">
-					<input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" />
-					<a class="btn" target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
+					<input type="text" name="url" id="url" class="extend" value="<?php echo $this->feed->url (); ?>" />
+					<a class="btn" target="_blank" href="<?php echo $this->feed->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
 				</div>
 
-				<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
+				<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
 			</div>
 		</div>
 		<div class="form-group">
@@ -54,7 +51,7 @@
 			<div class="group-controls">
 				<select name="category" id="category">
 				<?php foreach ($this->categories as $cat) { ?>
-				<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->flux->category () ? ' selected="selected"' : ''; ?>>
+				<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->feed->category () ? ' selected="selected"' : ''; ?>>
 					<?php echo $cat->name (); ?>
 				</option>
 				<?php } ?>
@@ -65,14 +62,14 @@
 			<label class="group-name" for="priority"><?php echo Minz_Translate::t ('show_in_all_flux'); ?></label>
 			<div class="group-controls">
 				<label class="checkbox" for="priority">
-					<input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->flux->priority () > 0 ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->feed->priority () > 0 ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('yes'); ?>
 				</label>
 			</div>
 		</div>
 		<div class="form-group">
 			<div class="group-controls">
-				<a href="<?php echo _url('stats', 'repartition', 'id', $this->flux->id()); ?>">
+				<a href="<?php echo _url('stats', 'repartition', 'id', $this->feed->id()); ?>">
 					<?php echo _i('stats'); ?> <?php echo _t('stats'); ?>
 				</a>
 			</div>
@@ -82,7 +79,7 @@
 				<button class="btn btn-important"><?php echo _t('save'); ?></button>
 				<button class="btn btn-attention confirm"
 				        data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>"
-				        formaction="<?php echo _url('feed', 'delete', 'id', $this->flux->id ()); ?>"
+				        formaction="<?php echo _url('feed', 'delete', 'id', $this->feed->id ()); ?>"
 				        formmethod="post"><?php echo _t('delete'); ?></button>
 			</div>
 		</div>
@@ -93,7 +90,7 @@
 			<div class="group-controls">
 				<div class="stick">
 					<input type="text" value="<?php echo _t('number_articles', $nbEntries); ?>" disabled="disabled" />
-					<a class="btn" href="<?php echo _url('feed', 'actualize', 'id', $this->flux->id ()); ?>">
+					<a class="btn" href="<?php echo _url('feed', 'actualize', 'id', $this->feed->id ()); ?>">
 						<?php echo _i('refresh'); ?> <?php echo _t('actualize'); ?>
 					</a>
 				</div>
@@ -104,7 +101,7 @@
 			<div class="group-controls">
 				<select class="number" name="keep_history" id="keep_history" required="required"><?php
 					foreach (array('' => '', -2 => Minz_Translate::t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
-						echo '<option value="' . $v . ($this->flux->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
+						echo '<option value="' . $v . ($this->feed->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
 					}
 				?></select>
 			</div>
@@ -119,13 +116,13 @@
 					                36000 => '10h', 43200 => '12h', 64800 => '18h',
 					                86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
 					                604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo', -1 => '∞') as $v => $t) {
-						echo '<option value="' . $v . ($this->flux->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
-						if ($this->flux->ttl() == $v) {
+						echo '<option value="' . $v . ($this->feed->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
+						if ($this->feed->ttl() == $v) {
 							$found = true;
 						}
 					}
 					if (!$found) {
-						echo '<option value="' . intval($this->flux->ttl()) . '" selected="selected">' . intval($this->flux->ttl()) . 's</option>';
+						echo '<option value="' . intval($this->feed->ttl()) . '" selected="selected">' . intval($this->feed->ttl()) . 's</option>';
 					}
 				?></select>
 			</div>
@@ -133,12 +130,12 @@
 		<div class="form-group form-actions">
 			<div class="group-controls">
 				<button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
-				<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'truncate', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('truncate'); ?></button>
+				<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'truncate', 'params' => array ('id' => $this->feed->id ()))); ?>"><?php echo Minz_Translate::t ('truncate'); ?></button>
 			</div>
 		</div>
 
 		<legend><?php echo Minz_Translate::t ('login_configuration'); ?></legend>
-		<?php $auth = $this->flux->httpAuth (false); ?>
+		<?php $auth = $this->feed->httpAuth (false); ?>
 		<div class="form-group">
 			<label class="group-name" for="http_user"><?php echo Minz_Translate::t ('http_username'); ?></label>
 			<div class="group-controls">
@@ -163,7 +160,7 @@
 		<div class="form-group">
 			<label class="group-name" for="path_entries"><?php echo Minz_Translate::t ('css_path_on_website'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
+				<input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->feed->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
 				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('retrieve_truncated_feeds'); ?>
 			</div>
 		</div>
@@ -176,7 +173,3 @@
 		</div>
 	</form>
 </div>
-
-<?php } else { ?>
-<div class="alert alert-warn"><span class="alert-head"><?php echo Minz_Translate::t ('no_selected_feed'); ?></span> <?php echo Minz_Translate::t ('think_to_add'); ?></div>
-<?php } ?>

+ 1 - 1
app/views/helpers/view/global_view.phtml

@@ -48,6 +48,6 @@
 <?php } else { ?>
 <div id="stream" class="prompt alert alert-warn global">
 	<h2><?php echo _t('no_feed_to_display'); ?></h2>
-	<a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
+	<a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
 </div>
 <?php } ?>

+ 1 - 1
app/views/helpers/view/normal_view.phtml

@@ -186,6 +186,6 @@ if (!empty($this->entries)) {
 <?php } else { ?>
 <div id="stream" class="prompt alert alert-warn normal">
 	<h2><?php echo _t('no_feed_to_display'); ?></h2>
-	<a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
+	<a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
 </div>
 <?php } ?>

+ 1 - 1
app/views/helpers/view/reader_view.phtml

@@ -39,6 +39,6 @@ if (!empty($this->entries)) {
 <?php } else { ?>
 <div id="stream" class="prompt alert alert-warn reader">
 	<h2><?php echo _t('no_feed_to_display'); ?></h2>
-	<a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
+	<a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br />
 </div>
 <?php } ?>

+ 1 - 1
app/views/importExport/index.phtml

@@ -1,4 +1,4 @@
-<?php $this->partial('aside_feed'); ?>
+<?php $this->partial('aside_subscription'); ?>
 
 <div class="post ">
 	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>

+ 1 - 1
app/views/stats/idle.phtml

@@ -25,7 +25,7 @@
 				<li class="item">
 					<div class="stick">
 						<a class="btn" href="<?php echo _url('index', 'index', 'get', 'f_' . $feed['id']); ?>"><?php echo _i('link'); ?> <?php echo _t('filter'); ?></a>
-						<a class="btn" href="<?php echo _url('configure', 'feed', 'id', $feed['id']); ?>"><?php echo _i('configure'); ?> <?php echo _t('administration'); ?></a>
+						<a class="btn" href="<?php echo _url('subscription', 'index', 'id', $feed['id']); ?>"><?php echo _i('configure'); ?> <?php echo _t('administration'); ?></a>
 						<button class="btn btn-attention confirm" form="form-delete" formaction="<?php echo _url('feed', 'delete', 'id', $feed['id'], 'r', $current_url); ?>"><?php echo _t('delete'); ?></button>
 					</div>
 				</li>

+ 1 - 1
app/views/stats/repartition.phtml

@@ -24,7 +24,7 @@
 	</select>
 
 	<?php if ($this->feed) {?>
-		<a class="btn" href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
+		<a class="btn" href="<?php echo _url('subscription', 'index', 'id', $this->feed->id()); ?>">
 			<?php echo _i('configure'); ?> <?php echo _t('administration'); ?>
 		</a>
 	<?php }?>

+ 15 - 0
app/views/subscription/feed.phtml

@@ -0,0 +1,15 @@
+<?php
+
+if (!Minz_Request::param('ajax')) {
+	$this->partial('aside_subscription');
+}
+
+if ($this->feed) {
+	$this->renderHelper('feed/update');
+} else {
+?>
+<div class="alert alert-warn">
+	<span class="alert-head"><?php echo _t('no_selected_feed'); ?></span>
+	<?php echo _t('think_to_add'); ?>
+</div>
+<?php } ?>

+ 145 - 0
app/views/subscription/index.phtml

@@ -0,0 +1,145 @@
+<?php $this->partial('aside_subscription'); ?>
+
+<div class="post">
+	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
+
+	<h2><?php echo _t('subscription_management'); ?></h2>
+
+	<form id="add_rss" method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off">
+		<div class="stick">
+			<input type="url" name="url_rss" class="extend" placeholder="<?php echo _t('add_rss_feed'); ?>" />
+			<div class="dropdown">
+				<div id="dropdown-cat" class="dropdown-target"></div>
+
+				<a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo _i('down'); ?></a>
+				<ul class="dropdown-menu">
+					<li class="dropdown-close"><a href="#close">❌</a></li>
+
+					<li class="dropdown-header"><?php echo _t('category'); ?></li>
+
+					<li class="input">
+						<select name="category" id="category">
+						<?php foreach ($this->categories as $cat) { ?>
+						<option value="<?php echo $cat->id(); ?>"<?php echo $cat->id() == 1 ? ' selected="selected"' : ''; ?>>
+							<?php echo $cat->name(); ?>
+						</option>
+						<?php } ?>
+						<option value="nc"><?php echo _t('new_category'); ?></option>
+						</select>
+					</li>
+
+					<li class="input" style="display:none">
+						<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo _t('new_category'); ?>" />
+					</li>
+
+					<li class="separator"></li>
+
+					<li class="dropdown-header"><?php echo _t('http_authentication'); ?></li>
+					<li class="input">
+						<input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo _t('username'); ?>" />
+					</li>
+					<li class="input">
+						<input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo _t('password'); ?>" />
+					</li>
+				</ul>
+			</div>
+			<button class="btn" type="submit"><?php echo _i('add'); ?></button>
+		</div>
+	</form>
+
+	<p class="alert alert-warn">
+		<?php echo _t('feeds_moved_category_deleted', $this->default_category->name()); ?>
+	</p>
+
+	<div class="box">
+		<div class="box-title"><label for="new-category"><?php echo _t('add_category'); ?></label></div>
+
+		<ul class="box-content box-content-centered">
+			<form action="<?php echo _url('category', 'create'); ?>" method="post">
+				<li class="item"><input type="text" id="new-category" name="new-category" placeholder="<?php echo _t('new_category'); ?>" /></li>
+				<li class="item"><button class="btn btn-important" type="submit"><?php echo _t('submit'); ?></button></li>
+			</form>
+		</ul>
+	</div>
+
+	<form id="controller-category" method="post" style="display: none;"></form>
+
+	<?php
+		foreach ($this->categories as $cat) {
+			$feeds = $cat->feeds();
+	?>
+	<div class="box">
+		<div class="box-title">
+			<form action="<?php echo _url('category', 'update', 'id', $cat->id()); ?>" method="post">
+				<input type="text" name="name" value="<?php echo $cat->name(); ?>" />
+
+				<div class="dropdown">
+					<div id="dropdown-cat-<?php echo $cat->id(); ?>" class="dropdown-target"></div>
+
+					<a class="dropdown-toggle btn" href="#dropdown-cat-<?php echo $cat->id(); ?>"><?php echo _i('down'); ?></a>
+					<ul class="dropdown-menu">
+						<li class="dropdown-close"><a href="#close">❌</a></li>
+
+						<li class="item"><a href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id()); ?>"><?php echo _t('filter'); ?></a></li>
+
+						<?php
+							$no_feed = empty($feeds);
+							$is_default = ($cat->id() === $this->default_category->id());
+
+							if (!$no_feed || !$is_default) {
+						?>
+						<li class="separator"></li>
+						<?php } if (!$no_feed) { ?>
+						<li class="item">
+							<button class="as-link confirm"
+							        data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>"
+							        type="submit"
+							        form="controller-category"
+							        formaction="<?php echo _url('category', 'empty', 'id', $cat->id()); ?>">
+							        <?php echo _t('ask_empty'); ?></button>
+						</li>
+						<?php } if (!$is_default) { ?>
+						<li class="item">
+							<button class="as-link confirm"
+							        data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>"
+							        type="submit"
+							        form="controller-category"
+							        formaction="<?php echo _url('category', 'delete', 'id', $cat->id()); ?>">
+							        <?php echo _t('delete'); ?></button>
+						</li>
+						<?php } ?>
+					</ul>
+				</div>
+			</form>
+		</div>
+
+		<ul class="box-content">
+			<?php if (!empty($feeds)) { ?>
+			<?php
+					foreach ($feeds as $feed) {
+						$error = $feed->inError() ? ' error' : '';
+						$empty = $feed->nbEntries() == 0 ? ' empty' : '';
+			?>
+			<li class="item<?php echo $error, $empty; ?>">
+				<a class="configure open-slider" href="<?php echo _url('subscription', 'feed', 'id', $feed->id()); ?>"><?php echo _i('configure'); ?></a>
+				<img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <?php echo $feed->name(); ?>
+			</li>
+			<?php 	}
+				} else {
+			?>
+			<li class="item"><?php echo _t('category_empty'); ?></li>
+			<?php } ?>
+		</ul>
+	</div>
+	<?php } ?>
+</div>
+
+<?php $class = isset($this->feed) ? ' class="active"' : ''; ?>
+<a href="#" id="close-slider"<?php echo $class; ?>></a>
+<div id="slider"<?php echo $class; ?>>
+<?php
+	if (isset($this->feed)) {
+		$this->renderHelper('feed/update');
+	}
+?>
+</div>

+ 37 - 0
p/scripts/main.js

@@ -1233,6 +1233,42 @@ function faviconNbUnread(n) {
 	}
 }
 
+function init_slider_observers() {
+	var slider = $('#slider'),
+	    closer = $('#close-slider');
+	if (slider.length < 1) {
+		return;
+	}
+
+	$('.open-slider').on('click', function() {
+		if (ajax_loading) {
+			return false;
+		}
+
+		ajax_loading = true;
+		var url_slide = $(this).attr('href');
+
+		$.ajax({
+			type: 'GET',
+			url: url_slide,
+			data : { ajax: true }
+		}).done(function (data) {
+			slider.html(data);
+			closer.addClass('active');
+			slider.addClass('active');
+			ajax_loading = false;
+		});
+
+		return false;
+	});
+
+	closer.on('click', function() {
+		closer.removeClass('active');
+		slider.removeClass('active');
+		return false;
+	});
+}
+
 function init_all() {
 	if (!(window.$ && window.url_freshrss)) {
 		if (window.console) {
@@ -1268,6 +1304,7 @@ function init_all() {
 		init_feed_observers();
 		init_password_observers();
 		init_stats_observers();
+		init_slider_observers();
 	}
 
 	if (window.console) {

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

@@ -435,6 +435,35 @@ a.btn {
 	font-size: 0;
 }
 
+/*=== Boxes */
+.box {
+	border: 1px solid #000;
+	border-radius: 5px;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: #26303F;
+	border-bottom: 1px solid #000;
+	border-radius: 5px 5px 0 0;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

+ 30 - 0
p/themes/Flat/flat.css

@@ -438,6 +438,36 @@ a.btn {
 	background: url("loader.gif") center center no-repeat #34495e;
 }
 
+/*=== Boxes */
+.box {
+	border: 1px solid #ddd;
+	border-radius: 5px;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: #ecf0f1;
+	color: #333;
+	border-bottom: 1px solid #ddd;
+	border-radius: 5px 5px 0 0;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

+ 30 - 0
p/themes/Origine/origine.css

@@ -467,6 +467,36 @@ a.btn {
 	font-size: 0;
 }
 
+/*=== Boxes */
+.box {
+	background: #fff;
+	border-radius: 5px;
+	box-shadow: 0 0 3px #bbb;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: #f6f6f6;
+	border-bottom: 1px solid #ddd;
+	border-radius: 5px 5px 0 0;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

+ 29 - 0
p/themes/Pafat/pafat.css

@@ -491,6 +491,35 @@ a.btn {
 	font-size: 0;
 }
 
+/*=== Boxes */
+.box {
+	border: 1px solid #aaa;
+	border-radius: 5px;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: #f6f6f6;
+	border-bottom: 1px solid #aaa;
+	border-radius: 5px 5px 0 0;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

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

@@ -497,6 +497,40 @@ a.btn {
 	font-size: 0;
 }
 
+/*=== Boxes */
+.box {
+	background: #EDE7DE;
+	border-radius: 4px 4px 0 0;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717;
+	background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%);
+	box-shadow: 0px -1px #fff inset,0 -2px #ccc inset;
+	color: #888;
+	text-shadow: 0 1px #ccc;
+	border-radius: 4px 4px 0 0;
+	font-size: 1.1rem;
+	font-weight: normal;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

+ 24 - 0
p/themes/base-theme/base.css

@@ -329,6 +329,30 @@ a.btn {
 	font-size: 0;
 }
 
+/*=== Boxes */
+.box {
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+}
+.box .box-content {
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */

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

@@ -280,6 +280,40 @@ a.btn {
 	width: 100px;
 }
 
+/*=== Boxes */
+.box {
+	display: inline-block;
+	width: 20rem;
+	max-width: 95%;
+	margin: 20px 10px;
+	border: 1px solid #ccc;
+	vertical-align: top;
+}
+.box .box-title {
+	font-size: 1.2rem;
+	font-weight: bold;
+	text-align: center;
+}
+.box .box-title form {
+	margin: 0;
+}
+.box .box-content {
+	display: block;
+	overflow: auto;
+}
+.box .box-content .item {
+	display: block;
+}
+
+.box .box-content-centered {
+	padding: 30px 5px;
+	text-align: center;
+}
+.box .box-content-centered .btn {
+	margin: 20px 0 0;
+}
+
+
 /*=== STRUCTURE */
 /*===============*/
 /*=== Header */
@@ -559,6 +593,8 @@ br + br + br {
 /*=== GLOBAL VIEW */
 /*================*/
 /*=== Category boxes */
+
+/* TODO <delete> */
 #stream.global .box-category {
 	display: inline-block;
 	width: 19em;
@@ -581,6 +617,7 @@ br + br + br {
 	width: 19em;
 	max-width: 90%;
 }
+/* TODO </delete */
 
 /*=== Panel */
 #overlay {
@@ -608,6 +645,33 @@ br + br + br {
 	display: none;
 }
 
+/*=== Slider */
+#slider {
+	position: fixed;
+	top: 0; bottom: 0;
+	left: 100%; right: 0;
+	overflow: auto;
+	background: #fff;
+	border-left: 1px solid #aaa;
+	transition: left 200ms linear;
+	-moz-transition: left 200ms linear;
+	-webkit-transition: left 200ms linear;
+	-o-transition: left 200ms linear;
+	-ms-transition: left 200ms linear;
+}
+#slider.active {
+	left: 40%;
+}
+#close-slider {
+	position: fixed;
+	top: 0; bottom: 0;
+	left: 100%; right: 0;
+	cursor: pointer;
+}
+#close-slider.active {
+	left: 0;
+}
+
 /*=== DIVERS */
 /*===========*/
 .nav-login,

+ 1 - 0
p/themes/icons/import.svg

@@ -0,0 +1 @@
+<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="16"><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"><path d="m84.406 657a0.5 0.5 0 0 0-0.312 0.219l-1 1.5a0.5 0.5 0 1 0 0.813 0.563l1-1.5A0.5 0.5 0 0 0 84.406 657zm7 0a0.5 0.5 0 0 0-0.312 0.781l1 1.5a0.5 0.5 0 1 0 0.813-0.562l-1-1.5A0.5 0.5 0 0 0 91.406 657z" style="-inkscape-font-specification:Sans;baseline-shift:baseline;block-progression:tb;direction:ltr;fill:#ffffff;font-family:Sans;font-size:medium;letter-spacing:normal;line-height:normal;text-align:start;text-anchor:start;text-decoration:none;text-indent:0;text-transform:none;word-spacing:normal;writing-mode:lr-tb"/><g transform="translate(-80,110)"><path d="m167 539 0 5.563-1.281-1.281C165.531 543.093 165.265 543 165 543l-1 0 0 1c0 0.265 0.093 0.531 0.281 0.719l3 3 0.281 0.281 0.875 0 0.281-0.281 3-3C171.907 544.531 172 544.265 172 544l0-1-1 0c-0.265 0-0.531 0.093-0.719 0.281L169 544.563 169 539z" style="-inkscape-font-specification:Bitstream Vera Sans;block-progression:tb;direction:ltr;fill:#ffffff;font-family:Bitstream Vera Sans;font-size:medium;letter-spacing:normal;line-height:normal;text-align:start;text-anchor:start;text-decoration:none;text-indent:0;text-transform:none;word-spacing:normal;writing-mode:lr-tb"/><path d="m163 549 0 4 10 0 0-4zm3.344 1.438c0.021-0.001 0.042-0.001 0.063 0 0.291-0.056 0.599 0.204 0.594 0.5l0 0.063 2 0 0-0.062c-0.004-0.264 0.236-0.507 0.5-0.507 0.264 0 0.504 0.243 0.5 0.507L170 551c0 0.545-0.455 1-1 1l-2 0c-0.545 0-1-0.455-1-1l0-0.062c-0.011-0.217 0.137-0.432 0.344-0.5z" fill="#ffffff"/></g></g><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/></svg>