Просмотр исходного кода

Merge branch 'dev' of github.com:marienfressinaud/freshrss into dev

Marien Fressinaud 11 лет назад
Родитель
Сommit
aafcd3a879

+ 14 - 1
app/Controllers/statsController.php

@@ -55,7 +55,20 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
 
 		$this->view->idleFeeds = $idleFeeds;
 	}
-	
+
+	public function repartitionAction() {
+		$statsDAO = FreshRSS_Factory::createStatsDAO();
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		$id = Minz_Request::param ('id', null);
+		$this->view->feed = $feedDAO->searchById($id);
+		$this->view->days = $statsDAO->getDays();
+		$this->view->months = $statsDAO->getMonths();
+		$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+		$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+		$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+	}
+
 	public function firstAction() {
 		if (!$this->view->loginOk) {
 			Minz_Error::error(

+ 2 - 2
app/Controllers/usersController.php

@@ -100,7 +100,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
 	public function createAction() {
 		if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
 			$db = Minz_Configuration::dataBase();
-			require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+			require_once(APP_PATH . '/SQL/install.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())) {
@@ -172,7 +172,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
 	public function deleteAction() {
 		if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
 			$db = Minz_Configuration::dataBase();
-			require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+			require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
 			$username = Minz_Request::param('username');
 			$ok = ctype_alnum($username);

+ 128 - 1
app/Models/StatsDAO.php

@@ -85,9 +85,83 @@ SQL;
 	 * @return array
 	 */
 	protected function initEntryCountArray() {
+		return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
+	}
+
+	/**
+	 * Calculates the number of article per hour of the day per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per day of week per feed
+	 *
+	 * @param integer $feed id
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per month per feed
+	 *
+	 * @param integer $feed
+	 * @return string
+	 */
+	public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
+		return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+	}
+
+	/**
+	 * Calculates the number of article per period per feed
+	 *
+	 * @param string $period format string to use for grouping
+	 * @param integer $feed id
+	 * @return string
+	 */
+	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+		if ($feed) {
+			$restrict = "WHERE e.id_feed = {$feed}";
+		} else {
+			$restrict = '';
+		}
+		$sql = <<<SQL
+SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+		foreach ($res as $value) {
+			$repartition[(int) $value['period']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($repartition);
+	}
+
+	/**
+	 * Initialize an array for statistics depending on a range
+	 *
+	 * @param integer $min
+	 * @param integer $max
+	 * @return array
+	 */
+	protected function initStatsArray($min, $max) {
 		return array_map(function () {
 			return 0;
-		}, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
+		}, array_flip(range($min, $max)));
 	}
 
 	/**
@@ -205,4 +279,57 @@ SQL;
 		return json_encode($serie);
 	}
 
+	/**
+	 * Gets days ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getDays() {
+		return $this->convertToTranslatedJson(array(
+			'sun',
+			'mon',
+			'tue',
+			'wed',
+			'thu',
+			'fri',
+			'sat',
+		));
+	}
+
+	/**
+	 * Gets months ready for graphs
+	 *
+	 * @return string
+	 */
+	public function getMonths() {
+		return $this->convertToTranslatedJson(array(
+			'jan',
+			'feb',
+			'mar',
+			'apr',
+			'may',
+			'jun',
+			'jul',
+			'aug',
+			'sep',
+			'oct',
+			'nov',
+			'dec',
+		));
+	}
+
+	/**
+	 * Translates array content and encode it as JSON
+	 *
+	 * @param array $data
+	 * @return string
+	 */
+	private function convertToTranslatedJson($data = array()) {
+		$translated = array_map(function ($a) {
+			return Minz_Translate::t($a);
+		}, $data);
+
+		return json_encode($translated);
+	}
+
 }

+ 27 - 1
app/Models/StatsDAOSQLite.php

@@ -28,10 +28,36 @@ SQL;
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
 
 		foreach ($res as $value) {
-			$count[(int)$value['day']] = (int) $value['count'];
+			$count[(int) $value['day']] = (int) $value['count'];
 		}
 
 		return $this->convertToSerie($count);
 	}
 
+	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+		if ($feed) {
+			$restrict = "WHERE e.id_feed = {$feed}";
+		} else {
+			$restrict = '';
+		}
+		$sql = <<<SQL
+SELECT strftime('{$period}', e.date, 'unixepoch') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+		foreach ($res as $value) {
+			$repartition[(int) $value['period']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($repartition);
+	}
+
 }

+ 3 - 3
app/Models/UserDAO.php

@@ -3,11 +3,11 @@
 class FreshRSS_UserDAO extends Minz_ModelPdo {
 	public function createUser($username) {
 		$db = Minz_Configuration::dataBase();
-		require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+		require_once(APP_PATH . '/SQL/install.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);
+			$stm = $this->bd->prepare($sql);
 			$ok = $stm && $stm->execute();
 		} else {
 			global $SQL_CREATE_TABLES;
@@ -32,7 +32,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
 
 	public function deleteUser($username) {
 		$db = Minz_Configuration::dataBase();
-		require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+		require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
 		$sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
 		$stm = $this->bd->prepare($sql);

+ 35 - 12
app/i18n/en.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistics',
 	'stats_idle'			=> 'Idle feeds',
 	'stats_main'			=> 'Main statistics',
+	'stats_repartition'		=> 'Articles repartition',
+	'stats_entry_per_hour'		=> 'Per hour',
+	'stats_entry_per_day_of_week'	=> 'Per day of week',
+	'stats_entry_per_month'		=> 'Per month',
     
 	'last_week'			=> 'Last week',
 	'last_month'			=> 'Last month',
@@ -341,18 +345,37 @@ return array (
 	'confirm_action'		=> 'Are you sure you want to perform this action? It cannot be cancelled!',
 
 	// DATE
-	'january'			=> 'january',
-	'february'			=> 'february',
-	'march'				=> 'march',
-	'april'				=> 'april',
-	'may'				=> 'may',
-	'june'				=> 'june',
-	'july'				=> 'july',
-	'august'			=> 'august',
-	'september'			=> 'september',
-	'october'			=> 'october',
-	'november'			=> 'november',
-	'december'			=> 'december',
+	'january'			=> 'January',
+	'february'			=> 'February',
+	'march'				=> 'March',
+	'april'				=> 'April',
+	'may'				=> 'May',
+	'june'				=> 'June',
+	'july'				=> 'July',
+	'august'			=> 'August',
+	'september'			=> 'September',
+	'october'			=> 'October',
+	'november'			=> 'November',
+	'december'			=> 'December',
+	'january'			=> 'Jan',
+	'february'			=> 'Feb',
+	'march'				=> 'Mar',
+	'april'				=> 'Apr',
+	'may'				=> 'May',
+	'june'				=> 'Jun',
+	'july'				=> 'Jul',
+	'august'			=> 'Aug',
+	'september'			=> 'Sep',
+	'october'			=> 'Oct',
+	'november'			=> 'Nov',
+	'december'			=> 'Dec',
+	'sun'				=> 'Sun',
+	'mon'				=> 'Mon',
+	'tue'				=> 'Tue',
+	'wed'				=> 'Wed',
+	'thu'				=> 'Thu',
+	'fri'				=> 'Fri',
+	'sat'				=> 'Sat',
 	// special format for date() function
 	'Jan'				=> '\J\a\n\u\a\r\y',
 	'Feb'				=> '\F\e\b\r\u\a\r\y',

+ 23 - 0
app/i18n/fr.php

@@ -48,6 +48,10 @@ return array (
 	'stats'				=> 'Statistiques',
 	'stats_idle'			=> 'Flux inactifs',
 	'stats_main'			=> 'Statistiques principales',
+	'stats_repartition'		=> 'Répartition des articles',
+	'stats_entry_per_hour'		=> 'Par heure',
+	'stats_entry_per_day_of_week'	=> 'Par jour de la semaine',
+	'stats_entry_per_month'		=> 'Par mois',
 
 	'last_week'			=> 'La dernière semaine',
 	'last_month'			=> 'Le dernier mois',
@@ -353,6 +357,25 @@ return array (
 	'october'			=> 'octobre',
 	'november'			=> 'novembre',
 	'december'			=> 'décembre',
+	'jan'				=> 'jan.',
+	'feb'				=> 'fév.',
+	'mar'				=> 'mar.',
+	'apr'				=> 'avr.',
+	'may'				=> 'mai.',
+	'jun'				=> 'juin',
+	'jul'				=> 'jui.',
+	'aug'				=> 'août',
+	'sep'				=> 'sep.',
+	'oct'				=> 'oct.',
+	'nov'				=> 'nov.',
+	'dec'				=> 'déc.',
+	'sun'				=> 'dim.',
+	'mon'				=> 'lun.',
+	'tue'				=> 'mar.',
+	'wed'				=> 'mer.',
+	'thu'				=> 'jeu.',
+	'fri'				=> 'ven.',
+	'sat'				=> 'sam.',
 	// format spécial pour la fonction date()
 	'Jan'				=> '\j\a\n\v\i\e\r',
 	'Feb'				=> '\f\é\v\r\i\e\r',

+ 4 - 3
app/layout/aside_flux.phtml

@@ -1,4 +1,4 @@
-<div class="aside aside_flux" id="aside_flux">
+<div class="aside aside_flux<?php if (($this->state & FreshRSS_Entry::STATE_NOT_READ) && !($this->state & FreshRSS_Entry::STATE_READ)) echo ' state_unread'; ?>" id="aside_flux">
 	<a class="toggle_aside" href="#close"><?php echo FreshRSS_Themes::icon('close'); ?></a>
 
 	<ul class="categories">
@@ -41,11 +41,11 @@
 		foreach ($this->cat_aside as $cat) {
 			$feeds = $cat->feeds ();
 			if (!empty ($feeds)) {
-				?><li><?php
 				$c_active = false;
 				if ($this->get_c == $cat->id ()) {
 					$c_active = true;
 				}
+				?><li data-unread="<?php echo $cat->nbNotRead(); ?>"<?php if ($c_active) echo ' class="active"'; ?>><?php
 				?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php
 					?><a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name (); ?></a><?php
 					?><a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a><?php
@@ -55,7 +55,7 @@
 					$feed_id = $feed->id ();
 					$nbEntries = $feed->nbEntries ();
 					$f_active = ($this->get_f == $feed_id);
-					?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"><?php
+					?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>"><?php
 						?><div class="dropdown"><?php
 							?><div class="dropdown-target"></div><?php
 							?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a><?php
@@ -77,6 +77,7 @@
 	<ul class="dropdown-menu">
 		<li class="dropdown-close"><a href="#close">❌</a></li>
 		<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('filter'); ?></a></li>
+		<li class="item"><a href="<?php echo _url ('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
 		<li class="item"><a target="_blank" href="http://example.net/"><?php echo Minz_Translate::t ('see_website'); ?></a></li>
 		<?php if ($this->loginOk) { ?>
 		<li class="separator"></li>

+ 3 - 0
app/layout/aside_stats.phtml

@@ -6,4 +6,7 @@
 	<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>
+	<li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a>
+	</li>
 </ul>

+ 8 - 2
app/views/stats/idle.phtml

@@ -5,7 +5,10 @@
 
 	<h1><?php echo _t('stats_idle'); ?></h1>
 
-	<?php foreach ($this->idleFeeds as $period => $feeds) { ?>
+	<?php
+		foreach ($this->idleFeeds as $period => $feeds) {
+			if (!empty($feeds)) {
+	?>
 		<div class="stat">
 			<h2><?php echo _t($period); ?></h2>
 
@@ -15,5 +18,8 @@
 				<?php } ?>
 			</ul>
 		</div>
-	<?php } ?>
+	<?php
+			}
+		}
+	?>
 </div>

+ 0 - 127
app/views/stats/main.phtml

@@ -1,127 +0,0 @@
-<?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_main'); ?></h1>
-
-        <div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_entry_repartition'); ?></h2>
-		<table>
-			<thead>
-				<tr>
-					<th> </th>
-					<th><?php echo Minz_Translate::t ('main_stream'); ?></th>
-					<th><?php echo Minz_Translate::t ('all_feeds'); ?></th>
-				</tr>
-			</thead>
-			<tbody>
-				<tr>
-					<th><?php echo Minz_Translate::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 Minz_Translate::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 Minz_Translate::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 Minz_Translate::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 Minz_Translate::t ('stats_entry_per_day'); ?></h2>
-		<div id="statsEntryPerDay" style="height: 300px"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_feed_per_category'); ?></h2>
-		<div id="statsFeedPerCategory" style="height: 300px"></div>
-		<div id="statsFeedPerCategoryLegend"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_entry_per_category'); ?></h2>
-		<div id="statsEntryPerCategory" style="height: 300px"></div>
-		<div id="statsEntryPerCategoryLegend"></div>
-	</div>
-	
-	<div class="stat">
-		<h2><?php echo Minz_Translate::t ('stats_top_feed'); ?></h2>
-		<table>
-			<thead>
-				<tr>
-					<th><?php echo Minz_Translate::t ('feed'); ?></th>
-					<th><?php echo Minz_Translate::t ('category'); ?></th>
-					<th><?php echo Minz_Translate::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>

+ 99 - 0
app/views/stats/repartition.phtml

@@ -0,0 +1,99 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+	<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
+	
+	<?php if ($this->feed) {?>
+		<h1>
+			<?php echo _t('stats_repartition'), " - "; ?>
+			<a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
+				<?php echo $this->feed->name(); ?>
+			</a>
+		</h1>
+	<?php } else {?>
+		<h1><?php echo _t('stats_repartition'); ?></h1>
+	<?php }?>
+	
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_hour'); ?></h2>
+		<div id="statsEntryPerHour" style="height: 300px"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2>
+		<div id="statsEntryPerDayOfWeek" style="height: 300px"></div>
+	</div>
+
+	<div class="stat">
+		<h2><?php echo _t('stats_entry_per_month'); ?></h2>
+		<div id="statsEntryPerMonth" style="height: 300px"></div>
+	</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 hour
+	Flotr.draw(document.getElementById('statsEntryPerHour'),
+		[<?php echo $this->repartitionHour ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 23,
+				tickFormatter: function(x) {
+					var x = parseInt(x);
+					return x + 1;
+				},
+				min: -0.9,
+				max: 23.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per day of week
+	Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
+		[<?php echo $this->repartitionDayOfWeek ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 6,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    days = <?php echo $this->days?>;
+					return days[x];
+				},
+				min: -0.9,
+				max: 6.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	// Entry per month
+	Flotr.draw(document.getElementById('statsEntryPerMonth'),
+		[<?php echo $this->repartitionMonth ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 12,
+				tickFormatter: function(x) {
+					var x = parseInt(x),
+					    months = <?php echo $this->months?>;
+					return months[(x - 1)];
+				},
+				min: 0.1,
+				max: 12.9,
+				tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+		});
+	
+}
+initStats();
+</script>

+ 8 - 0
p/scripts/main.js

@@ -69,6 +69,10 @@ function incUnreadsFeed(article, feed_id, nb) {
 		feed_priority = elem ? str2int(elem.getAttribute('data-priority')) : 0;
 	if (elem) {
 		elem.setAttribute('data-unread', numberFormat(feed_unreads + nb));
+		elem = $(elem).closest('li').get(0);
+		if (elem) {
+			elem.setAttribute('data-unread', feed_unreads + nb);
+		}
 	}
 
 	//Update unread: category
@@ -76,6 +80,10 @@ function incUnreadsFeed(article, feed_id, nb) {
 	feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0;
 	if (elem) {
 		elem.setAttribute('data-unread', numberFormat(feed_unreads + nb));
+		elem = $(elem).closest('li').get(0);
+		if (elem) {
+			elem.setAttribute('data-unread', feed_unreads + nb);
+		}
 	}
 
 	//Update unread: all

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

@@ -309,6 +309,9 @@ a.btn {
 	list-style: none;
 	margin: 0;
 }
+.state_unread li:not(.active)[data-unread="0"] {
+	display: none;
+}
 .category {
 	display: block;
 	overflow: hidden;

+ 3 - 0
p/themes/Flat/template.css

@@ -309,6 +309,9 @@ a.btn {
 	list-style: none;
 	margin: 0;
 }
+.state_unread li:not(.active)[data-unread="0"] {
+	display: none;
+}
 .category {
 	display: block;
 	overflow: hidden;

+ 3 - 0
p/themes/Origine/template.css

@@ -309,6 +309,9 @@ a.btn {
 	list-style: none;
 	margin: 0;
 }
+.state_unread li:not(.active)[data-unread="0"] {
+	display: none;
+}
 .category {
 	display: block;
 	overflow: hidden;

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

@@ -309,6 +309,9 @@ a.btn {
 	list-style: none;
 	margin: 0;
 }
+.state_unread li:not(.active)[data-unread="0"] {
+	display: none;
+}
 .category {
 	display: block;
 	overflow: hidden;