aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-07-21 18:00:32 +0200
committerGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-07-21 18:00:32 +0200
commit8d7ac978f9af4819fc788e7e0a514dc7ca9886d9 (patch)
treefcc7e399a5f628a904518b7b522b17cef67a76e1
parenta4dac0f791ae6d34b64cee5a3fae1815f6f70a2b (diff)
parent73bbdaa015ec8596603fa88bd2cb03f85548e05e (diff)
Merge branch 'dev' into beta
-rw-r--r--CHANGELOG14
-rw-r--r--README.md2
-rwxr-xr-xapp/Controllers/configureController.php290
-rwxr-xr-xapp/Controllers/entryController.php12
-rwxr-xr-xapp/Controllers/feedController.php106
-rw-r--r--app/Controllers/importExportController.php5
-rwxr-xr-xapp/Controllers/indexController.php42
-rwxr-xr-xapp/Controllers/javascriptController.php4
-rw-r--r--app/Controllers/statsController.php67
-rw-r--r--app/Controllers/usersController.php14
-rw-r--r--app/Models/Category.php2
-rw-r--r--app/Models/CategoryDAO.php16
-rw-r--r--app/Models/Configuration.php22
-rw-r--r--app/Models/Entry.php4
-rw-r--r--app/Models/EntryDAO.php570
-rw-r--r--app/Models/EntryDAOSQLite.php129
-rw-r--r--app/Models/Factory.php32
-rw-r--r--app/Models/Feed.php194
-rw-r--r--app/Models/FeedDAO.php292
-rw-r--r--app/Models/FeedDAOSQLite.php19
-rw-r--r--app/Models/StatsDAO.php88
-rw-r--r--app/Models/StatsDAOSQLite.php37
-rw-r--r--app/Models/Themes.php11
-rw-r--r--app/Models/UserDAO.php31
-rw-r--r--app/SQL/install.sql.mysql.php (renamed from app/sql.php)5
-rw-r--r--app/SQL/install.sql.sqlite.php58
-rw-r--r--app/i18n/en.php41
-rw-r--r--app/i18n/fr.php115
-rw-r--r--app/i18n/install.en.php2
-rw-r--r--app/i18n/install.fr.php2
-rw-r--r--app/install.php (renamed from p/i/install.php)140
-rw-r--r--app/layout/aside_configure.phtml3
-rw-r--r--app/layout/aside_stats.phtml9
-rw-r--r--app/layout/header.phtml101
-rw-r--r--app/layout/nav_menu.phtml57
-rw-r--r--app/views/configure/archiving.phtml21
-rw-r--r--app/views/configure/feed.phtml21
-rw-r--r--app/views/configure/queries.phtml90
-rw-r--r--app/views/configure/reading.phtml12
-rw-r--r--app/views/configure/sharing.phtml14
-rwxr-xr-xapp/views/helpers/pagination.phtml2
-rw-r--r--app/views/javascript/actualize.phtml6
-rw-r--r--app/views/stats/idle.phtml19
-rw-r--r--app/views/stats/index.phtml127
-rw-r--r--app/views/stats/main.phtml (renamed from app/views/index/stats.phtml)12
-rw-r--r--data/do-install.txt0
-rw-r--r--lib/Minz/Configuration.php80
-rw-r--r--lib/Minz/ModelPdo.php58
-rw-r--r--lib/Minz/Request.php5
-rw-r--r--lib/Minz/Translate.php34
-rw-r--r--lib/SimplePie/SimplePie.php38
-rw-r--r--lib/SimplePie/SimplePie/File.php12
-rw-r--r--lib/lib_rss.php8
-rw-r--r--p/api/greader.php14
-rwxr-xr-xp/i/index.php16
-rw-r--r--p/scripts/main.js60
-rw-r--r--p/themes/Dark/dark.css985
-rw-r--r--p/themes/Dark/freshrss.css926
-rw-r--r--p/themes/Dark/global.css525
-rw-r--r--p/themes/Dark/icons/icon.svg12
-rw-r--r--p/themes/Dark/metadata.json4
-rw-r--r--p/themes/Dark/template.css695
-rw-r--r--p/themes/Flat/flat.css950
-rw-r--r--p/themes/Flat/freshrss.css900
-rw-r--r--p/themes/Flat/global.css528
-rw-r--r--p/themes/Flat/icons/icon.svg12
-rw-r--r--p/themes/Flat/icons/key.svg7
-rw-r--r--p/themes/Flat/metadata.json4
-rw-r--r--p/themes/Flat/template.css695
-rw-r--r--p/themes/Origine/origine.css28
-rw-r--r--p/themes/Origine/template.css13
-rw-r--r--p/themes/base-theme/README.md12
-rw-r--r--p/themes/base-theme/base.css762
-rw-r--r--p/themes/base-theme/metadata.json7
-rw-r--r--p/themes/base-theme/template.css695
-rw-r--r--p/themes/icons/bookmark-add.svg6
76 files changed, 6929 insertions, 4022 deletions
diff --git a/CHANGELOG b/CHANGELOG
index d167dd917..039ba1364 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,19 @@
# Journal des modifications
+## 2014-xx-xx FreshRSS 0.7.x
+
+* New options
+ * Add system of user queries which are shortcuts to filter the view
+ * New TTL option to limit the frequency at which feeds are refreshed (by cron or manual refresh button).
+ It is still possible to manually refresh an individual feed at a higher frequency.
+* SQL
+ * Add support for SQLite (beta) in addition to MySQL
+* SimplePie
+ * Complies with HTTP "301 Moved Permanently" responses by automatically updating the URL of feeds that have changed address.
+* Themes
+ * Flat and Dark designs are based on same template file as Origine
+
+
## 2014-06-13 FreshRSS 0.7.2
* API compatible with Google Reader API level 2
diff --git a/README.md b/README.md
index 172ffd394..5e6912524 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ Privilégiez pour cela des demandes sur GitHub
* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv), [Zip](http://php.net/zip)
-* MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir)
+* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ (en bêta)
* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
* Fonctionne aussi sur mobile
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index a117e0f9c..79f40b30b 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -1,81 +1,81 @@
<?php
class FreshRSS_configure_Controller extends Minz_ActionController {
- public function firstAction () {
+ public function firstAction() {
if (!$this->view->loginOk) {
- Minz_Error::error (
+ Minz_Error::error(
403,
- array ('error' => array (Minz_Translate::t ('access_denied')))
+ array('error' => array(Minz_Translate::t('access_denied')))
);
}
- $catDAO = new FreshRSS_CategoryDAO ();
- $catDAO->checkDefault ();
+ $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO->checkDefault();
}
- public function categorizeAction () {
- $feedDAO = new FreshRSS_FeedDAO ();
- $catDAO = new FreshRSS_CategoryDAO ();
- $defaultCategory = $catDAO->getDefault ();
- $defaultId = $defaultCategory->id ();
+ 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', ''));
+ 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 (),
+ if (strlen($name) > 0) {
+ $cat = new FreshRSS_Category($name);
+ $values = array(
+ 'name' => $cat->name(),
);
- $catDAO->updateCategory ($ids[$key], $values);
+ $catDAO->updateCategory($ids[$key], $values);
} elseif ($ids[$key] != $defaultId) {
- $feedDAO->changeCategory ($ids[$key], $defaultId);
- $catDAO->deleteCategory ($ids[$key]);
+ $feedDAO->changeCategory($ids[$key], $defaultId);
+ $catDAO->deleteCategory($ids[$key]);
}
}
if ($newCat != '') {
- $cat = new FreshRSS_Category ($newCat);
- $values = array (
- 'id' => $cat->id (),
- 'name' => $cat->name (),
+ $cat = new FreshRSS_Category($newCat);
+ $values = array(
+ 'id' => $cat->id(),
+ 'name' => $cat->name(),
);
- if ($catDAO->searchByName ($newCat) == null) {
- $catDAO->addCategory ($values);
+ if ($catDAO->searchByName($newCat) == null) {
+ $catDAO->addCategory($values);
}
}
invalidateHttpCache();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('categories_updated')
+ 'content' => Minz_Translate::t('categories_updated')
);
- Minz_Session::_param ('notification', $notif);
+ Minz_Session::_param('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'categorize'), true);
}
- $this->view->categories = $catDAO->listCategories (false);
- $this->view->defaultCategory = $catDAO->getDefault ();
- $this->view->feeds = $feedDAO->listFeeds ();
+ $this->view->categories = $catDAO->listCategories(false);
+ $this->view->defaultCategory = $catDAO->getDefault();
+ $this->view->feeds = $feedDAO->listFeeds();
- Minz_View::prependTitle (Minz_Translate::t ('categories_management') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('categories_management') . ' · ');
}
- public function feedAction () {
- $catDAO = new FreshRSS_CategoryDAO ();
- $this->view->categories = $catDAO->listCategories (false);
+ public function feedAction() {
+ $catDAO = new FreshRSS_CategoryDAO();
+ $this->view->categories = $catDAO->listCategories(false);
- $feedDAO = new FreshRSS_FeedDAO ();
- $this->view->feeds = $feedDAO->listFeeds ();
+ $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 ();
+ $id = Minz_Request::param('id');
+ if ($id == false && !empty($this->view->feeds)) {
+ $id = current($this->view->feeds)->id();
}
$this->view->flux = false;
@@ -83,14 +83,14 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->flux = $this->view->feeds[$id];
if (!$this->view->flux) {
- Minz_Error::error (
+ Minz_Error::error(
404,
- array ('error' => array (Minz_Translate::t ('page_not_found')))
+ array('error' => array(Minz_Translate::t('page_not_found')))
);
} else {
- if (Minz_Request::isPost () && $this->view->flux) {
- $user = Minz_Request::param ('http_user', '');
- $pass = Minz_Request::param ('http_pass', '');
+ if (Minz_Request::isPost() && $this->view->flux) {
+ $user = Minz_Request::param('http_user', '');
+ $pass = Minz_Request::param('http_pass', '');
$httpAuth = '';
if ($user != '' || $pass != '') {
@@ -99,45 +99,46 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$cat = intval(Minz_Request::param('category', 0));
- $values = array (
- 'name' => Minz_Request::param ('name', ''),
+ $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)),
+ 'pathEntries' => Minz_Request::param('path_entries', ''),
+ 'priority' => intval(Minz_Request::param('priority', 0)),
'httpAuth' => $httpAuth,
- 'keep_history' => intval(Minz_Request::param ('keep_history', -2)),
+ '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);
+ if ($feedDAO->updateFeed($id, $values)) {
+ $this->view->flux->_category($cat);
$this->view->flux->faviconPrepare();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('feed_updated')
+ 'content' => Minz_Translate::t('feed_updated')
);
} else {
- $notif = array (
+ $notif = array(
'type' => 'bad',
- 'content' => Minz_Translate::t ('error_occurred_update')
+ 'content' => Minz_Translate::t('error_occurred_update')
);
}
invalidateHttpCache();
- Minz_Session::_param ('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true);
+ Minz_Session::_param('notification', $notif);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
}
- Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' — ' . $this->view->flux->name () . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · ');
}
} else {
- Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' · ');
}
}
- public function displayAction () {
+ public function displayAction() {
if (Minz_Request::isPost()) {
$this->view->conf->_language(Minz_Request::param('language', 'en'));
$themeId = Minz_Request::param('theme', '');
@@ -158,36 +159,37 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->conf->_bottomline_link(Minz_Request::param('bottomline_link', false));
$this->view->conf->save();
- Minz_Session::_param ('language', $this->view->conf->language);
- Minz_Translate::reset ();
+ Minz_Session::_param('language', $this->view->conf->language);
+ Minz_Translate::reset();
invalidateHttpCache();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('configuration_updated')
+ 'content' => Minz_Translate::t('configuration_updated')
);
- Minz_Session::_param ('notification', $notif);
+ Minz_Session::_param('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'display'), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'display'), true);
}
$this->view->themes = FreshRSS_Themes::get();
- Minz_View::prependTitle (Minz_Translate::t ('display_configuration') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('display_configuration') . ' · ');
}
- public function readingAction () {
+ public function readingAction() {
if (Minz_Request::isPost()) {
$this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
$this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
- $this->view->conf->_default_view (Minz_Request::param('default_view', 'a'));
+ $this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL));
$this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
$this->view->conf->_display_posts(Minz_Request::param('display_posts', false));
$this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
- $this->view->conf->_lazyload (Minz_Request::param('lazyload', false));
- $this->view->conf->_sticky_post (Minz_Request::param('sticky_post', false));
+ $this->view->conf->_lazyload(Minz_Request::param('lazyload', false));
+ $this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false));
+ $this->view->conf->_reading_confirm(Minz_Request::param('reading_confirm', false));
$this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
- $this->view->conf->_mark_when (array(
+ $this->view->conf->_mark_when(array(
'article' => Minz_Request::param('mark_open_article', false),
'site' => Minz_Request::param('mark_open_site', false),
'scroll' => Minz_Request::param('mark_scroll', false),
@@ -195,43 +197,43 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
));
$this->view->conf->save();
- Minz_Session::_param ('language', $this->view->conf->language);
- Minz_Translate::reset ();
+ Minz_Session::_param('language', $this->view->conf->language);
+ Minz_Translate::reset();
invalidateHttpCache();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('configuration_updated')
+ 'content' => Minz_Translate::t('configuration_updated')
);
- Minz_Session::_param ('notification', $notif);
+ Minz_Session::_param('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'reading'), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'reading'), true);
}
- Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('reading_configuration') . ' · ');
}
- public function sharingAction () {
- if (Minz_Request::isPost ()) {
+ public function sharingAction() {
+ if (Minz_Request::isPost()) {
$params = Minz_Request::params();
- $this->view->conf->_sharing ($params['share']);
+ $this->view->conf->_sharing($params['share']);
$this->view->conf->save();
invalidateHttpCache();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('configuration_updated')
+ 'content' => Minz_Translate::t('configuration_updated')
);
- Minz_Session::_param ('notification', $notif);
+ Minz_Session::_param('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'sharing'), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'sharing'), true);
}
- Minz_View::prependTitle (Minz_Translate::t ('sharing') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('sharing') . ' · ');
}
- public function shortcutAction () {
- $list_keys = array ('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter',
+ public function shortcutAction() {
+ $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter',
'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left',
'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right',
's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y',
@@ -240,9 +242,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
'f10', 'f11', 'f12');
$this->view->list_keys = $list_keys;
- if (Minz_Request::isPost ()) {
- $shortcuts = Minz_Request::param ('shortcuts');
- $shortcuts_ok = array ();
+ if (Minz_Request::isPost()) {
+ $shortcuts = Minz_Request::param('shortcuts');
+ $shortcuts_ok = array();
foreach ($shortcuts as $key => $value) {
if (in_array($value, $list_keys)) {
@@ -250,33 +252,35 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
}
}
- $this->view->conf->_shortcuts ($shortcuts_ok);
+ $this->view->conf->_shortcuts($shortcuts_ok);
$this->view->conf->save();
invalidateHttpCache();
- $notif = array (
+ $notif = array(
'type' => 'good',
- 'content' => Minz_Translate::t ('shortcuts_updated')
+ 'content' => Minz_Translate::t('shortcuts_updated')
);
- Minz_Session::_param ('notification', $notif);
+ Minz_Session::_param('notification', $notif);
- Minz_Request::forward (array ('c' => 'configure', 'a' => 'shortcut'), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'shortcut'), true);
}
- Minz_View::prependTitle (Minz_Translate::t ('shortcuts') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('shortcuts') . ' · ');
}
public function usersAction() {
- Minz_View::prependTitle(Minz_Translate::t ('users') . ' · ');
+ Minz_View::prependTitle(Minz_Translate::t('users') . ' · ');
}
- public function archivingAction () {
+ public function archivingAction() {
if (Minz_Request::isPost()) {
$old = Minz_Request::param('old_entries', 3);
$keepHistoryDefault = Minz_Request::param('keep_history_default', 0);
+ $ttlDefault = Minz_Request::param('ttl_default', -2);
$this->view->conf->_old_entries($old);
$this->view->conf->_keep_history_default($keepHistoryDefault);
+ $this->view->conf->_ttl_default($ttlDefault);
$this->view->conf->save();
invalidateHttpCache();
@@ -291,7 +295,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' · ');
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$this->view->nb_total = $entryDAO->count();
$this->view->size_user = $entryDAO->size();
@@ -299,4 +303,80 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->size_total = $entryDAO->size(true);
}
}
+
+ public function queriesAction() {
+ if (Minz_Request::isPost()) {
+ $queries = Minz_Request::param('queries', array());
+
+ foreach ($queries as $key => $query) {
+ if (!$query['name']) {
+ $query['name'] = Minz_Translate::t('query_number', $key + 1);
+ }
+ }
+ $this->view->conf->_queries($queries);
+ $this->view->conf->save();
+
+ $notif = array(
+ 'type' => 'good',
+ 'content' => Minz_Translate::t('configuration_updated')
+ );
+ Minz_Session::_param('notification', $notif);
+
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true);
+ } else {
+ $this->view->query_get = array();
+ foreach ($this->view->conf->queries as $key => $query) {
+ if (!isset($query['get'])) {
+ continue;
+ }
+
+ switch ($query['get'][0]) {
+ case 'c':
+ $dao = new FreshRSS_CategoryDAO();
+ $category = $dao->searchById(substr($query['get'], 2));
+ $this->view->query_get[$key] = array(
+ 'type' => 'category',
+ 'name' => $category->name(),
+ );
+ break;
+ case 'f':
+ $dao = FreshRSS_Factory::createFeedDao();
+ $feed = $dao->searchById(substr($query['get'], 2));
+ $this->view->query_get[$key] = array(
+ 'type' => 'feed',
+ 'name' => $feed->name(),
+ );
+ break;
+ case 's':
+ $this->view->query_get[$key] = array(
+ 'type' => 'favorite',
+ 'name' => 'favorite',
+ );
+ break;
+ case 'a':
+ $this->view->query_get[$key] = array(
+ 'type' => 'all',
+ 'name' => 'all',
+ );
+ break;
+ }
+ }
+ }
+
+ Minz_View::prependTitle(Minz_Translate::t('queries') . ' · ');
+ }
+
+ public function addQueryAction() {
+ $queries = $this->view->conf->queries;
+ $query = Minz_Request::params();
+ $query['name'] = Minz_Translate::t('query_number', count($queries) + 1);
+ unset($query['output']);
+ unset($query['token']);
+ $queries[] = $query;
+ $this->view->conf->_queries($queries);
+ $this->view->conf->save();
+
+ // Minz_Request::forward(array('params' => $query), true);
+ Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true);
+ }
}
diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php
index bbcb990f5..ac43587ea 100755
--- a/app/Controllers/entryController.php
+++ b/app/Controllers/entryController.php
@@ -43,7 +43,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
$nextGet = Minz_Request::param ('nextGet', $get);
$idMax = Minz_Request::param ('idMax', 0);
- $entryDAO = new FreshRSS_EntryDAO ();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
if ($id == false) {
if (!$get) {
$entryDAO->markReadEntries ($idMax);
@@ -85,7 +85,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
$id = Minz_Request::param ('id');
if ($id) {
- $entryDAO = new FreshRSS_EntryDAO ();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$entryDAO->markFavorite ($id, (bool)(Minz_Request::param ('is_favorite', true)));
}
}
@@ -97,10 +97,10 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
// La table des entrées a tendance à grossir énormément
// Cette action permet d'optimiser cette table permettant de grapiller un peu de place
// Cette fonctionnalité n'est à appeler qu'occasionnellement
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$entryDAO->optimizeTable();
- $feedDAO = new FreshRSS_FeedDAO();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$feedDAO->updateCachedValues();
invalidateHttpCache();
@@ -124,8 +124,8 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
$nb_month_old = max($this->view->conf->old_entries, 1);
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
- $feedDAO = new FreshRSS_FeedDAO();
- $feeds = $feedDAO->listFeedsOrderUpdate();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $feeds = $feedDAO->listFeeds();
$nbTotal = 0;
invalidateHttpCache();
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index fce008399..3326b2059 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -31,7 +31,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
), true);
}
- $feedDAO = new FreshRSS_FeedDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$this->catDAO = new FreshRSS_CategoryDAO ();
$this->catDAO->checkDefault ();
@@ -102,25 +102,30 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
- $entryDAO = new FreshRSS_EntryDAO ();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
// on calcule la date des articles les plus anciens qu'on accepte
$nb_month_old = $this->view->conf->old_entries;
$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
+ //MySQL: http://docs.oracle.com/cd/E17952_01/refman-5.5-en/optimizing-innodb-transaction-management.html
+ //SQLite: http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
+ $preparedStatement = $entryDAO->addEntryPrepare();
$transactionStarted = true;
- $feedDAO->beginTransaction ();
+ $feedDAO->beginTransaction();
// on ajoute les articles en masse sans vérification
foreach ($entries as $entry) {
- $values = $entry->toArray ();
- $values['id_feed'] = $feed->id ();
- $values['id'] = min(time(), $entry->date (true)) . uSecString();
+ $values = $entry->toArray();
+ $values['id_feed'] = $feed->id();
+ $values['id'] = min(time(), $entry->date(true)) . uSecString();
$values['is_read'] = $is_read;
- $entryDAO->addEntry ($values);
+ $entryDAO->addEntry($values, $preparedStatement);
+ }
+ $feedDAO->updateLastUpdate($feed->id());
+ if ($transactionStarted) {
+ $feedDAO->commit();
}
- $feedDAO->updateLastUpdate ($feed->id ());
- $feedDAO->commit ();
$transactionStarted = false;
// ok, ajout terminé
@@ -162,45 +167,46 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
- }
-
- // GET request so we must ask confirmation to user
- Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · ');
- $this->view->categories = $this->catDAO->listCategories();
- $this->view->feed = new FreshRSS_Feed($url);
- try {
- // We try to get some more information about the feed
- $this->view->feed->load(true);
- $this->view->load_ok = true;
- } catch (Exception $e) {
- $this->view->load_ok = false;
- }
+ } else {
- $feed = $feedDAO->searchByUrl($this->view->feed->url());
- if ($feed) {
- // Already subscribe so we redirect to the feed configuration page
- $notif = array(
- 'type' => 'bad',
- 'content' => Minz_Translate::t(
- 'already_subscribed', $feed->name()
- )
- );
- Minz_Session::_param('notification', $notif);
+ // GET request so we must ask confirmation to user
+ Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · ');
+ $this->view->categories = $this->catDAO->listCategories();
+ $this->view->feed = new FreshRSS_Feed($url);
+ try {
+ // We try to get some more information about the feed
+ $this->view->feed->load(true);
+ $this->view->load_ok = true;
+ } catch (Exception $e) {
+ $this->view->load_ok = false;
+ }
- Minz_Request::forward(array(
- 'c' => 'configure',
- 'a' => 'feed',
- 'params' => array(
- 'id' => $feed->id()
- )
- ), true);
+ $feed = $feedDAO->searchByUrl($this->view->feed->url());
+ if ($feed) {
+ // Already subscribe so we redirect to the feed configuration page
+ $notif = array(
+ 'type' => 'bad',
+ 'content' => Minz_Translate::t(
+ 'already_subscribed', $feed->name()
+ )
+ );
+ Minz_Session::_param('notification', $notif);
+
+ Minz_Request::forward(array(
+ 'c' => 'configure',
+ 'a' => 'feed',
+ 'params' => array(
+ 'id' => $feed->id()
+ )
+ ), true);
+ }
}
}
public function truncateAction () {
if (Minz_Request::isPost ()) {
$id = Minz_Request::param ('id');
- $feedDAO = new FreshRSS_FeedDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$n = $feedDAO->truncate($id);
$notif = array(
'type' => $n === false ? 'bad' : 'good',
@@ -215,8 +221,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
public function actualizeAction () {
@set_time_limit(300);
- $feedDAO = new FreshRSS_FeedDAO ();
- $entryDAO = new FreshRSS_EntryDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
Minz_Session::_param('actualize_feeds', false);
$id = Minz_Request::param ('id');
@@ -232,7 +238,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feeds = array ($feed);
}
} else {
- $feeds = $feedDAO->listFeedsOrderUpdate ();
+ $feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default);
}
// on calcule la date des articles les plus anciens qu'on accepte
@@ -264,22 +270,23 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feedHistory = $this->view->conf->keep_history_default;
}
+ $preparedStatement = $entryDAO->addEntryPrepare();
$hasTransaction = true;
$feedDAO->beginTransaction();
// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
// La BDD refusera l'ajout car (id_feed, guid) doit être unique
foreach ($entries as $entry) {
- $eDate = $entry->date (true);
- if ((!isset ($existingGuids[$entry->guid ()])) &&
+ $eDate = $entry->date(true);
+ if ((!isset($existingGuids[$entry->guid()])) &&
(($feedHistory != 0) || ($eDate >= $date_min))) {
- $values = $entry->toArray ();
+ $values = $entry->toArray();
//Use declared date at first import, otherwise use discovery date
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
min(time(), $eDate) . uSecString() :
uTimeString();
$values['is_read'] = $is_read;
- $entryDAO->addEntry ($values);
+ $entryDAO->addEntry($values, $preparedStatement);
}
}
}
@@ -300,7 +307,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feedDAO->commit();
}
$flux_update++;
- if ($feed->url() !== $url) { //URL has changed (auto-discovery)
+ if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently
+ Minz_Log::record('Feed ' . $url . ' moved permanently to ' . $feed->url(), Minz_Log::NOTICE);
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
}
} catch (FreshRSS_Feed_Exception $e) {
@@ -373,7 +381,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$type = Minz_Request::param ('type', 'feed');
$id = Minz_Request::param ('id');
- $feedDAO = new FreshRSS_FeedDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
if ($type == 'category') {
if ($feedDAO->deleteFeedByCategory ($id)) {
$notif = array (
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 3cd791781..ba172cc6d 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -12,8 +12,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
require_once(LIB_PATH . '/lib_opml.php');
$this->catDAO = new FreshRSS_CategoryDAO();
- $this->entryDAO = new FreshRSS_EntryDAO();
- $this->feedDAO = new FreshRSS_FeedDAO();
+ $this->entryDAO = FreshRSS_Factory::createEntryDao();
+ $this->feedDAO = FreshRSS_Factory::createFeedDao();
}
public function indexAction() {
@@ -266,6 +266,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
);
$entry->_tags($tags);
+ //FIME: Use entryDAO->addEntryPrepare(). Do not call entryDAO->listLastGuidsByFeed() for each entry. Consider using a transaction.
$id = $this->entryDAO->addEntryObject(
$entry, $this->view->conf, $feed->keepHistory()
);
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index c843748c3..9a46bde6c 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -45,7 +45,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
}
$catDAO = new FreshRSS_CategoryDAO();
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$this->view->cat_aside = $catDAO->listCategories ();
$this->view->nb_favorites = $entryDAO->countUnreadReadFavorites ();
@@ -70,11 +70,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
// mise à jour des titres
$this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title();
if ($this->view->nb_not_read > 0) {
- Minz_View::appendTitle (' (' . formatNumber($this->view->nb_not_read) . ')');
+ Minz_View::prependTitle('(' . formatNumber($this->view->nb_not_read) . ') ');
}
- Minz_View::prependTitle (
+ Minz_View::prependTitle(
+ ($this->nb_not_read_cat > 0 ? '(' . formatNumber($this->nb_not_read_cat) . ') ' : '') .
$this->view->currentName .
- ($this->nb_not_read_cat > 0 ? ' (' . formatNumber($this->nb_not_read_cat) . ')' : '') .
' · '
);
@@ -82,9 +82,6 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view);
$state_param = Minz_Request::param ('state', null);
$filter = Minz_Request::param ('search', '');
- if (!empty($filter)) {
- $state = FreshRSS_Entry::STATE_ALL; //Search always in read and unread articles
- }
$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order);
$nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page);
$first = Minz_Request::param ('next', '');
@@ -127,8 +124,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
// Si on a récupéré aucun article "non lus"
// on essaye de récupérer tous les articles
- if ($state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null)) {
- Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
+ if ($state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
+ Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ try {
+ $feedDAO->updateCachedValues();
+ } catch (Exception $ex) {
+ Minz_Log::record('Failed to automatically correct nbNotRead! ' + $ex->getMessage(), Minz_Log::NOTICE);
+ }
$this->view->state = FreshRSS_Entry::STATE_ALL;
$entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault);
}
@@ -184,7 +187,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
case 'f':
$feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
if (empty($feed)) {
- $feedDAO = new FreshRSS_FeedDAO();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($getId);
}
if ($feed) {
@@ -201,25 +204,6 @@ class FreshRSS_index_Controller extends Minz_ActionController {
}
}
- public function statsAction () {
- if (!$this->view->loginOk) {
- Minz_Error::error (
- 403,
- array ('error' => array (Minz_Translate::t ('access_denied')))
- );
- }
-
- Minz_View::prependTitle (Minz_Translate::t ('stats') . ' · ');
-
- $statsDAO = new FreshRSS_StatsDAO ();
- Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
- $this->view->repartition = $statsDAO->calculateEntryRepartition();
- $this->view->count = ($statsDAO->calculateEntryCount());
- $this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
- $this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
- $this->view->topFeed = $statsDAO->calculateTopFeed();
- }
-
public function aboutAction () {
Minz_View::prependTitle (Minz_Translate::t ('about') . ' · ');
}
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index 3d741e298..67148350f 100755
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -7,8 +7,8 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
public function actualizeAction () {
header('Content-Type: text/javascript; charset=UTF-8');
- $feedDAO = new FreshRSS_FeedDAO ();
- $this->view->feeds = $feedDAO->listFeedsOrderUpdate();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $this->view->feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default);
}
public function nbUnreadsPerFeedAction() {
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
new file mode 100644
index 000000000..9009468bc
--- /dev/null
+++ b/app/Controllers/statsController.php
@@ -0,0 +1,67 @@
+<?php
+
+class FreshRSS_stats_Controller extends Minz_ActionController {
+
+ public function indexAction() {
+ $statsDAO = FreshRSS_Factory::createStatsDAO();
+ Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+ $this->view->repartition = $statsDAO->calculateEntryRepartition();
+ $this->view->count = ($statsDAO->calculateEntryCount());
+ $this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
+ $this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
+ $this->view->topFeed = $statsDAO->calculateTopFeed();
+ }
+
+ public function idleAction() {
+ $statsDAO = FreshRSS_Factory::createStatsDAO();
+ $feeds = $statsDAO->calculateFeedLastDate();
+ $idleFeeds = array();
+ $now = new \DateTime();
+ $feedDate = clone $now;
+ $lastWeek = clone $now;
+ $lastWeek->modify('-1 week');
+ $lastMonth = clone $now;
+ $lastMonth->modify('-1 month');
+ $last3Month = clone $now;
+ $last3Month->modify('-3 month');
+ $last6Month = clone $now;
+ $last6Month->modify('-6 month');
+ $lastYear = clone $now;
+ $lastYear->modify('-1 year');
+
+ foreach ($feeds as $feed) {
+ $feedDate->setTimestamp($feed['last_date']);
+ if ($feedDate >= $lastWeek) {
+ continue;
+ }
+ if ($feedDate < $lastWeek) {
+ $idleFeeds['last_week'][] = $feed['name'];
+ }
+ if ($feedDate < $lastMonth) {
+ $idleFeeds['last_month'][] = $feed['name'];
+ }
+ if ($feedDate < $last3Month) {
+ $idleFeeds['last_3_month'][] = $feed['name'];
+ }
+ if ($feedDate < $last6Month) {
+ $idleFeeds['last_6_month'][] = $feed['name'];
+ }
+ if ($feedDate < $lastYear) {
+ $idleFeeds['last_year'][] = $feed['name'];
+ }
+ }
+
+ $this->view->idleFeeds = array_reverse($idleFeeds);
+ }
+
+ public function firstAction() {
+ if (!$this->view->loginOk) {
+ Minz_Error::error(
+ 403, array('error' => array(Minz_Translate::t('access_denied')))
+ );
+ }
+
+ Minz_View::prependTitle(Minz_Translate::t('stats') . ' · ');
+ }
+
+}
diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php
index fa967cedc..35fa3675f 100644
--- a/app/Controllers/usersController.php
+++ b/app/Controllers/usersController.php
@@ -17,7 +17,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
if (Minz_Request::isPost()) {
$ok = true;
- $passwordPlain = Minz_Request::param('passwordPlain', false);
+ $passwordPlain = Minz_Request::param('passwordPlain', '', true);
if ($passwordPlain != '') {
Minz_Request::_param('passwordPlain'); //Discard plain-text password ASAP
$_POST['passwordPlain'] = '';
@@ -32,7 +32,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
}
Minz_Session::_param('passwordHash', $this->view->conf->passwordHash);
- $passwordPlain = Minz_Request::param('apiPasswordPlain', false);
+ $passwordPlain = Minz_Request::param('apiPasswordPlain', '', true);
if ($passwordPlain != '') {
if (!function_exists('password_hash')) {
include_once(LIB_PATH . '/password_compat.php');
@@ -45,7 +45,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
}
if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
- $this->view->conf->_mail_login(Minz_Request::param('mail_login', false));
+ $this->view->conf->_mail_login(Minz_Request::param('mail_login', '', true));
}
$email = $this->view->conf->mail_login;
Minz_Session::_param('mail', $email);
@@ -99,7 +99,8 @@ class FreshRSS_users_Controller extends Minz_ActionController {
public function createAction() {
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
- require_once(APP_PATH . '/sql.php');
+ $db = Minz_Configuration::dataBase();
+ require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
$new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language);
if (!in_array($new_user_language, $this->view->conf->availableLanguages())) {
@@ -119,7 +120,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
}
if ($ok) {
- $passwordPlain = Minz_Request::param('new_user_passwordPlain', false);
+ $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
$passwordHash = '';
if ($passwordPlain != '') {
Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
@@ -170,7 +171,8 @@ class FreshRSS_users_Controller extends Minz_ActionController {
public function deleteAction() {
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
- require_once(APP_PATH . '/sql.php');
+ $db = Minz_Configuration::dataBase();
+ require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
$username = Minz_Request::param('username');
$ok = ctype_alnum($username);
diff --git a/app/Models/Category.php b/app/Models/Category.php
index 328bae799..0a0dbd3ca 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -44,7 +44,7 @@ class FreshRSS_Category extends Minz_Model {
}
public function feeds () {
if ($this->feeds === null) {
- $feedDAO = new FreshRSS_FeedDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$this->feeds = $feedDAO->listByCategory ($this->id ());
$this->nbFeed = 0;
$this->nbNotRead = 0;
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 6a9b839b9..f11f87f47 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -12,8 +12,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if ($stm && $stm->execute ($values)) {
return $this->bd->lastInsertId();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error addCategory: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
@@ -43,8 +43,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if ($stm && $stm->execute ($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateCategory: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
@@ -58,8 +58,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if ($stm && $stm->execute ($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error deleteCategory: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
@@ -102,7 +102,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
. ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
. 'FROM `' . $this->prefix . 'category` c '
- . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id '
+ . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id '
. 'GROUP BY f.id '
. 'ORDER BY c.name, f.name';
$stm = $this->bd->prepare ($sql);
@@ -166,7 +166,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
}
public function countNotRead ($id) {
- $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE category=? AND e.is_read=0';
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0';
$stm = $this->bd->prepare ($sql);
$values = array ($id);
$stm->execute ($values);
diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php
index 0d6666297..7596c54cd 100644
--- a/app/Models/Configuration.php
+++ b/app/Models/Configuration.php
@@ -7,6 +7,7 @@ class FreshRSS_Configuration {
'language' => 'en',
'old_entries' => 3,
'keep_history_default' => 0,
+ 'ttl_default' => 3600,
'mail_login' => '',
'token' => '',
'passwordHash' => '', //CRYPT_BLOWFISH
@@ -19,6 +20,7 @@ class FreshRSS_Configuration {
'onread_jump_next' => true,
'lazyload' => true,
'sticky_post' => true,
+ 'reading_confirm' => false,
'sort_order' => 'DESC',
'anon_access' => false,
'mark_when' => array(
@@ -53,6 +55,7 @@ class FreshRSS_Configuration {
'bottomline_date' => true,
'bottomline_link' => true,
'sharing' => array(),
+ 'queries' => array(),
);
private $available_languages = array(
@@ -147,6 +150,9 @@ class FreshRSS_Configuration {
public function _sticky_post($value) {
$this->data['sticky_post'] = ((bool)$value) && $value !== 'no';
}
+ public function _reading_confirm($value) {
+ $this->data['reading_confirm'] = ((bool)$value) && $value !== 'no';
+ }
public function _sort_order ($value) {
$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
}
@@ -158,6 +164,10 @@ class FreshRSS_Configuration {
$value = intval($value);
$this->data['keep_history_default'] = $value >= -1 ? $value : 0;
}
+ public function _ttl_default($value) {
+ $value = intval($value);
+ $this->data['ttl_default'] = $value >= -1 ? $value : 3600;
+ }
public function _shortcuts ($values) {
foreach ($values as $key => $value) {
if (isset($this->data['shortcuts'][$key])) {
@@ -219,6 +229,18 @@ class FreshRSS_Configuration {
$this->data['sharing'][] = $value;
}
}
+ public function _queries ($values) {
+ $this->data['queries'] = array();
+ foreach ($values as $value) {
+ $value = array_filter($value);
+ $params = $value;
+ unset($params['name']);
+ unset($params['url']);
+ $value['url'] = Minz_Url::display(array('params' => $params));
+
+ $this->data['queries'][] = $value;
+ }
+ }
public function _theme($value) {
$this->data['theme'] = $value;
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index fa9066d5b..0bf1f2616 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -74,7 +74,7 @@ class FreshRSS_Entry extends Minz_Model {
}
public function feed ($object = false) {
if ($object) {
- $feedDAO = new FreshRSS_FeedDAO ();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
return $feedDAO->searchById ($this->feed);
} else {
return $this->feed;
@@ -154,7 +154,7 @@ class FreshRSS_Entry extends Minz_Model {
// Gestion du contenu
// On cherche à récupérer les articles en entier... même si le flux ne le propose pas
if ($pathEntries) {
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$entry = $entryDAO->searchByGuid($this->feed, $this->guid);
if($entry) {
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 4e24541dc..8c001e73b 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -1,12 +1,25 @@
<?php
class FreshRSS_EntryDAO extends Minz_ModelPdo {
- public function addEntry ($valuesTmp) {
- $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, content_bin, link, date, is_read, is_favorite, id_feed, tags) '
- . 'VALUES(?, ?, ?, ?, COMPRESS(?), ?, ?, ?, ?, ?, ?)';
- $stm = $this->bd->prepare ($sql);
- $values = array (
+ public function isCompressed() {
+ return parent::$sharedDbType !== 'sqlite';
+ }
+
+ public function addEntryPrepare() {
+ $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, '
+ . ($this->isCompressed() ? 'content_bin' : 'content')
+ . ', link, date, is_read, is_favorite, id_feed, tags) '
+ . 'VALUES(?, ?, ?, ?, '
+ . ($this->isCompressed() ? 'COMPRESS(?)' : '?')
+ . ', ?, ?, ?, ?, ?, ?)';
+ return $this->bd->prepare($sql);
+ }
+
+ public function addEntry($valuesTmp, $preparedStatement = null) {
+ $stm = $preparedStatement === null ? addEntryPrepare() : $preparedStatement;
+
+ $values = array(
$valuesTmp['id'],
substr($valuesTmp['guid'], 0, 760),
substr($valuesTmp['title'], 0, 255),
@@ -20,12 +33,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
substr($valuesTmp['tags'], 0, 1023),
);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $this->bd->lastInsertId();
} else {
- $info = $stm->errorInfo();
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
- Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ Minz_Log::record('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR);
} /*else {
Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
@@ -69,23 +82,53 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
if (!is_array($ids)) {
$ids = array($ids);
}
- $sql = 'UPDATE `' . $this->prefix . 'entry` e '
- . 'SET e.is_favorite = ? '
- . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)';
- $values = array ($is_favorite ? 1 : 0);
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET is_favorite=? '
+ . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)';
+ $values = array($is_favorite ? 1 : 0);
$values = array_merge($values, $ids);
- $stm = $this->bd->prepare ($sql);
- if ($stm && $stm->execute ($values)) {
+ $stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markFavorite: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ protected function updateCacheUnreads($catId = false, $feedId = false) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'LEFT OUTER JOIN ('
+ . 'SELECT e.id_feed, '
+ . 'COUNT(*) AS nbUnreads '
+ . 'FROM `' . $this->prefix . 'entry` e '
+ . 'WHERE e.is_read=0 '
+ . 'GROUP BY e.id_feed'
+ . ') x ON x.id_feed=f.id '
+ . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
+ . 'WHERE 1';
+ $values = array();
+ if ($feedId !== false) {
+ $sql .= ' AND f.id=?';
+ $values[] = $id;
+ }
+ if ($catId !== false) {
+ $sql .= ' AND f.category=?';
+ $values[] = $catId;
+ }
+ $stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute($values)) {
+ return true;
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
public function markRead($ids, $is_read = true) {
- if (is_array($ids)) {
+ if (is_array($ids)) { //Many IDs at once (used by API)
if (count($ids) < 6) { //Speed heuristics
$affected = 0;
foreach ($ids as $id) {
@@ -94,316 +137,164 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
- $this->bd->beginTransaction();
- $sql = 'UPDATE `' . $this->prefix . 'entry` e '
- . 'SET e.is_read = ? '
- . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)';
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET is_read=? '
+ . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)';
$values = array($is_read ? 1 : 0);
$values = array_merge($values, $ids);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack();
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR);
return false;
}
$affected = $stm->rowCount();
-
- if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'INNER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, '
- . 'COUNT(e.id) AS nbEntries '
- . 'FROM `' . $this->prefix . 'entry` e '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
- $stm = $this->bd->prepare($sql);
- if (!($stm && $stm->execute())) {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack();
- return false;
- }
+ if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
+ return false;
}
-
- $this->bd->commit();
return $affected;
} else {
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = ?,'
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
+ . 'SET e.is_read=?,'
. 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
- . 'WHERE e.id=?';
- $values = array($is_read ? 1 : 0, $ids);
+ . 'WHERE e.id=? AND e.is_read=?';
+ $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
}
- public function markReadEntries ($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
+ public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
if ($idMax == 0) {
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
- . 'WHERE e.is_read = 0';
- if ($onlyFavorites) {
- $sql .= ' AND e.is_favorite = 1';
- } elseif ($priorityMin >= 0) {
- $sql .= ' AND f.priority > ' . intval($priorityMin);
- }
- $stm = $this->bd->prepare ($sql);
- if ($stm && $stm->execute ()) {
- return $stm->rowCount();
- } else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }
- } else {
- $this->bd->beginTransaction ();
-
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1 '
- . 'WHERE e.is_read = 0 AND e.id <= ?';
- if ($onlyFavorites) {
- $sql .= ' AND e.is_favorite = 1';
- } elseif ($priorityMin >= 0) {
- $sql .= ' AND f.priority > ' . intval($priorityMin);
- }
- $values = array ($idMax);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- $affected = $stm->rowCount();
-
- if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'LEFT OUTER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(*) AS nbUnreads '
- . 'FROM `' . $this->prefix . 'entry` e '
- . 'WHERE e.is_read = 0 '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)';
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ())) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- }
+ $idMax = time() . '000000';
+ Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
+ }
- $this->bd->commit ();
- return $affected;
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
+ . 'SET e.is_read=1 '
+ . 'WHERE e.is_read=0 AND e.id <= ?';
+ if ($onlyFavorites) {
+ $sql .= ' AND e.is_favorite=1';
+ } elseif ($priorityMin >= 0) {
+ $sql .= ' AND f.priority > ' . intval($priorityMin);
}
+ $values = array($idMax);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
+ return false;
+ }
+ return $affected;
}
- public function markReadCat ($id, $idMax = 0) {
+ public function markReadCat($id, $idMax = 0) {
if ($idMax == 0) {
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
- . 'WHERE f.category = ? AND e.is_read = 0';
- $values = array ($id);
- $stm = $this->bd->prepare ($sql);
- if ($stm && $stm->execute ($values)) {
- return $stm->rowCount();
- } else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }
- } else {
- $this->bd->beginTransaction ();
-
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1 '
- . 'WHERE f.category = ? AND e.is_read = 0 AND e.id <= ?';
- $values = array ($id, $idMax);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- $affected = $stm->rowCount();
-
- if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'LEFT OUTER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(*) AS nbUnreads '
- . 'FROM `' . $this->prefix . 'entry` e '
- . 'WHERE e.is_read = 0 '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
- . 'WHERE f.category = ?';
- $values = array ($id);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- }
+ $idMax = time() . '000000';
+ Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
+ }
- $this->bd->commit ();
- return $affected;
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
+ . 'SET e.is_read=1 '
+ . 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?';
+ $values = array($id, $idMax);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) {
+ return false;
}
+ return $affected;
}
- public function markReadCatName($name, $idMax = 0) {
+ public function markReadFeed($id, $idMax = 0) {
if ($idMax == 0) {
- $sql = 'UPDATE `' . $this->prefix . 'entry` e '
- . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
- . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
- . 'WHERE c.name = ?';
- $values = array($name);
- $stm = $this->bd->prepare($sql);
- if ($stm && $stm->execute($values)) {
- return $stm->rowCount();
- } else {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }
- } else {
- $this->bd->beginTransaction();
-
- $sql = 'UPDATE `' . $this->prefix . 'entry` e '
- . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
- . 'SET e.is_read = 1 '
- . 'WHERE c.name = ? AND e.id <= ?';
- $values = array($name, $idMax);
+ $idMax = time() . '000000';
+ Minz_Log::record($nb . 'Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG);
+ }
+ $this->bd->beginTransaction();
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET is_read=1 '
+ . 'WHERE id_feed=? AND is_read=0 AND id <= ?';
+ $values = array($id, $idMax);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack();
+ return false;
+ }
+ $affected = $stm->rowCount();
+
+ if ($affected > 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected
+ . ' WHERE id=?';
+ $values = array($id);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR);
$this->bd->rollBack();
return false;
}
- $affected = $stm->rowCount();
-
- if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'LEFT OUTER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(*) AS nbUnreads '
- . 'FROM `' . $this->prefix . 'entry` e '
- . 'WHERE e.is_read = 0 '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
- . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
- . 'WHERE c.name = ?';
- $values = array($name);
- $stm = $this->bd->prepare($sql);
- if (!($stm && $stm->execute($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack();
- return false;
- }
- }
-
- $this->bd->commit();
- return $affected;
}
- }
-
- public function markReadFeed ($id, $idMax = 0) {
- if ($idMax == 0) {
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
- . 'WHERE f.id=? AND e.is_read = 0';
- $values = array ($id);
- $stm = $this->bd->prepare ($sql);
- if ($stm && $stm->execute ($values)) {
- return $stm->rowCount();
- } else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }
- } else {
- $this->bd->beginTransaction ();
-
- $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'SET e.is_read = 1 '
- . 'WHERE f.id=? AND e.is_read = 0 AND e.id <= ?';
- $values = array ($id, $idMax);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- $affected = $stm->rowCount();
-
- if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected
- . ' WHERE f.id=?';
- $values = array ($id);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
- }
- $this->bd->commit ();
- return $affected;
- }
+ $this->bd->commit();
+ return $affected;
}
- public function searchByGuid ($feed_id, $id) {
+ public function searchByGuid($feed_id, $id) {
// un guid est unique pour un flux donné
- $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+ $sql = 'SELECT id, guid, title, author, '
+ . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
+ . ', link, date, is_read, is_favorite, id_feed, tags '
. 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array (
+ $values = array(
$feed_id,
$id
);
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
- $entries = self::daoToEntry ($res);
- return isset ($entries[0]) ? $entries[0] : null;
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $entries = self::daoToEntry($res);
+ return isset($entries[0]) ? $entries[0] : null;
}
- public function searchById ($id) {
- $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+ public function searchById($id) {
+ $sql = 'SELECT id, guid, title, author, '
+ . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
+ . ', link, date, is_read, is_favorite, id_feed, tags '
. 'FROM `' . $this->prefix . 'entry` WHERE id=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
+
+ $values = array($id);
- $values = array ($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $entries = self::daoToEntry($res);
+ return isset($entries[0]) ? $entries[0] : null;
+ }
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
- $entries = self::daoToEntry ($res);
- return isset ($entries[0]) ? $entries[0] : null;
+ protected function sqlConcat($s1, $s2) {
+ return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL
}
private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) {
@@ -419,39 +310,39 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$joinFeed = true;
break;
case 's': //Deprecated: use $state instead
- $where .= 'e1.is_favorite = 1 ';
+ $where .= 'e1.is_favorite=1 ';
break;
case 'c':
- $where .= 'f.category = ? ';
+ $where .= 'f.category=? ';
$values[] = intval($id);
$joinFeed = true;
break;
case 'f':
- $where .= 'e1.id_feed = ? ';
+ $where .= 'e1.id_feed=? ';
$values[] = intval($id);
break;
case 'A':
$where .= '1 ';
break;
default:
- throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!');
+ throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');
}
if ($state & FreshRSS_Entry::STATE_NOT_READ) {
if (!($state & FreshRSS_Entry::STATE_READ)) {
- $where .= 'AND e1.is_read = 0 ';
+ $where .= 'AND e1.is_read=0 ';
}
}
elseif ($state & FreshRSS_Entry::STATE_READ) {
- $where .= 'AND e1.is_read = 1 ';
+ $where .= 'AND e1.is_read=1 ';
}
if ($state & FreshRSS_Entry::STATE_FAVORITE) {
if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) {
- $where .= 'AND e1.is_favorite = 1 ';
+ $where .= 'AND e1.is_favorite=1 ';
}
}
elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
- $where .= 'AND e1.is_favorite = 0 ';
+ $where .= 'AND e1.is_favorite=0 ';
}
switch ($order) {
@@ -459,7 +350,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
case 'ASC':
break;
default:
- throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!');
+ throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!');
+ }
+ if ($firstId === '' && parent::$sharedDbType === 'mysql') {
+ $firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. Tested on MySQL 5.5 with 150k articles
}
if ($firstId !== '') {
$where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
@@ -467,7 +361,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
if (($date_min > 0) && ($type !== 's')) {
$where .= 'AND (e1.id >= ' . $date_min . '000000';
if ($showOlderUnreadsorFavorites) { //Lax date constraint
- $where .= ' OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0';
+ $where .= ' OR e1.is_read=0 OR e1.is_favorite=1 OR (f.keep_history <> 0';
if (intval($keepHistoryDefault) === 0) {
$where .= ' AND f.keep_history <> -2'; //default
}
@@ -520,7 +414,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$search .= 'AND e1.tags LIKE ? ';
$values[] = '%' . $word .'%';
} else {
- $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
+ $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
$values[] = '%' . $word .'%';
}
}
@@ -529,7 +423,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return array($values,
'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
- . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '')
+ . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '')
. 'WHERE ' . $where
. $search
. 'ORDER BY e1.id ' . $order
@@ -539,17 +433,19 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) {
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault);
- $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags '
+ $sql = 'SELECT e.id, e.guid, e.title, e.author, '
+ . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
+ . ', e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags '
. 'FROM `' . $this->prefix . 'entry` e '
. 'INNER JOIN ('
. $sql
- . ') e2 ON e2.id = e.id '
+ . ') e2 ON e2.id=e.id '
. 'ORDER BY e.id ' . $order;
- $stm = $this->bd->prepare ($sql);
- $stm->execute ($values);
+ $stm = $this->bd->prepare($sql);
+ $stm->execute($values);
- return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
+ return self::daoToEntry($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API
@@ -563,69 +459,85 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
public function listLastGuidsByFeed($id, $n) {
$sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
- $stm = $this->bd->prepare ($sql);
- $values = array ($id);
- $stm->execute ($values);
- return $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
}
- public function countUnreadRead () {
- $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0'
- . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0 AND is_read = 0';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
- $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ public function countUnreadRead() {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0'
+ . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0 AND is_read=0';
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
$all = empty($res[0]) ? 0 : $res[0];
$unread = empty($res[1]) ? 0 : $res[1];
return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
}
- public function count ($minPriority = null) {
- $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id';
+ public function count($minPriority = null) {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id';
if ($minPriority !== null) {
$sql = ' WHERE priority > ' . intval($minPriority);
}
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
- $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
}
- public function countNotRead ($minPriority = null) {
- $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE is_read = 0';
+ public function countNotRead($minPriority = null) {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE is_read=0';
if ($minPriority !== null) {
$sql = ' AND priority > ' . intval($minPriority);
}
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
- $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
}
- public function countUnreadReadFavorites () {
- $sql = 'SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1'
- . ' UNION SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read = 0';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
- $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ public function countUnreadReadFavorites() {
+ $sql = 'SELECT c FROM ('
+ . 'SELECT COUNT(id) AS c, 1 as o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 '
+ . 'UNION SELECT COUNT(id) AS c, 2 AS o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read=0'
+ . ') u ORDER BY o';
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
$all = empty($res[0]) ? 0 : $res[0];
$unread = empty($res[1]) ? 0 : $res[1];
return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
}
public function optimizeTable() {
- $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
+ $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ }
+
+ public function size($all = false) {
+ $db = Minz_Configuration::dataBase();
+ $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?'; //MySQL
+ $values = array($db['base']);
+ if (!$all) {
+ $sql .= ' AND table_name LIKE ?';
+ $values[] = $this->prefix . '%';
+ }
+ $stm = $this->bd->prepare($sql);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ return $res[0];
}
- public static function daoToEntry ($listDAO) {
- $list = array ();
+ public static function daoToEntry($listDAO) {
+ $list = array();
- if (!is_array ($listDAO)) {
- $listDAO = array ($listDAO);
+ if (!is_array($listDAO)) {
+ $listDAO = array($listDAO);
}
foreach ($listDAO as $key => $dao) {
- $entry = new FreshRSS_Entry (
+ $entry = new FreshRSS_Entry(
$dao['id_feed'],
$dao['guid'],
$dao['title'],
@@ -637,13 +549,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$dao['is_favorite'],
$dao['tags']
);
- if (isset ($dao['id'])) {
- $entry->_id ($dao['id']);
+ if (isset($dao['id'])) {
+ $entry->_id($dao['id']);
}
$list[] = $entry;
}
- unset ($listDAO);
+ unset($listDAO);
return $list;
}
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
new file mode 100644
index 000000000..3dabce4b2
--- /dev/null
+++ b/app/Models/EntryDAOSQLite.php
@@ -0,0 +1,129 @@
+<?php
+
+class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
+
+ protected function sqlConcat($s1, $s2) {
+ return $s1 . '||' . $s2;
+ }
+
+ protected function updateCacheUnreads($catId = false, $feedId = false) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET cache_nbUnreads=('
+ . 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e '
+ . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0) '
+ . 'WHERE 1';
+ $values = array();
+ if ($feedId !== false) {
+ $sql .= ' AND id=?';
+ $values[] = $feedId;
+ }
+ if ($catId !== false) {
+ $sql .= ' AND category=?';
+ $values[] = $catId;
+ }
+ $stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute($values)) {
+ return true;
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function markRead($ids, $is_read = true) {
+ if (is_array($ids)) { //Many IDs at once (used by API)
+ if (true) { //Speed heuristics //TODO: Not implemented yet for SQLite (so always call IDs one by one)
+ $affected = 0;
+ foreach ($ids as $id) {
+ $affected += $this->markRead($id, $is_read);
+ }
+ return $affected;
+ }
+ } else {
+ $this->bd->beginTransaction();
+ $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?';
+ $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markRead 1: ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack();
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if ($affected > 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` SET cache_nbUnreads=cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+ . 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)';
+ $values = array($ids);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markRead 2: ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack();
+ return false;
+ }
+ }
+ $this->bd->commit();
+ return $affected;
+ }
+ }
+
+ public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
+ if ($idMax == 0) {
+ $idMax = time() . '000000';
+ Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
+ }
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?';
+ if ($onlyFavorites) {
+ $sql .= ' AND is_favorite=1';
+ } elseif ($priorityMin >= 0) {
+ $sql .= ' AND id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.priority > ' . intval($priorityMin) . ')';
+ }
+ $values = array($idMax);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
+ return false;
+ }
+ return $affected;
+ }
+
+ public function markReadCat($id, $idMax = 0) {
+ if ($idMax == 0) {
+ $idMax = time() . '000000';
+ Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
+ }
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET is_read=1 '
+ . 'WHERE is_read=0 AND id <= ? AND '
+ . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)';
+ $values = array($idMax, $id);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) {
+ return false;
+ }
+ return $affected;
+ }
+
+ public function optimizeTable() {
+ //TODO: Search for an equivalent in SQLite
+ }
+
+ public function size($all = false) {
+ return @filesize(DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite');
+ }
+}
diff --git a/app/Models/Factory.php b/app/Models/Factory.php
new file mode 100644
index 000000000..08569b2e2
--- /dev/null
+++ b/app/Models/Factory.php
@@ -0,0 +1,32 @@
+<?php
+
+class FreshRSS_Factory {
+
+ public static function createFeedDao() {
+ $db = Minz_Configuration::dataBase();
+ if ($db['type'] === 'sqlite') {
+ return new FreshRSS_FeedDAOSQLite();
+ } else {
+ return new FreshRSS_FeedDAO();
+ }
+ }
+
+ public static function createEntryDao() {
+ $db = Minz_Configuration::dataBase();
+ if ($db['type'] === 'sqlite') {
+ return new FreshRSS_EntryDAOSQLite();
+ } else {
+ return new FreshRSS_EntryDAO();
+ }
+ }
+
+ public static function createStatsDAO() {
+ $db = Minz_Configuration::dataBase();
+ if ($db['type'] === 'sqlite') {
+ return new FreshRSS_StatsDAOSQLite();
+ } else {
+ return new FreshRSS_StatsDAO();
+ }
+ }
+
+}
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 757eacd59..576f37760 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -16,17 +16,19 @@ class FreshRSS_Feed extends Minz_Model {
private $httpAuth = '';
private $error = false;
private $keep_history = -2;
+ private $ttl = -2;
private $hash = null;
+ private $lockPath = '';
- public function __construct ($url, $validate=true) {
+ public function __construct($url, $validate=true) {
if ($validate) {
- $this->_url ($url);
+ $this->_url($url);
} else {
$this->url = $url;
}
}
- public function id () {
+ public function id() {
return $this->id;
}
@@ -37,74 +39,77 @@ class FreshRSS_Feed extends Minz_Model {
return $this->hash;
}
- public function url () {
+ public function url() {
return $this->url;
}
- public function category () {
+ public function category() {
return $this->category;
}
- public function entries () {
+ public function entries() {
return $this->entries === null ? array() : $this->entries;
}
- public function name () {
+ public function name() {
return $this->name;
}
- public function website () {
+ public function website() {
return $this->website;
}
- public function description () {
+ public function description() {
return $this->description;
}
- public function lastUpdate () {
+ public function lastUpdate() {
return $this->lastUpdate;
}
- public function priority () {
+ public function priority() {
return $this->priority;
}
- public function pathEntries () {
+ public function pathEntries() {
return $this->pathEntries;
}
- public function httpAuth ($raw = true) {
+ public function httpAuth($raw = true) {
if ($raw) {
return $this->httpAuth;
} else {
- $pos_colon = strpos ($this->httpAuth, ':');
- $user = substr ($this->httpAuth, 0, $pos_colon);
- $pass = substr ($this->httpAuth, $pos_colon + 1);
+ $pos_colon = strpos($this->httpAuth, ':');
+ $user = substr($this->httpAuth, 0, $pos_colon);
+ $pass = substr($this->httpAuth, $pos_colon + 1);
- return array (
+ return array(
'username' => $user,
'password' => $pass
);
}
}
- public function inError () {
+ public function inError() {
return $this->error;
}
- public function keepHistory () {
+ public function keepHistory() {
return $this->keep_history;
}
- public function nbEntries () {
+ public function ttl() {
+ return $this->ttl;
+ }
+ public function nbEntries() {
if ($this->nbEntries < 0) {
- $feedDAO = new FreshRSS_FeedDAO ();
- $this->nbEntries = $feedDAO->countEntries ($this->id ());
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $this->nbEntries = $feedDAO->countEntries($this->id());
}
return $this->nbEntries;
}
- public function nbNotRead () {
+ public function nbNotRead() {
if ($this->nbNotRead < 0) {
- $feedDAO = new FreshRSS_FeedDAO ();
- $this->nbNotRead = $feedDAO->countNotRead ($this->id ());
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $this->nbNotRead = $feedDAO->countNotRead($this->id());
}
return $this->nbNotRead;
}
public function faviconPrepare() {
$file = DATA_PATH . '/favicons/' . $this->hash() . '.txt';
- if (!file_exists ($file)) {
+ if (!file_exists($file)) {
$t = $this->website;
- if (empty($t)) {
+ if ($t == '') {
$t = $this->url;
}
file_put_contents($file, $t);
@@ -115,109 +120,123 @@ class FreshRSS_Feed extends Minz_Model {
@unlink($path . '.ico');
@unlink($path . '.txt');
}
- public function favicon () {
- return Minz_Url::display ('/f.php?' . $this->hash());
+ public function favicon() {
+ return Minz_Url::display('/f.php?' . $this->hash());
}
- public function _id ($value) {
+ public function _id($value) {
$this->id = $value;
}
- public function _url ($value, $validate=true) {
+ public function _url($value, $validate=true) {
+ $this->hash = null;
if ($validate) {
$value = checkUrl($value);
}
- if (empty ($value)) {
- throw new FreshRSS_BadUrl_Exception ($value);
+ if (empty($value)) {
+ throw new FreshRSS_BadUrl_Exception($value);
}
$this->url = $value;
}
- public function _category ($value) {
+ public function _category($value) {
$value = intval($value);
$this->category = $value >= 0 ? $value : 0;
}
- public function _name ($value) {
+ public function _name($value) {
$this->name = $value === null ? '' : $value;
}
- public function _website ($value, $validate=true) {
+ public function _website($value, $validate=true) {
if ($validate) {
$value = checkUrl($value);
}
- if (empty ($value)) {
+ if (empty($value)) {
$value = '';
}
$this->website = $value;
}
- public function _description ($value) {
+ public function _description($value) {
$this->description = $value === null ? '' : $value;
}
- public function _lastUpdate ($value) {
+ public function _lastUpdate($value) {
$this->lastUpdate = $value;
}
- public function _priority ($value) {
+ public function _priority($value) {
$value = intval($value);
$this->priority = $value >= 0 ? $value : 10;
}
- public function _pathEntries ($value) {
+ public function _pathEntries($value) {
$this->pathEntries = $value;
}
- public function _httpAuth ($value) {
+ public function _httpAuth($value) {
$this->httpAuth = $value;
}
- public function _error ($value) {
+ public function _error($value) {
$this->error = (bool)$value;
}
- public function _keepHistory ($value) {
+ public function _keepHistory($value) {
$value = intval($value);
$value = min($value, 1000000);
$value = max($value, -2);
$this->keep_history = $value;
}
- public function _nbNotRead ($value) {
+ public function _ttl($value) {
+ $value = intval($value);
+ $value = min($value, 100000000);
+ $value = max($value, -2);
+ $this->ttl = $value;
+ }
+ public function _nbNotRead($value) {
$this->nbNotRead = intval($value);
}
- public function _nbEntries ($value) {
+ public function _nbEntries($value) {
$this->nbEntries = intval($value);
}
- public function load ($loadDetails = false) {
+ public function load($loadDetails = false) {
if ($this->url !== null) {
if (CACHE_PATH === false) {
- throw new Minz_FileNotExistException (
+ throw new Minz_FileNotExistException(
'CACHE_PATH',
Minz_Exception::ERROR
);
} else {
- $url = htmlspecialchars_decode ($this->url, ENT_QUOTES);
+ $url = htmlspecialchars_decode($this->url, ENT_QUOTES);
if ($this->httpAuth != '') {
- $url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
+ $url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
}
$feed = customSimplePie();
- $feed->set_feed_url ($url);
+ $feed->set_feed_url($url);
+ if (!$loadDetails) { //Only activates auto-discovery when adding a new feed
+ $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE);
+ }
$mtime = $feed->init();
if ((!$mtime) || $feed->error()) {
- throw new FreshRSS_Feed_Exception ($feed->error() . ' [' . $url . ']');
- }
-
- // si on a utilisé l'auto-discover, notre url va avoir changé
- $subscribe_url = $feed->subscribe_url ();
- if ($subscribe_url !== null && $subscribe_url !== $this->url) {
- if ($this->httpAuth != '') {
- // on enlève les id si authentification HTTP
- $subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
- }
- $this->_url ($subscribe_url);
+ throw new FreshRSS_Feed_Exception($feed->error() . ' [' . $url . ']');
}
if ($loadDetails) {
+ // si on a utilisé l'auto-discover, notre url va avoir changé
+ $subscribe_url = $feed->subscribe_url(false);
+
$title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '&lt;', '>' => '&gt;', '"' => '&quot;')); //HTML to HTML-PRE //ENT_COMPAT except &
- $this->_name ($title == '' ? $this->url : $title);
+ $this->_name($title == '' ? $this->url : $title);
$this->_website(html_only_entity_decode($feed->get_link()));
$this->_description(html_only_entity_decode($feed->get_description()));
+ } else {
+ //The case of HTTP 301 Moved Permanently
+ $subscribe_url = $feed->subscribe_url(true);
+ }
+
+ if ($subscribe_url !== null && $subscribe_url !== $this->url) {
+ if ($this->httpAuth != '') {
+ // on enlève les id si authentification HTTP
+ $subscribe_url = preg_replace('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
+ }
+ $this->_url($subscribe_url);
}
- if (($mtime === true) || ($mtime > $this->lastUpdate)) {
+ if (($mtime === true) ||($mtime > $this->lastUpdate)) {
syslog(LOG_DEBUG, 'FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $subscribe_url);
$this->loadEntries($feed); // et on charge les articles du flux
} else {
@@ -231,25 +250,25 @@ class FreshRSS_Feed extends Minz_Model {
}
}
- private function loadEntries ($feed) {
- $entries = array ();
+ private function loadEntries($feed) {
+ $entries = array();
- foreach ($feed->get_items () as $item) {
- $title = html_only_entity_decode (strip_tags ($item->get_title ()));
- $author = $item->get_author ();
- $link = $item->get_permalink ();
- $date = @strtotime ($item->get_date ());
+ foreach ($feed->get_items() as $item) {
+ $title = html_only_entity_decode(strip_tags($item->get_title()));
+ $author = $item->get_author();
+ $link = $item->get_permalink();
+ $date = @strtotime($item->get_date());
// gestion des tags (catégorie == tag)
- $tags_tmp = $item->get_categories ();
- $tags = array ();
+ $tags_tmp = $item->get_categories();
+ $tags = array();
if ($tags_tmp !== null) {
foreach ($tags_tmp as $tag) {
- $tags[] = html_only_entity_decode ($tag->get_label ());
+ $tags[] = html_only_entity_decode($tag->get_label());
}
}
- $content = html_only_entity_decode ($item->get_content ());
+ $content = html_only_entity_decode($item->get_content());
$elinks = array();
foreach ($item->get_enclosures() as $enclosure) {
@@ -267,16 +286,16 @@ class FreshRSS_Feed extends Minz_Model {
}
}
- $entry = new FreshRSS_Entry (
- $this->id (),
- $item->get_id (),
+ $entry = new FreshRSS_Entry(
+ $this->id(),
+ $item->get_id(),
$title === null ? '' : $title,
- $author === null ? '' : html_only_entity_decode ($author->name),
+ $author === null ? '' : html_only_entity_decode($author->name),
$content === null ? '' : $content,
$link === null ? '' : $link,
- $date ? $date : time ()
+ $date ? $date : time()
);
- $entry->_tags ($tags);
+ $entry->_tags($tags);
// permet de récupérer le contenu des flux tronqués
$entry->loadCompleteContent($this->pathEntries());
@@ -288,20 +307,19 @@ class FreshRSS_Feed extends Minz_Model {
}
function lock() {
- $lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
- if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
- @unlink($lock);
+ $this->lockPath = TMP_PATH . '/' . $this->hash() . '.freshrss.lock';
+ if (file_exists($this->lockPath) && ((time() - @filemtime($this->lockPath)) > 3600)) {
+ @unlink($this->lockPath);
}
- if (($handle = @fopen($lock, 'x')) === false) {
+ if (($handle = @fopen($this->lockPath, 'x')) === false) {
return false;
}
- //register_shutdown_function('unlink', $lock);
+ //register_shutdown_function('unlink', $this->lockPath);
@fclose($handle);
return true;
}
function unlock() {
- $lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
- @unlink($lock);
+ @unlink($this->lockPath);
}
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index b65ff4af0..756b1f008 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -1,25 +1,25 @@
<?php
class FreshRSS_FeedDAO extends Minz_ModelPdo {
- public function addFeed ($valuesTmp) {
- $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2)';
- $stm = $this->bd->prepare ($sql);
+ public function addFeed($valuesTmp) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)';
+ $stm = $this->bd->prepare($sql);
- $values = array (
+ $values = array(
substr($valuesTmp['url'], 0, 511),
$valuesTmp['category'],
substr($valuesTmp['name'], 0, 255),
substr($valuesTmp['website'], 0, 255),
substr($valuesTmp['description'], 0, 1023),
$valuesTmp['lastUpdate'],
- base64_encode ($valuesTmp['httpAuth']),
+ base64_encode($valuesTmp['httpAuth']),
);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $this->bd->lastInsertId();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error addFeed: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
@@ -54,185 +54,163 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return $feed_search->id();
}
- public function updateFeed ($id, $valuesTmp) {
+ public function updateFeed($id, $valuesTmp) {
$set = '';
foreach ($valuesTmp as $key => $v) {
$set .= $key . '=?, ';
if ($key == 'httpAuth') {
- $valuesTmp[$key] = base64_encode ($v);
+ $valuesTmp[$key] = base64_encode($v);
}
}
- $set = substr ($set, 0, -2);
+ $set = substr($set, 0, -2);
$sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
foreach ($valuesTmp as $v) {
$values[] = $v;
}
$values[] = $id;
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateFeed: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function updateLastUpdate ($id, $inError = 0, $updateCache = true) {
+ public function updateLastUpdate($id, $inError = 0, $updateCache = true) {
if ($updateCache) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
- . 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),'
- . 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
+ $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
+ . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),'
. 'lastUpdate=?, error=? '
- . 'WHERE f.id=?';
+ . 'WHERE id=?';
} else {
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET lastUpdate=?, error=? '
- . 'WHERE f.id=?';
+ . 'WHERE id=?';
}
- $values = array (
+ $values = array(
time(),
$inError,
$id,
);
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateLastUpdate: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function changeCategory ($idOldCat, $idNewCat) {
- $catDAO = new FreshRSS_CategoryDAO ();
- $newCat = $catDAO->searchById ($idNewCat);
+ public function changeCategory($idOldCat, $idNewCat) {
+ $catDAO = new FreshRSS_CategoryDAO();
+ $newCat = $catDAO->searchById($idNewCat);
if (!$newCat) {
- $newCat = $catDAO->getDefault ();
+ $newCat = $catDAO->getDefault();
}
$sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array (
- $newCat->id (),
+ $values = array(
+ $newCat->id(),
$idOldCat
);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error changeCategory: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function deleteFeed ($id) {
- /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
- $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
- $stm = $this->bd->prepare ($sql);
- $values = array ($id);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }*/
-
+ public function deleteFeed($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array ($id);
+ $values = array($id);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error deleteFeed: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function deleteFeedByCategory ($id) {
- /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
- $sql = 'DELETE FROM `' . $this->prefix . 'entry` e '
- . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
- . 'WHERE f.category=?';
- $stm = $this->bd->prepare ($sql);
- $values = array ($id);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
- }*/
-
+ public function deleteFeedByCategory($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array ($id);
+ $values = array($id);
- if ($stm && $stm->execute ($values)) {
+ if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error deleteFeedByCategory: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function searchById ($id) {
+ public function searchById($id) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array ($id);
+ $values = array($id);
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
- $feed = self::daoToFeed ($res);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $feed = self::daoToFeed($res);
- if (isset ($feed[$id])) {
+ if (isset($feed[$id])) {
return $feed[$id];
} else {
return null;
}
}
- public function searchByUrl ($url) {
+ public function searchByUrl($url) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array ($url);
+ $values = array($url);
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
- $feed = current (self::daoToFeed ($res));
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $feed = current(self::daoToFeed($res));
- if (isset ($feed)) {
+ if (isset($feed)) {
return $feed;
} else {
return null;
}
}
- public function listFeeds () {
+ public function listFeeds() {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
- return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function arrayFeedCategoryNames() { //For API
$sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f '
. 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$feedCategoryNames = array();
foreach ($res as $line) {
@@ -244,49 +222,58 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return $feedCategoryNames;
}
- public function listFeedsOrderUpdate ($cacheDuration = 1500) {
- $sql = 'SELECT id, name, url, lastUpdate, pathEntries, httpAuth, keep_history '
+ public function listFeedsOrderUpdate($defaultCacheDuration = 3600) {
+ if ($defaultCacheDuration < 0) {
+ $defaultCacheDuration = 2147483647;
+ }
+ $sql = 'SELECT id, url, name, website, lastUpdate, pathEntries, httpAuth, keep_history, ttl '
. 'FROM `' . $this->prefix . 'feed` '
- . 'WHERE lastUpdate < ' . (time() - intval($cacheDuration))
- . ' ORDER BY lastUpdate';
- $stm = $this->bd->prepare ($sql);
- $stm->execute ();
+ . 'WHERE ttl <> -1 AND lastUpdate < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) '
+ . 'ORDER BY lastUpdate';
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute())) {
+ $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT -2'; //v0.7.3
+ $stm = $this->bd->prepare($sql2);
+ $stm->execute();
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ }
- return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
- public function listByCategory ($cat) {
+ public function listByCategory($cat) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
- $values = array ($cat);
+ $values = array($cat);
- $stm->execute ($values);
+ $stm->execute($values);
- return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
- public function countEntries ($id) {
+ public function countEntries($id) {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
- $stm = $this->bd->prepare ($sql);
- $values = array ($id);
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
- public function countNotRead ($id) {
+ public function countNotRead($id) {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0';
- $stm = $this->bd->prepare ($sql);
- $values = array ($id);
- $stm->execute ($values);
- $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
- public function updateCachedValues () { //For one single feed, call updateLastUpdate($id)
+ public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
$sql = 'UPDATE `' . $this->prefix . 'feed` f '
. 'INNER JOIN ('
. 'SELECT e.id_feed, '
@@ -296,50 +283,50 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
. 'GROUP BY e.id_feed'
. ') x ON x.id_feed=f.id '
. 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
- $stm = $this->bd->prepare ($sql);
+ $stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public function truncate ($id) {
- $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e WHERE e.id_feed=?';
+ public function truncate($id) {
+ $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
- $this->bd->beginTransaction ();
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
- return false;
- }
+ $this->bd->beginTransaction();
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack();
+ return false;
+ }
$affected = $stm->rowCount();
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'SET f.cache_nbEntries=0, f.cache_nbUnreads=0 WHERE f.id=?';
- $values = array ($id);
- $stm = $this->bd->prepare ($sql);
- if (!($stm && $stm->execute ($values))) {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- $this->bd->rollBack ();
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET cache_nbEntries=0, cache_nbUnreads=0 WHERE id=?';
+ $values = array($id);
+ $stm = $this->bd->prepare($sql);
+ if (!($stm && $stm->execute($values))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack();
return false;
}
- $this->bd->commit ();
+ $this->bd->commit();
return $affected;
}
- public function cleanOldEntries ($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after
- $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e '
- . 'WHERE e.id_feed = :id_feed AND e.id <= :id_max AND e.is_favorite = 0 AND e.id NOT IN '
- . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select because of: MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
- $stm = $this->bd->prepare ($sql);
+ public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after
+ $sql = 'DELETE FROM `' . $this->prefix . 'entry` '
+ . 'WHERE id_feed = :id_feed AND id <= :id_max AND is_favorite=0 AND id NOT IN '
+ . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
+ $stm = $this->bd->prepare($sql);
$id_max = intval($date_min) . '000000';
@@ -347,27 +334,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
$stm->bindParam(':id_max', $id_max, PDO::PARAM_INT);
$stm->bindParam(':keep', $keep, PDO::PARAM_INT);
- if ($stm && $stm->execute ()) {
+ if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error cleanOldEntries: ' . $info[2], Minz_Log::ERROR);
return false;
}
}
- public static function daoToFeed ($listDAO, $catID = null) {
- $list = array ();
+ public static function daoToFeed($listDAO, $catID = null) {
+ $list = array();
- if (!is_array ($listDAO)) {
- $listDAO = array ($listDAO);
+ if (!is_array($listDAO)) {
+ $listDAO = array($listDAO);
}
foreach ($listDAO as $key => $dao) {
- if (!isset ($dao['name'])) {
+ if (!isset($dao['name'])) {
continue;
}
- if (isset ($dao['id'])) {
+ if (isset($dao['id'])) {
$key = $dao['id'];
}
if ($catID === null) {
@@ -384,13 +371,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
$myFeed->_lastUpdate(isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0);
$myFeed->_priority(isset($dao['priority']) ? $dao['priority'] : 10);
$myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : '');
- $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : '');
+ $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : '');
$myFeed->_error(isset($dao['error']) ? $dao['error'] : 0);
$myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2);
+ $myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : -2);
$myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0);
$myFeed->_nbEntries(isset($dao['cache_nbEntries']) ? $dao['cache_nbEntries'] : 0);
- if (isset ($dao['id'])) {
- $myFeed->_id ($dao['id']);
+ if (isset($dao['id'])) {
+ $myFeed->_id($dao['id']);
}
$list[$key] = $myFeed;
}
diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php
new file mode 100644
index 000000000..0d1872389
--- /dev/null
+++ b/app/Models/FeedDAOSQLite.php
@@ -0,0 +1,19 @@
+<?php
+
+class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
+
+ public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
+ $stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute()) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+}
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 60cec7847..66f5104b3 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -2,6 +2,8 @@
class FreshRSS_StatsDAO extends Minz_ModelPdo {
+ const ENTRY_COUNT_PERIOD = 30;
+
/**
* Calculates entry repartition for all feeds and for main stream.
* The repartition includes:
@@ -9,7 +11,7 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
* - read entries
* - unread entries
* - favorite entries
- *
+ *
* @return type
*/
public function calculateEntryRepartition() {
@@ -50,50 +52,19 @@ SQL;
/**
* Calculates entry count per day on a 30 days period.
* Returns the result as a JSON string.
- *
+ *
* @return string
*/
public function calculateEntryCount() {
- $count = array();
+ $count = $this->initEntryCountArray();
+ $period = self::ENTRY_COUNT_PERIOD;
- // Generates a list of 30 last day to be sure we always have 30 days.
- // If we do not do that kind of thing, we'll end up with holes in the
- // days if the user do not have a lot of feeds.
- $sql = <<<SQL
-SELECT - (tens.val + units.val + 1) AS day
-FROM (
- SELECT 0 AS val
- UNION ALL SELECT 1
- UNION ALL SELECT 2
- UNION ALL SELECT 3
- UNION ALL SELECT 4
- UNION ALL SELECT 5
- UNION ALL SELECT 6
- UNION ALL SELECT 7
- UNION ALL SELECT 8
- UNION ALL SELECT 9
-) AS units
-CROSS JOIN (
- SELECT 0 AS val
- UNION ALL SELECT 10
- UNION ALL SELECT 20
-) AS tens
-ORDER BY day ASC
-SQL;
- $stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- foreach ($res as $value) {
- $count[$value['day']] = 0;
- }
-
- // Get stats per day for the last 30 days and applies the result on
- // the array created with the last query.
+ // Get stats per day for the last 30 days
$sql = <<<SQL
SELECT DATEDIFF(FROM_UNIXTIME(e.date), NOW()) AS day,
COUNT(1) AS count
FROM {$this->prefix}entry AS e
-WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -30 DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
+WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -{$period} DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
GROUP BY day
ORDER BY day ASC
SQL;
@@ -109,9 +80,20 @@ SQL;
}
/**
+ * Initialize an array for the entry count.
+ *
+ * @return array
+ */
+ protected function initEntryCountArray() {
+ return array_map(function () {
+ return 0;
+ }, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
+ }
+
+ /**
* Calculates feed count per category.
* Returns the result as a JSON string.
- *
+ *
* @return string
*/
public function calculateFeedByCategory() {
@@ -134,7 +116,7 @@ SQL;
/**
* Calculates entry count per category.
* Returns the result as a JSON string.
- *
+ *
* @return string
*/
public function calculateEntryByCategory() {
@@ -158,7 +140,7 @@ SQL;
/**
* Calculates the 10 top feeds based on their number of entries
- *
+ *
* @return array
*/
public function calculateTopFeed() {
@@ -172,7 +154,7 @@ FROM {$this->prefix}category AS c,
{$this->prefix}entry AS e
WHERE c.id = f.category
AND f.id = e.id_feed
-GROUP BY id
+GROUP BY f.id
ORDER BY count DESC
LIMIT 10
SQL;
@@ -181,7 +163,27 @@ SQL;
return $stm->fetchAll(PDO::FETCH_ASSOC);
}
- private function convertToSerie($data) {
+ /**
+ * Calculates the last publication date for each feed
+ *
+ * @return array
+ */
+ public function calculateFeedLastDate() {
+ $sql = <<<SQL
+SELECT MAX(f.name) AS name
+, MAX(date) AS last_date
+FROM {$this->prefix}feed AS f,
+{$this->prefix}entry AS e
+WHERE f.id = e.id_feed
+GROUP BY f.id
+ORDER BY name
+SQL;
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ return $stm->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ protected function convertToSerie($data) {
$serie = array();
foreach ($data as $key => $value) {
@@ -191,7 +193,7 @@ SQL;
return json_encode($serie);
}
- private function convertToPieSerie($data) {
+ protected function convertToPieSerie($data) {
$serie = array();
foreach ($data as $value) {
diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php
new file mode 100644
index 000000000..dea590c92
--- /dev/null
+++ b/app/Models/StatsDAOSQLite.php
@@ -0,0 +1,37 @@
+<?php
+
+class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
+
+ /**
+ * Calculates entry count per day on a 30 days period.
+ * Returns the result as a JSON string.
+ *
+ * @return string
+ */
+ public function calculateEntryCount() {
+ $count = $this->initEntryCountArray();
+ $period = parent::ENTRY_COUNT_PERIOD;
+
+ // Get stats per day for the last 30 days
+ $sql = <<<SQL
+SELECT round(julianday(e.date, 'unixepoch') - julianday('now')) AS day,
+COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+WHERE strftime('%Y%m%d', e.date, 'unixepoch')
+ BETWEEN strftime('%Y%m%d', 'now', '-{$period} days')
+ AND strftime('%Y%m%d', 'now', '-1 day')
+GROUP BY day
+ORDER BY day ASC
+SQL;
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($res as $value) {
+ $count[(int)$value['day']] = (int) $value['count'];
+ }
+
+ return $this->convertToSerie($count);
+ }
+
+}
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index 620149934..538eb6554 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -31,7 +31,10 @@ class FreshRSS_Themes extends Minz_Model {
if (file_exists($json_filename)) {
$content = file_get_contents($json_filename);
$res = json_decode($content, true);
- if ($res && isset($res['files']) && is_array($res['files'])) {
+ if ($res &&
+ !empty($res['name']) &&
+ isset($res['files']) &&
+ is_array($res['files'])) {
$res['id'] = $theme_id;
return $res;
}
@@ -70,6 +73,7 @@ class FreshRSS_Themes extends Minz_Model {
'add' => '✚',
'all' => '☰',
'bookmark' => '★',
+ 'bookmark-add' => '✚',
'category' => '☷',
'category-white' => '☷',
'close' => '❌',
@@ -77,6 +81,7 @@ class FreshRSS_Themes extends Minz_Model {
'down' => '▽',
'favorite' => '★',
'help' => 'ⓘ',
+ 'icon' => '⊚',
'key' => '⚿',
'link' => '↗',
'login' => '🔒',
@@ -109,3 +114,7 @@ class FreshRSS_Themes extends Minz_Model {
'<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />';
}
}
+
+function _i($icon, $url_only = false) {
+ return FreshRSS_Themes::icon($icon, $url_only);
+}
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index a25b57f89..dcf847a62 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -2,33 +2,44 @@
class FreshRSS_UserDAO extends Minz_ModelPdo {
public function createUser($username) {
- require_once(APP_PATH . '/sql.php');
$db = Minz_Configuration::dataBase();
+ require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+
+ if (defined('SQL_CREATE_TABLES')) {
+ $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category'));
+ $stm = $c->prepare($sql);
+ $ok = $stm && $stm->execute();
+ } else {
+ global $SQL_CREATE_TABLES;
+ if (is_array($SQL_CREATE_TABLES)) {
+ $ok = true;
+ foreach ($SQL_CREATE_TABLES as $instruction) {
+ $sql = sprintf($instruction, '', Minz_Translate::t('default_category'));
+ $stm = $c->prepare($sql);
+ $ok &= ($stm && $stm->execute());
+ }
+ }
+ }
- $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_');
- $stm = $this->bd->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true));
- $values = array(
- 'catName' => Minz_Translate::t('default_category'),
- );
- if ($stm && $stm->execute($values)) {
+ if ($ok) {
return true;
} else {
- $info = $stm->errorInfo();
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
return false;
}
}
public function deleteUser($username) {
- require_once(APP_PATH . '/sql.php');
$db = Minz_Configuration::dataBase();
+ require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
$sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
return true;
} else {
- $info = $stm->errorInfo();
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
return false;
}
diff --git a/app/sql.php b/app/SQL/install.sql.mysql.php
index 5cd7c52ed..16cb3a3b8 100644
--- a/app/sql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS `%1$sfeed` (
`httpAuth` varchar(511) DEFAULT NULL,
`error` boolean DEFAULT 0,
`keep_history` MEDIUMINT NOT NULL DEFAULT -2, -- v0.7
+ `ttl` INT NOT NULL DEFAULT -2, -- v0.7.3
`cache_nbEntries` int DEFAULT 0, -- v0.7
`cache_nbUnreads` int DEFAULT 0, -- v0.7
PRIMARY KEY (`id`),
@@ -52,7 +53,9 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
ENGINE = INNODB;
-INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, :catName);
+INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
');
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
+
+define('SQL_SHOW_TABLES', 'SHOW tables;');
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
new file mode 100644
index 000000000..b90a5ef5e
--- /dev/null
+++ b/app/SQL/install.sql.sqlite.php
@@ -0,0 +1,58 @@
+<?php
+$SQL_CREATE_TABLES = array(
+'CREATE TABLE IF NOT EXISTS `%1$scategory` (
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` varchar(255) NOT NULL,
+ UNIQUE (`name`)
+);',
+
+'CREATE TABLE IF NOT EXISTS `%1$sfeed` (
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `url` varchar(511) NOT NULL,
+ `%1$scategory` SMALLINT DEFAULT 0,
+ `name` varchar(255) NOT NULL,
+ `website` varchar(255),
+ `description` text,
+ `lastUpdate` int(11) DEFAULT 0,
+ `priority` tinyint(2) NOT NULL DEFAULT 10,
+ `pathEntries` varchar(511) DEFAULT NULL,
+ `httpAuth` varchar(511) DEFAULT NULL,
+ `error` boolean DEFAULT 0,
+ `keep_history` MEDIUMINT NOT NULL DEFAULT -2,
+ `ttl` INT NOT NULL DEFAULT -2,
+ `cache_nbEntries` int DEFAULT 0,
+ `cache_nbUnreads` int DEFAULT 0,
+ FOREIGN KEY (`%1$scategory`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
+ UNIQUE (`url`)
+);',
+
+'CREATE INDEX IF NOT EXISTS feed_name_index ON `%1$sfeed`(`name`);',
+'CREATE INDEX IF NOT EXISTS feed_priority_index ON `%1$sfeed`(`priority`);',
+'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `%1$sfeed`(`keep_history`);',
+
+'CREATE TABLE IF NOT EXISTS `%1$sentry` (
+ `id` bigint NOT NULL,
+ `guid` varchar(760) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `author` varchar(255),
+ `content` text,
+ `link` varchar(1023) NOT NULL,
+ `date` int(11),
+ `is_read` boolean NOT NULL DEFAULT 0,
+ `is_favorite` boolean NOT NULL DEFAULT 0,
+ `id_feed` SMALLINT,
+ `tags` varchar(1023),
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE (`id_feed`,`guid`)
+);',
+
+'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `%1$sentry`(`is_favorite`);',
+'CREATE INDEX IF NOT EXISTS entry_is_read_index ON `%1$sentry`(`is_read`);',
+
+'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
+);
+
+define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
+
+define('SQL_SHOW_TABLES', 'SELECT name FROM sqlite_master WHERE type="table"');
diff --git a/app/i18n/en.php b/app/i18n/en.php
index a3c1dfeb7..8634f99b5 100644
--- a/app/i18n/en.php
+++ b/app/i18n/en.php
@@ -15,8 +15,45 @@ return array (
'feed' => 'Feed',
'feeds' => 'Feeds',
'shortcuts' => 'Shortcuts',
+ 'queries' => 'User queries',
+ 'query_search' => 'Search for "%s"',
+ 'query_order_asc' => 'Display oldest articles first',
+ 'query_order_desc' => 'Display newest articles first',
+ 'query_get_category' => 'Display "%s" category',
+ 'query_get_feed' => 'Display "%s" feed',
+ 'query_get_all' => 'Display all articles',
+ 'query_get_favorite' => 'Display favorite articles',
+ 'query_state_0' => 'Display all articles',
+ 'query_state_1' => 'Display read articles',
+ 'query_state_2' => 'Display unread articles',
+ 'query_state_3' => 'Display all articles',
+ 'query_state_4' => 'Display favorite articles',
+ 'query_state_5' => 'Display read favorite articles',
+ 'query_state_6' => 'Display unread favorite articles',
+ 'query_state_7' => 'Display favorite articles',
+ 'query_state_8' => 'Display not favorite articles',
+ 'query_state_9' => 'Display read not favorite articles',
+ 'query_state_10' => 'Display unread not favorite articles',
+ 'query_state_11' => 'Display not favorite articles',
+ 'query_state_12' => 'Display all articles',
+ 'query_state_13' => 'Display read articles',
+ 'query_state_14' => 'Display unread articles',
+ 'query_state_15' => 'Display all articles',
+ 'query_number' => 'Query n°%d',
+ 'add_query' => 'Add a query',
+ 'no_query' => 'You haven’t created any user query yet.',
+ 'query_filter' => 'Filter applied:',
+ 'no_query_filter' => 'No filter',
'about' => 'About',
'stats' => 'Statistics',
+ 'stats_idle' => 'Idle feeds',
+ 'stats_main' => 'Main statistics',
+
+ 'last_week' => 'Last week',
+ 'last_month' => 'Last month',
+ 'last_3_month' => 'Last three months',
+ 'last_6_month' => 'Last six months',
+ 'last_year' => 'Last year',
'your_rss_feeds' => 'Your RSS feeds',
'add_rss_feed' => 'Add a RSS feed',
@@ -160,6 +197,7 @@ return array (
'by_feed' => 'by feed',
'by_default' => 'By default',
'keep_history' => 'Minimum number of articles to keep',
+ 'ttl' => 'Do not automatically refresh more often than',
'categorize' => 'Store in a category',
'truncate' => 'Delete all articles',
'advanced' => 'Advanced',
@@ -226,8 +264,9 @@ return array (
'bottom_line' => 'Bottom line',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
'sticky_post' => 'Stick the article to the top when opened',
+ 'reading_confirm' => 'Display a confirmation dialog on “mark all as read” actions',
'auto_read_when' => 'Mark article as read…',
- 'article_selected' => 'when article is selected',
+ 'article_viewed' => 'when article is viewed',
'article_open_on_website' => 'when article is opened on its original website',
'scroll' => 'during page scrolls',
'upon_reception' => 'upon reception of the article',
diff --git a/app/i18n/fr.php b/app/i18n/fr.php
index 4acf5b397..e04078dba 100644
--- a/app/i18n/fr.php
+++ b/app/i18n/fr.php
@@ -15,8 +15,45 @@ return array (
'feed' => 'Flux',
'feeds' => 'Flux',
'shortcuts' => 'Raccourcis',
+ 'queries' => 'Filtres utilisateurs',
+ 'query_search' => 'Recherche de "%s"',
+ 'query_order_asc' => 'Afficher les articles les plus anciens en premier',
+ 'query_order_desc' => 'Afficher les articles les plus récents en premier',
+ 'query_get_category' => 'Afficher la catégorie "%s"',
+ 'query_get_feed' => 'Afficher le flux "%s"',
+ 'query_get_all' => 'Afficher tous les articles',
+ 'query_get_favorite' => 'Afficher les articles favoris',
+ 'query_state_0' => 'Afficher tous les articles',
+ 'query_state_1' => 'Afficher les articles lus',
+ 'query_state_2' => 'Afficher les articles non lus',
+ 'query_state_3' => 'Afficher tous les articles',
+ 'query_state_4' => 'Afficher les articles favoris',
+ 'query_state_5' => 'Afficher les articles lus et favoris',
+ 'query_state_6' => 'Afficher les articles non lus et favoris',
+ 'query_state_7' => 'Afficher les articles favoris',
+ 'query_state_8' => 'Afficher les articles non favoris',
+ 'query_state_9' => 'Afficher les articles lus et non favoris',
+ 'query_state_10' => 'Afficher les articles non lus et non favoris',
+ 'query_state_11' => 'Afficher les articles non favoris',
+ 'query_state_12' => 'Afficher tous les articles',
+ 'query_state_13' => 'Afficher les articles lus',
+ 'query_state_14' => 'Afficher les articles non lus',
+ 'query_state_15' => 'Afficher tous les articles',
+ 'query_number' => 'Filtre n°%d',
+ 'add_query' => 'Créer un filtre',
+ 'no_query' => 'Vous n’avez pas encore créé de filtre.',
+ 'query_filter' => 'Filtres appliqués :',
+ 'no_query_filter' => 'Aucun filtre appliqué',
'about' => 'À propos',
'stats' => 'Statistiques',
+ 'stats_idle' => 'Flux inactifs',
+ 'stats_main' => 'Statistiques principales',
+
+ 'last_week' => 'La dernière semaine',
+ 'last_month' => 'Le dernier mois',
+ 'last_3_month' => 'Les derniers trois mois',
+ 'last_6_month' => 'Les derniers six mois',
+ 'last_year' => 'La dernière année',
'your_rss_feeds' => 'Vos flux RSS',
'add_rss_feed' => 'Ajouter un flux RSS',
@@ -33,7 +70,7 @@ return array (
'filter' => 'Filtrer',
'see_website' => 'Voir le site',
- 'administration' => 'Gestion',
+ 'administration' => 'Gérer',
'actualize' => 'Actualiser',
'mark_read' => 'Marquer comme lu',
@@ -66,49 +103,49 @@ return array (
'article_published_on' => 'Article publié initialement sur <a href="%s">%s</a>',
'article_published_on_author' => 'Article publié initialement sur <a href="%s">%s</a> par %s',
- 'access_denied' => 'Vous n’avez pas le droit d’accéder à cette page',
- 'page_not_found' => 'La page que vous cherchez n’existe pas',
- 'error_occurred' => 'Une erreur est survenue',
- 'error_occurred_update' => 'Rien n’a été modifié',
+ 'access_denied' => 'Vous n’avez pas le droit d’accéder à cette page !',
+ 'page_not_found' => 'La page que vous cherchez n’existe pas !',
+ 'error_occurred' => 'Une erreur est survenue !',
+ 'error_occurred_update' => 'Rien n’a été modifié !',
'default_category' => 'Sans catégorie',
- 'categories_updated' => 'Les catégories ont été mises à jour',
+ 'categories_updated' => 'Les catégories ont été mises à jour.',
'categories_management' => 'Gestion des catégories',
- 'feed_updated' => 'Le flux a été mis à jour',
+ 'feed_updated' => 'Le flux a été mis à jour.',
'rss_feed_management' => 'Gestion des flux RSS',
- 'configuration_updated' => 'La configuration a été mise à jour',
+ 'configuration_updated' => 'La configuration a été mise à jour.',
'sharing_management' => 'Gestion des options de partage',
- 'bad_opml_file' => 'Votre fichier OPML n’est pas valide',
- 'shortcuts_updated' => 'Les raccourcis ont été mis à jour',
+ 'bad_opml_file' => 'Votre fichier OPML n’est pas valide.',
+ 'shortcuts_updated' => 'Les raccourcis ont été mis à jour.',
'shortcuts_navigation' => 'Navigation',
'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.',
'shortcuts_article_action' => 'Actions associées à l’article courant',
'shortcuts_other_action' => 'Autres actions',
- 'feeds_marked_read' => 'Les flux ont été marqués comme lus',
- 'updated' => 'Modifications enregistrées',
+ 'feeds_marked_read' => 'Les flux ont été marqués comme lus.',
+ 'updated' => 'Modifications enregistrées.',
'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>',
- 'feed_added' => 'Le flux <em>%s</em> a bien été ajouté',
- 'feed_not_added' => '<em>%s</em> n’a pas pu être ajouté',
+ 'feed_added' => 'Le flux <em>%s</em> a bien été ajouté.',
+ 'feed_not_added' => '<em>%s</em> n’a pas pu être ajouté.',
'internal_problem_feed' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.',
- 'invalid_url' => 'L’url <em>%s</em> est invalide',
- 'feed_actualized' => '<em>%s</em> a été mis à jour',
- 'n_feeds_actualized' => '%d flux ont été mis à jour',
- 'feeds_actualized' => 'Les flux ont été mis à jour',
- 'no_feed_actualized' => 'Aucun flux n’a pu être mis à jour',
- 'n_entries_deleted' => '%d articles ont été supprimés',
- 'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues',
- 'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés',
- 'category_emptied' => 'La catégorie a été vidée',
- 'feed_deleted' => 'Le flux a été supprimé',
+ 'invalid_url' => 'L’url <em>%s</em> est invalide.',
+ 'feed_actualized' => '<em>%s</em> a été mis à jour.',
+ 'n_feeds_actualized' => '%d flux ont été mis à jour.',
+ 'feeds_actualized' => 'Les flux ont été mis à jour.',
+ 'no_feed_actualized' => 'Aucun flux n’a pu être mis à jour.',
+ 'n_entries_deleted' => '%d articles ont été supprimés.',
+ 'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.',
+ 'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.',
+ 'category_emptied' => 'La catégorie a été vidée.',
+ 'feed_deleted' => 'Le flux a été supprimé.',
'feed_validator' => 'Vérifier la valididé du flux',
- 'optimization_complete' => 'Optimisation terminée',
+ 'optimization_complete' => 'Optimisation terminée.',
'your_rss_feeds' => 'Vos flux RSS',
'your_favorites' => 'Vos favoris',
'public' => 'Public',
- 'invalid_login' => 'L’identifiant est invalide',
+ 'invalid_login' => 'L’identifiant est invalide !',
// VIEWS
'save' => 'Enregistrer',
@@ -120,12 +157,12 @@ return array (
'category_number' => 'Catégorie n°%d',
'ask_empty' => 'Vider ?',
'number_feeds' => '%d flux',
- 'can_not_be_deleted' => 'Ne peut pas être supprimée',
+ 'can_not_be_deleted' => 'Ne peut pas être supprimée.',
'add_category' => 'Ajouter une catégorie',
'new_category' => 'Nouvelle catégorie',
- 'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis',
- 'javascript_should_be_activated'=> 'Le JavaScript doit être activé',
+ 'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.',
+ 'javascript_should_be_activated'=> 'Le JavaScript doit être activé.',
'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
'see_on_website' => 'Voir sur le site d’origine',
'next_article' => 'Passer à l’article suivant',
@@ -136,7 +173,7 @@ return array (
'previous_page' => 'Passer à la page précédente',
'collapse_article' => 'Refermer',
'auto_share' => 'Partager',
- 'auto_share_help' => 'Si il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
+ 'auto_share_help' => 'S’il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
'focus_search' => 'Accéder à la recherche',
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
@@ -160,6 +197,7 @@ return array (
'by_feed' => 'par flux',
'by_default' => 'Par défaut',
'keep_history' => 'Nombre minimum d’articles à conserver',
+ 'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que',
'categorize' => 'Ranger dans une catégorie',
'truncate' => 'Supprimer tous les articles',
'advanced' => 'Avancé',
@@ -175,7 +213,7 @@ return array (
'share_name' => 'Nom du partage à afficher',
'share_url' => 'URL du partage à utiliser',
'not_yet_implemented' => 'Pas encore implémenté',
- 'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP',
+ 'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP.',
'no_selected_feed' => 'Aucun flux sélectionné.',
'think_to_add' => '<a href="./?c=configure&amp;a=feed">Vous pouvez ajouter des flux</a>.',
@@ -202,16 +240,16 @@ return array (
'username' => 'Nom d’utilisateur',
'password' => 'Mot de passe',
'create' => 'Créer',
- 'user_created' => 'L’utilisateur %s a été créé',
- 'user_deleted' => 'L’utilisateur %s a été supprimé',
+ 'user_created' => 'L’utilisateur %s a été créé.',
+ 'user_deleted' => 'L’utilisateur %s a été supprimé.',
'language' => 'Langue',
'month' => 'mois',
'archiving_configuration' => 'Archivage',
- 'delete_articles_every' => 'Supprimer les articles après',
+ 'delete_articles_every' => 'Supprimer les articles après',
'purge_now' => 'Purger maintenant',
- 'purge_completed' => 'Purge effectuée (%d articles supprimés)',
- 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux',
+ 'purge_completed' => 'Purge effectuée (%d articles supprimés).',
+ 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.',
'reading_configuration' => 'Lecture',
'display_configuration' => 'Affichage',
'articles_per_page' => 'Nombre d’articles par page',
@@ -226,8 +264,9 @@ return array (
'bottom_line' => 'Ligne du bas',
'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images',
'sticky_post' => 'Aligner l’article en haut quand il est ouvert',
+ 'reading_confirm' => 'Afficher une confirmation lors des actions “marquer tout comme lu”',
'auto_read_when' => 'Marquer un article comme lu…',
- 'article_selected' => 'lorsque l’article est sélectionné',
+ 'article_viewed' => 'lorsque l’article est affiché',
'article_open_on_website' => 'lorsque l’article est ouvert sur le site d’origine',
'scroll' => 'au défilement de la page',
'upon_reception' => 'dès la réception du nouvel article',
@@ -293,7 +332,7 @@ return array (
'version' => 'Version',
'logs' => 'Logs',
- 'logs_empty' => 'Les logs sont vides',
+ 'logs_empty' => 'Les logs sont vides.',
'clear_logs' => 'Effacer les logs',
'forbidden_access' => 'L’accès vous est interdit !',
diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php
index 0311ee9a4..553a79921 100644
--- a/app/i18n/install.en.php
+++ b/app/i18n/install.en.php
@@ -61,7 +61,7 @@ return array (
'update_end' => 'Update process is completed, now you can go to the final step.',
- 'installation_is_ok' => 'The installation process was successful.<br />The final step will now attempt to delete the <kbd>./p/i/install.php</kbd> file and any database backup created during the update process.<br />You may choose to skip this step and delete <kbd>./p/i/install.php</kbd> manually.',
+ 'installation_is_ok' => 'The installation process was successful.<br />The final step will now attempt to delete any file and database backup created during the update process.<br />You may choose to skip this step by deleting <kbd>./data/do-install.txt</kbd> manually.',
'finish_installation' => 'Complete installation',
'install_not_deleted' => 'Something went wrong; you must delete the file <em>%s</em> manually.',
);
diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php
index bb183642f..470d83e1a 100644
--- a/app/i18n/install.fr.php
+++ b/app/i18n/install.fr.php
@@ -60,7 +60,7 @@ return array (
'update_long' => 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.',
'update_end' => 'La mise à jour est terminée, vous pouvez maintenant passer à l’étape finale.',
- 'installation_is_ok' => 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer le fichier <kbd>./p/i/install.php</kbd>, ainsi que d’éventuelles copies de base de données créées durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape et de supprimer <kbd>./p/i/install.php</kbd> manuellement.',
+ 'installation_is_ok' => 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer les fichiers ainsi que d’éventuelles copies de base de données créés durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape en supprimant <kbd>./data/do-install.txt</kbd> manuellement.',
'finish_installation' => 'Terminer l’installation',
'install_not_deleted' => 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
);
diff --git a/p/i/install.php b/app/install.php
index bc9483f73..3767e3d91 100644
--- a/p/i/install.php
+++ b/app/install.php
@@ -3,28 +3,36 @@ if (function_exists('opcache_reset')) {
opcache_reset();
}
-require('../../constants.php');
define('BCRYPT_COST', 9);
-include(LIB_PATH . '/lib_rss.php');
-
session_name('FreshRSS');
session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true);
session_start();
if (isset ($_GET['step'])) {
- define ('STEP', $_GET['step']);
+ define ('STEP', (int)$_GET['step']);
} else {
define ('STEP', 1);
}
define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
-include(APP_PATH . '/sql.php');
+if (STEP === 3 && isset($_POST['type'])) {
+ $_SESSION['bd_type'] = $_POST['type'];
+}
-//<updates>
-define('SQL_SHOW_TABLES', 'SHOW tables;');
+if (isset($_SESSION['bd_type'])) {
+ switch ($_SESSION['bd_type']) {
+ case 'mysql':
+ include(APP_PATH . '/SQL/install.sql.mysql.php');
+ break;
+ case 'sqlite':
+ include(APP_PATH . '/SQL/install.sql.sqlite.php');
+ break;
+ }
+}
+//<updates>
define('SQL_BACKUP006', 'RENAME TABLE `%1$scategory` TO `%1$scategory006`, `%1$sfeed` TO `%1$sfeed006`, `%1$sentry` TO `%1$sentry006`;');
define('SQL_SHOW_COLUMNS_UPDATEv006', 'SHOW columns FROM `%1$sentry006` LIKE "id2";');
@@ -70,7 +78,9 @@ FROM `%1$sentry006` e0
INNER JOIN `%2$sentry` e1 ON e0.id2 = e1.id
WHERE e1.content_bin IS NULL');
-define('SQL_CONVERT_UPDATEv006', 'UPDATE `%1$sentry` SET content_bin=COMPRESS(?) WHERE id=?;');
+define('SQL_CONVERT_UPDATEv006', 'UPDATE `%1$sentry` SET '
+ . (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql' ? 'content_bin=COMPRESS(?)' : 'content=?')
+ . ' WHERE id=?;');
define('SQL_DROP_BACKUPv006', 'DROP TABLE IF EXISTS `%1$sentry006`, `%1$sfeed006`, `%1$scategory006`;');
@@ -210,21 +220,28 @@ function saveStep2 () {
function saveStep3 () {
if (!empty ($_POST)) {
- if (empty ($_POST['type']) ||
- empty ($_POST['host']) ||
- empty ($_POST['user']) ||
- empty ($_POST['base'])) {
- $_SESSION['bd_error'] = 'Missing parameters!';
+ if ($_SESSION['bd_type'] === 'sqlite') {
+ $_SESSION['bd_base'] = $_SESSION['default_user'];
+ $_SESSION['bd_host'] = '';
+ $_SESSION['bd_user'] = '';
+ $_SESSION['bd_password'] = '';
+ $_SESSION['bd_prefix'] = '';
+ $_SESSION['bd_prefix_user'] = ''; //No prefix for SQLite
+ } else {
+ if (empty ($_POST['type']) ||
+ empty ($_POST['host']) ||
+ empty ($_POST['user']) ||
+ empty ($_POST['base'])) {
+ $_SESSION['bd_error'] = 'Missing parameters!';
+ }
+ $_SESSION['bd_base'] = substr($_POST['base'], 0, 64);
+ $_SESSION['bd_host'] = $_POST['host'];
+ $_SESSION['bd_user'] = $_POST['user'];
+ $_SESSION['bd_password'] = $_POST['pass'];
+ $_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
+ $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
}
- $_SESSION['bd_type'] = isset ($_POST['type']) ? $_POST['type'] : 'mysql';
- $_SESSION['bd_host'] = $_POST['host'];
- $_SESSION['bd_user'] = $_POST['user'];
- $_SESSION['bd_password'] = $_POST['pass'];
- $_SESSION['bd_base'] = substr($_POST['base'], 0, 64);
- $_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
- $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
-
$ini_array = array(
'general' => array(
'environment' => empty($_SESSION['environment']) ? 'production' : $_SESSION['environment'],
@@ -285,9 +302,7 @@ function updateDatabase($perform = false) {
);
break;
case 'sqlite':
- $str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite';
- $driver_options = null;
- break;
+ return false; //No update for SQLite needed so far
default:
return false;
}
@@ -352,8 +367,10 @@ function newPdo() {
);
break;
case 'sqlite':
- $str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite';
- $driver_options = null;
+ $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite';
+ $driver_options = array(
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ );
break;
default:
return false;
@@ -364,13 +381,15 @@ function newPdo() {
function postUpdate() {
$c = newPdo();
- $sql = sprintf(SQL_UPDATE_HISTORYv007b, $_SESSION['bd_prefix_user']);
- $stm = $c->prepare($sql);
- $stm->execute();
+ if ($_SESSION['bd_type'] !== 'sqlite') { //No update for SQLite needed yet
+ $sql = sprintf(SQL_UPDATE_HISTORYv007b, $_SESSION['bd_prefix_user']);
+ $stm = $c->prepare($sql);
+ $stm->execute();
- $sql = sprintf(SQL_UPDATE_CACHED_VALUES, $_SESSION['bd_prefix_user']);
- $stm = $c->prepare($sql);
- $stm->execute();
+ $sql = sprintf(SQL_UPDATE_CACHED_VALUES, $_SESSION['bd_prefix_user']);
+ $stm = $c->prepare($sql);
+ $stm->execute();
+ }
//<favicons>
$sql = sprintf(SQL_GET_FEEDS, $_SESSION['bd_prefix_user']);
@@ -389,7 +408,7 @@ function postUpdate() {
}
function deleteInstall () {
- $res = unlink (INDEX_PATH . '/install.php');
+ $res = unlink (DATA_PATH . '/do-install.txt');
if ($res) {
header ('Location: index.php');
}
@@ -647,7 +666,10 @@ function checkBD () {
$str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
break;
case 'sqlite':
- $str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite';
+ $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite';
+ $driver_options = array(
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ );
break;
default:
return false;
@@ -655,20 +677,31 @@ function checkBD () {
$c = new PDO ($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
- $stm = $c->prepare(SQL_SHOW_TABLES);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- if (in_array($_SESSION['bd_prefix'] . 'entry', $res) && !in_array($_SESSION['bd_prefix'] . 'entry006', $res)) {
- $sql = sprintf(SQL_BACKUP006, $_SESSION['bd_prefix']); //v0.6
- $res = $c->query($sql); //Backup tables
+ if ($_SESSION['bd_type'] !== 'sqlite') { //No SQL backup for SQLite
+ $stm = $c->prepare(SQL_SHOW_TABLES);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ if (in_array($_SESSION['bd_prefix'] . 'entry', $res) && !in_array($_SESSION['bd_prefix'] . 'entry006', $res)) {
+ $sql = sprintf(SQL_BACKUP006, $_SESSION['bd_prefix']); //v0.6
+ $res = $c->query($sql); //Backup tables
+ }
}
- $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user']);
- $stm = $c->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true));
- $values = array(
- 'catName' => _t('default_category'),
- );
- $ok = $stm->execute($values);
+ if (defined('SQL_CREATE_TABLES')) {
+ $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user'], _t('default_category'));
+ $stm = $c->prepare($sql);
+ $ok = $stm->execute();
+ } else {
+ global $SQL_CREATE_TABLES;
+ if (is_array($SQL_CREATE_TABLES)) {
+ $ok = true;
+ foreach ($SQL_CREATE_TABLES as $instruction) {
+ $sql = sprintf($instruction, $_SESSION['bd_prefix_user'], _t('default_category'));
+ $stm = $c->prepare($sql);
+ $ok &= $stm->execute();
+ }
+ }
+ }
} catch (PDOException $e) {
$ok = false;
$_SESSION['bd_error'] = $e->getMessage();
@@ -889,20 +922,20 @@ function printStep3 () {
<div class="form-group">
<label class="group-name" for="type"><?php echo _t ('bdd_type'); ?></label>
<div class="group-controls">
- <select name="type" id="type">
+ <select name="type" id="type" onchange="mySqlShowHide()">
<option value="mysql"
<?php echo (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql') ? 'selected="selected"' : ''; ?>>
MySQL
</option>
- <!-- TODO : l'utilisation de SQLite n'est pas encore possible. Pour tester tout de même, décommentez ce bloc
<option value="sqlite"
<?php echo (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'sqlite') ? 'selected="selected"' : ''; ?>>
SQLite
- </option>-->
+ </option>
</select>
</div>
</div>
+ <div id="mysql">
<div class="form-group">
<label class="group-name" for="host"><?php echo _t ('host'); ?></label>
<div class="group-controls">
@@ -937,6 +970,13 @@ function printStep3 () {
<input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset ($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" />
</div>
</div>
+ </div>
+ <script>
+ function mySqlShowHide() {
+ document.getElementById('mysql').style.display = document.getElementById('type').value === 'mysql' ? 'block' : 'none';
+ }
+ mySqlShowHide();
+ </script>
<div class="form-group form-actions">
<div class="group-controls">
@@ -988,7 +1028,7 @@ function printStep5 () {
function printStep6 () {
?>
- <p class="alert alert-error"><span class="alert-head"><?php echo _t ('oops'); ?></span> <?php echo _t ('install_not_deleted', INDEX_PATH . '/install.php'); ?></p>
+ <p class="alert alert-error"><span class="alert-head"><?php echo _t ('oops'); ?></span> <?php echo _t ('install_not_deleted', DATA_PATH . '/do-install.txt'); ?></p>
<?php
}
diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml
index 43adeb3c6..e66f2f64c 100644
--- a/app/layout/aside_configure.phtml
+++ b/app/layout/aside_configure.phtml
@@ -15,6 +15,9 @@
<li class="item<?php echo Minz_Request::actionName () == 'shortcut' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a>
</li>
+ <li class="item<?php echo Minz_Request::actionName () == 'queries' ? ' active' : ''; ?>">
+ <a href="<?php echo _url ('configure', 'queries'); ?>"><?php echo Minz_Translate::t ('queries'); ?></a>
+ </li>
<li class="separator"></li>
<li class="item<?php echo Minz_Request::actionName () == 'users' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a>
diff --git a/app/layout/aside_stats.phtml b/app/layout/aside_stats.phtml
new file mode 100644
index 000000000..32a3f5dee
--- /dev/null
+++ b/app/layout/aside_stats.phtml
@@ -0,0 +1,9 @@
+<ul class="nav nav-list aside">
+ <li class="nav-header"><?php echo Minz_Translate::t ('stats'); ?></li>
+ <li class="item<?php echo Minz_Request::actionName () == 'index' ? ' active' : ''; ?>">
+ <a href="<?php echo _url ('stats', 'index'); ?>"><?php echo Minz_Translate::t ('stats_main'); ?></a>
+ </li>
+ <li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>">
+ <a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a>
+ </li>
+</ul>
diff --git a/app/layout/header.phtml b/app/layout/header.phtml
index 08aa7715d..2e42bfdea 100644
--- a/app/layout/header.phtml
+++ b/app/layout/header.phtml
@@ -2,20 +2,20 @@
if (Minz_Configuration::canLogIn()) {
?><ul class="nav nav-head nav-login"><?php
switch (Minz_Configuration::authType()) {
- case 'form':
- if ($this->loginOk) {
- ?><li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="<?php echo _url ('index', 'formLogout'); ?>"><?php echo Minz_Translate::t ('logout'); ?></a></li><?php
- } else {
- ?><li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="<?php echo _url ('index', 'formLogin'); ?>"><?php echo Minz_Translate::t ('login'); ?></a></li><?php
- }
- break;
- case 'persona':
- if ($this->loginOk) {
- ?><li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="#"><?php echo Minz_Translate::t ('logout'); ?></a></li><?php
- } else {
- ?><li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li><?php
- }
- break;
+ case 'form':
+ if ($this->loginOk) {
+ ?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _t('logout'); ?></a></li><?php
+ } else {
+ ?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php
+ }
+ break;
+ case 'persona':
+ if ($this->loginOk) {
+ ?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="#"><?php echo _t('logout'); ?></a></li><?php
+ } else {
+ ?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php
+ }
+ break;
}
?></ul><?php
}
@@ -24,36 +24,36 @@ if (Minz_Configuration::canLogIn()) {
<div class="header">
<div class="item title">
<h1>
- <a href="<?php echo _url ('index', 'index'); ?>">
- <img class="logo" src="<?php echo Minz_Url::display ('/themes/icons/icon.svg'); ?>" alt="⊚" />
- <?php echo Minz_Configuration::title (); ?>
+ <a href="<?php echo _url('index', 'index'); ?>">
+ <img class="logo" src="<?php echo _i('icon', true); ?>" alt="⊚" />
+ <?php echo Minz_Configuration::title(); ?>
</a>
</h1>
</div>
<div class="item search">
<?php if ($this->loginOk || Minz_Configuration::allowAnonymous()) { ?>
- <form action="<?php echo _url ('index', 'index'); ?>" method="get">
+ <form action="<?php echo _url('index', 'index'); ?>" method="get">
<div class="stick">
- <?php $search = Minz_Request::param ('search', ''); ?>
- <input type="search" name="search" id="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search'); ?>" />
+ <?php $search = Minz_Request::param('search', ''); ?>
+ <input type="search" name="search" id="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo _t('search'); ?>" />
- <?php $get = Minz_Request::param ('get', ''); ?>
- <?php if($get != '') { ?>
+ <?php $get = Minz_Request::param('get', ''); ?>
+ <?php if ($get != '') { ?>
<input type="hidden" name="get" value="<?php echo $get; ?>" />
<?php } ?>
- <?php $order = Minz_Request::param ('order', ''); ?>
- <?php if($order != '') { ?>
+ <?php $order = Minz_Request::param('order', ''); ?>
+ <?php if ($order != '') { ?>
<input type="hidden" name="order" value="<?php echo $order; ?>" />
<?php } ?>
- <?php $state = Minz_Request::param ('state', ''); ?>
- <?php if($state != '') { ?>
+ <?php $state = Minz_Request::param('state', ''); ?>
+ <?php if ($state != '') { ?>
<input type="hidden" name="state" value="<?php echo $state; ?>" />
<?php } ?>
- <button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('search'); ?></button>
+ <button class="btn" type="submit"><?php echo _i('search'); ?></button>
</div>
</form>
<?php } ?>
@@ -63,31 +63,32 @@ if (Minz_Configuration::canLogIn()) {
<div class="item configure">
<div class="dropdown">
<div id="dropdown-configure" class="dropdown-target"></div>
- <a class="btn dropdown-toggle" href="#dropdown-configure"><?php echo FreshRSS_Themes::icon('configure'); ?></a>
+ <a class="btn dropdown-toggle" href="#dropdown-configure"><?php echo _i('configure'); ?></a>
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close">❌</a></li>
- <li class="dropdown-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
- <li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('display_configuration'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('configure', 'reading'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a></li>
+ <li class="dropdown-header"><?php echo _t('configuration'); ?></li>
+ <li class="item"><a href="<?php echo _url('configure', 'display'); ?>"><?php echo _t('display_configuration'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'reading'); ?>"><?php echo _t('reading_configuration'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'archiving'); ?>"><?php echo _t('archiving_configuration'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'sharing'); ?>"><?php echo _t('sharing'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'shortcut'); ?>"><?php echo _t('shortcuts'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'queries'); ?>"><?php echo _t('queries'); ?></a></li>
<li class="separator"></li>
- <li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('configure', 'users'); ?>"><?php echo _t('users'); ?></a></li>
<li class="separator"></li>
- <li class="item"><a href="<?php echo _url ('index', 'stats'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Minz_Translate::t ('logs'); ?></a></li>
- <li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('stats', 'index'); ?>"><?php echo _t('stats'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('index', 'logs'); ?>"><?php echo _t('logs'); ?></a></li>
+ <li class="item"><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about'); ?></a></li>
<?php
if (Minz_Configuration::canLogIn()) {
?><li class="separator"></li><?php
switch (Minz_Configuration::authType()) {
- case 'form':
- ?><li class="item"><a class="signout" href="<?php echo _url ('index', 'formLogout'); ?>"><?php echo FreshRSS_Themes::icon('logout'), ' ', Minz_Translate::t ('logout'); ?></a></li><?php
- break;
- case 'persona':
- ?><li class="item"><a class="signout" href="#"><?php echo FreshRSS_Themes::icon('logout'), ' ', Minz_Translate::t ('logout'); ?></a></li><?php
- break;
+ case 'form':
+ ?><li class="item"><a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php
+ break;
+ case 'persona':
+ ?><li class="item"><a class="signout" href="#"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php
+ break;
}
} ?>
</ul>
@@ -96,12 +97,12 @@ if (Minz_Configuration::canLogIn()) {
<?php } elseif (Minz_Configuration::canLogIn()) {
?><div class="item configure"><?php
switch (Minz_Configuration::authType()) {
- case 'form':
- echo FreshRSS_Themes::icon('login'); ?><a class="signin" href="<?php echo _url ('index', 'formLogin'); ?>"><?php echo Minz_Translate::t ('login'); ?></a></li><?php
- break;
- case 'persona':
- echo FreshRSS_Themes::icon('login'); ?><a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li><?php
- break;
+ case 'form':
+ echo _i('login'); ?><a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php
+ break;
+ case 'persona':
+ echo _i('login'); ?><a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php
+ break;
}
?></div><?php
} ?>
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index b42f816b4..29ea9032c 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -7,18 +7,20 @@
<?php } ?>
<?php if ($this->loginOk) { ?>
- <?php $url_state = $this->url;
- if ($this->state & FreshRSS_Entry::STATE_READ) {
- $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_READ;
- $checked = 'true';
- $class = 'active';
- } else {
- $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_READ;
- $checked = 'false';
- $class = '';
- }
- ?>
<div class="stick">
+ <?php
+ $url_state = $this->url;
+
+ if ($this->state & FreshRSS_Entry::STATE_READ) {
+ $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_READ;
+ $checked = 'true';
+ $class = 'active';
+ } else {
+ $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_READ;
+ $checked = 'false';
+ $class = '';
+ }
+ ?>
<a id="toggle-read"
class="btn <?php echo $class; ?>"
aria-checked="<?php echo $checked; ?>"
@@ -26,6 +28,7 @@
title="<?php echo Minz_Translate::t ('show_read'); ?>">
<?php echo FreshRSS_Themes::icon('read'); ?>
</a>
+
<?php
if ($this->state & FreshRSS_Entry::STATE_NOT_READ) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_READ;
@@ -44,6 +47,7 @@
title="<?php echo Minz_Translate::t ('show_not_reads'); ?>">
<?php echo FreshRSS_Themes::icon('unread'); ?>
</a>
+
<?php
if ($this->state & FreshRSS_Entry::STATE_FAVORITE) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_FAVORITE;
@@ -62,6 +66,7 @@
title="<?php echo Minz_Translate::t ('show_favorite'); ?>">
<?php echo FreshRSS_Themes::icon('starred'); ?>
</a>
+
<?php
if ($this->state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_FAVORITE;
@@ -80,6 +85,34 @@
title="<?php echo Minz_Translate::t ('show_not_favorite'); ?>">
<?php echo FreshRSS_Themes::icon('non-starred'); ?>
</a>
+
+ <div class="dropdown">
+ <div id="dropdown-query" class="dropdown-target"></div>
+
+ <a class="dropdown-toggle btn" href="#dropdown-query"><?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('queries'); ?> <a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a></li>
+
+ <?php foreach ($this->conf->queries as $query) { ?>
+ <li class="item">
+ <a href="<?php echo $query['url']; ?>"><?php echo $query['name']; ?></a>
+ </li>
+ <?php } ?>
+
+ <?php if (count($this->conf->queries) > 0) { ?>
+ <li class="separator no-mobile"></li>
+ <?php } ?>
+
+ <?php
+ $url_query = $this->url;
+ $url_query['c'] = 'configure';
+ $url_query['a'] = 'addQuery';
+ ?>
+ <li class="item no-mobile"><a href="<?php echo Minz_Url::display($url_query); ?>"><?php echo FreshRSS_Themes::icon('bookmark-add'); ?> <?php echo Minz_Translate::t('add_query'); ?></a></li>
+ </ul>
+ </div>
</div>
<?php
$get = false;
@@ -148,7 +181,7 @@
?>
<div class="stick" id="nav_menu_read_all">
- <a class="read_all btn" href="<?php echo $markReadUrl; ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a>
+ <a class="read_all btn<?php if ($this->conf->reading_confirm) {echo ' confirm';} ?>" href="<?php echo $markReadUrl; ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a>
<div class="dropdown">
<div id="dropdown-read" class="dropdown-target"></div>
diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml
index e144d0f45..c9cc7fe02 100644
--- a/app/views/configure/archiving.phtml
+++ b/app/views/configure/archiving.phtml
@@ -24,6 +24,27 @@
?></select> (<?php echo Minz_Translate::t('by_default'); ?>)
</div>
</div>
+ <div class="form-group">
+ <label class="group-name" for="ttl_default"><?php echo Minz_Translate::t('ttl'); ?></label>
+ <div class="group-controls">
+ <select class="number" name="ttl_default" id="ttl_default" required="required"><?php
+ $found = false;
+ foreach (array(1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
+ 3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
+ 36000 => '10h', 43200 => '12h', 64800 => '18h',
+ 86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
+ 604800 => '1wk', -1 => '∞') as $v => $t) {
+ echo '<option value="' . $v . ($this->conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>';
+ if ($this->conf->ttl_default == $v) {
+ $found = true;
+ }
+ }
+ if (!$found) {
+ echo '<option value="' . intval($this->conf->ttl_default) . '" selected="selected">' . intval($this->conf->ttl_default) . 's</option>';
+ }
+ ?></select> (<?php echo Minz_Translate::t('by_default'); ?>)
+ </div>
+ </div>
<div class="form-group form-actions">
<div class="group-controls">
diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml
index 27b0990ff..a8dd9a8cb 100644
--- a/app/views/configure/feed.phtml
+++ b/app/views/configure/feed.phtml
@@ -103,6 +103,27 @@
?></select>
</div>
</div>
+ <div class="form-group">
+ <label class="group-name" for="ttl"><?php echo Minz_Translate::t('ttl'); ?></label>
+ <div class="group-controls">
+ <select class="number" name="ttl" id="ttl" required="required"><?php
+ $found = false;
+ foreach (array(-2 => Minz_Translate::t('by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
+ 3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
+ 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) {
+ $found = true;
+ }
+ }
+ if (!$found) {
+ echo '<option value="' . intval($this->flux->ttl()) . '" selected="selected">' . intval($this->flux->ttl()) . 's</option>';
+ }
+ ?></select>
+ </div>
+ </div>
<div class="form-group form-actions">
<div class="group-controls">
<button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml
new file mode 100644
index 000000000..2895f584c
--- /dev/null
+++ b/app/views/configure/queries.phtml
@@ -0,0 +1,90 @@
+<?php $this->partial('aside_configure'); ?>
+
+<div class="post">
+ <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
+
+ <form method="post" action="<?php echo _url('configure', 'queries'); ?>">
+ <legend><?php echo _t('queries'); ?></legend>
+
+ <?php foreach ($this->conf->queries as $key => $query) { ?>
+ <div class="form-group" id="query-group-<?php echo $key; ?>">
+ <label class="group-name" for="queries_<?php echo $key; ?>_name">
+ <?php echo _t('query_number', $key + 1); ?>
+ </label>
+
+ <div class="group-controls">
+ <input type="hidden" id="queries_<?php echo $key; ?>_search" name="queries[<?php echo $key; ?>][search]" value="<?php echo isset($query['search']) ? $query['search'] : ""; ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_state" name="queries[<?php echo $key; ?>][state]" value="<?php echo isset($query['state']) ? $query['state'] : ""; ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_order" name="queries[<?php echo $key; ?>][order]" value="<?php echo isset($query['order']) ? $query['order'] : ""; ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_get" name="queries[<?php echo $key; ?>][get]" value="<?php echo isset($query['get']) ? $query['get'] : ""; ?>"/>
+
+ <div class="stick">
+ <input class="extend"
+ type="text"
+ id="queries_<?php echo $key; ?>_name"
+ name="queries[<?php echo $key; ?>][name]"
+ value="<?php echo $query['name']; ?>"
+ />
+
+ <a class="btn" href="<?php echo $query['url']; ?>">
+ <?php echo _i('link'); ?>
+ </a>
+
+ <a class="btn btn-attention remove" href="#" data-remove="query-group-<?php echo $key; ?>">
+ <?php echo _i('close'); ?>
+ </a>
+ </div>
+
+ <?php
+ $exist = (isset($query['search']) ? 1 : 0)
+ + (isset($query['state']) ? 1 : 0)
+ + (isset($query['order']) ? 1 : 0)
+ + (isset($query['get']) ? 1 : 0);
+ // If the only filter is "all" articles, we consider there is no filter
+ $exist = ($exist === 1 && isset($query['get']) && $query['get'] === 'a') ? 0 : $exist;
+ ?>
+
+ <?php if ($exist === 0) { ?>
+ <div class="alert alert-warn">
+ <div class="alert-head"><?php echo _t('no_query_filter'); ?></div>
+ </div>
+ <?php } else { ?>
+ <div class="alert alert-success">
+ <div class="alert-head"><?php echo _t('query_filter'); ?></div>
+
+ <ul>
+ <?php if (isset($query['search'])) { $exist = true; ?>
+ <li class="item"><?php echo _t('query_search', $query['search']); ?></li>
+ <?php } ?>
+
+ <?php if (isset($query['state'])) { $exist = true; ?>
+ <li class="item"><?php echo _t('query_state_' . $query['state']); ?></li>
+ <?php } ?>
+
+ <?php if (isset($query['order'])) { $exist = true; ?>
+ <li class="item"><?php echo _t('query_order_' . strtolower($query['order'])); ?></li>
+ <?php } ?>
+
+ <?php if (isset($query['get'])) { $exist = true; ?>
+ <li class="item"><?php echo _t('query_get_' . $this->query_get[$key]['type'], $this->query_get[$key]['name']); ?></li>
+ <?php } ?>
+ </ul>
+ </div>
+ <?php } ?>
+ </div>
+ </div>
+ <?php } ?>
+
+ <?php if (count($this->conf->queries) > 0) { ?>
+ <div class="form-group form-actions">
+ <div class="group-controls">
+ <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button>
+ <button type="reset" class="btn"><?php echo _t('cancel'); ?></button>
+ </div>
+ </div>
+ <?php } else { ?>
+ <p class="alert alert-warn"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('no_query'); ?></p>
+ <?php } ?>
+ </form>
+
+</div> \ No newline at end of file
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index 456ab9522..4d439e83d 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -83,11 +83,21 @@
</div>
<div class="form-group">
+ <div class="group-controls">
+ <label class="checkbox" for="reading_confirm">
+ <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo $this->conf->reading_confirm ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t ('reading_confirm'); ?>
+ <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('auto_read_when'); ?></label>
<div class="group-controls">
<label class="checkbox" for="check_open_article">
<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
- <?php echo Minz_Translate::t ('article_selected'); ?>
+ <?php echo Minz_Translate::t('article_viewed'); ?>
</label>
<label class="checkbox" for="check_open_site">
<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml
index a952bc3b4..02ce331da 100644
--- a/app/views/configure/sharing.phtml
+++ b/app/views/configure/sharing.phtml
@@ -4,35 +4,35 @@
<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', 'sharing'); ?>"
- data-simple='<div class="form-group"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="share remove btn btn-attention"><?php echo FreshRSS_Themes::icon('close'); ?></a>
+ data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a>
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>'
- data-advanced='<div class="form-group"><label class="group-name">##label##</label><div class="group-controls">
+ data-advanced='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls">
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
<div class="stick">
<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
<input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
- <a href="#" class="share remove btn btn-attention"><?php echo FreshRSS_Themes::icon('close'); ?></a></div>
+ <a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a></div>
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="##help##"><?php echo FreshRSS_Themes::icon('help'); ?></a>
</div></div>'>
<legend><?php echo Minz_Translate::t ('sharing'); ?></legend>
<?php foreach ($this->conf->sharing as $key => $sharing): ?>
<?php $share = $this->conf->shares[$sharing['type']]; ?>
- <div class="form-group">
+ <div class="form-group" id="group-share-<?php echo $key; ?>">
<label class="group-name">
<?php echo Minz_Translate::t ($sharing['type']); ?>
</label>
<div class="group-controls">
<input type='hidden' id='share_<?php echo $key;?>_type' name="share[<?php echo $key;?>][type]" value='<?php echo $sharing['type']?>' />
- <?php if ($share['form'] === 'advanced'){ ?>
+ <?php if ($share['form'] === 'advanced') { ?>
<div class="stick">
<input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
<input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
- <a href='#' class='share remove btn btn-attention'><?php echo FreshRSS_Themes::icon('close'); ?></a>
+ <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a>
</div>
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="<?php echo $share['help']?>"><?php echo FreshRSS_Themes::icon('help'); ?></a>
<?php } else { ?>
- <a href='#' class='share remove btn btn-attention'><?php echo FreshRSS_Themes::icon('close'); ?></a>
+ <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a>
<?php } ?>
</div>
</div>
diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml
index d4983a32e..f38913c06 100755
--- a/app/views/helpers/pagination.phtml
+++ b/app/views/helpers/pagination.phtml
@@ -12,7 +12,7 @@
<?php $params['next'] = $this->nextId; ?>
<a id="load_more" href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t ('load_more'); ?></a>
<?php } elseif ($markReadUrl) { ?>
- <a id="bigMarkAsRead" href="<?php echo $markReadUrl; ?>">
+ <a id="bigMarkAsRead" href="<?php echo $markReadUrl; ?>"<?php if ($this->conf->reading_confirm) { echo ' class="confirm"';} ?>>
<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
<span class="bigTick">✔</span><br />
<?php echo Minz_Translate::t ('mark_all_read'); ?>
diff --git a/app/views/javascript/actualize.phtml b/app/views/javascript/actualize.phtml
index 6a92e5642..d08dc47d1 100644
--- a/app/views/javascript/actualize.phtml
+++ b/app/views/javascript/actualize.phtml
@@ -10,7 +10,7 @@ var feeds = [<?php
function initProgressBar(init) {
if (init) {
$("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\
- <?php echo Minz_Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
+ <?php echo _t('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
<progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feed_count + "\"></progress>\
</div>");
} else {
@@ -24,7 +24,8 @@ function updateProgressBar(i) {
function updateFeeds() {
if (feed_count === 0) {
- openNotification("<?php echo Minz_Translate::t ('no_feed_to_refresh'); ?>", "good");
+ openNotification("<?php echo _t('no_feed_to_refresh'); ?>", "good");
+ ajax_loading = false;
return;
}
initProgressBar(true);
@@ -39,6 +40,7 @@ function updateFeed() {
if (feed == undefined) {
return;
}
+
$.ajax({
type: 'POST',
url: feed,
diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml
new file mode 100644
index 000000000..356fea20f
--- /dev/null
+++ b/app/views/stats/idle.phtml
@@ -0,0 +1,19 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+ <a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
+
+ <h1><?php echo _t ('stats_idle'); ?></h1>
+
+ <?php foreach ($this->idleFeeds as $period => $feeds){ ?>
+ <div class="stat">
+ <h2><?php echo _t ($period); ?></h2>
+
+ <ul>
+ <?php foreach ($feeds as $feed){ ?>
+ <li><?php echo $feed; ?></li>
+ <?php } ?>
+ </ul>
+ </div>
+ <?php } ?>
+</div>
diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml
new file mode 100644
index 000000000..a48181fe4
--- /dev/null
+++ b/app/views/stats/index.phtml
@@ -0,0 +1,127 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+ <a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
+
+ <h1><?php echo _t ('stats_main'); ?></h1>
+
+ <div class="stat">
+ <h2><?php echo _t ('stats_entry_repartition'); ?></h2>
+ <table>
+ <thead>
+ <tr>
+ <th> </th>
+ <th><?php echo _t ('main_stream'); ?></th>
+ <th><?php echo _t ('all_feeds'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?php echo _t ('status_total'); ?></th>
+ <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
+ <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo _t ('status_read'); ?></th>
+ <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
+ <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo _t ('status_unread'); ?></th>
+ <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
+ <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
+ </tr>
+ <tr>
+ <th><?php echo _t ('status_favorites'); ?></th>
+ <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
+ <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t ('stats_entry_per_day'); ?></h2>
+ <div id="statsEntryPerDay" style="height: 300px"></div>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t ('stats_feed_per_category'); ?></h2>
+ <div id="statsFeedPerCategory" style="height: 300px"></div>
+ <div id="statsFeedPerCategoryLegend"></div>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t ('stats_entry_per_category'); ?></h2>
+ <div id="statsEntryPerCategory" style="height: 300px"></div>
+ <div id="statsEntryPerCategoryLegend"></div>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t ('stats_top_feed'); ?></h2>
+ <table>
+ <thead>
+ <tr>
+ <th><?php echo _t ('feed'); ?></th>
+ <th><?php echo _t ('category'); ?></th>
+ <th><?php echo _t ('stats_entry_count'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->topFeed as $feed): ?>
+ <tr>
+ <td><?php echo $feed['name']; ?></td>
+ <td><?php echo $feed['category']; ?></td>
+ <td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
+ </tr>
+ <?php endforeach;?>
+ </tbody>
+ </table>
+ </div>
+</div>
+
+<script>
+"use strict";
+function initStats() {
+ if (!window.Flotr) {
+ if (window.console) {
+ console.log('FreshRSS waiting for Flotr…');
+ }
+ window.setTimeout(initStats, 50);
+ return;
+ }
+ // Entry per day
+ Flotr.draw(document.getElementById('statsEntryPerDay'),
+ [<?php echo $this->count ?>],
+ {
+ grid: {verticalLines: false},
+ bars: {horizontal: false, show: true},
+ xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
+ yaxis: {min: 0},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+ });
+ // Feed per category
+ Flotr.draw(document.getElementById('statsFeedPerCategory'),
+ <?php echo $this->feedByCategory ?>,
+ {
+ grid: {verticalLines: false, horizontalLines: false},
+ pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
+ xaxis: {showLabels: false},
+ yaxis: {showLabels: false},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
+ legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
+ });
+ // Entry per category
+ Flotr.draw(document.getElementById('statsEntryPerCategory'),
+ <?php echo $this->entryByCategory ?>,
+ {
+ grid: {verticalLines: false, horizontalLines: false},
+ pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
+ xaxis: {showLabels: false},
+ yaxis: {showLabels: false},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
+ legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
+ });
+}
+initStats();
+</script>
diff --git a/app/views/index/stats.phtml b/app/views/stats/main.phtml
index b5c18813d..fe372e221 100644
--- a/app/views/index/stats.phtml
+++ b/app/views/stats/main.phtml
@@ -1,9 +1,11 @@
+<?php $this->partial('aside_stats'); ?>
+
<div class="post content">
- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
-
- <h1><?php echo Minz_Translate::t ('stats'); ?></h1>
-
- <div class="stat">
+ <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
+
+ <h1><?php echo Minz_Translate::t ('stats_main'); ?></h1>
+
+ <div class="stat">
<h2><?php echo Minz_Translate::t ('stats_entry_repartition'); ?></h2>
<table>
<thead>
diff --git a/data/do-install.txt b/data/do-install.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/data/do-install.txt
diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php
index 16c8eb727..4e9da58b4 100644
--- a/lib/Minz/Configuration.php
+++ b/lib/Minz/Configuration.php
@@ -297,41 +297,61 @@ class Minz_Configuration {
// Base de données
if (isset ($ini_array['db'])) {
$db = $ini_array['db'];
- if (empty($db['host'])) {
+ if (empty($db['type'])) {
throw new Minz_BadConfigurationException (
- 'host',
+ 'type',
Minz_Exception::ERROR
);
}
- if (empty($db['user'])) {
- throw new Minz_BadConfigurationException (
- 'user',
- Minz_Exception::ERROR
- );
- }
- if (!isset ($db['password'])) {
- throw new Minz_BadConfigurationException (
- 'password',
- Minz_Exception::ERROR
- );
- }
- if (empty($db['base'])) {
- throw new Minz_BadConfigurationException (
- 'base',
- Minz_Exception::ERROR
- );
- }
-
- if (!empty($db['type'])) {
- self::$db['type'] = $db['type'];
- }
- self::$db['host'] = $db['host'];
- self::$db['user'] = $db['user'];
- self::$db['password'] = $db['password'];
- self::$db['base'] = $db['base'];
- if (isset($db['prefix'])) {
- self::$db['prefix'] = $db['prefix'];
+ switch ($db['type']) {
+ case 'mysql':
+ if (empty($db['host'])) {
+ throw new Minz_BadConfigurationException (
+ 'host',
+ Minz_Exception::ERROR
+ );
+ }
+ if (empty($db['user'])) {
+ throw new Minz_BadConfigurationException (
+ 'user',
+ Minz_Exception::ERROR
+ );
+ }
+ if (!isset($db['password'])) {
+ throw new Minz_BadConfigurationException (
+ 'password',
+ Minz_Exception::ERROR
+ );
+ }
+ if (empty($db['base'])) {
+ throw new Minz_BadConfigurationException (
+ 'base',
+ Minz_Exception::ERROR
+ );
+ }
+ self::$db['host'] = $db['host'];
+ self::$db['user'] = $db['user'];
+ self::$db['password'] = $db['password'];
+ self::$db['base'] = $db['base'];
+ if (isset($db['prefix'])) {
+ self::$db['prefix'] = $db['prefix'];
+ }
+ break;
+ case 'sqlite':
+ self::$db['host'] = '';
+ self::$db['user'] = '';
+ self::$db['password'] = '';
+ self::$db['base'] = '';
+ self::$db['prefix'] = '';
+ break;
+ default:
+ throw new Minz_BadConfigurationException (
+ 'type',
+ Minz_Exception::ERROR
+ );
+ break;
}
+ self::$db['type'] = $db['type'];
}
}
diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php
index 831df13a2..1f56f09c2 100644
--- a/lib/Minz/ModelPdo.php
+++ b/lib/Minz/ModelPdo.php
@@ -16,6 +16,7 @@ class Minz_ModelPdo {
public static $useSharedBd = true;
private static $sharedBd = null;
private static $sharedPrefix;
+ protected static $sharedDbType;
/**
* $bd variable représentant la base de données
@@ -24,46 +25,57 @@ class Minz_ModelPdo {
protected $prefix;
+ public function dbType() {
+ return self::$sharedDbType;
+ }
+
/**
* Créé la connexion à la base de données à l'aide des variables
* HOST, BASE, USER et PASS définies dans le fichier de configuration
*/
- public function __construct () {
+ public function __construct() {
if (self::$useSharedBd && self::$sharedBd != null) {
$this->bd = self::$sharedBd;
$this->prefix = self::$sharedPrefix;
return;
}
- $db = Minz_Configuration::dataBase ();
- $driver_options = null;
+ $db = Minz_Configuration::dataBase();
try {
$type = $db['type'];
- if($type == 'mysql') {
- $string = $type
- . ':host=' . $db['host']
+ if ($type === 'mysql') {
+ $string = 'mysql:host=' . $db['host']
. ';dbname=' . $db['base']
. ';charset=utf8';
$driver_options = array(
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
+ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
+ );
+ $this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_';
+ } elseif ($type === 'sqlite') {
+ $string = 'sqlite:' . DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite';
+ $driver_options = array(
+ //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ );
+ $this->prefix = '';
+ } else {
+ throw new Minz_PDOConnectionException(
+ 'Invalid database type!',
+ $db['user'], Minz_Exception::ERROR
);
- } elseif($type == 'sqlite') {
- $string = $type . ':/' . DATA_PATH . $db['base'] . '.sqlite'; //TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797
}
+ self::$sharedDbType = $type;
+ self::$sharedPrefix = $this->prefix;
- $this->bd = new FreshPDO (
+ $this->bd = new FreshPDO(
$string,
$db['user'],
$db['password'],
$driver_options
);
self::$sharedBd = $this->bd;
-
- $this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_';
- self::$sharedPrefix = $this->prefix;
} catch (Exception $e) {
- throw new Minz_PDOConnectionException (
+ throw new Minz_PDOConnectionException(
$string,
$db['user'], Minz_Exception::ERROR
);
@@ -80,20 +92,6 @@ class Minz_ModelPdo {
$this->bd->rollBack();
}
- public function size($all = false) {
- $db = Minz_Configuration::dataBase ();
- $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = ?';
- $values = array ($db['base']);
- if (!$all) {
- $sql .= ' AND table_name LIKE ?';
- $values[] = $this->prefix . '%';
- }
- $stm = $this->bd->prepare ($sql);
- $stm->execute ($values);
- $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return $res[0];
- }
-
public static function clean() {
self::$sharedBd = null;
self::$sharedPrefix = '';
@@ -107,12 +105,12 @@ class FreshPDO extends PDO {
}
}
- public function prepare ($statement, $driver_options = array()) {
+ public function prepare($statement, $driver_options = array()) {
FreshPDO::check($statement);
return parent::prepare($statement, $driver_options);
}
- public function exec ($statement) {
+ public function exec($statement) {
FreshPDO::check($statement);
return parent::exec($statement);
}
diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php
index 7e3c59990..755784522 100644
--- a/lib/Minz/Request.php
+++ b/lib/Minz/Request.php
@@ -28,6 +28,9 @@ class Minz_Request {
return self::$params;
}
static function htmlspecialchars_utf8 ($p) {
+ if (is_array($p)) {
+ return array_map('self::htmlspecialchars_utf8', $p);
+ }
return htmlspecialchars($p, ENT_COMPAT, 'UTF-8');
}
public static function param ($key, $default = false, $specialchars = false) {
@@ -35,8 +38,6 @@ class Minz_Request {
$p = self::$params[$key];
if(is_object($p) || $specialchars) {
return $p;
- } elseif(is_array($p)) {
- return array_map('self::htmlspecialchars_utf8', $p);
} else {
return self::htmlspecialchars_utf8($p);
}
diff --git a/lib/Minz/Translate.php b/lib/Minz/Translate.php
index e14f783f7..df48350e9 100644
--- a/lib/Minz/Translate.php
+++ b/lib/Minz/Translate.php
@@ -18,28 +18,28 @@ class Minz_Translate {
* $translates est le tableau de correspondance
* $key => $traduction
*/
- private static $translates = array ();
+ private static $translates = array();
/**
* Inclus le fichier de langue qui va bien
* l'enregistre dans $translates
*/
- public static function init () {
- $l = Minz_Configuration::language ();
- self::$language = Minz_Session::param ('language', $l);
+ public static function init() {
+ $l = Minz_Configuration::language();
+ self::$language = Minz_Session::param('language', $l);
$l_path = APP_PATH . '/i18n/' . self::$language . '.php';
- if (file_exists ($l_path)) {
- self::$translates = include ($l_path);
+ if (file_exists($l_path)) {
+ self::$translates = include($l_path);
}
}
/**
* Alias de init
*/
- public static function reset () {
- self::init ();
+ public static function reset() {
+ self::init();
}
/**
@@ -48,24 +48,32 @@ class Minz_Translate {
* @return la valeur correspondante à la clé
* > si non présente dans le tableau, on retourne la clé elle-même
*/
- public static function t ($key) {
+ public static function t($key) {
$translate = $key;
- if (isset (self::$translates[$key])) {
+ if (isset(self::$translates[$key])) {
$translate = self::$translates[$key];
}
- $args = func_get_args ();
+ $args = func_get_args();
unset($args[0]);
- return vsprintf ($translate, $args);
+ return vsprintf($translate, $args);
}
/**
* Retourne la langue utilisée actuellement
* @return la langue
*/
- public static function language () {
+ public static function language() {
return self::$language;
}
}
+
+function _t($key) {
+ $args = func_get_args();
+ unset($args[0]);
+ array_unshift($args, $key);
+
+ return call_user_func_array("Minz_Translate::t", $args);
+}
diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php
index 685fe1cc0..06c100f59 100644
--- a/lib/SimplePie/SimplePie.php
+++ b/lib/SimplePie/SimplePie.php
@@ -446,6 +446,13 @@ class SimplePie
public $feed_url;
/**
+ * @var string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently
+ * @see SimplePie::subscribe_url()
+ * @access private
+ */
+ public $permanent_url = null; //FreshRSS
+
+ /**
* @var object Instance of SimplePie_File to use as a feed
* @see SimplePie::set_file()
* @access private
@@ -735,6 +742,7 @@ class SimplePie
else
{
$this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1));
+ $this->permanent_url = $this->feed_url; //FreshRSS
}
}
@@ -749,6 +757,7 @@ class SimplePie
if ($file instanceof SimplePie_File)
{
$this->feed_url = $file->url;
+ $this->permanent_url = $this->feed_url; //FreshRSS
$this->file =& $file;
return true;
}
@@ -1602,7 +1611,7 @@ class SimplePie
}
$this->raw_data = $file->body;
-
+ $this->permanent_url = $file->permanent_url; //FreshRSS
$headers = $file->headers;
$sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file));
$sniffed = $sniffer->get_type();
@@ -1788,26 +1797,39 @@ class SimplePie
/**
* Get the URL for the feed
+ *
+ * When the 'permanent' mode is enabled, returns the original feed URL,
+ * except in the case of an `HTTP 301 Moved Permanently` status response,
+ * in which case the location of the first redirection is returned.
*
- * May or may not be different from the URL passed to {@see set_feed_url()},
+ * When the 'permanent' mode is disabled (default),
+ * may or may not be different from the URL passed to {@see set_feed_url()},
* depending on whether auto-discovery was used.
*
* @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.)
- * @todo If we have a perm redirect we should return the new URL
- * @todo When we make the above change, let's support <itunes:new-feed-url> as well
+ * @todo Support <itunes:new-feed-url>
* @todo Also, |atom:link|@rel=self
+ * @param bool $permanent Permanent mode to return only the original URL or the first redirection
+ * iff it is a 301 redirection
* @return string|null
*/
- public function subscribe_url()
+ public function subscribe_url($permanent = false)
{
- if ($this->feed_url !== null)
+ if ($permanent) //FreshRSS
{
- return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI);
+ if ($this->permanent_url !== null)
+ {
+ return $this->sanitize($this->permanent_url, SIMPLEPIE_CONSTRUCT_IRI);
+ }
}
else
{
- return null;
+ if ($this->feed_url !== null)
+ {
+ return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI);
+ }
}
+ return null;
}
/**
diff --git a/lib/SimplePie/SimplePie/File.php b/lib/SimplePie/SimplePie/File.php
index faf5dd1f1..b1bbe4420 100644
--- a/lib/SimplePie/SimplePie/File.php
+++ b/lib/SimplePie/SimplePie/File.php
@@ -64,6 +64,7 @@ class SimplePie_File
var $redirects = 0;
var $error;
var $method = SIMPLEPIE_FILE_SOURCE_NONE;
+ var $permanent_url; //FreshRSS
public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false)
{
@@ -74,6 +75,7 @@ class SimplePie_File
$url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
}
$this->url = $url;
+ $this->permanent_url = $url; //FreshRSS
$this->useragent = $useragent;
if (preg_match('/^http(s)?:\/\//i', $url))
{
@@ -142,7 +144,10 @@ class SimplePie_File
{
$this->redirects++;
$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
- return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+ $previousStatusCode = $this->status_code;
+ $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+ $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS
+ return;
}
}
}
@@ -224,7 +229,10 @@ class SimplePie_File
{
$this->redirects++;
$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
- return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+ $previousStatusCode = $this->status_code;
+ $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+ $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS
+ return;
}
if (isset($this->headers['content-encoding']))
{
diff --git a/lib/lib_rss.php b/lib/lib_rss.php
index 0f8161129..7ca611b04 100644
--- a/lib/lib_rss.php
+++ b/lib/lib_rss.php
@@ -56,12 +56,6 @@ function checkUrl($url) {
}
}
-// tiré de Shaarli de Seb Sauvage //Format RFC 4648 base64url
-function small_hash ($txt) {
- $t = rtrim (base64_encode (hash ('crc32', $txt, true)), '=');
- return strtr ($t, '+/', '-_');
-}
-
function formatNumber($n, $precision = 0) {
return str_replace(' ', ' ', //Espace insécable //TODO: remplacer par une espace _fine_ insécable
number_format($n, $precision, '.', ' ')); //number_format does not seem to be Unicode-compatible
@@ -115,7 +109,7 @@ function customSimplePie() {
$simplePie = new SimplePie();
$simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
$simplePie->set_cache_location(CACHE_PATH);
- $simplePie->set_cache_duration(1500);
+ $simplePie->set_cache_duration(800);
$simplePie->strip_htmltags(array(
'base', 'blink', 'body', 'doctype', 'embed',
'font', 'form', 'frame', 'frameset', 'html',
diff --git a/p/api/greader.php b/p/api/greader.php
index 8a8623966..7a961225f 100644
--- a/p/api/greader.php
+++ b/p/api/greader.php
@@ -327,7 +327,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
logMe("streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation)\n");
header('Content-Type: application/json; charset=UTF-8');
- $feedDAO = new FreshRSS_FeedDAO();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
$arrayFeedCategoryNames = $feedDAO->arrayFeedCategoryNames();
switch ($path) {
@@ -364,7 +364,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
$count++; //Shift by one element
}
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, '', $start_time);
$items = array();
@@ -458,7 +458,7 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
break;
}
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
$ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time);
$itemRefs = array();
@@ -481,7 +481,7 @@ function editTag($e_ids, $a, $r) {
$e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/'
}
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
switch ($a) {
case 'user/-/state/com.google/read':
@@ -512,13 +512,15 @@ function editTag($e_ids, $a, $r) {
function markAllAsRead($streamId, $olderThanId) {
logMe("markAllAsRead($streamId, $olderThanId)\n");
- $entryDAO = new FreshRSS_EntryDAO();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
if (strpos($streamId, 'feed/') === 0) {
$f_id = basename($streamId);
$entryDAO->markReadFeed($f_id, $olderThanId);
} elseif (strpos($streamId, 'user/-/label/') === 0) {
$c_name = basename($streamId);
- $entryDAO->markReadCatName($c_name, $olderThanId);
+ $categoryDAO = new FreshRSS_CategoryDAO();
+ $cat = $categoryDAO->searchByName($c_name);
+ $entryDAO->markReadCat($cat === null ? -1 : $cat->id(), $olderThanId);
} elseif ($streamId === 'user/-/state/com.google/reading-list') {
$entryDAO->markReadEntries($olderThanId, false, -1);
}
diff --git a/p/i/index.php b/p/i/index.php
index 4afffb3dd..7b34eefd1 100755
--- a/p/i/index.php
+++ b/p/i/index.php
@@ -18,12 +18,12 @@
#
# ***** END LICENSE BLOCK *****
-if (file_exists ('install.php')) {
- require('install.php');
-} else {
- require('../../constants.php');
- require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
+require('../../constants.php');
+require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
+if (file_exists(DATA_PATH . '/do-install.txt')) {
+ require(APP_PATH . '/install.php');
+} else {
session_cache_limiter('');
Minz_Session::init('FreshRSS');
Minz_Session::_param('keepAlive', 1); //For Persona
@@ -42,11 +42,11 @@ if (file_exists ('install.php')) {
try {
$front_controller = new FreshRSS();
- $front_controller->init ();
- $front_controller->run ();
+ $front_controller->init();
+ $front_controller->run();
} catch (Exception $e) {
echo '### Fatal error! ###<br />', "\n";
- Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
+ Minz_Log::record($e->getMessage(), Minz_Log::ERROR);
echo 'See logs files.';
}
}
diff --git a/p/scripts/main.js b/p/scripts/main.js
index f1dd7117c..d0f3c27e9 100644
--- a/p/scripts/main.js
+++ b/p/scripts/main.js
@@ -1,7 +1,8 @@
"use strict";
var $stream = null,
isCollapsed = true,
- shares = 0;
+ shares = 0,
+ ajax_loading = false;
function is_normal_mode() {
return $stream.hasClass('normal');
@@ -54,9 +55,11 @@ function numberFormat(nStr) {
return x1 + x2;
}
-function incLabel(p, inc) {
+function incLabel(p, inc, spaceAfter) {
var i = str2int(p) + inc;
- return i > 0 ? ' (' + numberFormat(i) + ')' : '';
+ return i > 0
+ ? ((spaceAfter ? '' : ' ') + '(' + numberFormat(i) + ')' + (spaceAfter ? ' ' : ''))
+ : '';
}
function incUnreadsFeed(article, feed_id, nb) {
@@ -95,13 +98,16 @@ function incUnreadsFeed(article, feed_id, nb) {
var isCurrentView = false;
//Update unread: title
- document.title = document.title.replace(/((?: \([ 0-9]+\))?)( · .*?)((?: \([ 0-9]+\))?)$/, function (m, p1, p2, p3) {
+ document.title = document.title.replace(/^((?:\([ 0-9]+\) )?)(.*? · )((?:\([ 0-9]+\) )?)/, function (m, p1, p2, p3) {
var $feed = $('#' + feed_id);
if (article || ($feed.closest('.active').length > 0 && $feed.siblings('.active').length === 0)) {
isCurrentView = true;
- return incLabel(p1, nb) + p2 + incLabel(p3, feed_priority > 0 ? nb : 0);
+ return incLabel(p1, nb, true) + p2 + incLabel(p3, feed_priority > 0 ? nb : 0, true);
+ } else if ($('.all.active').length > 0) {
+ isCurrentView = feed_priority > 0;
+ return incLabel(p1, feed_priority > 0 ? nb : 0, true) + p2 + incLabel(p3, feed_priority > 0 ? nb : 0, true);
} else {
- return p1 + p2 + incLabel(p3, feed_priority > 0 ? nb : 0);
+ return p1 + p2 + incLabel(p3, feed_priority > 0 ? nb : 0, true);
}
});
return isCurrentView;
@@ -190,7 +196,7 @@ function mark_favorite(active) {
var favourites = $('.favorites>a').contents().last().get(0);
if (favourites && favourites.textContent) {
favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) {
- return incLabel(p1, inc);
+ return incLabel(p1, inc, false);
});
}
@@ -258,7 +264,7 @@ function toggleContent(new_active, old_active) {
}
}
- if (auto_mark_article) {
+ if (auto_mark_article && new_active.hasClass('active')) {
mark_read(new_active, true);
}
}
@@ -448,6 +454,7 @@ function init_posts() {
load_more_posts();
}
});
+ box_to_follow.scroll();
}
}
@@ -497,7 +504,13 @@ function init_shortcuts() {
shortcut.add("shift+" + shortcuts.mark_read, function () {
// on marque tout comme lu
var url = $(".nav_menu a.read_all").attr("href");
- redirect(url, false);
+ if ($(".nav_menu a.read_all").hasClass('confirm')) {
+ if (confirm(str_confirmation)) {
+ redirect(url, false);
+ }
+ } else {
+ redirect(url, false);
+ }
}, {
'disable_in_input': true
});
@@ -683,14 +696,22 @@ function init_actualize() {
var auto = false;
$("#actualize").click(function () {
+ if (ajax_loading) {
+ return false;
+ }
+
+ ajax_loading = true;
+
$.getScript('./?c=javascript&a=actualize').done(function () {
if (auto && feed_count < 1) {
auto = false;
- return;
+ ajax_loading = false;
+ return false;
}
updateFeeds();
});
+
return false;
});
@@ -975,11 +996,6 @@ function init_print_action() {
function init_share_observers() {
shares = $('.form-group:not(".form-actions")').length;
- $('.post').on('click', '.share.remove', function(e) {
- e.preventDefault();
- $(this).parents('.form-group').remove();
- });
-
$('.share.add').on('click', function(e) {
var opt = $(this).siblings('select').find(':selected');
var row = $(this).parents('form').data(opt.data('form'));
@@ -994,6 +1010,19 @@ function init_share_observers() {
});
}
+function init_remove_observers() {
+ $('.post').on('click', 'a.remove', function(e) {
+ var remove_what = $(this).attr('data-remove');
+
+ if (remove_what !== undefined) {
+ var remove_obj = $('#' + remove_what);
+ remove_obj.remove();
+ }
+
+ return false;
+ });
+}
+
function init_feed_observers() {
$('select[id="category"]').on('change', function() {
var detail = $('#new_category_name').parent();
@@ -1054,6 +1083,7 @@ function init_all() {
window.setInterval(refreshUnreads, 120000);
} else {
init_share_observers();
+ init_remove_observers();
init_feed_observers();
init_password_observers();
}
diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css
new file mode 100644
index 000000000..5edc09042
--- /dev/null
+++ b/p/themes/Dark/dark.css
@@ -0,0 +1,985 @@
+@charset "UTF-8";
+
+/*=== FONTS */
+@font-face {
+ font-family: "OpenSans";
+ src: url("../fonts/openSans.woff") format("woff");
+}
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ height: 100%;
+ font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
+ background: #1c1c1c;
+ color: #888;
+}
+
+/*=== Links */
+a {
+ outline: none;
+ color: #6986B2;
+}
+
+/*=== Images */
+img.favicon {
+ background: #fff;
+ border-radius: 2px;
+}
+
+/*=== Forms */
+legend {
+ margin: 20px 0 5px;
+ padding: 5px 0;
+ font-size: 1.4em;
+ border-bottom: 1px solid #2f2f2f;
+}
+label {
+ min-height: 25px;
+ padding: 5px 0;
+ cursor: pointer;
+}
+textarea {
+ width: 360px;
+ height: 100px;
+}
+input, select, textarea {
+ min-height: 25px;
+ padding: 5px;
+ line-height: 25px;
+ vertical-align: middle;
+ background: #333;
+ border: 1px solid #000;
+ border-radius: 3px;
+ color: #999;
+ box-shadow: 0 2px 2px #1d1d1d inset;
+}
+option {
+ padding: 0 .5em;
+}
+input:focus, select:focus, textarea:focus {
+ color: #6986b2;
+ border-color: #2f2f2f;
+}
+input:invalid, select:invalid {
+ border-color: #f00;
+ box-shadow: 0 0 2px 1px #f00;
+}
+input:disabled, select:disabled {
+ background: #666;
+ color: #aaa;
+}
+input.extend {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+}
+
+/*=== Tables */
+table {
+ border-collapse: collapse;
+}
+
+tr, th, td {
+ padding: 0.5em;
+ border: 1px solid #333;
+}
+th {
+ background: #222;
+}
+form td,
+form th {
+ font-weight: normal;
+ text-align: center;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group.form-actions {
+ padding: 5px 0;
+ background: #1a1a1a;
+ border-top: 1px solid #2f2f2f;
+}
+.form-group.form-actions .btn {
+ margin: 0 10px;
+}
+.form-group .group-name {
+ padding: 10px 0;
+ text-align: right;
+}
+.form-group .group-controls {
+ min-height: 25px;
+ padding: 5px 0;
+}
+.form-group table {
+ margin: 10px 0 0 220px;
+}
+
+/*=== Buttons */
+.stick {
+ vertical-align: middle;
+ font-size: 0;
+}
+.stick input,
+.stick .btn {
+ border-radius: 0;
+}
+.stick .btn:first-child,
+.stick input:first-child {
+ border-radius: 3px 0 0 3px;
+}
+.stick .btn-important:first-child {
+ border-right: 1px solid #000;
+}
+.stick .btn:last-child,
+.stick input:last-child {
+ border-radius: 0 3px 3px 0;
+}
+.stick .btn + .btn,
+.stick .btn + input,
+.stick .btn + .dropdown > .btn,
+.stick input + .btn,
+.stick input + input,
+.stick input + .dropdown > .btn,
+.stick .dropdown + .btn,
+.stick .dropdown + input,
+.stick .dropdown + .dropdown > .btn {
+ border-left: none;
+}
+.stick input:focus+input {
+ border-left: 1px solid #000;
+}
+.stick input+input:focus {
+ border-left: 1px solid #333;
+}
+.stick .btn + .dropdown > .btn {
+ border-left: none;
+ border-radius: 0 3px 3px 0;
+}
+
+.btn {
+ display: inline-block;
+ min-height: 37px;
+ min-width: 15px;
+ margin: 0;
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ vertical-align: middle;
+ cursor: pointer;
+ overflow: hidden;
+ background: #111;
+ border-radius: 3px;
+ border: 1px solid #000;
+ color: #888;
+}
+a.btn {
+ min-height: 25px;
+ line-height: 25px;
+}
+.btn:hover {
+ text-decoration: none;
+ background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+}
+.btn.active,
+.dropdown-target:target ~ .btn.dropdown-toggle {
+ background: #333;
+}
+.btn:active {
+ background: #26303F;
+}
+
+.btn-important {
+ font-weight: normal;
+ background: #26303F;
+}
+.btn-important:hover {
+ background: linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+ background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
+}
+.btn-important:active {
+ background: #26303F;
+}
+
+.btn-attention {
+ background: #880011;
+}
+.btn-attention:hover {
+ background: linear-gradient(top, #cc0044 0%, #880011 100%);
+ background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%);
+ background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%);
+ background: -o-linear-gradient(top, #cc0044 0%, #880011 100%);
+ background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%);
+}
+.btn-attention:active {
+ background: #880011;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ height: 2.5em;
+ line-height: 2.5em;
+ font-size: 0.9rem;
+}
+.nav-list .item:hover {
+ background: #26303F;
+}
+.nav-list .item.active {
+ background: #333;
+}
+.nav-list .item:hover a,
+.nav-list .item.active a {
+ color: #888;
+}
+.nav-list .disable {
+ text-align: center;
+ color: #aaa;
+ background: #fafafa;
+}
+.nav-list .item > a {
+ padding: 0 10px;
+}
+.nav-list a:hover {
+ text-decoration: none;
+}
+.nav-list .item.empty a {
+ color: #c95;
+}
+.nav-list .item:hover.empty a,
+.nav-list .item.active.empty a {
+ color: #fff;
+ background: #c95;
+}
+.nav-list .item.error a {
+ color: #a44;
+}
+.nav-list .item:hover.error a,
+.nav-list .item.active.error a {
+ color: #fff;
+ background: #a44;
+}
+
+.nav-list .nav-header {
+ padding: 0 10px;
+ font-weight: bold;
+ background: #111;
+ border-bottom: 1px solid #333;
+}
+
+.nav-list .nav-form {
+ padding: 3px;
+ text-align: center;
+}
+
+.nav-head {
+ margin: 0;
+ text-align: right;
+ background: #1c1c1c;
+ border-bottom: 1px solid #333;
+}
+.nav-head .item {
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ line-height: 1.5rem;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ margin: 0;
+ padding: 0;
+}
+.horizontal-list .item {
+ vertical-align: middle;
+}
+
+/*=== Dropdown */
+.dropdown-menu {
+ margin: 5px 0 0;
+ padding: 5px 0;
+ font-size: 0.8rem;
+ text-align: left;
+ background: #1a1a1a;
+ border: 1px solid #888;
+ border-radius: 5px;
+}
+.dropdown-menu:after {
+ content: "";
+ position: absolute;
+ top: -6px;
+ right: 13px;
+ width: 10px;
+ height: 10px;
+ z-index: -10;
+ transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ background: #1a1a1a;
+ border-top: 1px solid #888;
+ border-left: 1px solid #888;
+}
+.dropdown-header {
+ padding: 0 5px 5px;
+ font-weight: bold;
+ text-align: left;
+ color: #888;
+}
+.dropdown-menu > .item > a {
+ padding: 0 25px;
+ line-height: 2.5em;
+}
+.dropdown-menu > .item > span {
+ padding: 0 25px;
+ line-height: 2em;
+}
+.dropdown-menu > .item:hover {
+ background: #26303F;
+ color: #888;
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ font-weight: bold;
+ margin: 0 0 0 -14px;
+}
+.dropdown-menu > .item:hover > a {
+ text-decoration: none;
+ color: #888;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ margin: 0 auto 5px;
+ padding: 2px 5px;
+ border-radius: 3px;
+}
+
+.separator {
+ margin: 5px 0;
+ border-bottom: 1px solid #333;
+}
+
+/*=== Alerts */
+.alert {
+ margin: 15px auto;
+ padding: 10px 15px;
+ font-size: 0.9em;
+ background: #111;
+ border: 1px solid #888;
+ border-radius: 5px;
+ color: #aaa;
+}
+.alert-head {
+ font-size: 1.15em;
+}
+.alert > a {
+ text-decoration: underline;
+ color: inherit;
+}
+.alert-warn {
+ border: 1px solid #c95;
+ color: #c95;
+}
+.alert-success {
+ border: 1px solid #484;
+ color: #484;
+}
+.alert-error {
+ border: 1px solid #a44;
+ color: #a44;
+}
+
+/*=== Pagination */
+.pagination {
+ text-align: center;
+ font-size: 0.8em;
+ background: #1c1c1c;
+ color: #888;
+}
+.content .pagination {
+ margin: 0;
+ padding: 0;
+}
+.pagination .item.pager-current {
+ font-weight: bold;
+ font-size: 1.5em;
+ background: #111;
+}
+.pagination .item a {
+ display: block;
+ font-style: italic;
+ line-height: 3em;
+ text-decoration: none;
+ color: #666;
+}
+.pagination .item a:hover {
+ background-color: #111;
+}
+.pagination:first-child .item {
+ border-bottom: 1px solid #333;
+}
+.pagination:last-child .item {
+ border-top: 1px solid #333;
+}
+
+.pagination .loading,
+.pagination a:hover.loading {
+ font-size: 0;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ height: 85px;
+}
+.header > .item {
+ padding: 10px;
+ vertical-align: middle;
+ text-align: center;
+ border-bottom: 1px solid #333;
+}
+.header > .item.title{
+ width: 230px;
+}
+.header > .item.title h1 {
+ margin: 0.5em 0;
+}
+.header > .item.title h1 a {
+ text-decoration: none;
+}
+.header > .item.search input {
+ width: 230px;
+}
+.header .item.search input:focus {
+ width: 350px;
+}
+
+/*=== Body */
+#global {
+ height: calc(100% - 85px);
+}
+.aside {
+ border-right: 1px solid #333;
+ background: #1c1c1c;
+}
+.aside.aside_flux {
+ padding: 10px 0 50px;
+ border-right: 1px solid #333;
+ background: #1c1c1c;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ text-align: center;
+}
+.category {
+ width: 235px;
+ margin: 10px auto;
+ text-align: left;
+}
+.category .btn:first-child {
+ position: relative;
+ width: 213px;
+}
+.category.stick .btn:first-child {
+ width: 176px;
+}
+.category .btn:first-child:not([data-unread="0"]):after {
+ position: absolute;
+ top: 3px; right: 3px;
+ padding: 1px 5px;
+ background: #111;
+ color: #888;
+ border: 1px solid #000;
+ border-radius: 5px;
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds .item.active {
+ background: #333;
+}
+.categories .feeds .item.active .feed {
+ color: #888;
+}
+.categories .feeds .item.empty .feed {
+ color: #c95;
+}
+.categories .feeds .item.empty.active {
+ background: #c95;
+}
+.categories .feeds .item.empty.active .feed {
+ color: #fff;
+}
+.categories .feeds .item.error .feed {
+ color: #a44;
+}
+.categories .feeds .item.error.active {
+ background: #a44;
+}
+.categories .feeds .item.error.active .feed {
+ color: #fff;
+}
+.categories .feeds .item .feed {
+ margin: 0;
+ width: 165px;
+ line-height: 3em;
+ font-size: 0.8em;
+ text-align: left;
+ text-decoration: none;
+}
+.categories .feeds .feed:not([data-unread="0"]) {
+ font-weight: bold;
+}
+.categories .feeds .dropdown-menu:after {
+ left: 2px;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ vertical-align: middle;
+ background-color: #111;
+ border-radius: 3px;
+}
+
+/*=== Configuration pages */
+.post {
+ padding: 10px 50px;
+ font-size: 0.9em;
+}
+.post form {
+ margin: 10px 0;
+}
+.post.content {
+ max-width: 550px;
+}
+
+/*=== Prompt (centered) */
+.prompt {
+ text-align: center;
+}
+.prompt label {
+ text-align: left;
+}
+.prompt form {
+ margin: 10px auto 20px auto;
+ width: 180px;
+}
+.prompt input {
+ margin: 5px auto;
+ width: 100%;
+}
+.prompt p {
+ margin: 20px 0;
+}
+
+/*=== New article notification */
+#new-article {
+ text-align: center;
+ font-size: 0.9em;
+ background: #26303F;
+}
+#new-article:hover {
+ background: #4A5D7A;
+}
+#new-article > a {
+ line-height: 3em;
+ font-weight: bold;
+ color: #fff;
+}
+#new-article > a:hover {
+ text-decoration: none;
+}
+
+/*=== Day indication */
+.day {
+ padding: 0 10px;
+ font-weight: bold;
+ line-height: 3em;
+ border-top: 1px solid #333;
+ border-bottom: 1px solid #333;
+}
+.day .name {
+ padding: 0 10px 0 0;
+ font-size: 1.8em;
+ opacity: 0.3;
+ font-style: italic;
+ text-align: right;
+ color: #aab;
+ text-shadow: 0px -1px 0px #333;
+}
+
+/*=== Index menu */
+.nav_menu {
+ text-align: center;
+ padding: 5px 0;
+ border-bottom: 1px solid #2f2f2f;
+}
+
+/*=== Feed articles */
+.flux {
+ border-left: 2px solid #2f2f2f;
+}
+.flux:hover {
+ background: #111;
+}
+.flux.current {
+ border-left: 2px solid #0062BE;
+ background: #111;
+}
+.flux.not_read {
+ border-left: 2px solid #FF5300;
+}
+.flux.favorite {
+ border-left: 2px solid #FFC300;
+}
+
+
+.flux_header {
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+.flux_header .title {
+ font-size: 0.9rem;
+}
+.flux_header .item.title a {
+ color: #888;
+}
+.flux .website .favicon {
+ margin: 5px;
+}
+.flux .date {
+ font-size: 0.7rem;
+ color: #666;
+}
+.flux:not(.current):hover .item.title {
+ background: #111;
+}
+
+.flux .bottom {
+ font-size: 0.8rem;
+ text-align: center;
+}
+
+/*=== Content of feed articles */
+.content {
+ padding: 20px 10px;
+}
+.content > h1.title > a {
+ color: #888;
+}
+
+.content hr {
+ margin: 30px 10px;
+ height: 1px;
+ background: #666;
+ border: 0;
+ box-shadow: 0 2px 5px #666;
+}
+
+.content pre {
+ margin: 10px auto;
+ padding: 10px 20px;
+ overflow: auto;
+ background: #222;
+ color: #fff;
+ border: 1px solid #000;
+ font-size: 0.9rem;
+ border-radius: 3px;
+}
+.content code {
+ padding: 2px 5px;
+ color: #dd1144;
+ background: #000;
+ border: 1px solid #333;
+ border-radius: 3px;
+}
+.content pre code {
+ background: transparent;
+ color: #fff;
+ border: none;
+}
+
+.content blockquote {
+ display: block;
+ margin: 0;
+ padding: 5px 20px;
+ border-top: 1px solid #444;
+ border-bottom: 1px solid #444;
+ background: #222;
+ color: #999;
+}
+.content blockquote p {
+ margin: 0;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ padding: 0 0 0 5px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 0.9em;
+ line-height: 3em;
+ z-index: 10;
+ vertical-align: middle;
+ border-radius: 5px;
+ box-shadow: 0 0 5px #666;
+ background: #111;
+ color: #c95;
+ border: 1px solid #c95;
+}
+.notification.good {
+ border-color: #484;
+ color: #484;
+}
+.notification.bad {
+ border-color: #a44;
+ color: #a44;
+}
+.notification a.close {
+ padding: 0 15px;
+ line-height: 3em;
+}
+.notification a.close:hover {
+ background: #222;
+ border-radius: 0 3px 3px 0;
+}
+.notification.good a.close:hover {
+ background: #484;
+}
+.notification.bad a.close:hover {
+ background: #a44;
+}
+
+.notification#actualizeProgress {
+ line-height: 2em;
+}
+
+/*=== "Load more" part */
+#bigMarkAsRead {
+ text-align: center;
+ text-decoration: none;
+}
+#bigMarkAsRead:hover {
+ background: #111;
+ color: #aaa;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ margin: 0;
+ text-align: center;
+ line-height: 3em;
+ table-layout: fixed;
+ background: #111;
+ border-top: 1px solid #333;
+}
+
+/*=== READER VIEW */
+/*================*/
+#stream.reader .flux {
+ padding: 0 0 50px;
+ border: none;
+ background: #111;
+}
+#stream.reader .flux .author {
+ margin: 0 0 10px;
+ font-size: 90%;
+ color: #666;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+#stream.global .box-category {
+ text-align: left;
+ background: #1a1a1a;
+ border: 1px solid #000;
+ border-radius: 5px;
+ text-align: left;
+}
+#stream.global .category {
+ margin: 0;
+}
+#stream.global .btn {
+ width: auto;
+ height: 2em;
+ margin: 0;
+ padding: 0 10px;
+ line-height: 2em;
+ font-size: 1.2rem;
+ background: #26303F;
+ border: none;
+ border-bottom: 1px solid #000;
+ border-radius: 5px 5px 0 0;
+}
+#stream.global .btn:not([data-unread="0"]) {
+ font-weight: bold;
+ background: #34495e;
+ color: #fff;
+}
+#stream.global .btn:first-child:not([data-unread="0"]):after {
+ top: 0; right: 5px;
+ font-weight: bold;
+ border: 0;
+ background: none;
+ color: #fff;
+}
+#stream.global .box-category .feeds {
+ max-height: 250px;
+}
+#stream.global .box-category .feeds .item {
+ padding: 2px 10px;
+ font-size: 0.9rem;
+}
+
+/*=== Panel */
+#panel {
+ background: #1c1c1c;
+ border: 1px solid #666;
+ border-radius: 3px;
+}
+
+/*=== DIVERS */
+/*===========*/
+.aside.aside_feed .nav-form input,
+.aside.aside_feed .nav-form select {
+ width: 140px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu {
+ right: -20px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu:after {
+ right: 33px;
+}
+
+/*=== STATISTICS */
+/*===============*/
+.stat {
+ margin: 10px 0 20px;
+}
+
+.stat th,
+.stat td,
+.stat tr {
+ border: none;
+}
+.stat > table td,
+.stat > table th {
+ border-bottom: 1px solid #333;
+ text-align: center;
+}
+
+/*=== LOGS */
+/*=========*/
+.logs {
+ overflow: hidden;
+ border: 1px solid #333;
+}
+.log {
+ padding: 5px 10px;
+ font-size: 0.8rem;
+ background: #111;
+ color: #888;
+}
+.log+.log {
+ border-top: 1px solid #333;
+}
+.log .date {
+ display: block;
+ font-weight: bold;
+}
+.log.error {
+ background: #a44;
+ color: #fff;
+}
+.log.warning {
+ background: #c95;
+ color: #fff;
+}
+.log.notice {
+ background: #ec9;
+ color: #000;
+}
+.log.debug {
+ background: #111;
+ color: #eee;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .aside {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+ }
+ .aside .toggle_aside,
+ #panel .close {
+ position: absolute;
+ display: block;
+ top: 0; right: 0;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ background: #111;
+ border-left: 1px solid #333;
+ border-bottom: 1px solid #333;
+ border-radius: 0 0 0 5px;
+ }
+
+ .nav_menu .btn {
+ margin: 5px 10px;
+ }
+ .nav_menu .stick {
+ margin: 0 10px;
+ }
+ .nav_menu .stick .btn {
+ margin: 5px 0;
+ }
+ .nav_menu .search {
+ display: inline-block;
+ max-width: 97%;
+ }
+ .nav_menu .search input {
+ max-width: 97%;
+ width: 90px;
+ }
+ .nav_menu .search input:focus {
+ width: 400px;
+ }
+
+ .day .name {
+ font-size: 1.1rem;
+ }
+
+ .pagination {
+ margin: 0 0 3.5em;
+ }
+
+ .notification {
+ border-top: none;
+ border-right: none;
+ border-left: none;
+ border-radius: 0;
+ }
+ .notification a.close {
+ display: block;
+ left: 0;
+ }
+ .notification a.close:hover {
+ opacity: 0.5;
+ }
+ .notification a.close .icon {
+ display: none;
+ }
+}
diff --git a/p/themes/Dark/freshrss.css b/p/themes/Dark/freshrss.css
deleted file mode 100644
index aef9a7469..000000000
--- a/p/themes/Dark/freshrss.css
+++ /dev/null
@@ -1,926 +0,0 @@
-@charset "UTF-8";
-
-/* STRUCTURE */
-.header {
- display: table;
- width: 100%;
- background: #1c1c1c;
- table-layout: fixed;
-}
- .header > .item {
- display: table-cell;
- padding: 10px 0;
- border-bottom: 1px solid #2f2f2f;
- vertical-align: middle;
- text-align: center;
- }
- .header > .item.title {
- width: 250px;
- white-space: nowrap;
- }
- .logo {
- display: inline-block;
- font-size: 48px;
- height: 32px;
- width: 32px;
- padding: 10px;
- }
- .header > .item.title h1 {
- display: inline-block;
- margin: 0;
- }
- .header > .item.search input {
- width: 230px;
- }
- .header .item.search input:focus {
- width: 330px;
- }
- .header > .item.configure {
- width: 100px;
- }
-
-.item a:hover {
- text-decoration: none;
-}
-
-#global {
- display: table;
- width: 100%;
- height: 100%;
- background: #1c1c1c;
- table-layout: fixed;
-}
- .aside {
- display: table-cell;
- height: 100%;
- width: 250px;
- vertical-align: top;
- border-right: 1px solid #2f2f2f;
- background: #1c1c1c;
- }
- .aside .nav-form input {
- width: 180px;
- }
- .aside.aside_flux {
- padding: 10px 0 40px;
- }
- .aside.aside_feed .nav-form input {
- width: 140px;
- }
- .aside.aside_feed .nav-form .dropdown .dropdown-menu {
- right: -20px;
- }
- .aside.aside_feed .nav-form .dropdown .dropdown-menu:after {
- right: 33px;
- }
-
- .nav-login {
- display: none;
- }
-
- .nav_menu {
- width: 100%;
- background: #1c1c1c;
- border-bottom: 1px solid #2f2f2f;
- text-align: center;
- padding: 5px 0;
- }
- .nav_menu .search {
- display:none;
- }
-
-.favicon {
- height: 16px;
- width: 16px;
-}
-
-.categories {
- margin: 0;
- padding: 0;
- text-align: center;
- list-style: none;
-}
- .category {
- display: block;
- width: 220px;
- margin: 10px auto;
- text-align: left;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .category .btn:first-child {
- width: 195px;
- position: relative;
- }
- .category.stick .btn:first-child {
- width:160px;
- }
- .category .btn:first-child:not([data-unread="0"]):after {
- content: attr(data-unread);
- position: absolute;
- top: 3px; right: 3px;
- padding: 1px 5px;
- background: #1a1a1a;
- color: #888;
- font-size: 90%;
- border: 1px solid #000;
- border-radius: 5px;
- }
- .category + .feeds:not(.active) {
- display:none;
- }
- .categories .feeds {
- width: 100%;
- margin: 0;
- list-style: none;
- }
- .categories .feeds .item.active {
- background: #26303F;
- }
- .categories .feeds .item.active .feed {
- color: #888;
- }
- .categories .feeds .item.empty .feed {
- color: #e67e22;
- }
- .categories .feeds .item.empty.active {
- background: #e67e22;
- }
- .categories .feeds .item.empty.active .feed {
- color: #fff;
- }
- .categories .feeds .item.error .feed {
- color: #BD362F;
- }
- .categories .feeds .item .feed {
- display: inline-block;
- margin: 0;
- width: 165px;
- line-height: 35px;
- font-size: 90%;
- vertical-align: middle;
- text-align: left;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .feed:not([data-unread="0"]):before {
- content: "(" attr(data-unread) ") ";
- }
- .categories .feeds .dropdown-menu {
- left: 0;
- }
- .categories .feeds .dropdown-menu:after {
- left: 2px;
- }
- .categories .feeds .item .dropdown-toggle > .icon {
- visibility: hidden;
- cursor: pointer;
- }
- .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
- .categories .feeds .item:hover .dropdown-toggle > .icon,
- .categories .feeds .item.active .dropdown-toggle > .icon {
- background-color: #1c1c1c;
- border-radius: 3px;
- visibility: visible;
- }
-
-.post {
- padding: 10px 50px;
-}
- .post form {
- margin: 10px 0;
- }
-
-.day {
- min-height: 50px;
- padding: 0 10px;
- font-size: 130%;
- font-weight: bold;
- line-height: 50px;
- background: #1c1c1c;
- border-top: 1px solid #2f2f2f;
-}
- #new-article + .day {
- border-top: none;
- }
- .day .name {
- position: absolute;
- right: 0;
- width: 50%;
- height: 1.5em;
- padding: 0 10px 0 0;
- overflow: hidden;
- color: #aab;
- font-size: 1.8em;
- opacity: .3;
- text-shadow: 0px -1px 0px #333;
- font-style: italic;
- white-space: nowrap;
- text-overflow: ellipsis;
- text-align: right;
- }
-
-#new-article {
- display: none;
- min-height: 40px;
- background: #26303F;
- text-align: center;
-}
- #new-article:hover {
- background: #4A5D7A;
- }
- #new-article > a {
- display: block;
- line-height: 40px;
- color: #fff;
- font-weight: bold;
- }
- #new-article > a:hover {
- text-decoration: none;
- }
-
-.flux {
- border-left: 3px solid #2f2f2f;
- background: #1c1c1c;
-}
- .flux.not_read {
- border-left: 3px solid #FF5300;
- background: #1c1c1c;
- }
- .flux.favorite {
- border-left: 3px solid #FFC300;
- background: #1c1c1c;
- }
- .flux.current {
- border-left: 3px solid #0062BE;
- background: #1a1a1a;
- }
-
- .horizontal-list > .item:not(.title):not(.website) > a {
- display: block;
- }
-
- .flux_header {
- background: inherit;
- height: 25px;
- font-size: 12px;
- border-top: 1px solid #2f2f2f;
- cursor: pointer;
- }
- .flux .item {
- line-height: 40px;
- white-space: nowrap;
- }
- .flux_header > .item {
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .flux .item.manage {
- width: 40px;
- text-align: center;
- }
- .flux .item.website {
- width: 200px;
- }
- .website .favicon {
- padding: 5px;
- }
- .flux .item.title {
- background: inherit;
- }
- .flux .title a {
- color: #888;
- outline: none;
- }
- .flux.not_read .item.title,
- .flux.current .item.title {
- font-weight: bold;
- }
- .flux .item.date {
- width: 145px;
- padding:0 5px 0 0;
- text-align: right;
- font-size: 10px;
- color: #666;
- }
- .link {
- width: 40px;
- text-align: center;
- }
-
-#stream.reader .flux {
- padding: 0 0 30px;
- border: none;
- background: #1c1c1c;
- color: #888;
-}
- #stream.reader .flux .author {
- margin: 0 0 10px;
- font-size: 90%;
- color: #666;
- }
-
-#stream.global {
- text-align: center;
-}
- #stream.global .box-category {
- display: inline-block;
- width: 280px;
- margin: 20px 10px;
- vertical-align: top;
- background: #1a1a1a;
- border: 1px solid #000;
- border-radius: 5px;
- text-align: left;
- box-shadow: 0 0 5px #2f2f2f;
- }
- #stream.global .category {
- width: 100%;
- margin: 0;
- }
- #stream.global .btn {
- display: block;
- width: auto;
- height: 35px;
- margin: 0;
- padding: 0 10px;
- background: #26303F;
- border: none;
- border-bottom: 1px solid #2f2f2f;
- border-radius: 5px 5px 0 0;
- line-height: 35px;
- font-size: 120%;
- }
- #stream.global .btn:not([data-unread="0"]) {
- background: #34495e;
- color: #fff;
- font-weight: bold;
- }
- #stream.global .btn:first-child:not([data-unread="0"]):after {
- top: 0; right: 5px;
- border: 0;
- background: none;
- color: #fff;
- font-weight: bold;
- box-shadow: none;
- }
- #stream.global .box-category .feeds {
- display: block;
- max-height: 250px;
- margin: 0;
- list-style: none;
- overflow: auto;
- }
- #stream.global .box-category .feeds .item {
- padding: 2px 10px;
- font-size: 90%;
- }
- #stream.global .box-category .feed {
- width: 220px;
- }
-
-.content {
- min-height: 150px;
- margin: 0 auto;
- padding: 20px 10px;
- line-height: 170%;
- word-wrap: break-word;
-}
- .content.large {
- max-width: 1000px;
- }
- .content.medium {
- max-width: 800px;
- }
- .content.thin {
- max-width: 550px;
- }
- .content h1, .content h2, .content h3 {
- margin: 20px 0 5px;
- }
- .content > .title {
- font-size: x-large;
- margin: 0;
- }
-
- .content p {
- margin: 0 0 20px;
- }
- img.big {
- display: block;
- margin: 10px auto;
- }
- figure img.big {
- margin: 0;
- }
- .content hr {
- margin: 30px 0;
- height: 1px;
- background: #ddd;
- border: 0;
- }
- .content pre {
- margin: 10px auto;
- padding: 10px;
- overflow: auto;
- background: #000;
- color: #fff;
- font-size: 110%;
- }
- .content q, .content blockquote {
- display: block;
- margin: 5px 0;
- padding: 5px 20px;
- font-style: italic;
- border-left: 4px solid #ccc;
- color: #666;
- }
- .content blockquote p {
- margin: 0;
- }
-
-#panel {
- display: none;
- position: fixed;
- top: 10px; bottom: 10px;
- left: 100px; right: 100px;
- overflow: auto;
- background: #1c1c1c;
- border: 1px solid #95a5a6;
- border-radius: 5px;
-}
- #panel .close {
- position: fixed;
- top: 10px; right: 0;
- display: inline-block;
- width: 26px;
- height: 26px;
- margin: 0 10px 0 0;
- border: 1px solid #ccc;
- border-radius: 20px;
- text-align: center;
- line-height: 26px;
- background: #fff;
- }
-
-#overlay {
- display: none;
- position: fixed;
- top: 0; bottom: 0;
- left: 0; right: 0;
- background: rgba(0, 0, 0, 0.9);
-}
-
-.flux_content .bottom {
- font-size: 90%;
- text-align: center;
-}
-
-.hide_posts > :not(.active) > .flux_content {
- display:none;
-}
-
-/*** PAGINATION ***/
-.pagination {
- display: table;
- width: 100%;
- margin: 0;
- background: #1a1a1a;
- text-align: center;
- color: #888;
- font-size: 80%;
- line-height: 200%;
- table-layout: fixed;
-}
- .pagination .item {
- display: table-cell;
- line-height: 40px;
- }
- .pagination .item.pager-current {
- font-weight: bold;
- font-size: 140%;
- }
- .pagination .pager-first,
- .pagination .pager-previous,
- .pagination .pager-next,
- .pagination .pager-last {
- width: 100px;
- }
- .pagination .item a {
- display: block;
- color: #333;
- font-style: italic;
- }
- .pagination:first-child .item {
- border-bottom: 1px solid #2f2f2f;
- }
- .pagination:last-child .item {
- border-top: 1px solid #2f2f2f;
- }
-
-#nav_entries {
- display: table;
- width: 250px;
- height: 40px;
- position: fixed;
- bottom: 0;
- left: 0;
- margin: 0;
- background: #1c1c1c;
- border-top: 1px solid #2f2f2f;
- text-align: center;
- line-height: 40px;
- table-layout: fixed;
-}
- #nav_entries .item {
- display: table-cell;
- width: 30%;
- }
- #nav_entries a {
- display: block;
- }
- #nav_entries .i_up {
- margin: 5px 0 0;
- vertical-align: top;
- }
-
-.loading {
- background: url("loader.gif") center center no-repeat;
- font-size: 0;
-}
-
-#bigMarkAsRead {
- display: block;
- font-style: normal;
- padding: 32px 0 64px 0;
- text-align: center;
- text-decoration: none;
-}
- #bigMarkAsRead:hover {
- background: #1c1c1c;
- color: #888;
- }
- .bigTick {
- font-size: 72pt;
- line-height: 1.6em;
- }
-
-/*** NOTIFICATION ***/
-.notification {
- position: absolute;
- top: 10px;
- left: 25%; right: 25%;
- min-height: 30px;
- padding: 10px;
- line-height: 30px;
- text-align: center;
- border-radius: 5px;
- box-shadow: 0 0 5px #666;
- background: #1a1a1a;
- color: #888;
- border: 1px solid #f4f899;
- font-weight: bold;
- z-index: 10;
-}
- .notification.closed {
- display: none;
- }
- .notification a.close {
- position: absolute;
- top: -10px; right: -10px;
- display: inline-block;
- width: 16px;
- height: 16px;
- padding: 5px;
- background: #1a1a1a;
- border-radius: 50px;
- line-height: 16px;
- border:1px solid #f4f899;
- }
-
-.toggle_aside, .btn.toggle_aside {
- display: none;
-}
-
-#actualizeProgress {
- position: fixed;
-}
-#actualizeProgress progress {
- max-width: 100%;
- vertical-align: middle;
-}
-#actualizeProgress .progress {
- vertical-align: middle;
-}
-
-.logs {
- border: 1px solid #aaa;
-}
- .log {
- padding: 5px 2%;
- overflow: auto;
- background: #fafafa;
- border-bottom: 1px solid #999;
- color: #333;
- font-size: 90%;
- }
- .log .date {
- display: block;
- }
- .log.error {
- background: #fdd;
- color: #844;
- }
- .log.warning {
- background: #ffe;
- color: #c95;
- }
- .log.notice {
- background: #f4f4f4;
- color: #aaa;
- }
- .log.debug {
- background: #111;
- color: #eee;
- }
-
-.form-group table {
- border-collapse:collapse;
- margin:10px 0 0 220px;
- text-align:center;
-}
-
-.form-group tr, .form-group th, .form-group td {
- border:1px solid #2f2f2f;
- font-weight:normal;
- padding:.5em;
-}
-
-select.number option {
- text-align:right;
-}
-
-@media(min-width: 841px) {
- .flux:not(.current):hover .item.title {
- max-width: calc(100% - 580px);
- padding-right: 1.5em;
- position: absolute;
- }
-}
-
-@media(max-width: 840px) {
- .header,
- .aside .btn-important,
- .aside .feeds .dropdown,
- .flux_header .item.website span,
- .item.date {
- display: none;
- }
- .flux_header .item.website {
- width: 40px;
- text-align: center;
- }
- .flux_header .item.website .favicon {
- padding: 12px;
- }
-
- .nav-login {
- display: block;
- }
-
- .pagination {
- margin: 0 0 40px;
- }
- .pagination .pager-previous, .pagination .pager-next {
- width: 100px;
- }
-
- .toggle_aside, .btn.toggle_aside {
- display: inline-block;
- }
- .aside {
- position: fixed;
- top: 0; left: 0;
- width: 0;
- overflow: hidden;
- border-right: none;
- z-index: 10;
- transition: width 200ms linear;
- }
- .aside.aside_flux {
- padding: 10px 0 0;
- }
- .aside:target {
- width: 80%;
- border-right: 1px solid #aaa;
- overflow: auto;
- }
- .aside .toggle_aside {
- position: absolute;
- right: 0;
- display: inline-block;
- width: 26px;
- height: 26px;
- margin: 0 10px 0 0;
- border: 1px solid #ccc;
- border-radius: 20px;
- text-align: center;
- line-height: 26px;
- }
- .aside .categories {
- margin: 30px 0;
- }
-
- #nav_entries {
- width: 100%;
- }
-
- .nav_menu .btn {
- margin: 5px 10px;
- }
- .nav_menu .stick {
- margin: 0 10px;
- }
- .nav_menu .stick .btn {
- margin: 5px 0;
- }
- .nav_menu .search {
- display: inline-block;
- max-width: 97%;
- }
- .nav_menu .search input {
- max-width: 97%;
- width: 90px;
- }
- .nav_menu .search input:focus {
- width: 400px;
- }
-
- #panel {
- left: 5px; right: 5px;
- }
-
- .day .date {
- display: none;
- }
- .day .name {
- height: 2.6em;
- font-size: 1em;
- text-shadow: none;
- }
-
- .notification,
- #actualizeProgress {
- top: 0;
- left: 0;
- right: 0;
- border-radius: 0;
- border: none;
- border-bottom: 1px solid #f4f899;
- }
- .notification a.close {
- left: 0; right: 0;
- top: 0; bottom: 0;
- width: auto;
- height: auto;
- background: transparent;
- border: none;
- }
- .notification a.close .icon {
- display: none;
- }
-}
-
-/*** FALLBACK ***/
-.btn {
- background: #1c1c1c;
-}
- .btn:hover {
- background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- }
- .btn-important {
- background: #26303F;
- }
- .btn-important:hover {
- background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%);
- }
- .btn-attention {
- background: #880011;
- }
- .btn-attention:hover {
- background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%);
- background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%);
- background: -o-linear-gradient(top, #cc0044 0%, #880011 100%);
- background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%);
- }
-
-.dropdown-menu:after {
- -moz-transform: rotate(45deg);
- -webkit-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
-}
-
-.nav-head {
- background: #1c1c1c;
-}
-
-input.extend {
- -moz-transition: width 200ms linear;
- -webkit-transition: width 200ms linear;
- -o-transition: width 200ms linear;
- -ms-transition: width 200ms linear;
-}
-
-@media(max-width: 840px) {
- .aside {
- -moz-transition: width 200ms linear;
- -webkit-transition: width 200ms linear;
- -o-transition: width 200ms linear;
- -ms-transition: width 200ms linear;
- }
-}
-
-@media print {
- .header,
- .aside,
- .nav_menu,
- .day,
- .flux_header,
- .flux_content .bottom,
- .pagination {
- display: none;
- }
-
- html, body {
- background: #fff;
- color: #000;
- font-family: Serif;
- font-size: 12pt;
- }
-
- #global,
- .flux_content {
- display: block !important;
- }
-
- .flux_content .content {
- width: 100% !important;
- text-align: justify;
- }
-
- .flux_content .content a {
- color: #000;
- }
- .flux_content .content a:after {
- content: " (" attr(href) ") ";
- text-decoration: underline;
- }
-}
-
-.stat {
- border:1px solid #2f2f2f;
- border-radius:10px;
- margin:10px 0;
- padding:0 5px;
-}
-.stat > h2 {
- border-bottom:1px solid #2f2f2f;
- margin:0 -5px;
- padding-left:5px;
-}
-.stat > div {
- margin:5px 0;
-}
-.stat > table {
- border-collapse:collapse;
- margin:5px 0;
- width:100%;
-}
-.stat > table > thead > tr {
- border-bottom:2px solid #2f2f2f;
-}
-.stat > table > tbody > tr {
- border-bottom:1px solid #2f2f2f;
-}
-.stat > table > tbody > tr:last-child {
- border-bottom:0;
-}
-.stat > table th, .stat > table td {
- border-left:2px solid #2f2f2f;
- padding:5px;
-}
-.stat > table th:first-child, .stat > table td:first-child {
- border-left:0;
-}
-.stat > table td.numeric{
- margin:5px 0;
- text-align:center;
-}
diff --git a/p/themes/Dark/global.css b/p/themes/Dark/global.css
deleted file mode 100644
index 0f1baa216..000000000
--- a/p/themes/Dark/global.css
+++ /dev/null
@@ -1,525 +0,0 @@
-@charset "UTF-8";
-
-/* FONTS */
-@font-face {
- font-family: "OpenSans";
- src: url("../fonts/openSans.woff") format("woff");
-}
-
-
-* {
- margin: 0;
- padding: 0;
-}
-html, body {
- height: 100%;
- font-size: 95%;
- font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
- color: #888;
-}
-
-/* LIENS */
-a {
- color: #6986B2;
- text-decoration: none;
-}
- a:hover {
- text-decoration: underline;
- }
-
-/* LISTES */
-ul, ol, dl {
- margin: 10px 0 10px 30px;
- line-height: 190%;
-}
- dd {
- margin: 0 0 10px 30px;
- }
-
-/* TITRES */
-h1, h2, h3 {
- min-height: 40px;
- margin: 15px 0 5px;
- line-height: 40px;
-}
-
-/* IMG */
-figure {
- margin: 5px 0 10px;
- text-align: center;
-}
- figcaption {
- display: inline-block;
- padding: 3px 20px;
- color: #999;
- font-style: italic;
- border-bottom: 1px solid #ccc;
- }
-img {
- height: auto;
- max-width: 100%;
- vertical-align: middle;
-}
- a img {
- border: none;
- }
-
-/* VIDEOS */
-iframe, embed, object, video {
- max-width: 100%;
-}
-
-/* FORMULAIRES */
-legend {
- display: block;
- width: 100%;
- margin: 20px 0 5px;
- padding: 5px 0;
- border-bottom: 1px solid #2f2f2f;
- font-size: 150%;
- clear: both;
-}
-label {
- display: block;
- min-height: 25px;
- padding: 5px 0;
- font-size: 14px;
- line-height: 25px;
- cursor: pointer;
-}
-input {
- width: 180px;
-}
-textarea {
- width: 360px;
- height: 100px;
-}
-input, select, textarea {
- display: inline-block;
- max-width: 100%;
- min-height: 25px;
- padding: 5px;
- background: #333;
- border: 1px solid #000;
- border-radius: 3px;
- color: #999;
- line-height: 25px;
- vertical-align: middle;
- box-shadow: 0 2px 2px #1d1d1d inset;
-}
- option {
- padding:0 .5em 0 .5em;
- }
- input[type="radio"],
- input[type="checkbox"] {
- width: 15px !important;
- min-height: 15px !important;
- }
- input:focus, select:focus, textarea:focus {
- color: #6986b2;
- border-color: #2f2f2f;
- }
- input:invalid, select:invalid {
- border-color: red;
- box-shadow: 0 0 2px 1px red;
- }
- input:focus.extend {
- width: 300px;
- transition: width 200ms linear;
- }
-
-.form-group {
- margin: 0;
-}
- .form-group:after {
- content: "";
- display: block;
- clear: both;
- }
- .form-group.form-actions {
- min-width: 250px;
- padding: 5px 0;
- background: #1a1a1a;
- border-top: 1px solid #2f2f2f;
- }
- .form-group.form-actions .btn {
- margin: 0 10px;
- }
- .form-group .group-name {
- display: block;
- float: left;
- width: 200px;
- padding: 10px 0;
- text-align: right;
- }
- .form-group .group-controls {
- min-width: 250px;
- min-height: 25px;
- margin: 0 0 0 220px;
- padding: 5px 0;
- }
- .form-group .group-controls .control {
- display: block;
- min-height: 30px;
- padding: 5px 0;
- line-height: 25px;
- font-size: 14px;
- }
-
-.stick {
- display: inline-block;
- white-space: nowrap;
- font-size: 0px;
- vertical-align: middle;
-}
- .stick input,
- .stick .btn {
- border-radius: 0;
- font-size: 14px;
- }
- .stick .btn:first-child,
- .stick input:first-child {
- border-radius: 3px 0 0 3px;
- }
- .stick .btn-important:first-child {
- border-right: 1px solid #000;
- }
- .stick .btn:last-child,
- .stick input:last-child {
- border-radius: 0 3px 3px 0;
- }
- .stick .btn + .btn,
- .stick .btn + input,
- .stick .btn + .dropdown > .btn,
- .stick input + .btn,
- .stick input + input,
- .stick input + .dropdown > .btn,
- .stick .dropdown + .btn,
- .stick .dropdown + input,
- .stick .dropdown + .dropdown > .btn {
- border-left: none;
- }
- .stick .btn + .dropdown > .btn {
- border-left: none;
- border-radius: 0 3px 3px 0;
- }
- .stick .btn + .dropdown a {
- font-size: 12px;
- }
-
-.btn {
- display: inline-block;
- min-height: 37px;
- min-width: 15px;
- padding: 5px 10px;
- background: linear-gradient(to bottom, #fff 0%, #eee 100%);
- border-radius: 3px;
- border: 1px solid #000;
- color: #888;
- line-height: 20px;
- vertical-align: middle;
- cursor: pointer;
- overflow: hidden;
-}
- a.btn {
- min-height: 25px;
- line-height: 25px;
- }
- .btn:hover {
- background: linear-gradient(to bottom, #4A5D7A, #26303F);
- text-decoration: none;
- }
- .btn.active,
- .btn:active,
- .dropdown-target:target ~ .btn.dropdown-toggle {
- background: #26303F;
- }
-
- .btn-important {
- background: linear-gradient(to bottom, #0084CC, #0045CC);
- color: #888888;
- border: 1px solid #000000;
- }
- .btn-important:hover {
- background: linear-gradient(to bottom, #0066CC, #0045CC);
- }
- .btn-important:active {
- background: #0044CB;
- box-shadow: none;
- }
-
- .btn-attention {
- background: linear-gradient(to bottom, #E95B57, #BD362F);
- color: #888888;
- border: 1px solid #000000;
- }
- .btn-attention:hover {
- background: linear-gradient(to bottom, #D14641, #BD362F);
- }
- .btn-attention:active {
- background: #BD362F;
- box-shadow: none;
- }
-
-/* NAVIGATION */
-.nav-list .nav-header,
-.nav-list .item {
- display: block;
- height: 35px;
- line-height: 35px;
-}
- .nav-list .item:hover {
- background: #1a1a1a;
- }
- .nav-list .item:hover a {
- color: #26303F;
- }
- .nav-list .item.active {
- background: #26303F;
- color: #1a1a1a;
- }
- .nav-list .item.active a {
- color: #888;
- }
- .nav-list .disable {
- color: #aaa;
- background: #fafafa;
- text-align: center;
- }
- .nav-list .item > * {
- display: block;
- padding: 0 10px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .nav-list a:hover {
- text-decoration: none;
- }
- .nav-list .item.error a {
- color: #BD362F;
- }
- .nav-list .item.active.error a {
- color: #fff;
- background: #BD362F;
- }
- .nav-list .item.empty a {
- color: #f39c12;
- }
- .nav-list .item.active.empty a {
- color: #fff;
- background: #f39c12;
- }
-
- .nav-list .nav-header {
- padding: 0 10px;
- background: #1a1a1a;
- border-bottom: 1px solid #2f2f2f;
- font-weight: bold;
- }
- .nav-list .separator {
- display: block;
- height: 0;
- margin: 5px 0;
- border-bottom: 1px solid #2f2f2f;
- }
-
- .nav-list .nav-form {
- padding: 3px;
- text-align: center;
- }
-
-.nav-head {
- display: block;
- margin: 0;
- background: linear-gradient(to bottom, #fff, #f0f0f0);
- border-bottom: 1px solid #2f2f2f;
- text-align: right;
-}
- .nav-head .item {
- display: inline-block;
- padding: 5px 10px;
- }
-
-/* HORIZONTAL-LIST */
-.horizontal-list {
- display: table;
- table-layout: fixed;
- margin: 0;
- padding: 0;
- width: 100%;
-}
- .horizontal-list .item {
- display: table-cell;
- vertical-align: middle;
- }
-
-/* DROPDOWN */
-.dropdown {
- position: relative;
- display: inline-block;
-}
- .dropdown-target {
- display: none;
- }
-
- .dropdown-menu {
- display: none;
- min-width: 200px;
- margin: 5px 0 0;
- padding: 5px 0;
- position: absolute;
- right: 0px;
- background: #1a1a1a;
- border: 1px solid #888;
- border-radius: 5px;
- text-align: left;
- }
- .dropdown-menu:after {
- content: "";
- position: absolute;
- top: -6px;
- right: 13px;
- width: 10px;
- height: 10px;
- background: #1a1a1a;
- border-top: 1px solid #888;
- border-left: 1px solid #888;
- z-index: -10;
- transform: rotate(45deg);
- }
- .dropdown-header {
- display: block;
- padding: 0 5px;
- color: #888;
- font-weight: bold;
- font-size: 14px;
- line-height: 30px;
- }
- .dropdown-menu .item {
- display: block;
- height: 30px;
- font-size: 90%;
- line-height: 30px;
- }
- .dropdown-menu > .item > a {
- display: block;
- padding: 0 25px;
- line-height: 30px;
- }
- .dropdown-menu > .item.share > a {
- display: list-item;
- list-style-position:inside;
- list-style-type:decimal;
- }
- .dropdown-menu > .item:hover {
- background: #26303F;
- color: #888;
- }
- .dropdown-menu > .item:hover > a {
- color: #888;
- text-decoration: none;
- }
- .dropdown-menu .input {
- display: block;
- height: 40px;
- font-size: 90%;
- line-height: 30px;
- }
- .dropdown-menu .input select,
- .dropdown-menu .input input {
- display: block;
- height: 20px;
- width: 95%;
- margin: auto;
- padding: 2px 5px;
- border-radius: 3px;
- }
- .dropdown-menu .input select {
- width: 70%;
- height: auto;
- }
- .dropdown-menu .separator {
- display: block;
- height: 0;
- margin: 5px 0;
- border-bottom: 1px solid #888;
- }
- .dropdown-target:target ~ .dropdown-menu {
- display: block;
- z-index: 10;
- }
- .dropdown-close {
- display: inline;
- }
- .dropdown-close a {
- font-size: 0;
- position: fixed;
- top: 0; bottom: 0;
- left: 0; right: 0;
- display: block;
- z-index: -10;
- }
-
-/* ALERTS */
-.alert {
- display: block;
- width: 90%;
- margin: 15px auto;
- padding: 10px 15px;
- background: #1a1a1a;
- border: 1px solid #ccc;
- border-right: 1px solid #aaa;
- border-bottom: 1px solid #aaa;
- border-radius: 5px;
- color: #aaa;
-}
- .alert-head {
- margin: 0;
- font-weight: bold;
- font-size: 110%;
- }
- .alert > a {
- color: inherit;
- text-decoration: underline;
- }
- .alert-warn {
- border: 1px solid #c95;
- color: #c95;
- }
- .alert-success {
- border: 1px solid #484;
- color: #484;
- }
- .alert-error {
- border: 1px solid #844;
- color: #844;
- }
-
-/* ICÔNES */
-.icon {
- display: inline-block;
- width: 16px;
- height: 16px;
- vertical-align: middle;
- line-height: 16px;
-}
-
-/* Prompt (centré) */
-.prompt {
- text-align: center;
-}
- .prompt label {
- text-align: left;
- }
- .prompt form {
- margin: 1em auto 2.5em auto;
- width: 10em;
- }
- .prompt input {
- margin: .4em auto 1.1em auto;
- width: 100%;
- }
- .prompt p {
- margin: 20px 0;
- }
diff --git a/p/themes/Dark/icons/icon.svg b/p/themes/Dark/icons/icon.svg
new file mode 100644
index 000000000..4eb53dda9
--- /dev/null
+++ b/p/themes/Dark/icons/icon.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
+ <title>Logo FreshRSS</title>
+ <circle fill="#6986B2" cx="128" cy="128" r="33"/>
+ <g fill="none" stroke="#6986B2" stroke-width="24">
+ <g stroke-opacity="0.3">
+ <path d="M12,128 A116,116 0 1,1 128,244"/>
+ <path d="M54,128 A74,74 0 1,1 128,202"/>
+ </g>
+ <path d="M128,12 A116,116 0 0,1 244,128"/>
+ <path d="M128,54 A74,74 0 0,1 202,128"/>
+ </g>
+</svg>
diff --git a/p/themes/Dark/metadata.json b/p/themes/Dark/metadata.json
index 27cae27df..5eb3a05e8 100644
--- a/p/themes/Dark/metadata.json
+++ b/p/themes/Dark/metadata.json
@@ -2,6 +2,6 @@
"name": "Dark",
"author": "AD",
"description": "Le coté obscur du thème “Origine”",
- "version": 0.1,
- "files": ["global.css", "freshrss.css"]
+ "version": 0.2,
+ "files": ["template.css", "dark.css"]
}
diff --git a/p/themes/Dark/template.css b/p/themes/Dark/template.css
new file mode 100644
index 000000000..09ecaf685
--- /dev/null
+++ b/p/themes/Dark/template.css
@@ -0,0 +1,695 @@
+@charset "UTF-8";
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ margin: 0;
+ padding: 0;
+ font-size: 100%;
+}
+
+/*=== Links */
+a {
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+/*=== Lists */
+ul, ol, dd {
+ margin: 0;
+ padding: 0;
+}
+
+/*=== Titles */
+h1 {
+ margin: 0.6em 0 0.3em;
+ font-size: 1.5em;
+ line-height: 1.6em;
+}
+h2 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.3em;
+ line-height: 2em;
+}
+h3 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.1em;
+ line-height: 2em;
+}
+
+/*=== Paragraphs */
+p {
+ margin: 1em 0 0.5em;
+ font-size: 1em;
+}
+
+/*=== Images */
+img {
+ height: auto;
+ max-width: 100%;
+}
+img.favicon {
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+}
+
+/*=== Videos */
+iframe, embed, object, video {
+ max-width: 100%;
+}
+
+/*=== Forms */
+legend {
+ display: block;
+ width: 100%;
+ clear: both;
+}
+label {
+ display: block;
+}
+input {
+ width: 180px;
+}
+textarea {
+ width: 300px;
+}
+input, select, textarea {
+ display: inline-block;
+ max-width: 100%;
+}
+input[type="radio"],
+input[type="checkbox"] {
+ width: 15px !important;
+ min-height: 15px !important;
+}
+input.extend:focus {
+ width: 300px;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+.form-group.form-actions {
+ min-width: 250px;
+}
+.form-group .group-name {
+ display: block;
+ float: left;
+ width: 200px;
+}
+.form-group .group-controls {
+ min-width: 250px;
+ margin: 0 0 0 220px;
+}
+.form-group .group-controls .control {
+ display: block;
+}
+
+/*=== Buttons */
+.stick {
+ display: inline-block;
+ white-space: nowrap;
+}
+.btn,
+a.btn {
+ display: inline-block;
+ cursor: pointer;
+ overflow: hidden;
+}
+.btn-important {
+ font-weight: bold;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ display: block;
+}
+.nav-list .item,
+.nav-list .item > a {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.nav-head {
+ display: block;
+}
+.nav-head .item {
+ display: inline-block;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+.horizontal-list .item {
+ display: table-cell;
+}
+
+/*=== Dropdown */
+.dropdown {
+ position: relative;
+ display: inline-block;
+}
+.dropdown-target {
+ display: none;
+}
+.dropdown-menu {
+ display: none;
+ min-width: 200px;
+ margin: 0;
+ position: absolute;
+ right: 0;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.dropdown-header {
+ display: block;
+}
+.dropdown-menu > .item {
+ display: block;
+}
+.dropdown-menu > .item > a,
+.dropdown-menu > .item > span {
+ display: block;
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ content: '✓';
+}
+.dropdown-menu .input {
+ display: block;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ display: block;
+ max-width: 95%;
+}
+.dropdown-target:target ~ .dropdown-menu {
+ display: block;
+ z-index: 10;
+}
+.dropdown-close {
+ display: inline;
+}
+.dropdown-close a {
+ font-size: 0;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+ z-index: -10;
+}
+.separator {
+ display: block;
+ height: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+/*=== Alerts */
+.alert {
+ display: block;
+ width: 90%;
+}
+.group-controls .alert {
+ width: 100%
+}
+.alert-head {
+ margin: 0;
+ font-weight: bold;
+}
+.alert ul {
+ margin: 5px 20px;
+}
+
+/*=== Icons */
+.icon {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ line-height: 16px;
+}
+
+/*=== Pagination */
+.pagination {
+ display: table;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+}
+.pagination .item {
+ display: table-cell;
+}
+.pagination .pager-first,
+.pagination .pager-previous,
+.pagination .pager-next,
+.pagination .pager-last {
+ width: 100px;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+.header > .item {
+ display: table-cell;
+}
+.header > .item.title {
+ width: 250px;
+ white-space: nowrap;
+}
+.header > .item.title h1 {
+ display: inline-block;
+}
+.header > .item.title .logo {
+ display: inline-block;
+ height: 32px;
+ width: 32px;
+ vertical-align: middle;
+}
+.header > .item.configure {
+ width: 100px;
+}
+
+/*=== Body */
+#global {
+ display: table;
+ width: 100%;
+ height: 100%;
+ table-layout: fixed;
+}
+.aside {
+ display: table-cell;
+ height: 100%;
+ width: 250px;
+ vertical-align: top;
+}
+.aside.aside_flux {
+ background: #fff;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ list-style: none;
+ margin: 0;
+}
+.category {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.category .btn:not([data-unread="0"]):after {
+ content: attr(data-unread);
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds {
+ width: 100%;
+ list-style: none;
+}
+.categories .feeds:not(.active) {
+ display: none;
+}
+.categories .feeds .feed {
+ display: inline-block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+.categories .feeds .feed:not([data-unread="0"]):before {
+ content: "(" attr(data-unread) ") ";
+}
+.categories .feeds .dropdown-menu {
+ left: 0;
+}
+.categories .feeds .item .dropdown-toggle > .icon {
+ visibility: hidden;
+ cursor: pointer;
+ vertical-align: top;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ visibility: visible;
+}
+
+/*=== New article notification */
+#new-article {
+ display: none;
+}
+#new-article > a {
+ display: block;
+}
+
+/*=== Day indication */
+.day .name {
+ position: absolute;
+ right: 0;
+ width: 50%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+/*=== Feed article header and footer */
+.flux_header {
+ position: relative;
+}
+.flux .item {
+ line-height: 40px;
+ white-space: nowrap;
+}
+.flux .item.manage,
+.flux .item.link {
+ width: 40px;
+ text-align: center;
+}
+.flux .item.website {
+ width: 200px;
+}
+.flux.not_read .item.title,
+.flux.current .item.title {
+ font-weight: bold;
+}
+.flux:not(.current):hover .item.title {
+ position: absolute;
+ max-width: calc(100% - 320px);
+ background: #fff;
+}
+.flux .item.title a {
+ color: #000;
+ text-decoration: none;
+}
+.flux .item.date {
+ width: 145px;
+ text-align: right;
+}
+.flux .item > a {
+ display: block;
+}
+.flux .item > a {
+ display: block;
+ text-decoration: none;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.flux .item.share > a {
+ display: list-item;
+ list-style-position: inside;
+ list-style-type: decimal;
+}
+
+/*=== Feed article content */
+.hide_posts > .flux:not(.active) > .flux_content {
+ display: none;
+}
+.content {
+ min-height: 20em;
+ margin: auto;
+ line-height: 1.7em;
+ word-wrap: break-word;
+}
+.content.large {
+ max-width: 1000px;
+}
+.content.medium {
+ max-width: 800px;
+}
+.content.thin {
+ max-width: 550px;
+}
+.content ul,
+.content ol,
+.content dd {
+ margin: 0 0 0 15px;
+ padding: 0 0 5px 15px;
+}
+.content pre {
+ overflow: auto;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ position: absolute;
+ top: 1em;
+ left: 25%; right: 25%;
+ z-index: 10;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.notification.closed {
+ display: none;
+}
+.notification a.close {
+ position: absolute;
+ top: 0; bottom: 0;
+ right: 0;
+ display: inline-block;
+}
+
+#actualizeProgress {
+ position: fixed;
+}
+#actualizeProgress progress {
+ max-width: 100%;
+ vertical-align: middle;
+}
+#actualizeProgress .progress {
+ vertical-align: middle;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ position: fixed;
+ bottom: 0; left: 0;
+ display: table;
+ width: 250px;
+ background: #fff;
+ table-layout: fixed;
+}
+#nav_entries .item {
+ display: table-cell;
+ width: 30%;
+}
+#nav_entries a {
+ display: block;
+}
+
+/*=== "Load more" part */
+#load_more {
+ min-height: 40px;
+}
+.loading {
+ background: url("loader.gif") center center no-repeat;
+ font-size: 0;
+}
+#bigMarkAsRead {
+ display: block;
+ padding: 3em 0;
+ text-align: center;
+}
+.bigTick {
+ font-size: 7em;
+ line-height: 1.6em;
+}
+
+/*=== Statistiques */
+.stat > table {
+ width: 100%;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+/*=== Category boxes */
+#stream.global .box-category {
+ display: inline-block;
+ width: 19em;
+ max-width: 95%;
+ margin: 20px 10px;
+ border: 1px solid #ccc;
+ vertical-align: top;
+}
+#stream.global .category {
+ width: 100%;
+}
+#stream.global .btn {
+ display: block;
+}
+#stream.global .box-category .feeds {
+ display: block;
+ overflow: auto;
+}
+#stream.global .box-category .feed {
+ width: 19em;
+ max-width: 90%;
+}
+
+/*=== Panel */
+#overlay {
+ display: none;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ background: rgba(0, 0, 0, 0.9);
+}
+#panel {
+ display: none;
+ position: fixed;
+ top: 1em; bottom: 1em;
+ left: 2em; right: 2em;
+ overflow: auto;
+ background: #fff;
+}
+#panel .close {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+}
+#panel .close img {
+ display: none;
+}
+
+/*=== DIVERS */
+/*===========*/
+.nav-login,
+.nav_menu .search,
+.nav_menu .toggle_aside {
+ display: none;
+}
+
+.aside .toggle_aside {
+ position: absolute;
+ right: 0;
+ display: none;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .header,
+ .aside .btn-important,
+ .aside .feeds .dropdown,
+ .flux_header .item.website span,
+ .item.date, .day .date,
+ .dropdown-menu > .no-mobile,
+ .no-mobile {
+ display: none;
+ }
+ .nav-login {
+ display: block;
+ }
+ .nav_menu .toggle_aside,
+ .aside .toggle_aside,
+ .nav_menu .search,
+ #panel .close img {
+ display: inline-block;
+ }
+
+ .aside {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0;
+ width: 0;
+ overflow: hidden;
+ z-index: 100;
+ }
+ .aside:target {
+ width: 90%;
+ overflow: auto;
+ }
+ .aside .categories {
+ margin: 10px 0 75px;
+ }
+
+ .flux_header .item.website {
+ width: 40px;
+ }
+
+ .flux:not(.current):hover .item.title {
+ position: relative;
+ width: auto;
+ white-space: nowrap;
+ }
+
+ .notification {
+ top: 0;
+ left: 0;
+ right: 0;
+ }
+
+ #nav_entries {
+ width: 100%;
+ }
+
+ #stream.global .box-category {
+ margin: 10px 0;
+ }
+
+ #panel {
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ }
+ #panel .close {
+ top: 0; right: 0;
+ left: auto; bottom: auto;
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ }
+}
+
+/*=== PRINTER */
+/*============*/
+@media print {
+ .header, .aside,
+ .nav_menu, .day,
+ .flux_header,
+ .flux_content .bottom,
+ .pagination,
+ #nav_entries {
+ display: none;
+ }
+ html, body {
+ background: #fff;
+ color: #000;
+ font-family: Serif;
+ }
+ #global,
+ .flux_content {
+ display: block !important;
+ }
+ .flux_content .content {
+ width: 100% !important;
+ }
+ .flux_content .content a {
+ color: #000;
+ }
+ .flux_content .content a:after {
+ content: " [" attr(href) "] ";
+ font-style: italic;
+ }
+}
diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css
new file mode 100644
index 000000000..4ae3e98e9
--- /dev/null
+++ b/p/themes/Flat/flat.css
@@ -0,0 +1,950 @@
+@charset "UTF-8";
+
+/*=== FONTS */
+@font-face {
+ font-family: "OpenSans";
+ src: url("../fonts/openSans.woff") format("woff");
+}
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ height: 100%;
+ font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
+ background: #fafafa;
+}
+
+/*=== Links */
+a {
+ color: #2980b9;
+ outline: none;
+}
+
+/*=== Forms */
+legend {
+ display: inline-block;
+ width: auto;
+ margin: 20px 0 5px;
+ padding: 5px 20px;
+ font-size: 1.4em;
+ clear: both;
+ background: #ecf0f1;
+ border-radius: 20px;
+}
+label {
+ min-height: 25px;
+ padding: 5px 0;
+ cursor: pointer;
+ color: #444;
+}
+textarea {
+ width: 360px;
+ height: 100px;
+}
+input, select, textarea {
+ min-height: 25px;
+ padding: 5px;
+ line-height: 25px;
+ vertical-align: middle;
+ background: #fff;
+ border: none;
+ border-bottom: 3px solid #ddd;
+ color: #666;
+ border-radius: 5px;
+}
+option {
+ padding: 0 .5em;
+}
+input:focus, select:focus, textarea:focus {
+ color: #333;
+ border-color: #2980b9;
+}
+input:invalid, select:invalid {
+ color: #f00;
+ border-color: #f00;
+ box-shadow: none;
+}
+input:disabled, select:disabled {
+ background: #eee;
+}
+input.extend {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+}
+
+/*=== Tables */
+table {
+ border-collapse: collapse;
+}
+
+tr, th, td {
+ padding: 0.5em;
+ border: 1px solid #ddd;
+}
+th {
+ background: #f6f6f6;
+}
+form td,
+form th {
+ font-weight: normal;
+ text-align: center;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group {
+ padding: 5px;
+ border: 1px solid transparent;
+ border-radius: 3px;
+}
+.form-group:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+.form-group:hover {
+ background: #fff;
+ border: 1px solid #eee;
+ border-radius: 3px;
+ border: 1px solid #eee;
+}
+.form-group.form-actions {
+ margin: 15px 0 25px;
+ padding: 5px 0;
+ background: #ecf0f1;
+ border-top: 3px solid #bdc3c7;
+ border-radius: 5px 5px 0 0;
+}
+.form-group.form-actions .btn {
+ margin: 0 10px;
+}
+.form-group .group-name {
+ padding: 10px 0;
+ text-align: right;
+}
+.form-group .group-controls {
+ min-height: 25px;
+ padding: 5px 0;
+}
+.form-group .group-controls .control {
+ line-height: 2.0em;
+}
+.form-group table {
+ margin: 10px 0 0 220px;
+}
+
+/*=== Buttons */
+.stick {
+ vertical-align: middle;
+ font-size: 0;
+}
+.stick input,
+.stick .btn {
+ border-radius: 0;
+}
+.stick .btn:first-child,
+.stick input:first-child {
+ border-radius: 5px 0 0 5px;
+}
+.stick .btn:last-child,
+.stick input:last-child,
+.stick .btn + .dropdown > .btn {
+ border-radius: 0 5px 5px 0;
+}
+.stick .btn + input,
+.stick input + input,
+.stick .dropdown + input {
+ border-left: 1px solid #ddd;
+}
+
+.btn {
+ display: inline-block;
+ min-height: 38px;
+ min-width: 15px;
+ margin: 0;
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ vertical-align: middle;
+ cursor: pointer;
+ overflow: hidden;
+ background: #3498db;
+ border-radius: 5px;
+ border: none;
+ border-bottom: 3px solid #2980b9;
+ color: #fff;
+}
+a.btn {
+ min-height: 25px;
+ line-height: 25px;
+}
+.btn:hover {
+ text-decoration: none;
+}
+.btn.active,
+.btn:active,
+.btn:hover,
+.dropdown-target:target ~ .btn.dropdown-toggle {
+ background: #2980b9;
+}
+
+.btn-important {
+ font-weight: normal;
+ background: #e67e22;
+ color: #fff;
+ border-bottom: 3px solid #d35400;
+}
+.btn-important:hover,
+.btn-important:active {
+ background: #d35400;
+}
+
+.btn-attention {
+ background: #e74c3c;
+ color: #fff;
+ border-bottom: 3px solid #c0392b;
+}
+.btn-attention:hover,
+.btn-attention:active {
+ background: #c0392b;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ height: 2.5em;
+ line-height: 2.5em;
+ font-size: 0.9rem;
+}
+.nav-list .item:hover,
+.nav-list .item.active {
+ background: #2980b9;
+ color: #fff;
+}
+.nav-list .item:hover a,
+.nav-list .item.active a {
+ color: #fff;
+}
+.nav-list .disable {
+ text-align: center;
+ background: #fafafa;
+ color: #aaa;
+}
+.nav-list .item > a {
+ padding: 0 10px;
+}
+.nav-list a:hover {
+ text-decoration: none;
+}
+.nav-list .item.empty a {
+ color: #f39c12;
+}
+.nav-list .item:hover.empty a,
+.nav-list .item.active.empty a {
+ color: #fff;
+ background: #f39c12;
+}
+.nav-list .item.error a {
+ color: #bd362f;
+}
+.nav-list .item:hover.error a,
+.nav-list .item.active.error a {
+ color: #fff;
+ background: #bd362f;
+}
+
+.nav-list .nav-header {
+ padding: 0 10px;
+ font-weight: bold;
+ background: #34495e;
+ color: #fff;
+}
+
+.nav-list .nav-form {
+ padding: 3px;
+ text-align: center;
+}
+
+.nav-head {
+ margin: 0;
+ text-align: right;
+ background: #34495e;
+ color: #fff;
+}
+.nav-head a {
+ color: #fff;
+}
+.nav-head .item {
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ line-height: 1.5rem;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ margin: 0;
+ padding: 0;
+}
+.horizontal-list .item {
+ vertical-align: middle;
+}
+
+/*=== Dropdown */
+.dropdown-menu {
+ margin: 5px 0 0;
+ padding: 5px 0;
+ font-size: 0.8rem;
+ text-align: left;
+ border: 1px solid #95a5a6;
+ border-radius: 3px;
+}
+.dropdown-menu:after {
+ content: "";
+ position: absolute;
+ top: -6px;
+ right: 13px;
+ width: 10px;
+ height: 10px;
+ z-index: -10;
+ transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ background: #fff;
+ border-top: 1px solid #95a5a6;
+ border-left: 1px solid #95a5a6;
+}
+.dropdown-header {
+ padding: 0 5px 5px;
+ font-weight: bold;
+ text-align: left;
+ color: #34495e;
+}
+.dropdown-menu > .item > a {
+ padding: 0 25px;
+ line-height: 2.5em;
+}
+.dropdown-menu > .item > span {
+ padding: 0 25px;
+ line-height: 2em;
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ font-weight: bold;
+ margin: 0 0 0 -14px;
+}
+.dropdown-menu > .item:hover > a {
+ text-decoration: none;
+ background: #2980b9;
+ color: #fff;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ margin: 0 auto 5px;
+ padding: 2px 5px;
+ border-radius: 3px;
+}
+
+.separator {
+ margin: 5px 0;
+ border-bottom: 1px solid #ddd;
+}
+
+/*=== Alerts */
+.alert {
+ margin: 15px auto;
+ padding: 10px 15px;
+ font-size: 0.9em;
+ background: #f4f4f4;
+ border: 1px solid #ccc;
+ border-right: 1px solid #aaa;
+ border-bottom: 1px solid #aaa;
+ border-radius: 5px;
+ color: #aaa;
+ text-shadow: 0 0 1px #eee;
+}
+.alert-head {
+ font-size: 1.15em;
+}
+.alert > a {
+ text-decoration: underline;
+ color: inherit;
+}
+.alert-warn {
+ background: #ffe;
+ border: 1px solid #eeb;
+ color: #c95;
+}
+.alert-success {
+ background: #dfd;
+ border: 1px solid #cec;
+ color: #484;
+}
+.alert-error {
+ background: #fdd;
+ border: 1px solid #ecc;
+ color: #844;
+}
+
+/*=== Pagination */
+.pagination {
+ text-align: center;
+ font-size: 0.8em;
+ background: #ecf0f1;
+ color: #000;
+}
+.content .pagination {
+ margin: 0;
+ padding: 0;
+}
+.pagination .item.pager-current {
+ font-weight: bold;
+ font-size: 1.5em;
+ background: #34495e;
+ color: #ecf0f1;
+}
+.pagination .item a {
+ display: block;
+ font-style: italic;
+ line-height: 3em;
+ text-decoration: none;
+ color: #000;
+}
+.pagination .item a:hover {
+ background: #34495e;
+ color: #ecf0f1;
+}
+
+.pagination .loading,
+.pagination a:hover.loading {
+ font-size: 0;
+ background: url("loader.gif") center center no-repeat #34495e;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ height: 85px;
+ background: #ecf0f1;
+}
+.header > .item {
+ padding: 10px;
+ vertical-align: middle;
+ text-align: center;
+}
+.header > .item.title{
+ width: 230px;
+}
+.header > .item.title h1 {
+ margin: 0.5em 0;
+}
+.header > .item.title h1 a {
+ text-decoration: none;
+}
+.header > .item.search input {
+ width: 230px;
+}
+.header .item.search input:focus {
+ width: 350px;
+}
+
+/*=== Body */
+#global {
+ height: calc(100% - 85px);
+}
+.aside {
+ background: #ecf0f1;
+}
+.aside.aside_flux {
+ padding: 10px 0 50px;
+ background: #ecf0f1;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ text-align: center;
+}
+.category {
+ width: 233px;
+ margin: 10px auto;
+ text-align: left;
+}
+.category .btn:first-child {
+ position: relative;
+ width: 212px;
+}
+.category.stick .btn:first-child {
+ width: 175px;
+}
+.category .btn:first-child:not([data-unread="0"]):after {
+ position: absolute;
+ top: 5px; right: 5px;
+ padding: 1px 5px;
+ background: #3498DB;
+ color: #fff;
+ border-radius: 5px;
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds .item.active {
+ background: #2980b9;
+}
+.categories .feeds .item.active .feed,
+.categories .feeds .item.empty.active .feed {
+ color: #fff;
+}
+.categories .feeds .item.empty.active {
+ background: #f39c12;
+}
+.categories .feeds .item.error.active {
+ background: #bd362f;
+}
+.categories .feeds .item.empty .feed {
+ color: #e67e22;
+}
+.categories .feeds .item.error .feed {
+ color: #bd362f;
+}
+.categories .feeds .item .feed {
+ margin: 0;
+ width: 165px;
+ line-height: 3em;
+ font-size: 0.8em;
+ text-align: left;
+ text-decoration: none;
+}
+.categories .feeds .feed:not([data-unread="0"]) {
+ font-weight: bold;
+}
+.categories .feeds .dropdown-menu:after {
+ left: 2px;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ vertical-align: middle;
+ background-color: #95a5a6;
+ border-radius: 3px;
+}
+
+/*=== Configuration pages */
+.post {
+ padding: 10px 50px;
+ font-size: 0.9em;
+}
+.post form {
+ margin: 10px 0;
+}
+.post.content {
+ max-width: 550px;
+}
+
+/*=== Prompt (centered) */
+.prompt {
+ text-align: center;
+}
+.prompt label {
+ text-align: left;
+}
+.prompt form {
+ margin: 10px auto 20px auto;
+ width: 180px;
+}
+.prompt input {
+ margin: 5px auto;
+ width: 100%;
+}
+.prompt p {
+ margin: 20px 0;
+}
+
+/*=== New article notification */
+#new-article {
+ text-align: center;
+ font-size: 0.9em;
+ background: #3498db;
+}
+#new-article:hover {
+ background: #2980b9;
+}
+#new-article > a {
+ line-height: 3em;
+ font-weight: bold;
+ color: #fff;
+}
+#new-article > a:hover {
+ text-decoration: none;
+}
+
+/*=== Day indication */
+.day {
+ padding: 0 10px;
+ font-weight: bold;
+ line-height: 3em;
+ border-left: 2px solid #ecf0f1;
+}
+.day .name {
+ padding: 0 10px 0 0;
+ font-size: 1.8em;
+ opacity: 0.3;
+ font-style: italic;
+ text-align: right;
+ color: #aab;
+}
+
+/*=== Index menu */
+.nav_menu {
+ text-align: center;
+ padding: 5px 0;
+}
+
+/*=== Feed articles */
+.flux {
+ border-left: 2px solid #ecf0f1;
+}
+.flux:hover {
+ background: #fff;
+}
+.flux.current {
+ border-left-color: #3498db;
+}
+.flux.not_read {
+ background: #FFF3ED;
+ border-left-color: #FF5300;
+}
+.flux.not_read:not(.current):hover .item.title {
+ background: #FFF3ED;
+}
+.flux.favorite {
+ background: #FFF6DA;
+ border-left-color: #FFC300;
+}
+.flux.favorite:not(.current):hover .item.title {
+ background: #FFF6DA;
+}
+.flux.current {
+ background: #fff;
+}
+
+
+.flux_header {
+ font-size: 0.8rem;
+ cursor: pointer;
+ border-top: 1px solid #ecf0f1;
+}
+.flux_header .title {
+ font-size: 0.9rem;
+}
+.flux .website .favicon {
+ padding: 5px;
+}
+.flux .date {
+ font-size: 0.7rem;
+ color: #666;
+}
+
+.flux .bottom {
+ font-size: 0.8rem;
+ text-align: center;
+}
+
+/*=== Content of feed articles */
+.content {
+ padding: 20px 10px;
+}
+.content > h1.title > a {
+ color: #000;
+}
+
+.content hr {
+ margin: 30px 10px;
+ height: 1px;
+ background: #ddd;
+ border: 0;
+ box-shadow: 0 2px 5px #ccc;
+}
+
+.content pre {
+ margin: 10px auto;
+ padding: 10px 20px;
+ overflow: auto;
+ background: #222;
+ color: #fff;
+ font-size: 0.9rem;
+ border-radius: 3px;
+}
+.content code {
+ padding: 2px 5px;
+ color: #dd1144;
+ background: #fafafa;
+ border: 1px solid #eee;
+ border-radius: 3px;
+}
+.content pre code {
+ background: transparent;
+ color: #fff;
+ border: none;
+}
+
+.content blockquote {
+ display: block;
+ margin: 0;
+ padding: 5px 20px;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ background: #fafafa;
+ color: #333;
+}
+.content blockquote p {
+ margin: 0;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ padding: 0 0 0 5px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 0.9em;
+ line-height: 3em;
+ z-index: 10;
+ vertical-align: middle;
+ background: #ddd;
+ color: #666;
+ border-radius: 3px;
+ border: none;
+}
+.notification.good {
+ background: #1abc9c;
+ color: #fff;
+}
+.notification.bad {
+ background: #e74c3c;
+ color: #fff;
+}
+.notification a.close {
+ padding: 0 15px;
+ line-height: 3em;
+ border-radius: 0 3px 3px 0;
+}
+.notification.good a.close:hover {
+ background: #16a085;
+}
+.notification.bad a.close:hover {
+ background: #c0392b;
+}
+
+.notification#actualizeProgress {
+ line-height: 2em;
+}
+
+/*=== "Load more" part */
+#bigMarkAsRead {
+ text-align: center;
+ text-decoration: none;
+ background: #ecf0f1;
+}
+#bigMarkAsRead:hover {
+ background: #34495e;
+ color: #fff;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ margin: 0;
+ text-align: center;
+ line-height: 3em;
+ table-layout: fixed;
+ background: #34495e;
+}
+
+/*=== READER VIEW */
+/*================*/
+#stream.reader .flux {
+ padding: 0 0 50px;
+ background: #ecf0f1;
+ color: #34495e;
+ border: none;
+}
+#stream.reader .flux .author {
+ margin: 0 0 10px;
+ font-size: 90%;
+ color: #999;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+#stream.global .box-category {
+ text-align: left;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+}
+#stream.global .category {
+ margin: 0;
+}
+#stream.global .btn {
+ width: auto;
+ height: 2em;
+ margin: 0;
+ padding: 0 10px;
+ line-height: 2em;
+ font-size: 1.2rem;
+ background: #ecf0f1;
+ color: #333;
+ border-bottom: 1px solid #ddd;
+ border-radius: 5px 5px 0 0;
+}
+#stream.global .btn:not([data-unread="0"]) {
+ font-weight: bold;
+ background: #3498db;
+ color: #fff;
+}
+#stream.global .btn:first-child:not([data-unread="0"]):after {
+ top: 0; right: 5px;
+ font-weight: bold;
+ background: none;
+ border: 0;
+ color: #fff;
+}
+#stream.global .box-category .feeds {
+ max-height: 250px;
+}
+#stream.global .box-category .feeds .item {
+ padding: 2px 10px;
+ font-size: 0.9rem;
+}
+
+/*=== DIVERS */
+/*===========*/
+.aside.aside_feed .nav-form input,
+.aside.aside_feed .nav-form select {
+ width: 140px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu {
+ right: -20px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu:after {
+ right: 33px;
+}
+
+/*=== STATISTICS */
+/*===============*/
+.stat {
+ margin: 10px 0 20px;
+}
+
+.stat th,
+.stat td,
+.stat tr {
+ border: none;
+}
+.stat > table td,
+.stat > table th {
+ border-bottom: 1px solid #ddd;
+ text-align: center;
+}
+
+/*=== LOGS */
+/*=========*/
+.logs {
+ overflow: hidden;
+ border: 1px solid #aaa;
+}
+.log {
+ margin: 10px 0;
+ padding: 5px 2%;
+ overflow: auto;
+ font-size: 0.8rem;
+ background: #fafafa;
+ color: #666;
+}
+
+.log > .date {
+ margin: 0 10px 0 0;
+ padding: 5px 10px;
+ border-radius: 20px;
+}
+.log.error > .date {
+ background: #e74c3c;
+ color: #fff;
+}
+.log.warning > .date {
+ background: #f39c12;
+}
+.log.notice > .date {
+ background: #ecf0f1;
+}
+.log.debug > .date {
+ background: #111;
+ color: #eee;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .aside {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+ }
+ .aside .toggle_aside,
+ #panel .close {
+ position: absolute;
+ display: block;
+ top: 0; right: 0;
+ width: 32px;
+ height: 32px;
+ line-height: 30px;
+ text-align: center;
+ background: #34495e;
+ border-radius: 0 0 0 5px;
+ }
+
+ .nav_menu .btn {
+ margin: 5px 10px;
+ }
+ .nav_menu .stick {
+ margin: 0 10px;
+ }
+ .nav_menu .stick .btn {
+ margin: 5px 0;
+ }
+ .nav_menu .search {
+ display: inline-block;
+ max-width: 97%;
+ }
+ .nav_menu .search input {
+ max-width: 97%;
+ width: 90px;
+ }
+ .nav_menu .search input:focus {
+ width: 400px;
+ }
+
+ .day .name {
+ font-size: 1.1rem;
+ }
+
+ .pagination {
+ margin: 0 0 3.5em;
+ }
+
+ .notification {
+ border-radius: 0;
+ }
+ .notification a.close {
+ display: block;
+ left: 0;
+ background: transparent;
+ }
+ .notification a.close:hover {
+ opacity: 0.5;
+ }
+ .notification a.close .icon {
+ display: none;
+ }
+}
diff --git a/p/themes/Flat/freshrss.css b/p/themes/Flat/freshrss.css
deleted file mode 100644
index 503eec813..000000000
--- a/p/themes/Flat/freshrss.css
+++ /dev/null
@@ -1,900 +0,0 @@
-@charset "UTF-8";
-
-/* STRUCTURE */
-body {
- background: #fafafa;
-}
-
-.header {
- display: table;
- width: 100%;
- table-layout: fixed;
- background: #ecf0f1;
-}
- .header > .item {
- display: table-cell;
- padding: 10px 0;
- vertical-align: middle;
- text-align: center;
- }
- .header > .item.title {
- width: 250px;
- white-space: nowrap;
- }
- .logo {
- display: inline-block;
- font-size: 48px;
- height: 32px;
- width: 32px;
- padding: 10px;
- }
- .header > .item.title h1 {
- display: inline-block;
- margin: 0;
- }
- .header > .item.search input {
- width: 230px;
- }
- .header .item.search input:focus {
- width: 330px;
- }
- .header > .item.configure {
- width: 100px;
- }
-
-.item a:hover {
- text-decoration: none;
-}
-
-#global {
- display: table;
- width: 100%;
- height: 100%;
- table-layout: fixed;
-}
- .aside {
- display: table-cell;
- height: 100%;
- width: 250px;
- vertical-align: top;
- background: #ecf0f1;
- }
- .aside .nav-form input {
- width: 180px;
- }
- .aside.aside_flux {
- padding: 10px 0 40px;
- }
- .aside.aside_feed .nav-form input {
- width: 140px;
- }
- .aside.aside_feed .nav-form .dropdown-menu {
- right: -20px;
- }
- .aside.aside_feed .nav-form .dropdown-menu:after {
- right: 33px;
- }
-
- .nav-login {
- display: none;
- }
-
- .nav_menu {
- width: 100%;
- text-align: center;
- padding: 5px 0;
- }
- .nav_menu .search {
- display:none;
- }
-
-.favicon {
- height: 16px;
- width: 16px;
-}
-
-.categories {
- margin: 0;
- padding: 0;
- text-align: center;
- list-style: none;
-}
- .category {
- display: block;
- width: 220px;
- margin: 10px auto;
- text-align: left;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .category .btn:first-child {
- width: 195px;
- position: relative;
- }
- .category.stick .btn:first-child {
- width:160px;
- }
- .category .btn:first-child:not([data-unread="0"]):after {
- content: attr(data-unread);
- position: absolute;
- top: 5px; right: 5px;
- padding: 0 5px;
- color: #fff;
- font-size: 90%;
- background: #3498DB;
- border-radius: 5px;
- }
- .category + .feeds:not(.active) {
- display:none;
- }
- .categories .feeds {
- width: 100%;
- margin: 0 auto;
- list-style: none;
- }
- .categories .feeds .item.active:after {
- content: "⇢";
- line-height: 35px;
- float: right;
- }
- .categories .feeds .item.empty .feed {
- color: #e67e22;
- }
- .categories .feeds .item.error .feed {
- color: #BD362F;
- }
- .categories .feeds .item .feed {
- display: inline-block;
- margin: 0;
- width: 165px;
- line-height: 35px;
- font-size: 90%;
- vertical-align: middle;
- text-align: left;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .feed:not([data-unread="0"]) {
- font-weight:bold;
- }
- .feed:not([data-unread="0"]):before {
- content: "(" attr(data-unread) ") ";
- }
- .categories .feeds .dropdown-menu {
- left: 0;
- }
- .categories .feeds .dropdown-menu:after {
- left: 2px;
- }
- .categories .feeds .item .dropdown-toggle > .icon {
- visibility: hidden;
- cursor: pointer;
- }
- .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
- .categories .feeds .item:hover .dropdown-toggle > .icon,
- .categories .feeds .item.active .dropdown-toggle > .icon {
- background-color: #95a5a6;
- border-radius: 3px;
- visibility: visible;
- }
- .categories .btn:hover .notRead,
- .categories .btn.active .notRead {
- background: #2980B9;
- border-left: 3px solid #3498DB;
- }
-
-.post {
- padding: 10px 50px;
-}
- .post form {
- margin: 10px 0;
- }
-
-.day {
- padding: 5px 15px;
- font-size: 130%;
- font-weight: bold;
- line-height: 50px;
- border-left: 3px solid #ecf0f1;
-}
- .day .name {
- position: absolute;
- right: 0;
- width: 50%;
- height: 1.5em;
- padding: 0 10px 0 0;
- overflow: hidden;
- color: #aab;
- font-size: 1.8em;
- opacity: .3;
- font-style: italic;
- text-align: right;
- white-space: nowrap;
- text-overflow: ellipsis;
- z-index: -10;
- }
-
-#new-article {
- display: none;
- min-height: 40px;
- background: #3498db;
- text-align: center;
-}
- #new-article:hover {
- background: #2980b9;
- }
- #new-article > a {
- display: block;
- line-height: 40px;
- color: #fff;
- font-weight: bold;
- }
- #new-article > a:hover {
- text-decoration: none;
- }
-
-.flux {
- border-left: 3px solid #ecf0f1;
-}
- .flux:hover {
- background: #fff;
- }
- .flux.not_read {
- border-left-color: #FF5300;
- background: #FFF3ED;
- }
- .flux.favorite {
- border-left-color: #FFC300;
- background: #FFF6DA;
- }
- .flux.current {
- border-left-color: #3498db;
- background: #fff;
- }
-
- .horizontal-list > .item:not(.title):not(.website) > a {
- display: block;
- }
-
- .flux_header {
- background: inherit;
- height: 25px;
- font-size: 12px;
- border-top: 1px solid #ecf0f1;
- cursor: pointer;
- }
- .flux .item {
- line-height: 40px;
- white-space: nowrap;
- }
- .flux_header > .item {
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .flux .item.manage {
- width: 40px;
- text-align: center;
- }
- .flux .item.website {
- width: 200px;
- }
- .website .favicon {
- padding: 5px;
- }
- .flux .item.title {
- background: inherit;
- }
- .flux .title a {
- color: #333;
- outline: none;
- }
- .flux.current .item.title,
- .flux.not_read .item.title {
- font-weight: bold;
- }
- .flux .item.date {
- width: 145px;
- padding:0 5px 0 0;
- text-align: right;
- font-size: 10px;
- color: #666;
- }
- .link {
- width: 40px;
- text-align: center;
- }
-
-#stream.reader .flux {
- position: relative;
- padding: 0 0 30px;
- border: none;
- background: #ecf0f1;
- color: #34495e;
- font-size: 120%;
-}
- #stream.reader .flux .author {
- margin: 0 0 10px;
- font-size: 90%;
- color: #aaa;
- }
-
-#stream.global {
- text-align: center;
-}
- #stream.global .box-category {
- display: inline-block;
- width: 280px;
- margin: 20px 10px;
- vertical-align: top;
- border: 1px solid #ddd;
- border-radius: 5px;
- text-align: left;
- }
- #stream.global .category {
- width: 100%;
- margin: 0;
- }
- #stream.global .btn {
- display: block;
- width: auto;
- height: 35px;
- margin: 0;
- padding: 0 10px;
- background: #ecf0f1;
- color: #333;
- border-bottom: 1px solid #ddd;
- border-radius: 5px 5px 0 0;
- line-height: 35px;
- font-size: 120%;
- }
- #stream.global .btn:not([data-unread="0"]) {
- background: #3498db;
- color: #fff;
- font-weight: bold;
- }
- #stream.global .btn:first-child:not([data-unread="0"]):after {
- top: 0; right: 5px;
- border: 0;
- background: none;
- color: #fff;
- font-weight: bold;
- box-shadow: none;
- }
- #stream.global .box-category .feeds {
- display: block;
- max-height: 250px;
- margin: 0;
- list-style: none;
- overflow: auto;
- }
- #stream.global .box-category .feeds .item {
- padding: 2px 10px;
- font-size: 90%;
- }
- #stream.global .box-category .feed {
- width: 220px;
- }
-
-.content {
- min-height: 150px;
- margin: 0 auto;
- padding: 20px 10px;
- line-height: 170%;
- word-wrap: break-word;
-}
- .content.large {
- max-width: 1000px;
- }
- .content.medium {
- max-width: 800px;
- }
- .content.thin {
- max-width: 550px;
- }
- .content h1, .content h2, .content h3 {
- margin: 20px 0 5px;
- }
- .content > .title {
- font-size: x-large;
- margin: 0;
- }
-
- .content p {
- margin: 0 0 20px;
- }
- img.big {
- display: block;
- margin: 10px auto;
- }
- figure img.big {
- margin: 0;
- }
- .content hr {
- margin: 30px 0;
- height: 1px;
- background: #ddd;
- border: 0;
- }
- .content pre {
- margin: 10px auto;
- padding: 10px;
- overflow: auto;
- background: #000;
- color: #fff;
- font-size: 110%;
- }
- .content q, .content blockquote {
- display: block;
- margin: 5px 0;
- padding: 5px 20px;
- font-style: italic;
- border-left: 4px solid #ccc;
- color: #666;
- }
- .content blockquote p {
- margin: 0;
- }
-
-#panel {
- display: none;
- position: fixed;
- top: 10px; bottom: 10px;
- left: 100px; right: 100px;
- overflow: auto;
- background: #fff;
- border: 1px solid #95a5a6;
- border-radius: 5px;
-}
- #panel .close {
- position: fixed;
- top: 10px; right: 0px;
- display: inline-block;
- width: 26px;
- height: 26px;
- margin: 0 10px 0 0;
- border-radius: 3px;
- text-align: center;
- line-height: 24px;
- background: #95a5a6;
- }
- #panel .close:hover {
- background: #7f8c8d;
- }
-
-#overlay {
- display: none;
- position: fixed;
- top: 0; bottom: 0;
- left: 0; right: 0;
- background: rgba(0, 0, 0, 0.9);
-}
-
-.flux_content .bottom {
- font-size: 90%;
- text-align: center;
-}
-
-.hide_posts > :not(.active) > .flux_content {
- display:none;
-}
-
-/*** PAGINATION ***/
-.pagination {
- display: table;
- width: 100%;
- margin: 0;
- background: #ecf0f1;
- text-align: center;
- color: #000;
- font-size: 80%;
- line-height: 200%;
- table-layout: fixed;
- font-weight: bold;
-}
- .pagination .item {
- display: table-cell;
- line-height: 40px;
- vertical-align: top;
- }
- .pagination .item.pager-current {
- font-weight: bold;
- font-size: 140%;
- color: #ecf0f1;
- background: #34495e;
- }
- .pagination .item.pager-first,
- .pagination .item.pager-previous,
- .pagination .item.pager-next,
- .pagination .item.pager-last {
- width: 100px;
- }
- .pagination .item a {
- display: block;
- color: #000;
- font-weight: bold;
- line-height: 40px;
- }
- .pagination .item a:hover {
- color: #ecf0f1;
- background: #34495e;
- }
-
-#nav_entries {
- display: table;
- width: 250px;
- height: 40px;
- position: fixed;
- bottom: 0;
- left: 0;
- margin: 0;
- background: #34495e;
- text-align: center;
- line-height: 40px;
- table-layout: fixed;
-}
- #nav_entries .item {
- display: table-cell;
- width: 30%;
- }
- #nav_entries a {
- display: block;
- }
- #nav_entries .i_up {
- margin: 5px 0 0;
- vertical-align: top;
- }
-
-.pagination .loading,
-.pagination a:hover.loading {
- background: url("loader.gif") center center no-repeat #34495e;
- font-size: 0;
-}
-
-#bigMarkAsRead {
- background: #ecf0f1;
- display: block;
- font-style: normal;
- padding: 32px 0 64px 0;
- text-align: center;
- text-decoration: none;
- text-shadow: 0 -1px 0 #aaa;
-}
- #bigMarkAsRead:hover {
- background: #34495e;
- color: #fff;
- }
- .bigTick {
- font-size: 72pt;
- line-height: 1.6em;
- }
-
-/*** NOTIFICATION ***/
-.notification {
- position: absolute;
- top: 10px;
- left: 25%; right: 25%;
- min-height: 30px;
- padding: 10px;
- line-height: 30px;
- text-align: center;
- border-radius: 3px;
- background: #ddd;
- color: #666;
- font-weight: bold;
- z-index: 10;
-}
- .notification.closed {
- display: none;
- }
- .notification.good {
- background: #1abc9c;
- color: #fff;
- }
- .notification.bad {
- background: #e74c3c;
- color: #fff;
- }
- .notification a.close {
- position: absolute;
- top: -6px; right: -6px;
- display: inline-block;
- width: 16px;
- height: 16px;
- padding: 5px;
- border-radius: 3px;
- line-height: 16px;
- }
- .notification.good a.close {
- background: #1abc9c;
- }
- .notification.bad a.close {
- background: #e74c3c;
- }
-
-.toggle_aside, .btn.toggle_aside {
- display: none;
-}
-
-#actualizeProgress {
- position: fixed;
-}
-#actualizeProgress progress {
- max-width: 100%;
- vertical-align: middle;
-}
-#actualizeProgress .progress {
- vertical-align: middle;
-}
-
-.logs {
- border: 1px solid #34495e;
-}
- .log {
- margin: 10px 0;
- padding: 5px 2%;
- overflow: auto;
- background: #fafafa;
- color: #666;
- font-size: 90%;
- }
- .log>.date {
- margin: 0 10px 0 0;
- padding: 5px 10px;
- border-radius: 20px;
- }
- .log.error>.date {
- background: #e74c3c;
- color: #fff;
- }
- .log.warning>.date {
- background: #f39c12;
- }
- .log.notice>.date {
- background: #ecf0f1;
- }
- .log.debug>.date {
- background: #111;
- color: #eee;
- }
-
-.form-group table {
- border-collapse:collapse;
- margin:10px 0 0 220px;
- text-align:center;
-}
-
-.form-group tr, .form-group th, .form-group td {
- font-weight:normal;
- padding:.5em;
-}
-
-select.number option {
- text-align:right;
-}
-
-@media(min-width: 841px) {
- .flux:not(.current):hover .item.title {
- max-width: calc(100% - 580px);
- padding-right: 1.5em;
- position: absolute;
- }
-}
-
-@media(max-width: 840px) {
- .header,
- .aside .btn-important,
- .aside .feeds .dropdown,
- .flux_header .item.website span,
- .item.date {
- display: none;
- }
- .flux_header .item.website {
- width: 40px;
- text-align: center;
- }
- .flux_header .item.website .favicon {
- padding: 12px;
- }
-
- .nav-login {
- display: block;
- }
-
- .pagination {
- margin: 0 0 40px;
- }
- .pagination .pager-previous, .pagination .pager-next {
- width: 100px;
- }
-
- .toggle_aside, .btn.toggle_aside {
- display: inline-block;
- }
- .aside {
- position: fixed;
- top: 0; left: 0;
- width: 0;
- overflow: hidden;
- z-index: 10;
- transition: width 200ms linear;
- background: #ecf0f1;
- }
- .aside.aside_flux {
- padding: 10px 0 0;
- }
- .aside:target {
- width: 80%;
- border-right: 1px solid #aaa;
- overflow: auto;
- }
- .aside .toggle_aside {
- position: absolute;
- right: 10px;
- display: inline-block;
- width: 26px;
- height: 26px;
- margin: 0 10px 0 0;
- border-radius: 3px;
- text-align: center;
- line-height: 24px;
- background: #95a5a6;
- }
- .aside .toggle_aside:hover {
- background: #7f8c8d;
- }
- .aside .categories {
- margin: 30px 0;
- }
-
- #nav_entries {
- width: 100%;
- }
-
- .nav_menu .btn {
- margin: 5px 10px;
- }
- .nav_menu .stick {
- margin: 0 10px;
- }
- .nav_menu .stick .btn {
- margin: 5px 0;
- }
- .nav_menu .search {
- display: inline-block;
- max-width: 97%;
- }
- .nav_menu .search input {
- max-width: 97%;
- width: 90px;
- }
- .nav_menu .search input:focus {
- width: 400px;
- }
-
- #panel {
- left: 5px; right: 5px;
- }
-
- .day .date {
- display: none;
- }
- .day .name {
- height: 2.6em;
- font-size: 1em;
- text-shadow: none;
- }
-
- .notification {
- top: 0;
- left: 0;
- right: 0;
- border-radius: 0;
- }
- .notification a.close,
- .notification.good a.close,
- .notification.bad a.close {
- left: 0; right: 0;
- top: 0; bottom: 0;
- width: auto;
- height: auto;
- background: transparent;
- border: none;
- }
- .notification a.close .icon {
- display: none;
- }
-}
-
-/*** FALLBACK ***/
-.dropdown-menu:after {
- -moz-transform: rotate(45deg);
- -webkit-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
-}
-
-input.extend {
- -moz-transition: width 200ms linear;
- -webkit-transition: width 200ms linear;
- -o-transition: width 200ms linear;
- -ms-transition: width 200ms linear;
-}
-
-@media print {
- .header,
- .aside,
- .nav_menu,
- .day,
- .flux_header,
- .flux_content .bottom,
- .pagination {
- display: none;
- }
-
- html, body {
- background: #fff;
- color: #000;
- font-family: Serif;
- font-size: 12pt;
- }
-
- #global,
- .flux_content {
- display: block !important;
- }
-
- .flux_content .content {
- width: 100% !important;
- text-align: justify;
- }
-
- .flux_content .content a {
- color: #000;
- }
- .flux_content .content a:after {
- content: " (" attr(href) ") ";
- text-decoration: underline;
- }
-}
-
-.stat {
- border:1px solid #aaa;
- border-radius:10px;
- box-shadow:2px 2px 5px #aaa;
- margin:10px 0;
- padding:0 5px;
-}
-.stat > h2 {
- border-bottom:1px solid #aaa;
- margin:0 -5px;
- padding-left:5px;
-}
-.stat > div {
- margin:5px 0;
-}
-.stat > table {
- border-collapse:collapse;
- margin:5px 0;
- width:100%;
-}
-.stat > table > thead > tr {
- border-bottom:2px solid #aaa;
-}
-.stat > table > tbody > tr {
- border-bottom:1px solid #aaa;
-}
-.stat > table > tbody > tr:last-child {
- border-bottom:0;
-}
-.stat > table th, .stat > table td {
- border-left:2px solid #aaa;
- padding:5px;
-}
-.stat > table th:first-child, .stat > table td:first-child {
- border-left:0;
-}
-.stat > table td.numeric{
- margin:5px 0;
- text-align:center;
-} \ No newline at end of file
diff --git a/p/themes/Flat/global.css b/p/themes/Flat/global.css
deleted file mode 100644
index 68e9fa359..000000000
--- a/p/themes/Flat/global.css
+++ /dev/null
@@ -1,528 +0,0 @@
-@charset "UTF-8";
-
-/* FONTS */
-@font-face {
- font-family: "OpenSans";
- src: url("../fonts/openSans.woff") format("woff");
-}
-
-
-* {
- margin: 0;
- padding: 0;
-}
-html, body {
- height: 100%;
- font-size: 95%;
- font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
-}
-
-/* LIENS */
-a {
- color: #2980b9;
- text-decoration: none;
-}
- a:hover {
- text-decoration: underline;
- }
-
-/* LISTES */
-ul, ol, dl {
- margin: 10px 0 10px 30px;
- line-height: 190%;
-}
- dd {
- margin: 0 0 10px 30px;
- }
-
-/* TITRES */
-h1, h2, h3 {
- min-height: 40px;
- margin: 15px 0 5px;
- line-height: 40px;
-}
-
-/* IMG */
-figure {
- margin: 5px 0 10px;
- text-align: center;
-}
- figcaption {
- display: inline-block;
- padding: 3px 20px;
- color: #999;
- font-style: italic;
- border-bottom: 1px solid #ccc;
- }
-img {
- height: auto;
- max-width: 100%;
- vertical-align: middle;
-}
- a img {
- border: none;
- }
-
-/* VIDEOS */
-iframe, embed, object, video {
- max-width: 100%;
-}
-
-/* FORMULAIRES */
-legend {
- display: inline-block;
- margin: 20px 0 5px;
- padding: 5px 20px;
- font-size: 150%;
- clear: both;
- background: #ecf0f1;
- border-radius: 20px;
-}
-label {
- display: block;
- min-height: 25px;
- padding: 5px 0;
- font-size: 12px;
- line-height: 25px;
- cursor: pointer;
- font-weight: bold;
- color: #444;
-}
-input {
- width: 180px;
-}
-textarea {
- width: 360px;
- height: 100px;
-}
-input, select, textarea {
- display: inline-block;
- max-width: 100%;
- min-height: 25px;
- padding: 5px;
- background: #FFF;
- border: none;
- border-bottom: 3px solid #ddd;
- color: #666;
- line-height: 25px;
- vertical-align: middle;
- border-radius: 5px;
-}
- option {
- padding:0 .5em 0 .5em;
- }
- input[type="radio"],
- input[type="checkbox"] {
- width: 15px !important;
- min-height: 15px !important;
- }
- input:focus, select:focus, textarea:focus {
- color: #333;
- border-color: #2980b9;
- }
- input:invalid, select:invalid {
- border-color: red;
- box-shadow: 0 0 2px 1px red;
- }
- input:focus.extend {
- width: 300px;
- transition: width 200ms linear;
- }
-
-.form-group {
- margin: 5px 0;
- border: 1px solid transparent;
-}
- .form-group:after {
- content: "";
- display: block;
- clear: both;
- }
- .form-group:hover {
- background: #fff;
- border: 1px solid #eee;
- border-radius: 3px;
- }
- .form-group.form-actions {
- min-width: 250px;
- margin: 20px 0;
- padding: 5px 0;
- background: #ecf0f1;
- border-top: 3px solid #bdc3c7;
- border-radius: 5px 5px 0 0;
- }
- .form-group.form-actions .btn {
- margin: 0 10px;
- }
- .form-group .group-name {
- display: block;
- float: left;
- width: 200px;
- padding: 10px 0;
- text-align: right;
- }
- .form-group .group-controls {
- min-width: 250px;
- min-height: 25px;
- margin: 0 0 0 220px;
- padding: 5px 0;
- }
- .form-group .group-controls label {
- font-weight: normal;
- font-size: 14px;
- color: #000;
- }
- .form-group .group-controls .control {
- display: block;
- min-height: 30px;
- padding: 5px 0;
- line-height: 25px;
- font-size: 14px;
- }
-
-.stick {
- display: inline-block;
- white-space: nowrap;
- font-size: 0px;
- vertical-align: middle;
-}
- .stick .btn,
- .stick input {
- font-size: 14px;
- border-radius: 0;
- }
- .stick .btn:first-child,
- .stick input:first-child {
- border-radius: 5px 0 0 5px;
- }
- .stick .btn:last-child,
- .stick input:last-child,
- .stick .btn + .dropdown > .btn {
- border-radius: 0 5px 5px 0;
- }
- .stick .btn + .dropdown a {
- font-size: 12px;
- }
-
-.btn {
- display: inline-block;
- min-height: 38px;
- min-width: 18px;
- padding: 5px 10px;
- background: #3498db;
- border-radius: 5px;
- border: none;
- border-bottom: 3px solid #2980b9;
- color: #fff;
- line-height: 20px;
- vertical-align: middle;
- cursor: pointer;
- overflow: hidden;
-}
- a.btn {
- min-height: 25px;
- line-height: 25px;
- }
- .btn.active,
- .btn:active,
- .btn:hover,
- .dropdown-target:target ~ .btn.dropdown-toggle {
- background: #2980b9;
- text-decoration: none;
- }
-
- .btn-important {
- background: #e67e22;
- color: #fff;
- border-bottom: 3px solid #d35400;
- }
- .btn-important:active,
- .btn-important:hover {
- background: #d35400;
- }
-
- .btn-attention {
- background: #e74c3c;
- color: #fff;
- border-bottom: 3px solid #c0392b;
- }
- .btn-attention:hover,
- .btn-attention:active {
- background: #c0392b;
- }
-
-/* NAVIGATION */
-.nav-list {
- border-right: 1px solid #ecf0f1;
-}
-.nav-list .nav-header,
-.nav-list .item {
- display: block;
- height: 35px;
- line-height: 35px;
- margin: 5px 0;
-}
- .nav-list .item:hover,
- .nav-list .item.active {
- background: #2980b9;
- color: #fff;
- }
- .nav-list .item:hover a,
- .nav-list .item.active a {
- color: #fff;
- }
- .nav-list .disable {
- color: #aaa;
- background: #fafafa;
- text-align: center;
- }
- .nav-list .item > * {
- display: block;
- padding: 0 10px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .nav-list a:hover {
- text-decoration: none;
- }
- .nav-list .item.error a {
- color: #BD362F;
- }
- .nav-list .item:hover.error a,
- .nav-list .item.active.error a {
- color: #fff;
- background: #BD362F;
- }
- .nav-list .item.empty a {
- color: #f39c12;
- }
- .nav-list .item:hover.empty a,
- .nav-list .item.active.empty a {
- color: #fff;
- background: #f39c12;
- }
-
- .nav-list .nav-header {
- padding: 0 10px;
- margin: 0;
- color: #fff;
- background: #34495e;
- font-weight: bold;
- }
- .nav-list .separator {
- display: block;
- height: 0;
- margin: 5px 0;
- border-bottom: 1px solid #ddd;
- }
-
- .nav-list .nav-form {
- padding: 3px;
- text-align: center;
- }
-
-.nav-head {
- display: block;
- margin: 0;
- background: #34495e;
- color: #fff;
- text-align: right;
-}
- .nav-head a {
- color: #fff;
- }
- .nav-head .item {
- display: inline-block;
- padding: 5px 10px;
- }
-
-/* HORIZONTAL-LIST */
-.horizontal-list {
- display: table;
- table-layout: fixed;
- margin: 0;
- padding: 0;
- width: 100%;
-}
- .horizontal-list .item {
- display: table-cell;
- vertical-align: middle;
- }
-
-/* DROPDOWN */
-.dropdown {
- position: relative;
- display: inline-block;
-}
- .dropdown-target {
- display: none;
- }
-
- .dropdown-menu {
- display: none;
- min-width: 200px;
- margin: 5px 0 0;
- padding: 5px 0;
- position: absolute;
- right: 0px;
- background: #fff;
- border: 1px solid #95a5a6;
- border-radius: 3px;
- text-align: left;
- }
- .dropdown-menu:after {
- content: "";
- position: absolute;
- top: -6px;
- right: 13px;
- width: 10px;
- height: 10px;
- background: #fff;
- border-top: 1px solid #95a5a6;
- border-left: 1px solid #95a5a6;
- z-index: -10;
- transform: rotate(45deg);
- }
- .dropdown-header {
- display: block;
- padding: 0 5px;
- color: #34495e;
- font-weight: bold;
- font-size: 14px;
- line-height: 30px;
- }
- .dropdown-menu > .item {
- display: block;
- height: 30px;
- font-size: 90%;
- line-height: 30px;
- }
- .dropdown-menu > .item > a {
- display: block;
- padding: 0 25px;
- line-height: 30px;
- }
- .dropdown-menu > .item.share > a {
- display: list-item;
- list-style-position:inside;
- list-style-type:decimal;
- }
- .dropdown-menu > .item:hover > a {
- background: #2980b9;
- color: #fff;
- }
- .dropdown-menu > .item:hover > a {
- color: #fff;
- text-decoration: none;
- }
- .dropdown-menu .input {
- display: block;
- height: 40px;
- font-size: 90%;
- line-height: 30px;
- }
- .dropdown-menu label {
- font-weight: normal;
- }
- .dropdown-menu .input select,
- .dropdown-menu .input input {
- display: block;
- height: 20px;
- width: 95%;
- margin: auto;
- padding: 2px 5px;
- border-radius: 3px;
- }
- .dropdown-menu .input select {
- width: 70%;
- height: auto;
- }
- .dropdown-menu .separator {
- display: block;
- height: 0;
- margin: 5px 0;
- border-bottom: 1px solid #95a5a6;
- }
- .dropdown-target:target ~ .dropdown-menu {
- display: block;
- z-index: 10;
- }
- .dropdown-close {
- display: inline;
- }
- .dropdown-close a {
- font-size: 0;
- position: fixed;
- top: 0; bottom: 0;
- left: 0; right: 0;
- display: block;
- z-index: -10;
- }
-
-/* ALERTS */
-.alert {
- display: block;
- width: 90%;
- margin: 15px auto;
- padding: 10px 15px;
- background: #f4f4f4;
- border: 1px solid #ccc;
- border-right: 1px solid #aaa;
- border-bottom: 1px solid #aaa;
- border-radius: 5px;
- color: #aaa;
- text-shadow: 0 0 1px #eee;
-}
- .alert-head {
- margin: 0;
- font-weight: bold;
- font-size: 110%;
- }
- .alert > a {
- color: inherit;
- text-decoration: underline;
- }
- .alert-warn {
- background: #ffe;
- border: 1px solid #eeb;
- color: #c95;
- }
- .alert-success {
- background: #dfd;
- border: 1px solid #cec;
- color: #484;
- }
- .alert-error {
- background: #fdd;
- border: 1px solid #ecc;
- color: #844;
- }
-
-/* ICÔNES */
-.icon {
- display: inline-block;
- width: 16px;
- height: 16px;
- vertical-align: middle;
- line-height: 16px;
-}
-
-/* Prompt (centré) */
-.prompt {
- text-align: center;
-}
- .prompt label {
- text-align: left;
- }
- .prompt form {
- margin: 1em auto 2.5em auto;
- width: 10em;
- }
- .prompt input {
- margin: .4em auto 1.1em auto;
- width: 100%;
- }
- .prompt p {
- margin: 20px 0;
- }
diff --git a/p/themes/Flat/icons/icon.svg b/p/themes/Flat/icons/icon.svg
new file mode 100644
index 000000000..9657d020c
--- /dev/null
+++ b/p/themes/Flat/icons/icon.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
+ <title>Logo FreshRSS</title>
+ <circle fill="#2980b9" cx="128" cy="128" r="33"/>
+ <g fill="none" stroke="#2980b9" stroke-width="24">
+ <g stroke-opacity="0.3">
+ <path d="M12,128 A116,116 0 1,1 128,244"/>
+ <path d="M54,128 A74,74 0 1,1 128,202"/>
+ </g>
+ <path d="M128,12 A116,116 0 0,1 244,128"/>
+ <path d="M128,54 A74,74 0 0,1 202,128"/>
+ </g>
+</svg>
diff --git a/p/themes/Flat/icons/key.svg b/p/themes/Flat/icons/key.svg
new file mode 100644
index 000000000..77d0218e5
--- /dev/null
+++ b/p/themes/Flat/icons/key.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(-340.99994,-257)" fill="#ffffff">
+<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m346,260c-2.7496,0-5,2.2504-5,5s2.2504,5,5,5c1.5862,0,2.9034-0.84459,3.8125-2h4.8438,0.75l0.21875-0.75,1.0312-4,0.3125-1.25h-1.2812-5.875c-0.90914-1.1554-2.2263-2-3.8125-2zm0,2c1.1158,0,2.0379,0.59507,2.5625,1.5l0.3125,0.5h0.5625,4.9688l-0.53125,2h-4.4375-0.5625l-0.3125,0.5c-0.52462,0.90493-1.4466,1.5-2.5625,1.5-1.6687,0-3-1.3313-3-3s1.3313-3,3-3z"/>
+<path opacity="0.35" style="enable-background:accumulate;color:#000000;" d="M355.5,265,350,265,349.44,267,355,267z" fill-rule="nonzero"/>
+<path style="enable-background:accumulate;color:#000000;" d="m346,265c0,0.55228-0.44772,1-1,1s-1-0.44772-1-1,0.44772-1,1-1,1,0.44772,1,1z" fill-rule="nonzero"/>
+</g>
+</svg>
diff --git a/p/themes/Flat/metadata.json b/p/themes/Flat/metadata.json
index 6b94d11c2..182c82470 100644
--- a/p/themes/Flat/metadata.json
+++ b/p/themes/Flat/metadata.json
@@ -2,6 +2,6 @@
"name": "Flat design",
"author": "Marien Fressinaud",
"description": "Thème plat pour FreshRSS",
- "version": 0.1,
- "files": ["global.css", "freshrss.css"]
+ "version": 0.2,
+ "files": ["template.css", "flat.css"]
} \ No newline at end of file
diff --git a/p/themes/Flat/template.css b/p/themes/Flat/template.css
new file mode 100644
index 000000000..09ecaf685
--- /dev/null
+++ b/p/themes/Flat/template.css
@@ -0,0 +1,695 @@
+@charset "UTF-8";
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ margin: 0;
+ padding: 0;
+ font-size: 100%;
+}
+
+/*=== Links */
+a {
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+/*=== Lists */
+ul, ol, dd {
+ margin: 0;
+ padding: 0;
+}
+
+/*=== Titles */
+h1 {
+ margin: 0.6em 0 0.3em;
+ font-size: 1.5em;
+ line-height: 1.6em;
+}
+h2 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.3em;
+ line-height: 2em;
+}
+h3 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.1em;
+ line-height: 2em;
+}
+
+/*=== Paragraphs */
+p {
+ margin: 1em 0 0.5em;
+ font-size: 1em;
+}
+
+/*=== Images */
+img {
+ height: auto;
+ max-width: 100%;
+}
+img.favicon {
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+}
+
+/*=== Videos */
+iframe, embed, object, video {
+ max-width: 100%;
+}
+
+/*=== Forms */
+legend {
+ display: block;
+ width: 100%;
+ clear: both;
+}
+label {
+ display: block;
+}
+input {
+ width: 180px;
+}
+textarea {
+ width: 300px;
+}
+input, select, textarea {
+ display: inline-block;
+ max-width: 100%;
+}
+input[type="radio"],
+input[type="checkbox"] {
+ width: 15px !important;
+ min-height: 15px !important;
+}
+input.extend:focus {
+ width: 300px;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+.form-group.form-actions {
+ min-width: 250px;
+}
+.form-group .group-name {
+ display: block;
+ float: left;
+ width: 200px;
+}
+.form-group .group-controls {
+ min-width: 250px;
+ margin: 0 0 0 220px;
+}
+.form-group .group-controls .control {
+ display: block;
+}
+
+/*=== Buttons */
+.stick {
+ display: inline-block;
+ white-space: nowrap;
+}
+.btn,
+a.btn {
+ display: inline-block;
+ cursor: pointer;
+ overflow: hidden;
+}
+.btn-important {
+ font-weight: bold;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ display: block;
+}
+.nav-list .item,
+.nav-list .item > a {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.nav-head {
+ display: block;
+}
+.nav-head .item {
+ display: inline-block;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+.horizontal-list .item {
+ display: table-cell;
+}
+
+/*=== Dropdown */
+.dropdown {
+ position: relative;
+ display: inline-block;
+}
+.dropdown-target {
+ display: none;
+}
+.dropdown-menu {
+ display: none;
+ min-width: 200px;
+ margin: 0;
+ position: absolute;
+ right: 0;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.dropdown-header {
+ display: block;
+}
+.dropdown-menu > .item {
+ display: block;
+}
+.dropdown-menu > .item > a,
+.dropdown-menu > .item > span {
+ display: block;
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ content: '✓';
+}
+.dropdown-menu .input {
+ display: block;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ display: block;
+ max-width: 95%;
+}
+.dropdown-target:target ~ .dropdown-menu {
+ display: block;
+ z-index: 10;
+}
+.dropdown-close {
+ display: inline;
+}
+.dropdown-close a {
+ font-size: 0;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+ z-index: -10;
+}
+.separator {
+ display: block;
+ height: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+/*=== Alerts */
+.alert {
+ display: block;
+ width: 90%;
+}
+.group-controls .alert {
+ width: 100%
+}
+.alert-head {
+ margin: 0;
+ font-weight: bold;
+}
+.alert ul {
+ margin: 5px 20px;
+}
+
+/*=== Icons */
+.icon {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ line-height: 16px;
+}
+
+/*=== Pagination */
+.pagination {
+ display: table;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+}
+.pagination .item {
+ display: table-cell;
+}
+.pagination .pager-first,
+.pagination .pager-previous,
+.pagination .pager-next,
+.pagination .pager-last {
+ width: 100px;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+.header > .item {
+ display: table-cell;
+}
+.header > .item.title {
+ width: 250px;
+ white-space: nowrap;
+}
+.header > .item.title h1 {
+ display: inline-block;
+}
+.header > .item.title .logo {
+ display: inline-block;
+ height: 32px;
+ width: 32px;
+ vertical-align: middle;
+}
+.header > .item.configure {
+ width: 100px;
+}
+
+/*=== Body */
+#global {
+ display: table;
+ width: 100%;
+ height: 100%;
+ table-layout: fixed;
+}
+.aside {
+ display: table-cell;
+ height: 100%;
+ width: 250px;
+ vertical-align: top;
+}
+.aside.aside_flux {
+ background: #fff;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ list-style: none;
+ margin: 0;
+}
+.category {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.category .btn:not([data-unread="0"]):after {
+ content: attr(data-unread);
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds {
+ width: 100%;
+ list-style: none;
+}
+.categories .feeds:not(.active) {
+ display: none;
+}
+.categories .feeds .feed {
+ display: inline-block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+.categories .feeds .feed:not([data-unread="0"]):before {
+ content: "(" attr(data-unread) ") ";
+}
+.categories .feeds .dropdown-menu {
+ left: 0;
+}
+.categories .feeds .item .dropdown-toggle > .icon {
+ visibility: hidden;
+ cursor: pointer;
+ vertical-align: top;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ visibility: visible;
+}
+
+/*=== New article notification */
+#new-article {
+ display: none;
+}
+#new-article > a {
+ display: block;
+}
+
+/*=== Day indication */
+.day .name {
+ position: absolute;
+ right: 0;
+ width: 50%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+/*=== Feed article header and footer */
+.flux_header {
+ position: relative;
+}
+.flux .item {
+ line-height: 40px;
+ white-space: nowrap;
+}
+.flux .item.manage,
+.flux .item.link {
+ width: 40px;
+ text-align: center;
+}
+.flux .item.website {
+ width: 200px;
+}
+.flux.not_read .item.title,
+.flux.current .item.title {
+ font-weight: bold;
+}
+.flux:not(.current):hover .item.title {
+ position: absolute;
+ max-width: calc(100% - 320px);
+ background: #fff;
+}
+.flux .item.title a {
+ color: #000;
+ text-decoration: none;
+}
+.flux .item.date {
+ width: 145px;
+ text-align: right;
+}
+.flux .item > a {
+ display: block;
+}
+.flux .item > a {
+ display: block;
+ text-decoration: none;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.flux .item.share > a {
+ display: list-item;
+ list-style-position: inside;
+ list-style-type: decimal;
+}
+
+/*=== Feed article content */
+.hide_posts > .flux:not(.active) > .flux_content {
+ display: none;
+}
+.content {
+ min-height: 20em;
+ margin: auto;
+ line-height: 1.7em;
+ word-wrap: break-word;
+}
+.content.large {
+ max-width: 1000px;
+}
+.content.medium {
+ max-width: 800px;
+}
+.content.thin {
+ max-width: 550px;
+}
+.content ul,
+.content ol,
+.content dd {
+ margin: 0 0 0 15px;
+ padding: 0 0 5px 15px;
+}
+.content pre {
+ overflow: auto;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ position: absolute;
+ top: 1em;
+ left: 25%; right: 25%;
+ z-index: 10;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.notification.closed {
+ display: none;
+}
+.notification a.close {
+ position: absolute;
+ top: 0; bottom: 0;
+ right: 0;
+ display: inline-block;
+}
+
+#actualizeProgress {
+ position: fixed;
+}
+#actualizeProgress progress {
+ max-width: 100%;
+ vertical-align: middle;
+}
+#actualizeProgress .progress {
+ vertical-align: middle;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ position: fixed;
+ bottom: 0; left: 0;
+ display: table;
+ width: 250px;
+ background: #fff;
+ table-layout: fixed;
+}
+#nav_entries .item {
+ display: table-cell;
+ width: 30%;
+}
+#nav_entries a {
+ display: block;
+}
+
+/*=== "Load more" part */
+#load_more {
+ min-height: 40px;
+}
+.loading {
+ background: url("loader.gif") center center no-repeat;
+ font-size: 0;
+}
+#bigMarkAsRead {
+ display: block;
+ padding: 3em 0;
+ text-align: center;
+}
+.bigTick {
+ font-size: 7em;
+ line-height: 1.6em;
+}
+
+/*=== Statistiques */
+.stat > table {
+ width: 100%;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+/*=== Category boxes */
+#stream.global .box-category {
+ display: inline-block;
+ width: 19em;
+ max-width: 95%;
+ margin: 20px 10px;
+ border: 1px solid #ccc;
+ vertical-align: top;
+}
+#stream.global .category {
+ width: 100%;
+}
+#stream.global .btn {
+ display: block;
+}
+#stream.global .box-category .feeds {
+ display: block;
+ overflow: auto;
+}
+#stream.global .box-category .feed {
+ width: 19em;
+ max-width: 90%;
+}
+
+/*=== Panel */
+#overlay {
+ display: none;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ background: rgba(0, 0, 0, 0.9);
+}
+#panel {
+ display: none;
+ position: fixed;
+ top: 1em; bottom: 1em;
+ left: 2em; right: 2em;
+ overflow: auto;
+ background: #fff;
+}
+#panel .close {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+}
+#panel .close img {
+ display: none;
+}
+
+/*=== DIVERS */
+/*===========*/
+.nav-login,
+.nav_menu .search,
+.nav_menu .toggle_aside {
+ display: none;
+}
+
+.aside .toggle_aside {
+ position: absolute;
+ right: 0;
+ display: none;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .header,
+ .aside .btn-important,
+ .aside .feeds .dropdown,
+ .flux_header .item.website span,
+ .item.date, .day .date,
+ .dropdown-menu > .no-mobile,
+ .no-mobile {
+ display: none;
+ }
+ .nav-login {
+ display: block;
+ }
+ .nav_menu .toggle_aside,
+ .aside .toggle_aside,
+ .nav_menu .search,
+ #panel .close img {
+ display: inline-block;
+ }
+
+ .aside {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0;
+ width: 0;
+ overflow: hidden;
+ z-index: 100;
+ }
+ .aside:target {
+ width: 90%;
+ overflow: auto;
+ }
+ .aside .categories {
+ margin: 10px 0 75px;
+ }
+
+ .flux_header .item.website {
+ width: 40px;
+ }
+
+ .flux:not(.current):hover .item.title {
+ position: relative;
+ width: auto;
+ white-space: nowrap;
+ }
+
+ .notification {
+ top: 0;
+ left: 0;
+ right: 0;
+ }
+
+ #nav_entries {
+ width: 100%;
+ }
+
+ #stream.global .box-category {
+ margin: 10px 0;
+ }
+
+ #panel {
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ }
+ #panel .close {
+ top: 0; right: 0;
+ left: auto; bottom: auto;
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ }
+}
+
+/*=== PRINTER */
+/*============*/
+@media print {
+ .header, .aside,
+ .nav_menu, .day,
+ .flux_header,
+ .flux_content .bottom,
+ .pagination,
+ #nav_entries {
+ display: none;
+ }
+ html, body {
+ background: #fff;
+ color: #000;
+ font-family: Serif;
+ }
+ #global,
+ .flux_content {
+ display: block !important;
+ }
+ .flux_content .content {
+ width: 100% !important;
+ }
+ .flux_content .content a {
+ color: #000;
+ }
+ .flux_content .content a:after {
+ content: " [" attr(href) "] ";
+ font-style: italic;
+ }
+}
diff --git a/p/themes/Origine/origine.css b/p/themes/Origine/origine.css
index 1835e9ff9..0b95e2d70 100644
--- a/p/themes/Origine/origine.css
+++ b/p/themes/Origine/origine.css
@@ -11,11 +11,13 @@
html, body {
height: 100%;
font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
+ background: #fafafa;
}
/*=== Links */
a {
color: #0062be;
+ outline: none;
}
/*=== Forms */
@@ -362,6 +364,10 @@ a.btn {
padding: 0 25px;
line-height: 2.5em;
}
+.dropdown-menu > .item > span {
+ padding: 0 25px;
+ line-height: 2em;
+}
.dropdown-menu > .item:hover {
background: #0062BE;
color: #fff;
@@ -400,7 +406,7 @@ a.btn {
font-size: 0.9em;
}
.alert-head {
- font-size: 1.2em;
+ font-size: 1.15em;
}
.alert > a {
color: inherit;
@@ -464,15 +470,20 @@ a.btn {
/*===============*/
/*=== Header */
.header {
+ height: 85px;
background: #f4f4f4;
}
.header > .item {
- padding: 10px 0;
+ padding: 10px;
border-bottom: 1px solid #aaa;
vertical-align: middle;
text-align: center;
}
+.header > .item.title{
+ width: 230px;
+}
.header > .item.title h1 {
+ margin: 0.5em 0;
text-shadow: 1px -1px 0 #ccc;
}
.header > .item.title h1 a {
@@ -487,14 +498,14 @@ a.btn {
/*=== Body */
#global {
- background: #fafafa;
+ height: calc(100% - 85px);
}
.aside {
border-right: 1px solid #aaa;
background: #fff;
}
.aside.aside_flux {
- padding: 10px 0 40px;
+ padding: 10px 0 50px;
}
/*=== Aside main page (categories) */
@@ -690,9 +701,6 @@ a.btn {
color: #666;
font-size: 0.7rem;
}
-.flux:not(.current):hover .item.title {
- top: 1px;
-}
.flux .bottom {
font-size: 0.8rem;
@@ -920,7 +928,7 @@ a.btn {
padding: 5px 10px;
background: #fafafa;
color: #333;
- font-size: 90%;
+ font-size: 0.8rem;
}
.log+.log {
border-top: 1px solid #aaa;
@@ -998,10 +1006,6 @@ a.btn {
text-shadow: none;
}
- .flux_header .item.website .favicon {
- padding: 12px;
- }
-
.pagination {
margin: 0 0 3.5em;
}
diff --git a/p/themes/Origine/template.css b/p/themes/Origine/template.css
index f68fdfca3..09ecaf685 100644
--- a/p/themes/Origine/template.css
+++ b/p/themes/Origine/template.css
@@ -180,7 +180,8 @@ a.btn {
.dropdown-menu > .item {
display: block;
}
-.dropdown-menu > .item > a {
+.dropdown-menu > .item > a,
+.dropdown-menu > .item > span {
display: block;
}
.dropdown-menu > .item[aria-checked="true"] > a:before {
@@ -220,10 +221,16 @@ a.btn {
display: block;
width: 90%;
}
+.group-controls .alert {
+ width: 100%
+}
.alert-head {
margin: 0;
font-weight: bold;
}
+.alert ul {
+ margin: 5px 20px;
+}
/*=== Icons */
.icon {
@@ -587,7 +594,9 @@ a.btn {
.aside .btn-important,
.aside .feeds .dropdown,
.flux_header .item.website span,
- .item.date, .day .date {
+ .item.date, .day .date,
+ .dropdown-menu > .no-mobile,
+ .no-mobile {
display: none;
}
.nav-login {
diff --git a/p/themes/base-theme/README.md b/p/themes/base-theme/README.md
new file mode 100644
index 000000000..6f186e15c
--- /dev/null
+++ b/p/themes/base-theme/README.md
@@ -0,0 +1,12 @@
+FreshRSS-base-theme
+===================
+
+A base theme for [FreshRSS](http://freshrss.org)
+
+1. Custom ```base.css``` file with colors, backgrounds and borders
+2. Change information in ```metadata.json``` file (at least, give a name!)
+3. Choose your new theme in FreshRSS configuration
+4. Enjoy your wonderful theme!
+
+Don't hesitate to share your theme with us [on Github](https://github.com/marienfressinaud/FreshRSS/issues) :)
+
diff --git a/p/themes/base-theme/base.css b/p/themes/base-theme/base.css
new file mode 100644
index 000000000..c45b1812e
--- /dev/null
+++ b/p/themes/base-theme/base.css
@@ -0,0 +1,762 @@
+@charset "UTF-8";
+
+/*=== FONTS */
+@font-face {
+ font-family: "OpenSans";
+ src: url("../fonts/openSans.woff") format("woff");
+}
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ height: 100%;
+ font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
+}
+
+/*=== Links */
+a {
+ outline: none;
+}
+
+/*=== Forms */
+legend {
+ margin: 20px 0 5px;
+ padding: 5px 0;
+ font-size: 1.4em;
+}
+label {
+ min-height: 25px;
+ padding: 5px 0;
+ cursor: pointer;
+}
+textarea {
+ width: 360px;
+ height: 100px;
+}
+input, select, textarea {
+ min-height: 25px;
+ padding: 5px;
+ line-height: 25px;
+ vertical-align: middle;
+}
+option {
+ padding: 0 .5em;
+}
+input:focus, select:focus, textarea:focus {
+}
+input:invalid, select:invalid {
+}
+input:disabled, select:disabled {
+}
+input.extend {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+}
+
+/*=== Tables */
+table {
+ border-collapse: collapse;
+}
+
+tr, th, td {
+ padding: 0.5em;
+}
+th {
+}
+form td,
+form th {
+ font-weight: normal;
+ text-align: center;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group.form-actions {
+ padding: 5px 0;
+}
+.form-group.form-actions .btn {
+ margin: 0 10px;
+}
+.form-group .group-name {
+ padding: 10px 0;
+ text-align: right;
+}
+.form-group .group-controls {
+ min-height: 25px;
+ padding: 5px 0;
+}
+.form-group table {
+ margin: 10px 0 0 220px;
+}
+
+/*=== Buttons */
+.stick {
+ vertical-align: middle;
+ font-size: 0;
+}
+.stick input,
+.stick .btn {
+}
+.stick .btn:first-child,
+.stick input:first-child {
+}
+.stick .btn-important:first-child {
+}
+.stick .btn:last-child,
+.stick input:last-child {
+}
+.stick .btn + .btn,
+.stick .btn + input,
+.stick .btn + .dropdown > .btn,
+.stick input + .btn,
+.stick input + input,
+.stick input + .dropdown > .btn,
+.stick .dropdown + .btn,
+.stick .dropdown + input,
+.stick .dropdown + .dropdown > .btn {
+}
+.stick input + .btn {
+}
+.stick .btn + .dropdown > .btn {
+}
+
+.btn {
+ display: inline-block;
+ min-height: 37px;
+ min-width: 15px;
+ margin: 0;
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ vertical-align: middle;
+ cursor: pointer;
+ overflow: hidden;
+}
+a.btn {
+ min-height: 25px;
+ line-height: 25px;
+}
+.btn:hover {
+ text-decoration: none;
+}
+.btn.active,
+.btn:active,
+.dropdown-target:target ~ .btn.dropdown-toggle {
+}
+
+.btn-important {
+ font-weight: normal;
+}
+.btn-important:hover {
+}
+.btn-important:active {
+}
+
+.btn-attention {
+}
+.btn-attention:hover {
+}
+.btn-attention:active {
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ height: 2.5em;
+ line-height: 2.5em;
+ font-size: 0.9rem;
+}
+.nav-list .item:hover {
+}
+.nav-list .item:hover a {
+}
+.nav-list .item.active {
+}
+.nav-list .item.active a {
+}
+.nav-list .disable {
+ text-align: center;
+}
+.nav-list .item > a {
+ padding: 0 10px;
+}
+.nav-list a:hover {
+ text-decoration: none;
+}
+.nav-list .item.empty a {
+}
+.nav-list .item.active.empty a {
+}
+.nav-list .item.error a {
+}
+.nav-list .item.active.error a {
+}
+
+.nav-list .nav-header {
+ padding: 0 10px;
+ font-weight: bold;
+}
+
+.nav-list .nav-form {
+ padding: 3px;
+ text-align: center;
+}
+
+.nav-head {
+ margin: 0;
+ text-align: right;
+}
+.nav-head .item {
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ line-height: 1.5rem;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ margin: 0;
+ padding: 0;
+}
+.horizontal-list .item {
+ vertical-align: middle;
+}
+
+/*=== Dropdown */
+.dropdown-menu {
+ margin: 5px 0 0;
+ padding: 5px 0;
+ font-size: 0.8rem;
+ text-align: left;
+}
+.dropdown-menu:after {
+ content: "";
+ position: absolute;
+ top: -6px;
+ right: 13px;
+ width: 10px;
+ height: 10px;
+ z-index: -10;
+ transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+}
+.dropdown-header {
+ padding: 0 5px 5px;
+ font-weight: bold;
+ text-align: left;
+}
+.dropdown-menu > .item {
+}
+.dropdown-menu > .item > a {
+ padding: 0 25px;
+ line-height: 2.5em;
+}
+.dropdown-menu > .item > span {
+ padding: 0 25px;
+ line-height: 2em;
+}
+.dropdown-menu > .item:hover {
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ font-weight: bold;
+ margin: 0 0 0 -14px;
+}
+.dropdown-menu > .item:hover > a {
+ text-decoration: none;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ margin: 0 auto 5px;
+ padding: 2px 5px;
+}
+
+.separator {
+ margin: 5px 0;
+}
+
+/*=== Alerts */
+.alert {
+ margin: 15px auto;
+ padding: 10px 15px;
+ font-size: 0.9em;
+}
+.alert-head {
+ font-size: 1.15em;
+}
+.alert > a {
+ text-decoration: underline;
+}
+.alert-warn {
+}
+.alert-success {
+}
+.alert-error {
+}
+
+/*=== Pagination */
+.pagination {
+ text-align: center;
+ font-size: 0.8em;
+}
+.content .pagination {
+ margin: 0;
+ padding: 0;
+}
+.pagination .item.pager-current {
+ font-weight: bold;
+ font-size: 1.5em;
+}
+.pagination .item a {
+ display: block;
+ font-style: italic;
+ line-height: 3em;
+ text-decoration: none;
+}
+.pagination .item a:hover {
+}
+.pagination:first-child .item {
+}
+.pagination:last-child .item {
+}
+
+.pagination .loading,
+.pagination a:hover.loading {
+ font-size: 0;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ height: 85px;
+}
+.header > .item {
+ padding: 10px;
+ vertical-align: middle;
+ text-align: center;
+}
+.header > .item.title{
+ width: 230px;
+}
+.header > .item.title h1 {
+ margin: 0.5em 0;
+}
+.header > .item.title h1 a {
+ text-decoration: none;
+}
+.header > .item.search input {
+ width: 230px;
+}
+.header .item.search input:focus {
+ width: 350px;
+}
+
+/*=== Body */
+#global {
+ height: calc(100% - 85px);
+}
+.aside {
+}
+.aside.aside_flux {
+ padding: 10px 0 50px;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ text-align: center;
+}
+.category {
+ width: 235px;
+ margin: 10px auto;
+ text-align: left;
+}
+.category .btn:first-child {
+ position: relative;
+ width: 213px;
+}
+.category.stick .btn:first-child {
+ width: 176px;
+}
+.category .btn:first-child:not([data-unread="0"]):after {
+ position: absolute;
+ top: 3px; right: 3px;
+ padding: 1px 5px;
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds .item.active {
+}
+.categories .feeds .item.active .feed {
+}
+.categories .feeds .item.empty .feed {
+}
+.categories .feeds .item.empty.active {
+}
+.categories .feeds .item.empty.active .feed {
+}
+.categories .feeds .item.error .feed {
+}
+.categories .feeds .item .feed {
+ margin: 0;
+ width: 165px;
+ line-height: 3em;
+ font-size: 0.8em;
+ text-align: left;
+ text-decoration: none;
+}
+.categories .feeds .feed:not([data-unread="0"]) {
+ font-weight: bold;
+}
+.categories .feeds .dropdown-menu:after {
+ left: 2px;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ vertical-align: middle;
+}
+
+/*=== Configuration pages */
+.post {
+ padding: 10px 50px;
+ font-size: 0.9em;
+}
+.post form {
+ margin: 10px 0;
+}
+.post.content {
+ max-width: 550px;
+}
+
+/*=== Prompt (centered) */
+.prompt {
+ text-align: center;
+}
+.prompt label {
+ text-align: left;
+}
+.prompt form {
+ margin: 10px auto 20px auto;
+ width: 180px;
+}
+.prompt input {
+ margin: 5px auto;
+ width: 100%;
+}
+.prompt p {
+ margin: 20px 0;
+}
+
+/*=== New article notification */
+#new-article {
+ text-align: center;
+ font-size: 0.9em;
+}
+#new-article:hover {
+}
+#new-article > a {
+ line-height: 3em;
+ font-weight: bold;
+}
+#new-article > a:hover {
+ text-decoration: none;
+}
+
+/*=== Day indication */
+.day {
+ padding: 0 10px;
+ font-weight: bold;
+ line-height: 3em;
+}
+#new-article + .day {
+}
+.day .name {
+ padding: 0 10px 0 0;
+ font-size: 1.8em;
+ opacity: 0.3;
+ font-style: italic;
+ text-align: right;
+}
+
+/*=== Index menu */
+.nav_menu {
+ text-align: center;
+ padding: 5px 0;
+}
+
+/*=== Feed articles */
+.flux {
+}
+.flux:hover {
+}
+.flux.current {
+}
+.flux.not_read {
+}
+.flux.not_read:not(.current):hover .item.title {
+}
+.flux.favorite {
+}
+.flux.favorite:not(.current):hover .item.title {
+}
+.flux.current {
+}
+
+
+.flux_header {
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+.flux_header .title {
+ font-size: 0.9rem;
+}
+.flux .website .favicon {
+ padding: 5px;
+}
+.flux .date {
+ font-size: 0.7rem;
+}
+.flux:not(.current):hover .item.title {
+}
+
+.flux .bottom {
+ font-size: 0.8rem;
+ text-align: center;
+}
+
+/*=== Content of feed articles */
+.content {
+ padding: 20px 10px;
+}
+.content > h1.title > a {
+}
+
+.content hr {
+ margin: 30px 10px;
+ height: 1px;
+}
+
+.content pre {
+ margin: 10px auto;
+ padding: 10px 20px;
+ overflow: auto;
+ font-size: 0.9rem;
+}
+.content code {
+ padding: 2px 5px;
+}
+.content pre code {
+}
+
+.content blockquote {
+ display: block;
+ margin: 0;
+ padding: 5px 20px;
+}
+.content blockquote p {
+ margin: 0;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ padding: 0 0 0 5px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 0.9em;
+ line-height: 3em;
+ z-index: 10;
+ vertical-align: middle;
+}
+.notification.good {
+}
+.notification.bad {
+}
+.notification a.close {
+ padding: 0 15px;
+ line-height: 3em;
+}
+.notification.good a.close:hover {
+}
+.notification.bad a.close:hover {
+}
+
+.notification#actualizeProgress {
+ line-height: 2em;
+}
+
+/*=== "Load more" part */
+#bigMarkAsRead {
+ text-align: center;
+ text-decoration: none;
+}
+#bigMarkAsRead:hover {
+}
+#bigMarkAsRead:hover .bigTick {
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ margin: 0;
+ text-align: center;
+ line-height: 3em;
+ table-layout: fixed;
+}
+
+/*=== READER VIEW */
+/*================*/
+#stream.reader .flux {
+ padding: 0 0 50px;
+}
+#stream.reader .flux .author {
+ margin: 0 0 10px;
+ font-size: 90%;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+#stream.global .box-category {
+ text-align: left;
+}
+#stream.global .category {
+ margin: 0;
+}
+#stream.global .btn {
+ width: auto;
+ height: 2em;
+ margin: 0;
+ padding: 0 10px;
+ line-height: 2em;
+ font-size: 1.2rem;
+}
+#stream.global .btn:not([data-unread="0"]) {
+ font-weight: bold;
+}
+#stream.global .btn:first-child:not([data-unread="0"]):after {
+ top: 0; right: 5px;
+ font-weight: bold;
+}
+#stream.global .box-category .feeds {
+ max-height: 250px;
+}
+#stream.global .box-category .feeds .item {
+ padding: 2px 10px;
+ font-size: 0.9rem;
+}
+
+/*=== DIVERS */
+/*===========*/
+.aside.aside_feed .nav-form input,
+.aside.aside_feed .nav-form select {
+ width: 140px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu {
+ right: -20px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu:after {
+ right: 33px;
+}
+
+/*=== STATISTICS */
+/*===============*/
+.stat {
+ margin: 10px 0 20px;
+}
+
+.stat th,
+.stat td,
+.stat tr {
+}
+.stat > table td,
+.stat > table th {
+ text-align: center;
+}
+
+/*=== LOGS */
+/*=========*/
+.logs {
+ overflow: hidden;
+}
+.log {
+ padding: 5px 10px;
+ font-size: 0.8rem;
+}
+.log+.log {
+}
+.log .date {
+ display: block;
+ font-weight: bold;
+}
+.log.error {
+}
+.log.warning {
+}
+.log.notice {
+}
+.log.debug {
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .aside {
+ transition: width 200ms linear;
+ -moz-transition: width 200ms linear;
+ -webkit-transition: width 200ms linear;
+ -o-transition: width 200ms linear;
+ -ms-transition: width 200ms linear;
+ }
+ .aside .toggle_aside,
+ #panel .close {
+ position: absolute;
+ display: block;
+ top: 0; right: 0;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ }
+
+ .nav_menu .btn {
+ margin: 5px 10px;
+ }
+ .nav_menu .stick {
+ margin: 0 10px;
+ }
+ .nav_menu .stick .btn {
+ margin: 5px 0;
+ }
+ .nav_menu .search {
+ display: inline-block;
+ max-width: 97%;
+ }
+ .nav_menu .search input {
+ max-width: 97%;
+ width: 90px;
+ }
+ .nav_menu .search input:focus {
+ width: 400px;
+ }
+
+ .day .name {
+ font-size: 1.1rem;
+ }
+
+ .pagination {
+ margin: 0 0 3.5em;
+ }
+
+ .notification a.close {
+ display: block;
+ left: 0;
+ }
+ .notification a.close:hover {
+ opacity: 0.5;
+ }
+ .notification a.close .icon {
+ display: none;
+ }
+}
diff --git a/p/themes/base-theme/metadata.json b/p/themes/base-theme/metadata.json
new file mode 100644
index 000000000..4d59365a8
--- /dev/null
+++ b/p/themes/base-theme/metadata.json
@@ -0,0 +1,7 @@
+{
+ "name": "",
+ "author": "Your name",
+ "description": "A wonderful base theme",
+ "version": 0.1,
+ "files": ["template.css", "base.css"]
+}
diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css
new file mode 100644
index 000000000..09ecaf685
--- /dev/null
+++ b/p/themes/base-theme/template.css
@@ -0,0 +1,695 @@
+@charset "UTF-8";
+
+/*=== GENERAL */
+/*============*/
+html, body {
+ margin: 0;
+ padding: 0;
+ font-size: 100%;
+}
+
+/*=== Links */
+a {
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+
+/*=== Lists */
+ul, ol, dd {
+ margin: 0;
+ padding: 0;
+}
+
+/*=== Titles */
+h1 {
+ margin: 0.6em 0 0.3em;
+ font-size: 1.5em;
+ line-height: 1.6em;
+}
+h2 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.3em;
+ line-height: 2em;
+}
+h3 {
+ margin: 0.5em 0 0.25em;
+ font-size: 1.1em;
+ line-height: 2em;
+}
+
+/*=== Paragraphs */
+p {
+ margin: 1em 0 0.5em;
+ font-size: 1em;
+}
+
+/*=== Images */
+img {
+ height: auto;
+ max-width: 100%;
+}
+img.favicon {
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+}
+
+/*=== Videos */
+iframe, embed, object, video {
+ max-width: 100%;
+}
+
+/*=== Forms */
+legend {
+ display: block;
+ width: 100%;
+ clear: both;
+}
+label {
+ display: block;
+}
+input {
+ width: 180px;
+}
+textarea {
+ width: 300px;
+}
+input, select, textarea {
+ display: inline-block;
+ max-width: 100%;
+}
+input[type="radio"],
+input[type="checkbox"] {
+ width: 15px !important;
+ min-height: 15px !important;
+}
+input.extend:focus {
+ width: 300px;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group:after {
+ content: "";
+ display: block;
+ clear: both;
+}
+.form-group.form-actions {
+ min-width: 250px;
+}
+.form-group .group-name {
+ display: block;
+ float: left;
+ width: 200px;
+}
+.form-group .group-controls {
+ min-width: 250px;
+ margin: 0 0 0 220px;
+}
+.form-group .group-controls .control {
+ display: block;
+}
+
+/*=== Buttons */
+.stick {
+ display: inline-block;
+ white-space: nowrap;
+}
+.btn,
+a.btn {
+ display: inline-block;
+ cursor: pointer;
+ overflow: hidden;
+}
+.btn-important {
+ font-weight: bold;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+ display: block;
+}
+.nav-list .item,
+.nav-list .item > a {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.nav-head {
+ display: block;
+}
+.nav-head .item {
+ display: inline-block;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+.horizontal-list .item {
+ display: table-cell;
+}
+
+/*=== Dropdown */
+.dropdown {
+ position: relative;
+ display: inline-block;
+}
+.dropdown-target {
+ display: none;
+}
+.dropdown-menu {
+ display: none;
+ min-width: 200px;
+ margin: 0;
+ position: absolute;
+ right: 0;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.dropdown-header {
+ display: block;
+}
+.dropdown-menu > .item {
+ display: block;
+}
+.dropdown-menu > .item > a,
+.dropdown-menu > .item > span {
+ display: block;
+}
+.dropdown-menu > .item[aria-checked="true"] > a:before {
+ content: '✓';
+}
+.dropdown-menu .input {
+ display: block;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+ display: block;
+ max-width: 95%;
+}
+.dropdown-target:target ~ .dropdown-menu {
+ display: block;
+ z-index: 10;
+}
+.dropdown-close {
+ display: inline;
+}
+.dropdown-close a {
+ font-size: 0;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+ z-index: -10;
+}
+.separator {
+ display: block;
+ height: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+/*=== Alerts */
+.alert {
+ display: block;
+ width: 90%;
+}
+.group-controls .alert {
+ width: 100%
+}
+.alert-head {
+ margin: 0;
+ font-weight: bold;
+}
+.alert ul {
+ margin: 5px 20px;
+}
+
+/*=== Icons */
+.icon {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ line-height: 16px;
+}
+
+/*=== Pagination */
+.pagination {
+ display: table;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+}
+.pagination .item {
+ display: table-cell;
+}
+.pagination .pager-first,
+.pagination .pager-previous,
+.pagination .pager-next,
+.pagination .pager-last {
+ width: 100px;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+ display: table;
+ width: 100%;
+ table-layout: fixed;
+}
+.header > .item {
+ display: table-cell;
+}
+.header > .item.title {
+ width: 250px;
+ white-space: nowrap;
+}
+.header > .item.title h1 {
+ display: inline-block;
+}
+.header > .item.title .logo {
+ display: inline-block;
+ height: 32px;
+ width: 32px;
+ vertical-align: middle;
+}
+.header > .item.configure {
+ width: 100px;
+}
+
+/*=== Body */
+#global {
+ display: table;
+ width: 100%;
+ height: 100%;
+ table-layout: fixed;
+}
+.aside {
+ display: table-cell;
+ height: 100%;
+ width: 250px;
+ vertical-align: top;
+}
+.aside.aside_flux {
+ background: #fff;
+}
+
+/*=== Aside main page (categories) */
+.categories {
+ list-style: none;
+ margin: 0;
+}
+.category {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.category .btn:not([data-unread="0"]):after {
+ content: attr(data-unread);
+}
+
+/*=== Aside main page (feeds) */
+.categories .feeds {
+ width: 100%;
+ list-style: none;
+}
+.categories .feeds:not(.active) {
+ display: none;
+}
+.categories .feeds .feed {
+ display: inline-block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+.categories .feeds .feed:not([data-unread="0"]):before {
+ content: "(" attr(data-unread) ") ";
+}
+.categories .feeds .dropdown-menu {
+ left: 0;
+}
+.categories .feeds .item .dropdown-toggle > .icon {
+ visibility: hidden;
+ cursor: pointer;
+ vertical-align: top;
+}
+.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.categories .feeds .item:hover .dropdown-toggle > .icon,
+.categories .feeds .item.active .dropdown-toggle > .icon {
+ visibility: visible;
+}
+
+/*=== New article notification */
+#new-article {
+ display: none;
+}
+#new-article > a {
+ display: block;
+}
+
+/*=== Day indication */
+.day .name {
+ position: absolute;
+ right: 0;
+ width: 50%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+/*=== Feed article header and footer */
+.flux_header {
+ position: relative;
+}
+.flux .item {
+ line-height: 40px;
+ white-space: nowrap;
+}
+.flux .item.manage,
+.flux .item.link {
+ width: 40px;
+ text-align: center;
+}
+.flux .item.website {
+ width: 200px;
+}
+.flux.not_read .item.title,
+.flux.current .item.title {
+ font-weight: bold;
+}
+.flux:not(.current):hover .item.title {
+ position: absolute;
+ max-width: calc(100% - 320px);
+ background: #fff;
+}
+.flux .item.title a {
+ color: #000;
+ text-decoration: none;
+}
+.flux .item.date {
+ width: 145px;
+ text-align: right;
+}
+.flux .item > a {
+ display: block;
+}
+.flux .item > a {
+ display: block;
+ text-decoration: none;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.flux .item.share > a {
+ display: list-item;
+ list-style-position: inside;
+ list-style-type: decimal;
+}
+
+/*=== Feed article content */
+.hide_posts > .flux:not(.active) > .flux_content {
+ display: none;
+}
+.content {
+ min-height: 20em;
+ margin: auto;
+ line-height: 1.7em;
+ word-wrap: break-word;
+}
+.content.large {
+ max-width: 1000px;
+}
+.content.medium {
+ max-width: 800px;
+}
+.content.thin {
+ max-width: 550px;
+}
+.content ul,
+.content ol,
+.content dd {
+ margin: 0 0 0 15px;
+ padding: 0 0 5px 15px;
+}
+.content pre {
+ overflow: auto;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+ position: absolute;
+ top: 1em;
+ left: 25%; right: 25%;
+ z-index: 10;
+ background: #fff;
+ border: 1px solid #aaa;
+}
+.notification.closed {
+ display: none;
+}
+.notification a.close {
+ position: absolute;
+ top: 0; bottom: 0;
+ right: 0;
+ display: inline-block;
+}
+
+#actualizeProgress {
+ position: fixed;
+}
+#actualizeProgress progress {
+ max-width: 100%;
+ vertical-align: middle;
+}
+#actualizeProgress .progress {
+ vertical-align: middle;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+ position: fixed;
+ bottom: 0; left: 0;
+ display: table;
+ width: 250px;
+ background: #fff;
+ table-layout: fixed;
+}
+#nav_entries .item {
+ display: table-cell;
+ width: 30%;
+}
+#nav_entries a {
+ display: block;
+}
+
+/*=== "Load more" part */
+#load_more {
+ min-height: 40px;
+}
+.loading {
+ background: url("loader.gif") center center no-repeat;
+ font-size: 0;
+}
+#bigMarkAsRead {
+ display: block;
+ padding: 3em 0;
+ text-align: center;
+}
+.bigTick {
+ font-size: 7em;
+ line-height: 1.6em;
+}
+
+/*=== Statistiques */
+.stat > table {
+ width: 100%;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+/*=== Category boxes */
+#stream.global .box-category {
+ display: inline-block;
+ width: 19em;
+ max-width: 95%;
+ margin: 20px 10px;
+ border: 1px solid #ccc;
+ vertical-align: top;
+}
+#stream.global .category {
+ width: 100%;
+}
+#stream.global .btn {
+ display: block;
+}
+#stream.global .box-category .feeds {
+ display: block;
+ overflow: auto;
+}
+#stream.global .box-category .feed {
+ width: 19em;
+ max-width: 90%;
+}
+
+/*=== Panel */
+#overlay {
+ display: none;
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ background: rgba(0, 0, 0, 0.9);
+}
+#panel {
+ display: none;
+ position: fixed;
+ top: 1em; bottom: 1em;
+ left: 2em; right: 2em;
+ overflow: auto;
+ background: #fff;
+}
+#panel .close {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ display: block;
+}
+#panel .close img {
+ display: none;
+}
+
+/*=== DIVERS */
+/*===========*/
+.nav-login,
+.nav_menu .search,
+.nav_menu .toggle_aside {
+ display: none;
+}
+
+.aside .toggle_aside {
+ position: absolute;
+ right: 0;
+ display: none;
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+ .header,
+ .aside .btn-important,
+ .aside .feeds .dropdown,
+ .flux_header .item.website span,
+ .item.date, .day .date,
+ .dropdown-menu > .no-mobile,
+ .no-mobile {
+ display: none;
+ }
+ .nav-login {
+ display: block;
+ }
+ .nav_menu .toggle_aside,
+ .aside .toggle_aside,
+ .nav_menu .search,
+ #panel .close img {
+ display: inline-block;
+ }
+
+ .aside {
+ position: fixed;
+ top: 0; bottom: 0;
+ left: 0;
+ width: 0;
+ overflow: hidden;
+ z-index: 100;
+ }
+ .aside:target {
+ width: 90%;
+ overflow: auto;
+ }
+ .aside .categories {
+ margin: 10px 0 75px;
+ }
+
+ .flux_header .item.website {
+ width: 40px;
+ }
+
+ .flux:not(.current):hover .item.title {
+ position: relative;
+ width: auto;
+ white-space: nowrap;
+ }
+
+ .notification {
+ top: 0;
+ left: 0;
+ right: 0;
+ }
+
+ #nav_entries {
+ width: 100%;
+ }
+
+ #stream.global .box-category {
+ margin: 10px 0;
+ }
+
+ #panel {
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ }
+ #panel .close {
+ top: 0; right: 0;
+ left: auto; bottom: auto;
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ }
+}
+
+/*=== PRINTER */
+/*============*/
+@media print {
+ .header, .aside,
+ .nav_menu, .day,
+ .flux_header,
+ .flux_content .bottom,
+ .pagination,
+ #nav_entries {
+ display: none;
+ }
+ html, body {
+ background: #fff;
+ color: #000;
+ font-family: Serif;
+ }
+ #global,
+ .flux_content {
+ display: block !important;
+ }
+ .flux_content .content {
+ width: 100% !important;
+ }
+ .flux_content .content a {
+ color: #000;
+ }
+ .flux_content .content a:after {
+ content: " [" attr(href) "] ";
+ font-style: italic;
+ }
+}
diff --git a/p/themes/icons/bookmark-add.svg b/p/themes/icons/bookmark-add.svg
new file mode 100644
index 000000000..51db9c498
--- /dev/null
+++ b/p/themes/icons/bookmark-add.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">
+<g transform="translate(-141.0002,-807)" fill="#bebebe">
+<path d="m143,807,0,13,4-4,4,4,0-4,0-1-2,0,0-4,2,0,0-4z"/>
+<path d="m152,810,0,2-2,0,0,2,2,0,0,2,2,0,0-2,2,0,0-2-2,0,0-2-2,0z"/>
+</g>
+</svg> \ No newline at end of file