Browse Source

Remember open categories (#3185)

* feature(normal) - Remember opened categories in the left menu

Session storage based implementation to remember opened categories in left menu

Issue Ref: #2248

* lib_phpQuery updates

* Updates covering feedback points and functionality fixes

* Feedback updates

* Revert "lib_phpQuery updates"

This reverts commit dcd23b9418405a2d14ee03c1fcadf90c04b267e1.

* First review

Change variable name to "remember" instead of "open".
Start using localStorage instead of sessionStorage.
Simplify code.

* Simplify remember categories init function

Replace 'session' with 'local' in function names and comment

Set open categories CSS as same as when category is opened in 'active' unfold mode

* Remove URLSearchParams check in remember categories init function

* Delete open categories on login and logout

* JSHint check fix

* Second review

* Make new mode the default for new users
* Always open active category
* Reduce / simplify code

* i18n French

* Revert default value

Wait a bit more for this decision / change

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Prashant Tholia 5 years ago
parent
commit
6f23999c7b

+ 1 - 1
app/Models/ConfigurationSetter.php

@@ -183,7 +183,7 @@ class FreshRSS_ConfigurationSetter {
 	}
 
 	private function _display_categories(&$data, $value) {
-		if (!in_array($value, [ 'active', 'all', 'none' ], true)) {
+		if (!in_array($value, [ 'active', 'remember', 'all', 'none' ], true)) {
 			$value = $value === true ? 'all' : 'active';
 		}
 		$data['display_categories'] = $value;

+ 1 - 1
app/Models/Context.php

@@ -71,7 +71,7 @@ class FreshRSS_Context {
 		}
 
 		//Legacy < 1.16.1
-		if (!in_array(FreshRSS_Context::$user_conf->display_categories, [ 'active', 'all', 'none' ], true)) {
+		if (!in_array(FreshRSS_Context::$user_conf->display_categories, [ 'active', 'remember', 'all', 'none' ], true)) {
 			FreshRSS_Context::$user_conf->display_categories = FreshRSS_Context::$user_conf->display_categories === true ? 'all' : 'active';
 		}
 	}

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Zobrazit všechny články',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Zobrazit jen nepřečtené',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO - Translation

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Alle Artikel zeigen',
 			'all_categories' => 'Alle Kategorien',
 			'no_category' => 'Keine Kategorie',
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Nur ungelesene zeigen',
 		),
 		'sides_close_article' => 'Klick außerhalb des Artikel-Textes schließt den Artikel',

+ 1 - 0
app/i18n/en-us/conf.php

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Show all articles',
 			'all_categories' => 'All categories',
 			'no_category' => 'No category',
+			'remember_categories' => 'Remember open categories',
 			'unread' => 'Show only unread',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Show all articles',
 			'all_categories' => 'All categories',
 			'no_category' => 'No category',
+			'remember_categories' => 'Remember open categories',
 			'unread' => 'Show only unread',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Mostrar todos los artículos',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Mostrar solo pendientes',
 		),
 		'sides_close_article' => 'Pinchar fuera del área de texto del artículo lo cerrará',

+ 1 - 0
app/i18n/fr/conf.php

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Afficher tous les articles',
 			'all_categories' => 'Toutes les catégories',
 			'no_category' => 'Aucune catégorie',
+			'remember_categories' => 'Se souvenir des catégories dépliées',
 			'unread' => 'Afficher les non lus',
 		),
 		'sides_close_article' => 'Cliquer hors de la zone de texte ferme l’article',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'הצגת כל המאמרים',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'הצגת מאמרים שלא נקראו בלבד',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO - Translation

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Mostra tutti gli articoli',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Mostra solo non letti',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO - Translation

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => '모든 글 표시',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => '읽지 않은 글만 표시',
 		),
 		'sides_close_article' => '글 영역 바깥을 클릭하면 글 접기',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Bekijk alle artikelen',
 			'all_categories' => 'Alle categorieën',
 			'no_category' => 'Geen categorie',
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Bekijk alleen ongelezen',
 		),
 		'sides_close_article' => 'Sluit het artikel door buiten de artikeltekst te klikken',

+ 1 - 0
app/i18n/oc/conf.php

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Mostrar totes los articles',
 			'all_categories' => 'Totas las categorias',
 			'no_category' => 'Cap de categoria',
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Mostrar pas que los pas legits',
 		),
 		'sides_close_article' => 'Clicar fòra de la zòna de tèxte tampa l’article',

+ 1 - 0
app/i18n/pl/conf.php

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Wszystkie wiadomości',
 			'all_categories' => 'Wszystkie',
 			'no_category' => 'Żadna',
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Tylko nieprzeczytane',
 		),
 		'sides_close_article' => 'Kliknięcie poza zawartością wiadomości zamyka widok wiadomości',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Exibir todos os artigos',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Exibir apenas não lido',
 		),
 		'sides_close_article' => 'Clicando fora da área do texto do artigo fecha o mesmo',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Show all articles',	// TODO - Translation
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Show only unread',	// TODO - Translation
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO - Translation

+ 1 - 0
app/i18n/sk/conf.php

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Zobraziť všetky články',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Zobraziť iba neprečítané',
 		),
 		'sides_close_article' => 'Po kliknutí mimo textu článku sa článok zatvorí',

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => 'Tüm makaleleri göster',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => 'Sadece okunmamış makaleleri göster',
 		),
 		'sides_close_article' => 'Clicking outside of article text area closes the article',	// TODO - Translation

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

@@ -124,6 +124,7 @@ return array(
 			'all_articles' => '显示所有',
 			'all_categories' => 'All categories',	// TODO - Translation
 			'no_category' => 'No category',	// TODO - Translation
+			'remember_categories' => 'Remember open categories',	// TODO - Translation
 			'unread' => '只显示未读',
 		),
 		'sides_close_article' => '点击文章区域外以关闭',

+ 4 - 4
app/layout/aside_feed.phtml

@@ -46,9 +46,9 @@
 
 		<?php
 			$t_active = FreshRSS_Context::isCurrentGet('T');
-			$t_show = ($t_active && FreshRSS_Context::$user_conf->display_categories === 'active') || FreshRSS_Context::$user_conf->display_categories === 'all';
+			$t_show = ($t_active && in_array(FreshRSS_Context::$user_conf->display_categories, [ 'active', 'remember' ])) || FreshRSS_Context::$user_conf->display_categories === 'all';
 		?>
-		<li class="tree-folder category tags<?= $t_active ? ' active' : '' ?>">
+		<li id="tags" class="tree-folder category tags<?= $t_active ? ' active' : '' ?>">
 			<div class="tree-folder-title">
 				<a class="dropdown-toggle" href="#"><?= _i($t_active ? 'up' : 'down') ?></a>
 				<a class="title" data-unread="<?= format_number($this->nbUnreadTags) ?>" href="<?= _url('index', $actual_view, 'get', 'T') ?>"><?= _t('index.menu.tags') ?></a>
@@ -75,9 +75,9 @@
 				$position = $cat->attributes('position');
 				if (!empty($feeds)) {
 					$c_active = FreshRSS_Context::isCurrentGet('c_' . $cat->id());
-					$c_show = ($c_active && FreshRSS_Context::$user_conf->display_categories === 'active') || FreshRSS_Context::$user_conf->display_categories === 'all';
+					$c_show = ($c_active && in_array(FreshRSS_Context::$user_conf->display_categories, [ 'active', 'remember' ])) || FreshRSS_Context::$user_conf->display_categories === 'all';
 		?>
-		<li class="tree-folder category<?= $c_active ? ' active' : '' ?>"<?= null === $position ? '' : "data-position='$position'" ?> data-unread="<?= $cat->nbNotRead() ?>">
+		<li id="c_<?= $cat->id() ?>" class="tree-folder category<?= $c_active ? ' active' : '' ?>"<?= null === $position ? '' : "data-position='$position'" ?> data-unread="<?= $cat->nbNotRead() ?>">
 			<div class="tree-folder-title">
 				<a class="dropdown-toggle" href="#"><?= _i($c_show ? 'up' : 'down') ?></a>
 				<a class="title<?= $cat->hasFeedsWithError() ? ' error' : '' ?>" data-unread="<?= format_number($cat->nbNotRead()) ?>" href="<?= _url('index', $actual_view, 'get', 'c_' . $cat->id()) ?>"><?= $cat->name() ?></a>

+ 1 - 0
app/views/configure/reading.phtml

@@ -52,6 +52,7 @@
 			<div class="group-controls">
 				<select name="display_categories" id="display_categories" data-leave-validation="<?= FreshRSS_Context::$user_conf->display_categories ?>">
 					<option value="active"<?= FreshRSS_Context::$user_conf->display_categories === 'active' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.show.active_category') ?></option>
+					<option value="remember"<?= FreshRSS_Context::$user_conf->display_categories === 'remember' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.show.remember_categories') ?></option>
 					<option value="all"<?= FreshRSS_Context::$user_conf->display_categories === 'all' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.show.all_categories') ?></option>
 					<option value="none"<?= FreshRSS_Context::$user_conf->display_categories === 'none' ? ' selected="selected"' : '' ?>><?= _t('conf.reading.show.no_category') ?></option>
 				</select>

+ 1 - 0
app/views/helpers/javascript_vars.phtml

@@ -7,6 +7,7 @@ echo htmlspecialchars(json_encode(array(
 		'auto_remove_article' => !!FreshRSS_Context::isAutoRemoveAvailable(),
 		'hide_posts' => !(FreshRSS_Context::$user_conf->display_posts || Minz_Request::actionName() === 'reader'),
 		'display_order' => Minz_Request::param('order', FreshRSS_Context::$user_conf->sort_order),
+		'display_categories' => FreshRSS_Context::$user_conf->display_categories,
 		'auto_mark_article' => !!$mark['article'],
 		'auto_mark_site' => !!$mark['site'],
 		'auto_mark_scroll' => !!$mark['scroll'],

+ 1 - 0
cli/i18n/ignore/en-us.php

@@ -235,6 +235,7 @@ return array(
 	'conf.reading.show.all_articles',
 	'conf.reading.show.all_categories',
 	'conf.reading.show.no_category',
+	'conf.reading.show.remember_categories',
 	'conf.reading.show.unread',
 	'conf.reading.sides_close_article',
 	'conf.reading.sort._',

+ 1 - 1
config-user.default.php

@@ -32,7 +32,7 @@ return array (
 	'show_fav_unread' => false,
 	'auto_load_more' => true,
 	'display_posts' => false,
-	'display_categories' => 'active',	//{ active, all, none }
+	'display_categories' => 'active',	//{ active, remember, all, none }
 	'hide_read_feeds' => true,
 	'onread_jump_next' => true,
 	'lazyload' => true,

+ 6 - 0
p/scripts/extra.js

@@ -30,6 +30,10 @@ function poormanSalt() {	//If crypto.getRandomValues is not available
 	return text;
 }
 
+function forgetOpenCategories() {
+	localStorage.removeItem('FreshRSS_open_categories');
+}
+
 function init_crypto_form() {
 	/* globals dcodeIO */
 	const crypto_form = document.getElementById('crypto-form');
@@ -45,6 +49,8 @@ function init_crypto_form() {
 		return;
 	}
 
+	forgetOpenCategories();
+
 	crypto_form.onsubmit = function (e) {
 		const submit_button = this.querySelector('button[type="submit"]');
 		submit_button.disabled = true;

+ 31 - 0
p/scripts/main.js

@@ -692,6 +692,26 @@ function init_posts() {
 	}
 }
 
+function rememberOpenCategory(category_id, isOpen) {
+	if (context.display_categories === 'remember') {
+		const open_categories = JSON.parse(localStorage.getItem('FreshRSS_open_categories') || '{}');
+		if (isOpen) {
+			open_categories[category_id] = true;
+		} else {
+			delete open_categories[category_id];
+		}
+		localStorage.setItem('FreshRSS_open_categories', JSON.stringify(open_categories));
+	}
+}
+
+function openCategory(category_id) {
+	const category_element = document.getElementById(category_id);
+	category_element.querySelector('.tree-folder-items').classList.add('active');
+	const img = category_element.querySelector('a.dropdown-toggle img');
+	img.src = img.src.replace('/icons/down.', '/icons/up.');
+	img.alt = '△';
+}
+
 function init_column_categories() {
 	if (context.current_view !== 'normal' && context.current_view !== 'reader') {
 		return;
@@ -700,16 +720,27 @@ function init_column_categories() {
 	//Restore sidebar scroll position
 	document.getElementById('sidebar').scrollTop = +sessionStorage.getItem('FreshRSS_sidebar_scrollTop');
 
+	//Restore open categories
+	if (context.display_categories === 'remember') {
+		const open_categories = JSON.parse(localStorage.getItem('FreshRSS_open_categories') || '{}');
+		Object.keys(open_categories).forEach(function (category_id) {
+			openCategory(category_id);
+		});
+	}
+
 	document.getElementById('aside_feed').onclick = function (ev) {
 		let a = ev.target.closest('.tree-folder > .tree-folder-title > a.dropdown-toggle');
 		if (a) {
 			const img = a.querySelector('img');
+			const category_id = a.closest('.category').id;
 			if (img.alt === '▽') {
 				img.src = img.src.replace('/icons/down.', '/icons/up.');
 				img.alt = '△';
+				rememberOpenCategory(category_id, true);
 			} else {
 				img.src = img.src.replace('/icons/up.', '/icons/down.');
 				img.alt = '▽';
+				rememberOpenCategory(category_id, false);
 			}
 
 			const ul = a.closest('li').querySelector('.tree-folder-items');