From 17413078a72e1298c6dc8953c40e8d83ce38c49c Mon Sep 17 00:00:00 2001
From: Andrew Dolgov <noreply@fakecake.org>
Date: Sat, 13 Feb 2021 18:32:02 +0300
Subject: [PATCH] pref feeds: index cleanup, split into several methods, use
 tabs to maximize space for feed tree, persist feed tree state

---
 classes/pref/feeds.php   | 333 +++++++++++++++++++++------------------
 classes/pref/filters.php |   6 -
 classes/pref/labels.php  |   7 -
 js/PrefFeedTree.js       |  37 ++++-
 plugins/share/init.php   |   6 +-
 themes/compact.css       |  15 +-
 themes/compact_night.css |  15 +-
 themes/light.css         |  15 +-
 themes/light/prefs.less  |  14 +-
 themes/night.css         |  15 +-
 themes/night_blue.css    |  15 +-
 11 files changed, 235 insertions(+), 243 deletions(-)

diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php
index ff9e69336..d8495a59c 100755
--- a/classes/pref/feeds.php
+++ b/classes/pref/feeds.php
@@ -1197,12 +1197,7 @@ class Pref_Feeds extends Handler_Protected {
 		$opml->opml_import($_SESSION["uid"]);
 	}
 
-	function index() {
-
-		print "<div dojoType='dijit.layout.AccordionContainer' region='center'>";
-		print "<div style='padding : 0px' dojoType='dijit.layout.AccordionPane'
-			title=\"<i class='material-icons'>rss_feed</i> ".__('Feeds')."\">";
-
+	private function index_feeds() {
 		$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors
 			FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
 		$sth->execute([$_SESSION['uid']]);
@@ -1214,16 +1209,15 @@ class Pref_Feeds extends Handler_Protected {
 		}
 
 		if ($num_errors > 0) {
-			$error_button = "<button dojoType=\"dijit.form.Button\"
-			  		onclick=\"CommonDialogs.showFeedsWithErrors()\" id=\"errorButton\">" .
-				__("Feeds with errors") . "</button>";
+			$error_button = "<button dojoType='dijit.form.Button' onclick='CommonDialogs.showFeedsWithErrors()' id='errorButton'>".
+				__("Feeds with errors")."</button>";
 		} else {
 			$error_button = "";
 		}
 
-		$inactive_button = "<button dojoType=\"dijit.form.Button\"
-				id=\"pref_feeds_inactive_btn\"
-				style=\"display : none\"
+		$inactive_button = "<button dojoType='dijit.form.Button'
+				id='pref_feeds_inactive_btn'
+				style='display : none'
 				onclick=\"dijit.byId('feedTree').showInactiveFeeds()\">" .
 				__("Inactive feeds") . "</button>";
 
@@ -1235,175 +1229,202 @@ class Pref_Feeds extends Handler_Protected {
 			$feed_search = $_SESSION["prefs_feed_search"] ?? "";
 		}
 
-		print '<div dojoType="dijit.layout.BorderContainer" gutters="false">';
-
-		print "<div region='top' dojoType=\"fox.Toolbar\">"; #toolbar
-
-		print "<div style='float : right; padding-right : 4px;'>
-			<input dojoType=\"dijit.form.TextBox\" id=\"feed_search\" size=\"20\" type=\"search\"
-				value=\"$feed_search\">
-			<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('feedTree').reload()\">".
-				__('Search')."</button>
-			</div>";
-
-		print "<div dojoType=\"fox.form.DropDownButton\">".
-				"<span>" . __('Select')."</span>";
-		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(true)\"
-			dojoType=\"dijit.MenuItem\">".__('All')."</div>";
-		print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(false)\"
-			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
-		print "</div></div>";
-
-		print "<div dojoType=\"fox.form.DropDownButton\">".
-				"<span>" . __('Feeds')."</span>";
-		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"CommonDialogs.quickAddFeed()\"
-			dojoType=\"dijit.MenuItem\">".__('Subscribe to feed')."</div>";
-		print "<div onclick=\"dijit.byId('feedTree').editSelectedFeed()\"
-			dojoType=\"dijit.MenuItem\">".__('Edit selected feeds')."</div>";
-		print "<div onclick=\"dijit.byId('feedTree').resetFeedOrder()\"
-			dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
-		print "<div onclick=\"dijit.byId('feedTree').batchSubscribe()\"
-			dojoType=\"dijit.MenuItem\">".__('Batch subscribe')."</div>";
-		print "<div dojoType=\"dijit.MenuItem\" onclick=\"dijit.byId('feedTree').removeSelectedFeeds()\">"
-			.__('Unsubscribe')."</div> ";
-		print "</div></div>";
-
-		if (get_pref('ENABLE_FEED_CATS')) {
-			print "<div dojoType=\"fox.form.DropDownButton\">".
-					"<span>" . __('Categories')."</span>";
-			print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-			print "<div onclick=\"dijit.byId('feedTree').createCategory()\"
-				dojoType=\"dijit.MenuItem\">".__('Add category')."</div>";
-			print "<div onclick=\"dijit.byId('feedTree').resetCatOrder()\"
-				dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
-			print "<div onclick=\"dijit.byId('feedTree').removeSelectedCategories()\"
-				dojoType=\"dijit.MenuItem\">".__('Remove selected')."</div>";
-			print "</div></div>";
-
-		}
-
-		print $error_button;
-		print $inactive_button;
-
-		print "</div>"; # toolbar
-
-		//print '</div>';
-		print '<div style="padding : 0px" dojoType="dijit.layout.ContentPane" region="center">';
-
-		print "<div id=\"feedlistLoading\">
-		<img src='images/indicator_tiny.gif'>".
-		 __("Loading, please wait...")."</div>";
-
-		$auto_expand = $feed_search != "" ? "true" : "false";
-
-		print "<div dojoType=\"fox.PrefFeedStore\" jsId=\"feedStore\"
-			url=\"backend.php?op=pref-feeds&method=getfeedtree\">
-		</div>
-		<div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"feedModel\" store=\"feedStore\"
-		query=\"{id:'root'}\" rootId=\"root\" rootLabel=\"Feeds\"
-			childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
+		?>
+
+		<div dojoType="dijit.layout.BorderContainer" gutters="false">
+			<div region='top' dojoType="fox.Toolbar">
+				<div style='float : right'>
+					<input dojoType="dijit.form.TextBox" id="feed_search" size="20" type="search"
+						value="<?php echo htmlspecialchars($feed_search) ?>">
+					<button dojoType="dijit.form.Button" onclick="dijit.byId('feedTree').reload()">
+						<?php echo __('Search') ?></button>
+				</div>
+
+				<div dojoType="fox.form.DropDownButton">
+					<span><?php echo __('Select') ?></span>
+					<div dojoType="dijit.Menu" style="display: none;">
+						<div onclick="dijit.byId('feedTree').model.setAllChecked(true)"
+							dojoType="dijit.MenuItem"><?php echo __('All') ?></div>
+						<div onclick="dijit.byId('feedTree').model.setAllChecked(false)"
+							dojoType="dijit.MenuItem"><?php echo __('None') ?></div>
+					</div>
+				</div>
+
+				<div dojoType="fox.form.DropDownButton">
+					<span><?php echo __('Feeds') ?></span>
+					<div dojoType="dijit.Menu" style="display: none">
+						<div onclick="CommonDialogs.quickAddFeed()"
+							dojoType="dijit.MenuItem"><?php echo __('Subscribe to feed') ?></div>
+						<div onclick="dijit.byId('feedTree').editSelectedFeed()"
+							dojoType="dijit.MenuItem"><?php echo __('Edit selected feeds') ?></div>
+						<div onclick="dijit.byId('feedTree').resetFeedOrder()"
+							dojoType="dijit.MenuItem"><?php echo __('Reset sort order') ?></div>
+						<div onclick="dijit.byId('feedTree').batchSubscribe()"
+							dojoType="dijit.MenuItem"><?php echo __('Batch subscribe') ?></div>
+						<div dojoType="dijit.MenuItem" onclick="dijit.byId('feedTree').removeSelectedFeeds()">
+							<?php echo __('Unsubscribe') ?></div>
+					</div>
+				</div>
+
+				<?php if (get_pref('ENABLE_FEED_CATS')) { ?>
+					<div dojoType="fox.form.DropDownButton">
+						<span><?php echo __('Categories') ?></span>
+						<div dojoType="dijit.Menu" style="display: none">
+							<div onclick="dijit.byId('feedTree').createCategory()"
+								dojoType="dijit.MenuItem"><?php echo __('Add category') ?></div>
+							<div onclick="dijit.byId('feedTree').resetCatOrder()"
+								dojoType="dijit.MenuItem"><?php echo __('Reset sort order') ?></div>
+							<div onclick="dijit.byId('feedTree').removeSelectedCategories()"
+								dojoType="dijit.MenuItem"><?php echo __('Remove selected') ?></div>
+						</div>
+					</div>
+				<?php } ?>
+				<?php echo $error_button ?>
+				<?php echo $inactive_button ?>
+			</div>
+			<div style="padding : 0px" dojoType="dijit.layout.ContentPane" region="center">
+				<div dojoType="fox.PrefFeedStore" jsId="feedStore"
+					url="backend.php?op=pref-feeds&method=getfeedtree">
+				</div>
+
+				<div dojoType="lib.CheckBoxStoreModel" jsId="feedModel" store="feedStore"
+					query="{id:'root'}" rootId="root" rootLabel="Feeds" childrenAttrs="items"
+					checkboxStrict="false" checkboxAll="false">
+				</div>
+
+				<div dojoType="fox.PrefFeedTree" id="feedTree"
+					dndController="dijit.tree.dndSource"
+					betweenThreshold="5"
+					autoExpand="<?php echo (!empty($feed_search) ? "true" : "false") ?>"
+					persist="true"
+					model="feedModel"
+					openOnClick="false">
+					<script type="dojo/method" event="onClick" args="item">
+						var id = String(item.id);
+						var bare_id = id.substr(id.indexOf(':')+1);
+
+						if (id.match('FEED:')) {
+							CommonDialogs.editFeed(bare_id);
+						} else if (id.match('CAT:')) {
+							dijit.byId('feedTree').editCategory(bare_id, item);
+						}
+					</script>
+					<script type="dojo/method" event="onLoad" args="item">
+						dijit.byId('feedTree').checkInactiveFeeds();
+					</script>
+				</div>
+			</div>
 		</div>
-		<div dojoType=\"fox.PrefFeedTree\" id=\"feedTree\"
-			dndController=\"dijit.tree.dndSource\"
-			betweenThreshold=\"5\"
-			autoExpand='$auto_expand'
-			model=\"feedModel\" openOnClick=\"false\">
-		<script type=\"dojo/method\" event=\"onClick\" args=\"item\">
-			var id = String(item.id);
-			var bare_id = id.substr(id.indexOf(':')+1);
-
-			if (id.match('FEED:')) {
-				CommonDialogs.editFeed(bare_id);
-			} else if (id.match('CAT:')) {
-				dijit.byId('feedTree').editCategory(bare_id, item);
-			}
-		</script>
-		<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
-			Element.hide(\"feedlistLoading\");
-
-			dijit.byId('feedTree').checkInactiveFeeds();
-		</script>
-		</div>";
-
-#		print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedTree\" position=\"below\">
-#			".__('<b>Hint:</b> you can drag feeds and categories around.')."
-#			</div>";
+	<?php
 
-		print '</div>';
-		print '</div>';
-
-		print "</div>"; # feeds pane
+	}
 
-		print "<div dojoType='dijit.layout.AccordionPane'
-			title='<i class=\"material-icons\">import_export</i> ".__('OPML')."'>";
+	private function index_opml() {
+		?>
 
-		print "<h3>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") . "</h3>";
+		<h3><?php echo __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") ?></h3>
 
-		print_notice("Only main settings profile can be migrated using OPML.");
+		<?php print_notice("Only main settings profile can be migrated using OPML.") ?>
 
-		print "<form id='opml_import_form' method='post' enctype='multipart/form-data' >
-			<label class='dijitButton'>".__("Choose file...")."
-				<input style='display : none' id='opml_file' name='opml_file' type='file'>&nbsp;
+		<form id='opml_import_form' method='post' enctype='multipart/form-data'>
+			<label class='dijitButton'><?php echo __("Choose file...") ?>
+				<input style='display : none' id='opml_file' name='opml_file' type='file'>
 			</label>
 			<input type='hidden' name='op' value='pref-feeds'>
-			<input type='hidden' name='csrf_token' value='".$_SESSION['csrf_token']."'>
+			<input type='hidden' name='csrf_token' value="<?php echo $_SESSION['csrf_token'] ?>">
 			<input type='hidden' name='method' value='importOpml'>
-			<button dojoType='dijit.form.Button' class='alt-primary' onclick=\"return Helpers.OPML.import();\" type=\"submit\">" .
-			__('Import OPML') . "</button>";
-
-		print "</form>";
+			<button dojoType='dijit.form.Button' class='alt-primary' onclick="return Helpers.OPML.import()" type="submit">
+				<?php echo __('Import OPML') ?>
+			</button>
+		</form>
 
-		print "<form dojoType='dijit.form.Form' id='opmlExportForm' style='display : inline-block'>";
+		<hr/>
 
-		print "<button dojoType='dijit.form.Button'
-			onclick='Helpers.OPML.export()' >" .
-			__('Export OPML') . "</button>";
+		<form dojoType='dijit.form.Form' id='opmlExportForm' style='display : inline-block'>
+			<button dojoType='dijit.form.Button' onclick='Helpers.OPML.export()'>
+				<?php echo __('Export OPML') ?>
+			</button>
 
-		print " <label class='checkbox'>";
-		print_checkbox("include_settings", true, "1", "");
-		print " " . __("Include settings");
-		print "</label>";
-
-		print "</form>";
+			<label class='checkbox'>
+				<?php print_checkbox("include_settings", true, "1", "") ?>
+				<?php echo __("Include settings") ?>
+			</label>
+		</form>
 
-		print "<p/>";
+		<hr/>
 
-		print "<h2>" . __("Published OPML") . "</h2>";
+		<h2><?php echo __("Published OPML") ?></h2>
 
-		print "<p>" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') .
-			" " .
-			__("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.") . "</p>";
+		<p>
+			<?php echo __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') ?>
+			<?php echo __("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.") ?>
+		</p>
 
-		print "<button dojoType='dijit.form.Button' class='alt-primary' onclick=\"return CommonDialogs.publishedOPML()\">".
-			__('Display published OPML URL')."</button> ";
+		<button dojoType='dijit.form.Button' class='alt-primary' onclick="return CommonDialogs.publishedOPML()">
+			<?php echo __('Display published OPML URL') ?>
+		</button>
 
+		<?php
 		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION, "prefFeedsOPML");
+	}
 
-		print "</div>"; # pane
-
-		print "<div dojoType=\"dijit.layout.AccordionPane\"
-			title=\"<i class='material-icons'>share</i> ".__('Published & shared articles / Generated feeds')."\">";
-
-		print "<h3>" . __('Published articles can be subscribed by anyone who knows the following URL:') . "</h3>";
-
+	private function index_shared() {
 		$rss_url = htmlspecialchars(get_self_url_prefix() .
-				"/public.php?op=rss&id=-2&view-mode=all_articles");;
+			"/public.php?op=rss&id=-2&view-mode=all_articles");
+		?>
 
-		print "<button dojoType='dijit.form.Button' class='alt-primary'
-			onclick='CommonDialogs.generatedFeed(-2, false, \"$rss_url\", \"".__("Published articles")."\")'>".
-			__('Display URL')."</button>
-		<button class='alt-danger' dojoType='dijit.form.Button' onclick='return Helpers.Feeds.clearFeedAccessKeys()'>".
-			__('Clear all generated URLs')."</button> ";
+		<h3><?php echo __('Published articles can be subscribed by anyone who knows the following URL:') ?></h3>
 
-		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION, "prefFeedsPublishedGenerated");
+		<button dojoType='dijit.form.Button' class='alt-primary'
+			onclick='CommonDialogs.generatedFeed(-2, false, "<?php echo $rss_url ?>", "<?php echo __("Published articles") ?>")'>
+			<?php echo __('Display URL') ?>
+		</button>
 
-		print "</div>"; #pane
+		<button class='alt-danger' dojoType='dijit.form.Button' onclick='return Helpers.Feeds.clearFeedAccessKeys()'>
+			<?php echo __('Clear all generated URLs') ?>
+		</button>
 
-		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB, "prefFeeds");
+		<?php
+		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION, "prefFeedsPublishedGenerated");
+	}
 
-		print "</div>"; #container
+	function index() {
+		?>
+
+		<div dojoType='dijit.layout.TabContainer' tabPosition='left-h'>
+			<div style='padding : 0px' dojoType='dijit.layout.ContentPane'
+				title="<i class='material-icons'>rss_feed</i> <?php echo __('My feeds') ?>">
+				<?php $this->index_feeds() ?>
+			</div>
+
+			<div dojoType='dijit.layout.ContentPane'
+						title="<i class='material-icons'>import_export</i> <?php echo __('OPML') ?>">
+						<?php $this->index_opml() ?>
+					</div>
+
+			<div dojoType="dijit.layout.ContentPane"
+				title="<i class='material-icons'>share</i> <?php echo __('Sharing') ?>">
+				<?php $this->index_shared() ?>
+			</div>
+
+			<?php
+				ob_start();
+				PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB, "prefFeeds");
+				$plugin_data = trim((string)ob_get_contents());
+				ob_end_clean();
+			?>
+
+			<?php if ($plugin_data) { ?>
+				<div dojoType='dijit.layout.ContentPane'
+					title="<i class='material-icons'>extension</i> <?php echo __('Plugins') ?>">
+
+					<div dojoType='dijit.layout.AccordionContainer' region='center'>
+						<?php echo $plugin_data ?>
+					</div>
+				</div>
+			<?php } ?>
+		</div>
+		<?php
 	}
 
 	private function feedlist_init_cat($cat_id) {
diff --git a/classes/pref/filters.php b/classes/pref/filters.php
index c83299678..571ddce4a 100755
--- a/classes/pref/filters.php
+++ b/classes/pref/filters.php
@@ -746,9 +746,6 @@ class Pref_Filters extends Handler_Protected {
 				</div>
 			</div>
 			<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>
-				<div id='filterlistLoading'>
-					<img src='images/indicator_tiny.gif'> <?php echo __("Loading, please wait...") ?>
-				</div>
 				<div dojoType="fox.PrefFilterStore" jsId="filterStore"
 					url="backend.php?op=pref-filters&method=getfiltertree">
 				</div>
@@ -758,9 +755,6 @@ class Pref_Filters extends Handler_Protected {
 				</div>
 				<div dojoType="fox.PrefFilterTree" id="filterTree" dndController="dijit.tree.dndSource"
 					betweenThreshold="5" model="filterModel" openOnClick="true">
-					<script type="dojo/method" event="onLoad" args="item">
-						Element.hide("filterlistLoading");
-					</script>
 					<script type="dojo/method" event="onClick" args="item">
 						var id = String(item.id);
 						var bare_id = id.substr(id.indexOf(':')+1);
diff --git a/classes/pref/labels.php b/classes/pref/labels.php
index 92acabd9e..22a2dddea 100644
--- a/classes/pref/labels.php
+++ b/classes/pref/labels.php
@@ -217,10 +217,6 @@ class Pref_Labels extends Handler_Protected {
 			</div>
 
 			<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>
-				<div id='labellistLoading'>
-					<img src='images/indicator_tiny.gif'><?php echo("Loading, please wait...") ?>
-				</div>
-
 				<div dojoType='dojo.data.ItemFileWriteStore' jsId='labelStore'
 					url='backend.php?op=pref-labels&method=getlabeltree'>
 				</div>
@@ -231,9 +227,6 @@ class Pref_Labels extends Handler_Protected {
 				</div>
 
 				<div dojoType='fox.PrefLabelTree' id='labelTree' model='labelModel' openOnClick='true'>
-					<script type='dojo/method' event='onLoad' args='item'>
-						Element.hide('labellistLoading');
-					</script>
 					<script type='dojo/method' event='onClick' args='item'>
 						var id = String(item.id);
 						var bare_id = id.substr(id.indexOf(':')+1);
diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js
index 89195e616..7684c7f9d 100644
--- a/js/PrefFeedTree.js
+++ b/js/PrefFeedTree.js
@@ -1,9 +1,44 @@
 /* eslint-disable prefer-rest-params */
 /* global __, lib, dijit, define, dojo, CommonDialogs, Notify, Tables, xhrPost, fox, App */
 
-define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) {
+define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_base/array", "dojo/cookie"],
+	function (declare, domConstruct, checkBoxTree, array, cookie) {
 
 	return declare("fox.PrefFeedTree", lib.CheckBoxTree, {
+		// save state in localStorage instead of cookies
+		// reference: https://stackoverflow.com/a/27968996
+		_saveExpandedNodes: function(){
+			if (this.persist && this.cookieName){
+				const ary = [];
+				for (const id in this._openedNodes){
+					ary.push(id);
+				}
+				// Was:
+				// cookie(this.cookieName, ary.join(","), {expires: 365});
+				localStorage.setItem(this.cookieName, ary.join(","));
+			}
+		},
+		_initState: function(){
+			this.cookieName = 'prefs:' + this.cookieName;
+			// summary:
+			//    Load in which nodes should be opened automatically
+			this._openedNodes = {};
+			if (this.persist && this.cookieName){
+				// Was:
+				// var oreo = cookie(this.cookieName);
+				let oreo = localStorage.getItem(this.cookieName);
+				// migrate old data if nothing in localStorage
+				if (oreo == null || oreo === '') {
+					oreo = cookie(this.cookieName);
+					cookie(this.cookieName, null, { expires: -1 });
+				}
+				if (oreo){
+					array.forEach(oreo.split(','), function(item){
+						this._openedNodes[item] = true;
+					}, this);
+				}
+			}
+		},
 		_createTreeNode: function(args) {
 			const tnode = this.inherited(arguments);
 
diff --git a/plugins/share/init.php b/plugins/share/init.php
index 0794f5125..42923ed8a 100644
--- a/plugins/share/init.php
+++ b/plugins/share/init.php
@@ -42,13 +42,13 @@ class Share extends Plugin {
 	function hook_prefs_tab_section($id) {
 		if ($id == "prefFeedsPublishedGenerated") {
 
-			print "<h3>" . __("You can disable all articles shared by unique URLs here.") . "</h3>";
+			print "<hr/>";
+
+			print "<h2>" . __("You can disable all articles shared by unique URLs here.") . "</h2>";
 
 			print "<button class='alt-danger' dojoType='dijit.form.Button' onclick=\"return Plugins.Share.clearKeys()\">".
 				__('Unshare all articles')."</button> ";
 
-			print "</p>";
-
 		}
 	}
 
diff --git a/themes/compact.css b/themes/compact.css
index 080f82961..f923b2ee1 100644
--- a/themes/compact.css
+++ b/themes/compact.css
@@ -1450,21 +1450,12 @@ body.ttrss_prefs .dijitAccordionTitle i.material-icons {
 body.ttrss_prefs .dijitAccordionTitleSelected i.material-icons {
   color: white;
 }
+body.ttrss_prefs #feedsTab {
+  background: #f5f5f5;
+}
 body.ttrss_prefs .dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
   padding: 0px;
 }
-body.ttrss_prefs div#feedlistLoading,
-body.ttrss_prefs div#filterlistLoading,
-body.ttrss_prefs div#labellistLoading {
-  text-align: center;
-  padding: 5px;
-  color: #555;
-}
-body.ttrss_prefs div#feedlistLoading img,
-body.ttrss_prefs div#filterlistLoading img,
-body.ttrss_prefs div#labellistLoading {
-  margin-right: 5px;
-}
 body.ttrss_prefs #errorButton {
   color: red;
 }
diff --git a/themes/compact_night.css b/themes/compact_night.css
index be6a25a2e..e512e8176 100644
--- a/themes/compact_night.css
+++ b/themes/compact_night.css
@@ -1450,21 +1450,12 @@ body.ttrss_prefs .dijitAccordionTitle i.material-icons {
 body.ttrss_prefs .dijitAccordionTitleSelected i.material-icons {
   color: white;
 }
+body.ttrss_prefs #feedsTab {
+  background: #222;
+}
 body.ttrss_prefs .dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
   padding: 0px;
 }
-body.ttrss_prefs div#feedlistLoading,
-body.ttrss_prefs div#filterlistLoading,
-body.ttrss_prefs div#labellistLoading {
-  text-align: center;
-  padding: 5px;
-  color: #ccc;
-}
-body.ttrss_prefs div#feedlistLoading img,
-body.ttrss_prefs div#filterlistLoading img,
-body.ttrss_prefs div#labellistLoading {
-  margin-right: 5px;
-}
 body.ttrss_prefs #errorButton {
   color: red;
 }
diff --git a/themes/light.css b/themes/light.css
index e16ff83dd..a19467c41 100644
--- a/themes/light.css
+++ b/themes/light.css
@@ -1450,21 +1450,12 @@ body.ttrss_prefs .dijitAccordionTitle i.material-icons {
 body.ttrss_prefs .dijitAccordionTitleSelected i.material-icons {
   color: white;
 }
+body.ttrss_prefs #feedsTab {
+  background: #f5f5f5;
+}
 body.ttrss_prefs .dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
   padding: 0px;
 }
-body.ttrss_prefs div#feedlistLoading,
-body.ttrss_prefs div#filterlistLoading,
-body.ttrss_prefs div#labellistLoading {
-  text-align: center;
-  padding: 5px;
-  color: #555;
-}
-body.ttrss_prefs div#feedlistLoading img,
-body.ttrss_prefs div#filterlistLoading img,
-body.ttrss_prefs div#labellistLoading {
-  margin-right: 5px;
-}
 body.ttrss_prefs #errorButton {
   color: red;
 }
diff --git a/themes/light/prefs.less b/themes/light/prefs.less
index 95ddefc34..0206916ae 100644
--- a/themes/light/prefs.less
+++ b/themes/light/prefs.less
@@ -57,18 +57,12 @@ body.ttrss_prefs {
 		color : white;
 	}
 
-	.dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
-		padding : 0px;
-	}
-
-	div#feedlistLoading, div#filterlistLoading, div#labellistLoading {
-		text-align : center;
-		padding : 5px;
-		color : @default-text;
+	#feedsTab {
+		background : @color-panel-bg;
 	}
 
-	div#feedlistLoading img, div#filterlistLoading img, div#labellistLoading {
-		margin-right : 5px;
+	.dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
+		padding : 0px;
 	}
 
 	#errorButton {
diff --git a/themes/night.css b/themes/night.css
index 6090890e2..e2bd50142 100644
--- a/themes/night.css
+++ b/themes/night.css
@@ -1451,21 +1451,12 @@ body.ttrss_prefs .dijitAccordionTitle i.material-icons {
 body.ttrss_prefs .dijitAccordionTitleSelected i.material-icons {
   color: white;
 }
+body.ttrss_prefs #feedsTab {
+  background: #222;
+}
 body.ttrss_prefs .dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
   padding: 0px;
 }
-body.ttrss_prefs div#feedlistLoading,
-body.ttrss_prefs div#filterlistLoading,
-body.ttrss_prefs div#labellistLoading {
-  text-align: center;
-  padding: 5px;
-  color: #ccc;
-}
-body.ttrss_prefs div#feedlistLoading img,
-body.ttrss_prefs div#filterlistLoading img,
-body.ttrss_prefs div#labellistLoading {
-  margin-right: 5px;
-}
 body.ttrss_prefs #errorButton {
   color: red;
 }
diff --git a/themes/night_blue.css b/themes/night_blue.css
index 4bea2256f..93027e8be 100644
--- a/themes/night_blue.css
+++ b/themes/night_blue.css
@@ -1451,21 +1451,12 @@ body.ttrss_prefs .dijitAccordionTitle i.material-icons {
 body.ttrss_prefs .dijitAccordionTitleSelected i.material-icons {
   color: white;
 }
+body.ttrss_prefs #feedsTab {
+  background: #222;
+}
 body.ttrss_prefs .dijitDialog #pref-profiles-list .dijitInlineEditBoxDisplayMode {
   padding: 0px;
 }
-body.ttrss_prefs div#feedlistLoading,
-body.ttrss_prefs div#filterlistLoading,
-body.ttrss_prefs div#labellistLoading {
-  text-align: center;
-  padding: 5px;
-  color: #ccc;
-}
-body.ttrss_prefs div#feedlistLoading img,
-body.ttrss_prefs div#filterlistLoading img,
-body.ttrss_prefs div#labellistLoading {
-  margin-right: 5px;
-}
 body.ttrss_prefs #errorButton {
   color: red;
 }
-- 
GitLab