Browse Source

konflikti

Schunka Calcifer 1 year ago
parent
commit
eca251b3f4
100 changed files with 6761 additions and 7744 deletions
  1. 0 0
      cache/js/.empty
  2. 63 61
      classes/article.php
  3. 5 3
      classes/db/prefs.php
  4. 12 10
      classes/dlg.php
  5. 106 147
      classes/feeds.php
  6. 16 13
      classes/handler/public.php
  7. 2 2
      classes/labels.php
  8. 2 5
      classes/opml.php
  9. 68 63
      classes/pref/feeds.php
  10. 25 25
      classes/pref/filters.php
  11. 17 24
      classes/pref/labels.php
  12. 82 65
      classes/pref/prefs.php
  13. 4 3
      classes/pref/system.php
  14. 31 36
      classes/pref/users.php
  15. 1 1
      classes/rpc.php
  16. 0 4
      css/Makefile
  17. 95 98
      css/cdm.less
  18. 0 0
      css/default.css
  19. 0 0
      css/default.css.map
  20. 1 25
      css/default.less
  21. 70 0
      css/defines.less
  22. 92 673
      css/dijit.less
  23. 62 18
      css/prefs.less
  24. 310 345
      css/tt-rss.less
  25. 1 1
      css/utility.less
  26. 26 21
      css/zoom.less
  27. BIN
      images/alert.png
  28. BIN
      images/archive.png
  29. BIN
      images/collapse.png
  30. BIN
      images/cross.png
  31. BIN
      images/error.png
  32. BIN
      images/feed.png
  33. BIN
      images/filter.png
  34. BIN
      images/folder.png
  35. BIN
      images/fresh.png
  36. BIN
      images/label.png
  37. BIN
      images/mark_set.png
  38. BIN
      images/mark_unset.png
  39. BIN
      images/new_version.png
  40. BIN
      images/page_white_go.png
  41. BIN
      images/plugin.png
  42. BIN
      images/plugin_disabled.png
  43. BIN
      images/pub_unset.png
  44. BIN
      images/score_half_high.png
  45. BIN
      images/score_half_low.png
  46. BIN
      images/score_high.png
  47. BIN
      images/score_low.png
  48. BIN
      images/score_neutral.png
  49. BIN
      images/star.png
  50. BIN
      images/tick.png
  51. BIN
      images/time.png
  52. BIN
      images/treeExpandImages.png
  53. BIN
      images/untick.png
  54. BIN
      images/user.png
  55. 4 10
      include/feedbrowser.php
  56. 35 61
      include/functions.php
  57. 29 51
      include/login_form.php
  58. 0 4
      include/sanity_check.php
  59. 125 144
      index.php
  60. 414 0
      js/AppBase.js
  61. 356 0
      js/Article.js
  62. 29 0
      js/ArticleCache.js
  63. 469 0
      js/CommonDialogs.js
  64. 379 0
      js/CommonFilters.js
  65. 37 24
      js/FeedTree.js
  66. 639 0
      js/Feeds.js
  67. 1205 0
      js/Headlines.js
  68. 3 3
      js/PluginHost.js
  69. 300 6
      js/PrefFeedTree.js
  70. 176 12
      js/PrefFilterTree.js
  71. 230 0
      js/PrefHelpers.js
  72. 134 10
      js/PrefLabelTree.js
  73. 122 0
      js/PrefUsers.js
  74. 331 0
      js/common.js
  75. 0 532
      js/feedlist.js
  76. 0 1521
      js/functions.js
  77. 138 1207
      js/prefs.js
  78. 502 851
      js/tt-rss.js
  79. 0 1656
      js/viewfeed.js
  80. 0 0
      lib/dijit/Dialog.js
  81. 0 0
      lib/dijit/Editor.js
  82. 0 0
      lib/dijit/Tree.js
  83. 0 0
      lib/dijit/_HasDropDown.js
  84. 0 0
      lib/dijit/_WidgetBase.js
  85. 0 0
      lib/dijit/_editor/RichText.js
  86. 0 0
      lib/dijit/_editor/_Plugin.js
  87. 0 0
      lib/dijit/_editor/plugins/FontChoice.js
  88. 0 0
      lib/dijit/_editor/plugins/LinkDialog.js
  89. 0 0
      lib/dijit/_editor/plugins/ViewSource.js
  90. 1 1
      lib/dijit/bower.json
  91. 0 0
      lib/dijit/form/NumberTextBox.js
  92. 1 1
      lib/dijit/form/TimeTextBox.js
  93. 1 1
      lib/dijit/form/_FormValueMixin.js
  94. 0 0
      lib/dijit/form/_FormWidgetMixin.js
  95. 0 0
      lib/dijit/layout/ContentPane.js
  96. 2 2
      lib/dijit/package.json
  97. 0 1
      lib/dijit/tree/ObjectStoreModel.js
  98. 2 2
      lib/dojo-src/rebuild-dojo.sh
  99. 5 0
      lib/dojo-src/tt-rss.profile.js
  100. 1 1
      lib/dojo/LICENSE

+ 0 - 0
cache/js/.empty


+ 63 - 61
classes/article.php

@@ -253,6 +253,7 @@ class Article extends Handler_Protected {
 
 		print json_encode(array("id" => $ids,
 			"score" => (int)$score,
+			"score_class" => get_score_class($score),
 			"score_pic" => get_score_pic($score)));
 	}
 
@@ -626,7 +627,8 @@ class Article extends Handler_Protected {
 
 			} else {
 				if ($line["comments"] && $line["link"] != $line["comments"]) {
-					$entry_comments = "<a class=\"comments\" target='_blank' rel=\"noopener noreferrer\" href=\"".htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
+					$entry_comments = "<a class=\"comments\" target='_blank' rel=\"noopener noreferrer\" href=\"".
+						htmlspecialchars($line["comments"])."\">".__("comments")."</a>";
 				}
 			}
 
@@ -675,82 +677,101 @@ class Article extends Handler_Protected {
 					$rv['content'] .= "<meta property=\"og:image\" content=\"" . htmlspecialchars($og_image) . "\"/>";
 				}
 
-				$rv['content'] .= "<body class=\"claro ttrss_utility ttrss_zoom\">";
+				$rv['content'] .= "<body class='flat ttrss_utility ttrss_zoom'>";
 			}
 
-			$rv['content'] .= "<div class=\"post\" id=\"POST-$id\">";
+			$rv['content'] .= "<div class='post post-$id'>";
 
-			$rv['content'] .= "<div class=\"header\">";
+			/* header */
 
-			$entry_author = $line["author"];
-
-			if ($entry_author) {
-				$entry_author = __(" - ") . $entry_author;
-			}
+			$rv['content'] .= "<div class='header'>";
+			$rv['content'] .= "<div class='row'>"; # row
 
+			//$entry_author = $line["author"] ? " - " . $line["author"] : "";
 			$parsed_updated = make_local_datetime($line["updated"], true,
 				$owner_uid, true);
 
-			if (!$zoom_mode)
-				$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
-
 			if ($line["link"]) {
 				$rv['content'] .= "<div class='title'><a target='_blank' rel='noopener noreferrer'
 					title=\"".htmlspecialchars($line['title'])."\"
-					href=\"" .
-					htmlspecialchars($line["link"]) . "\">" .
-					$line["title"] . "</a>" .
-					"<span class='author'>$entry_author</span></div>";
+					href=\"" .htmlspecialchars($line["link"]) . "\">" .	$line["title"] . "</a></div>";
 			} else {
-				$rv['content'] .= "<div class='title'>" . $line["title"] . "$entry_author</div>";
+				$rv['content'] .= "<div class='title'>" . $line["title"] . "</div>";
 			}
 
-			if ($zoom_mode) {
-				$feed_title = htmlspecialchars($line["feed_title"]);
+			if (!$zoom_mode)
+				$rv['content'] .= "<div class='date'>$parsed_updated<br/></div>";
 
-				$rv['content'] .= "<div class=\"feed-title\">$feed_title</div>";
+			$rv['content'] .= "</div>"; # row
 
-				$rv['content'] .= "<div class=\"date\">$parsed_updated</div>";
+			$rv['content'] .= "<div class='row'>"; # row
+
+			/* left buttons */
+
+			$rv['content'] .= "<div class='buttons left'>";
+			foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
+				$rv['content'] .= $p->hook_article_left_button($line);
 			}
+			$rv['content'] .= "</div>";
+
+			/* comments */
+
+			$rv['content'] .= "<div class='comments'>$entry_comments</div>";
+			$rv['content'] .= "<div class='author'>".$line['author']."</div>";
+
+			/* tags */
 
 			$tags_str = Article::format_tags_string($line["tags"], $id);
 			$tags_str_full = join(", ", $line["tags"]);
 
 			if (!$tags_str_full) $tags_str_full = __("no tags");
 
-			if (!$entry_comments) $entry_comments = "&nbsp;"; # placeholder
-
-			$rv['content'] .= "<div class='tags' style='float : right'>
-				<img src='images/tag.png'
-				class='tagsPic' alt='Tags' title='Tags'>&nbsp;";
+			$rv['content'] .= "<i class='material-icons'>label_outline</i><div>";
 
 			if (!$zoom_mode) {
 				$rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>
 					<a title=\"".__('Edit tags for this article')."\"
-					href=\"#\" onclick=\"editArticleTags($id, $feed_id)\">(+)</a>";
+					href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>";
 
 				$rv['content'] .= "<div dojoType=\"dijit.Tooltip\"
 					id=\"ATSTRTIP-$id\" connectId=\"ATSTR-$id\"
 					position=\"below\">$tags_str_full</div>";
 
-				foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
-					$rv['content'] .= $p->hook_article_button($line);
-				}
-
 			} else {
 				$tags_str = strip_tags($tags_str);
 				$rv['content'] .= "<span id=\"ATSTR-$id\">$tags_str</span>";
 			}
+
 			$rv['content'] .= "</div>";
-			$rv['content'] .= "<div clear='both'>";
 
-			foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_LEFT_BUTTON) as $p) {
-				$rv['content'] .= $p->hook_article_left_button($line);
+			/* buttons */
+
+			$rv['content'] .= "<div class='buttons right'>";
+			foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
+				$rv['content'] .= $p->hook_article_button($line);
+			}
+			$rv['content'] .= "</div>";
+
+			$rv['content'] .= "</div>"; # row
+
+			$rv['content'] .= "</div>"; # header
+
+			/* note */
+
+			$rv['content'] .= "<div id=\"POSTNOTE-$id\">";
+			if ($line['note']) {
+				$rv['content'] .= Article::format_article_note($id, $line['note'], !$zoom_mode);
 			}
+			$rv['content'] .= "</div>";
+
+			/* content */
 
-			$rv['content'] .= "$entry_comments</div>";
+			$lang = $line['lang'] ? $line['lang'] : "en";
+			$rv['content'] .= "<div class=\"content\" lang=\"$lang\">";
 
-			if ($line["orig_feed_id"]) {
+			/* originally from */
+
+			if (!$zoom_mode && $line["orig_feed_id"]) {
 
 				$of_sth = $pdo->prepare("SELECT * FROM ttrss_archived_feeds
 					WHERE id = ? AND owner_uid = ?");
@@ -770,23 +791,12 @@ class Article extends Handler_Protected {
 					$rv['content'] .= "&nbsp;";
 
 					$rv['content'] .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
-					$rv['content'] .= "<img title='".__('Feed URL')."' class='tinyFeedIcon' src='images/pub_set.png'></a>";
 
 					$rv['content'] .= "</div>";
 				}
 			}
 
-			$rv['content'] .= "</div>";
-
-			$rv['content'] .= "<div id=\"POSTNOTE-$id\">";
-			if ($line['note']) {
-				$rv['content'] .= Article::format_article_note($id, $line['note'], !$zoom_mode);
-			}
-			$rv['content'] .= "</div>";
-
-			if (!$line['lang']) $line['lang'] = 'en';
-
-			$rv['content'] .= "<div class=\"content\" lang=\"".$line['lang']."\">";
+			/* content body */
 
 			$rv['content'] .= $line["content"];
 
@@ -797,18 +807,10 @@ class Article extends Handler_Protected {
 					$line["hide_images"]);
 			}
 
-			$rv['content'] .= "</div>";
+			$rv['content'] .= "</div>"; # content
 
-			$rv['content'] .= "</div>";
-
-		}
+			$rv['content'] .= "</div>"; # post
 
-		if ($zoom_mode) {
-			$rv['content'] .= "
-				<div class='footer'>
-				<button onclick=\"return window.close()\">".
-				__("Close this window")."</button></div>";
-			$rv['content'] .= "</body></html>";
 		}
 
 		foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) {
@@ -877,7 +879,7 @@ class Article extends Handler_Protected {
 			$tags_str = "";
 
 			for ($i = 0; $i < $maxtags; $i++) {
-				$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"viewfeed({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
+				$tags_str .= "<a class=\"tag\" href=\"#\" onclick=\"Feeds.open({feed:'".$tags[$i]."'})\">" . $tags[$i] . "</a>, ";
 			}
 
 			$tags_str = mb_substr($tags_str, 0, mb_strlen($tags_str)-2);
@@ -907,8 +909,8 @@ class Article extends Handler_Protected {
 
 	static function format_article_note($id, $note, $allow_edit = true) {
 
-		$str = "<div class='articleNote'	onclick=\"editArticleNote($id)\">
-			<div class='noteEdit' onclick=\"editArticleNote($id)\">".
+		$str = "<div class='articleNote'	onclick=\"Plugins.Note.edit($id)\">
+			<div class='noteEdit' onclick=\"Plugins.Note.edit($id)\">".
 			($allow_edit ? __('(edit note)') : "")."</div>$note</div>";
 
 		return $str;

+ 5 - 3
classes/db/prefs.php

@@ -26,7 +26,7 @@ class Db_Prefs {
 		$user_id = $_SESSION["uid"];
 		@$profile = $_SESSION["profile"];
 
-		if (!$profile || get_schema_version() < 63) $profile = null;
+		if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
 
 		$sth = $this->pdo->prepare("SELECT
 			value,ttrss_prefs_types.type_name as type_name,ttrss_prefs.pref_name AS pref_name
@@ -65,7 +65,7 @@ class Db_Prefs {
 			return $this->convert($tuple["value"], $tuple["type"]);
 		}
 
-		if (!$profile || get_schema_version() < 63) $profile = null;
+		if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
 
 		$sth = $this->pdo->prepare("SELECT
 			value,ttrss_prefs_types.type_name as type_name
@@ -112,9 +112,11 @@ class Db_Prefs {
 		if (!$user_id) {
 			$user_id = $_SESSION["uid"];
 			@$profile = $_SESSION["profile"];
+		} else {
+			$profile = null;
 		}
 
-		if (!$profile || get_schema_version() < 63) $profile = null;
+		if (!is_numeric($profile) || !$profile || get_schema_version() < 63) $profile = null;
 
 		$type_name = "";
 		$current_value = "";

+ 12 - 10
classes/dlg.php

@@ -49,10 +49,10 @@ class Dlg extends Handler_Protected {
 
 		print "<div align='center'>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return opmlRegenKey()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.OPML.changeKey()\">".
 			__('Generate new URL')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.closeInfoBox()\">".
 			__('Close this window')."</button>";
 
 		print "</div>";
@@ -85,7 +85,7 @@ class Dlg extends Handler_Protected {
 
 		print "<div align='center'>";
 
-		print "<button onclick=\"return closeInfoBox()\">".
+		print "<button onclick=\"return CommonDialogs.closeInfoBox()\">".
 			__('Close this window')."</button>";
 
 		print "</div>";
@@ -139,7 +139,7 @@ class Dlg extends Handler_Protected {
 
 			$key_escaped = str_replace("'", "\\'", $key);
 
-			echo "<a href=\"javascript:viewfeed({feed:'$key_escaped'}) \" style=\"font-size: " .
+			echo "<a href=\"#\" onclick=\"Feeds.open({feed:'$key_escaped'}) \" style=\"font-size: " .
 				$size . "px\" title=\"$value articles tagged with " .
 				$key . '">' . $key . '</a> ';
 		}
@@ -150,7 +150,7 @@ class Dlg extends Handler_Protected {
 
 		print "<div align='center'>";
 		print "<button dojoType=\"dijit.form.Button\"
-			onclick=\"return closeInfoBox()\">".
+			onclick=\"return CommonDialogs.closeInfoBox()\">".
 			__('Close this window')."</button>";
 		print "</div>";
 
@@ -166,7 +166,9 @@ class Dlg extends Handler_Protected {
 
 		$url_path = htmlspecialchars($this->params[2]) . "&key=" . $key;
 
-		print "<h2>".__("You can view this feed as RSS using the following URL:")."</h2>";
+		$feed_title = Feeds::getFeedTitle($feed_id, $is_cat);
+
+		print "<div>".T_sprintf("%s can be accessed via the following secret URL:", $feed_title)."</div>";
 
 		print "<div class=\"tagCloudContainer\">";
 		print "<a id='gen_feed_url' href='$url_path' target='_blank'>$url_path</a>";
@@ -174,10 +176,10 @@ class Dlg extends Handler_Protected {
 
 		print "<div align='center'>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return genUrlChangeKey('$feed_id', '$is_cat')\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.genUrlChangeKey('$feed_id', '$is_cat')\">".
 			__('Generate new URL')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return closeInfoBox()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.closeInfoBox()\">".
 			__('Close this window')."</button>";
 
 		print "</div>";
@@ -190,10 +192,10 @@ class Dlg extends Handler_Protected {
     	print_warning(__("You are using default tt-rss password. Please change it in the Preferences (Personal data / Authentication)."));
 
 		print "<div align='center'>";
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"gotoPreferences()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"document.location.href = 'prefs.php'\">".
 			__('Open Preferences')."</button> ";
 		print "<button dojoType=\"dijit.form.Button\"
-			onclick=\"return closeInfoBox()\">".
+			onclick=\"return CommonDialogs.closeInfoBox()\">".
 			__('Close this window')."</button>";
 		print "</div>";
 	}

+ 106 - 147
classes/feeds.php

@@ -15,22 +15,6 @@ class Feeds extends Handler_Protected {
 			$feed_id, $is_cat, $search,
 			$error, $feed_last_updated) {
 
-		$catchup_sel_link = "catchupSelection()";
-
-		$archive_sel_link = "archiveSelection()";
-		$delete_sel_link = "deleteSelection()";
-
-		$sel_all_link = "selectArticles('all')";
-		$sel_unread_link = "selectArticles('unread')";
-		$sel_none_link = "selectArticles('none')";
-		$sel_inv_link = "selectArticles('invert')";
-
-		$tog_unread_link = "selectionToggleUnread()";
-		$tog_marked_link = "selectionToggleMarked()";
-		$tog_published_link = "selectionTogglePublished()";
-
-		$set_score_link = "setSelectionScore()";
-
 		if ($is_cat) $cat_q = "&is_cat=$is_cat";
 
 		if ($search) {
@@ -39,97 +23,72 @@ class Feeds extends Handler_Protected {
 			$search_q = "";
 		}
 
-		$reply = "<span class=\"holder\">";
+		$reply = "";
 
 		$rss_link = htmlspecialchars(get_self_url_prefix() .
 			"/public.php?op=rss&id=$feed_id$cat_q$search_q");
 
-		// right part
+		$reply .= "<span class='left'>";
 
-		$error_class = $error ? "error" : "";
+		$reply .= "<a href=\"#\"
+				title=\"".__("Show as feed")."\"
+				onclick=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
+				<i class='icon-syndicate material-icons'>rss_feed</i></a>";
 
-		$reply .= "<span class='r'>
-			<a href=\"#\"
-				title=\"".__("View as RSS feed")."\"
-				onclick=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">
-				<img class=\"noborder\" src=\"images/pub_set.png\"></a>";
-
-
-#		$reply .= "<span>";
-		$reply .= "<span id='feed_title' class='$error_class'>";
+		$reply .= "<span id='feed_title'>";
 
 		if ($feed_site_url) {
-			$last_updated = T_sprintf("Last updated: %s",
-				$feed_last_updated);
+			$last_updated = T_sprintf("Last updated: %s", $feed_last_updated);
 
-			$target = "target=\"_blank\"";
-			$reply .= "<a title=\"$last_updated\" $target href=\"$feed_site_url\">".
+			$reply .= "<a title=\"$last_updated\" target='_blank' href=\"$feed_site_url\">".
 				truncate_string(strip_tags($feed_title), 30)."</a>";
-
-			if ($error) {
-				$error = htmlspecialchars($error);
-				$reply .= "&nbsp;<img title=\"$error\" src='images/error.png' alt='error' class=\"noborder\">";
-			}
-
 		} else {
 			$reply .= strip_tags($feed_title);
 		}
 
-		$reply .= "</span>";
-
-		$reply .= "</span>";
+		if ($error)
+			$reply .= " <i title=\"" . htmlspecialchars($error) . "\" class='material-icons icon-error'>error</i>";
 
-#		$reply .= "</span>";
-
-		// left part
+		$reply .= "</span></span>";
 
-		$reply .= "<span class=\"main\">";
+		$reply .= "<span class=\"right\">";
 		$reply .= "<span id='selected_prompt'></span>";
-
-		/*$reply .= "<span class=\"sel_links\">
-			<a href=\"#\" onclick=\"$sel_all_link\">".__('All')."</a>,
-			<a href=\"#\" onclick=\"$sel_unread_link\">".__('Unread')."</a>,
-			<a href=\"#\" onclick=\"$sel_inv_link\">".__('Invert')."</a>,
-			<a href=\"#\" onclick=\"$sel_none_link\">".__('None')."</a></li>";
-
-		$reply .= "</span> "; */
-
+		$reply .= "&nbsp;";
 		$reply .= "<select dojoType=\"dijit.form.Select\"
-			onchange=\"headlineActionsChange(this)\">";
+			onchange=\"Headlines.onActionChanged(this)\">";
 
 		$reply .= "<option value=\"0\" disabled='1'>".__('Select...')."</option>";
 
-		$reply .= "<option value=\"$sel_all_link\">".__('All')."</option>";
-		$reply .= "<option value=\"$sel_unread_link\">".__('Unread')."</option>";
-		$reply .= "<option value=\"$sel_inv_link\">".__('Invert')."</option>";
-		$reply .= "<option value=\"$sel_none_link\">".__('None')."</option>";
+		$reply .= "<option value=\"Headlines.select('all')\">".__('All')."</option>";
+		$reply .= "<option value=\"Headlines.select('unread')\">".__('Unread')."</option>";
+		$reply .= "<option value=\"Headlines.select('invert')\">".__('Invert')."</option>";
+		$reply .= "<option value=\"Headlines.select('none')\">".__('None')."</option>";
 
 		$reply .= "<option value=\"0\" disabled=\"1\">".__('Selection toggle:')."</option>";
 
-		$reply .= "<option value=\"$tog_unread_link\">".__('Unread')."</option>
-			<option value=\"$tog_marked_link\">".__('Starred')."</option>
-			<option value=\"$tog_published_link\">".__('Published')."</option>";
+		$reply .= "<option value=\"Headlines.selectionToggleUnread()\">".__('Unread')."</option>
+			<option value=\"Headlines.selectionToggleMarked()\">".__('Starred')."</option>
+			<option value=\"Headlines.selectionTogglePublished()\">".__('Published')."</option>";
 
 		$reply .= "<option value=\"0\" disabled=\"1\">".__('Selection:')."</option>";
 
-		$reply .= "<option value=\"$catchup_sel_link\">".__('Mark as read')."</option>";
-		$reply .= "<option value=\"$set_score_link\">".__('Set score')."</option>";
+		$reply .= "<option value=\"Headlines.catchupSelection()\">".__('Mark as read')."</option>";
+		$reply .= "<option value=\"Article.selectionSetScore()\">".__('Set score')."</option>";
 
-		if ($feed_id != "0") {
-			$reply .= "<option value=\"$archive_sel_link\">".__('Archive')."</option>";
+		if ($feed_id == 0 && !$is_cat) {
+			$reply .= "<option value=\"Headlines.archiveSelection()\">".__('Move back')."</option>";
+			$reply .= "<option value=\"Headlines.deleteSelection()\">".__('Delete')."</option>";
 		} else {
-			$reply .= "<option value=\"$archive_sel_link\">".__('Move back')."</option>";
-			$reply .= "<option value=\"$delete_sel_link\">".__('Delete')."</option>";
-
+			$reply .= "<option value=\"Headlines.archiveSelection()\">".__('Archive')."</option>";
 		}
 
 		if (PluginHost::getInstance()->get_plugin("mail")) {
-			$reply .= "<option value=\"emailArticle(false)\">".__('Forward by email').
+			$reply .= "<option value=\"Plugins.Mail.send()\">".__('Forward by email').
 				"</option>";
 		}
 
 		if (PluginHost::getInstance()->get_plugin("mailto")) {
-			$reply .= "<option value=\"mailtoArticle(false)\">".__('Forward by email').
+			$reply .= "<option value=\"Plugins.Mailto.send()\">".__('Forward by email').
 				"</option>";
 		}
 
@@ -137,7 +96,8 @@ class Feeds extends Handler_Protected {
 
 		//$reply .= "<option value=\"catchupPage()\">".__('Mark as read')."</option>";
 
-		$reply .= "<option value=\"displayDlg('".__("View as RSS")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">".__('View as RSS')."</option>";
+		$reply .= "<option value=\"App.displayDlg('".__("Show as feed")."','generatedFeed', '$feed_id:$is_cat:$rss_link')\">".
+            __('Show as feed')."</option>";
 
 		$reply .= "</select>";
 
@@ -147,7 +107,7 @@ class Feeds extends Handler_Protected {
 			 $reply .= $p->hook_headline_toolbar_button($feed_id, $is_cat);
 		}
 
-		$reply .= "</span></span>";
+		$reply .= "</span>";
 
 		return $reply;
 	}
@@ -299,7 +259,13 @@ class Feeds extends Handler_Protected {
 				$label_cache = $line["label_cache"];
 				$labels = false;
 
-				$mouseover_attrs = "onmouseover='postMouseIn(event, $id)' onmouseout='postMouseOut($id)'";
+				// normalize archived feed
+				if ($feed_id === null) {
+					$feed_id = 0;
+					$line["feed_title"] = __("Archived articles");
+				}
+
+				$mouseover_attrs = "onmouseover='Article.mouseIn($id)' onmouseout='Article.mouseOut($id)'";
 
 				if ($label_cache) {
 					$label_cache = json_decode($label_cache, true);
@@ -329,13 +295,11 @@ class Feeds extends Handler_Protected {
 					++$num_unread;
 				}
 
-				$marked_pic_src = $line["marked"] ? "mark_set.png" : "mark_unset.png";
 				$class .= $line["marked"] ? " marked" : "";
-				$marked_pic = "<img src=\"images/$marked_pic_src\" class=\"marked-pic marked-$id\" onclick='toggleMark($id)'>";
+				$marked_pic = "<i class=\"marked-pic marked-$id material-icons\" onclick='Headlines.toggleMark($id)'>star</i>";
 
-				$published_pic_src = $line["published"] ? "pub_set.png" : "pub_unset.png";
 				$class .= $line["published"] ? " published" : "";
-                $published_pic = "<img src=\"images/$published_pic_src\" class=\"pub-pic pub-$id\" onclick='togglePub($id)'>";
+                $published_pic = "<i class=\"pub-pic pub-$id material-icons\" onclick='Headlines.togglePub($id)'>rss_feed</i>";
 
 				$updated_fmt = make_local_datetime($line["updated"], false, false, false, true);
 				$date_entered_fmt = T_sprintf("Imported at %s",
@@ -343,18 +307,10 @@ class Feeds extends Handler_Protected {
 
 				$score = $line["score"];
 
-				$score_pic = "images/" . get_score_pic($score);
-
-				$score_pic = "<img class='score-pic' score='$score' onclick='changeScore($id, this)' src=\"$score_pic\"
-                title=\"$score\">";
-
-				if ($score > 500) {
-					$hlc_suffix = "high";
-				} else if ($score < -100) {
-					$hlc_suffix = "low";
-				} else {
-					$hlc_suffix = "";
-				}
+				$score_pic = "<i class='material-icons icon-score' title='$score' 
+                       data-score='$score' onclick='Article.setScore($id, this)'>" .
+                        get_score_pic($score) . "</i>";
+                $score_class = get_score_class($score);
 
 				$entry_author = $line["author"];
 
@@ -363,9 +319,9 @@ class Feeds extends Handler_Protected {
 				}
 
 				if (feeds::feedHasIcon($feed_id)) {
-					$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">";
+					$feed_icon_img = "<img class=\"icon\" src=\"".ICONS_URL."/$feed_id.ico\" alt=\"\">";
 				} else {
-					$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"images/pub_set.png\" alt=\"\">";
+					$feed_icon_img = "<i class='icon-no-feed material-icons'>rss_feed</i>";
 				}
 
 				$entry_site_url = $line["site_url"];
@@ -384,28 +340,26 @@ class Feeds extends Handler_Protected {
 				if (!get_pref('COMBINED_DISPLAY_MODE')) {
 
 					if ($vfeed_group_enabled) {
-						if ($feed_id != $vgroup_last_feed && $line["feed_title"]) {
+						if ($feed_id != $vgroup_last_feed) {
 
 							$vgroup_last_feed = $feed_id;
 
-							$vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
+							$vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
 
-							$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-titl'>".
+							$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>".
 								"<div style='float : right'>$feed_icon_img</div>".
-								"<a class='title' href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
+								"<a class='title' href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">".
 								$line["feed_title"]."</a>
                             $vf_catchup_link</div>";
-
-
 						}
 					}
 
-					$reply['content'] .= "<div class='hl hlMenuAttach $class' data-orig-feed-id='$feed_id' data-article-id='$id' id='RROW-$id' $mouseover_attrs>";
+					$reply['content'] .= "<div class='hl $class $score_class' data-orig-feed-id='$feed_id' data-article-id='$id' id='RROW-$id' $mouseover_attrs>";
 
 					$reply['content'] .= "<div class='left'>";
 
 					$reply['content'] .= "<input dojoType=\"dijit.form.CheckBox\"
-                        type=\"checkbox\" onclick=\"toggleSelectRow2(this)\"
+                        type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\"
                         class='rchk'>";
 
 					$reply['content'] .= "$marked_pic";
@@ -413,9 +367,9 @@ class Feeds extends Handler_Protected {
 
 					$reply['content'] .= "</div>";
 
-					$reply['content'] .= "<div onclick='return hlClicked(event, $id)'
-                    class=\"title\"><span class='hl-content $hlc_suffix'>";
-					$reply['content'] .= "<a class=\"title $hlc_suffix\"
+					$reply['content'] .= "<div onclick='return Headlines.click(event, $id)'
+                    class=\"title\"><span data-article-id=\"$id\" class='hl-content hlMenuAttach'>";
+					$reply['content'] .= "<a class=\"title\"
                     href=\"" . htmlspecialchars($line["link"]) . "\"
                     onclick=\"\">" .
 						truncate_string($line["title"], 200);
@@ -434,7 +388,7 @@ class Feeds extends Handler_Protected {
 						if (@$line["feed_title"]) {
 							$rgba = @$rgba_cache[$feed_id];
 
-							$reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"viewfeed({feed:$feed_id})\">".
+							$reply['content'] .= "<span class=\"feed\"><a style=\"background : rgba($rgba, 0.3)\" href=\"#\" onclick=\"Feeds.open({feed:$feed_id})\">".
 								truncate_string($line["feed_title"],30)."</a></span>";
 						}
 					}
@@ -449,9 +403,8 @@ class Feeds extends Handler_Protected {
 
 					$reply['content'] .= $score_pic;
 
-					if ($line["feed_title"] && !$vfeed_group_enabled) {
-
-						$reply['content'] .= "<span onclick=\"viewfeed({feed:$feed_id})\"
+					if (!$vfeed_group_enabled) {
+						$reply['content'] .= "<span onclick=\"Feeds.open({feed:$feed_id})\"
                         style=\"cursor : pointer\"
                         title=\"".htmlspecialchars($line['feed_title'])."\">
                         $feed_icon_img</span>";
@@ -481,14 +434,14 @@ class Feeds extends Handler_Protected {
 
 							$vgroup_last_feed = $feed_id;
 
-							$vf_catchup_link = "<a class='catchup' onclick='catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
+							$vf_catchup_link = "<a class='catchup' onclick='Feeds.catchupFeedInGroup($feed_id);' href='#'>".__('mark feed as read')."</a>";
 
 							$feed_icon_src = Feeds::getFeedIcon($feed_id);
-							$feed_icon_img = "<img class=\"tinyFeedIcon\" src=\"$feed_icon_src\">";
+							$feed_icon_img = "<img class=\"icon\" src=\"$feed_icon_src\">";
 
 							$reply['content'] .= "<div data-feed-id='$feed_id' class='feed-title'>".
 								"<div style=\"float : right\">$feed_icon_img</div>".
-								"<a href=\"#\" class='title' onclick=\"viewfeed({feed:$feed_id})\">".
+								"<a href=\"#\" class='title' onclick=\"Feeds.open({feed:$feed_id})\">".
 								$line["feed_title"]."</a> $vf_catchup_link</div>";
 
 						}
@@ -497,14 +450,14 @@ class Feeds extends Handler_Protected {
                     $content_encoded = htmlspecialchars($line["content"]);
 
 					$expanded_class = get_pref("CDM_EXPANDED") ? "expanded" : "expandable";
-                    $tmp_content = "<div class=\"cdm $expanded_class $hlc_suffix $class\"
+                    $tmp_content = "<div class=\"cdm $expanded_class $score_class $class\"
                         id=\"RROW-$id\" data-content=\"$content_encoded\" data-article-id='$id' data-orig-feed-id='$feed_id' $mouseover_attrs>";
 
 					$tmp_content .= "<div class=\"header\">";
-					$tmp_content .= "<div style=\"vertical-align : middle\">";
+					$tmp_content .= "<div class=\"left\">";
 
 					$tmp_content .= "<input dojoType=\"dijit.form.CheckBox\"
-                        type=\"checkbox\" onclick=\"toggleSelectRow2(this, false, true)\"
+                        type=\"checkbox\" onclick=\"Headlines.onRowChecked(this)\"
                         class='rchk'>";
 
 					$tmp_content .= "$marked_pic";
@@ -523,10 +476,10 @@ class Feeds extends Handler_Protected {
 
 					// data-article-id included for context menu
 					$tmp_content .= "<span
-                    onclick=\"return cdmClicked(event, $id);\"
+                    onclick=\"return Headlines.click(event, $id);\"
                     data-article-id=\"$id\"
-                    class=\"titleWrap hlMenuAttach $hlc_suffix\">
-                    <a class=\"title $hlc_suffix\"
+                    class=\"titleWrap hlMenuAttach\">
+                    <a class=\"title\"
                     title=\"".htmlspecialchars($line["title"])."\"
                     target=\"_blank\" rel=\"noopener noreferrer\" href=\"".
 						htmlspecialchars($line["link"])."\">".
@@ -535,9 +488,15 @@ class Feeds extends Handler_Protected {
 
 					$tmp_content .= $labels_str;
 
-					$tmp_content .= "<span class='collapse'>
-                        <img src=\"images/collapse.png\" onclick=\"return cdmCollapseActive(event)\"
-                        title=\"".__("Collapse article")."\"/></span>";
+					if (!get_pref("CDM_EXPANDED")) {
+						$tmp_content .= "<span class='collapse'>
+                            <i class=\"material-icons\" onclick=\"return Article.cdmUnsetActive(event)\"
+                            title=\"" . __("Collapse article") . "\">remove_circle</i></span>";
+
+						if (get_pref('SHOW_CONTENT_PREVIEW')) {
+							$tmp_content .= "<span class='excerpt'>" . $line["content_preview"] . "</span>";
+						}
+					}
 
 					$tmp_content .= "</span>";
 
@@ -547,7 +506,7 @@ class Feeds extends Handler_Protected {
 
 							$tmp_content .= "<div class=\"feed\">
                             <a href=\"#\" style=\"background-color: rgba($rgba,0.3)\"
-                            onclick=\"viewfeed({feed:$feed_id})\">".
+                            onclick=\"Feeds.open({feed:$feed_id})\">".
 								truncate_string($line["feed_title"],30)."</a>
                         </div>";
 						}
@@ -555,19 +514,19 @@ class Feeds extends Handler_Protected {
 
 					$tmp_content .= "<span class='updated' title='$date_entered_fmt'>$updated_fmt</span>";
 
-					$tmp_content .= "<div style=\"vertical-align : middle\">";
+					$tmp_content .= "<div class='right'>";
 					$tmp_content .= "$score_pic";
 
-					if (!get_pref("VFEED_GROUP_BY_FEED") && $line["feed_title"]) {
+					if (!get_pref("VFEED_GROUP_BY_FEED")) {
 						$tmp_content .= "<span style=\"cursor : pointer\"
                         title=\"".htmlspecialchars($line["feed_title"])."\"
-                        onclick=\"viewfeed({feed:$feed_id})\">$feed_icon_img</span>";
+                        onclick=\"Feeds.open({feed:$feed_id})\">$feed_icon_img</span>";
 					}
 					$tmp_content .= "</div>"; //score wrapper2
 
 					$tmp_content .= "</div>"; //header
 
-					$tmp_content .= "<div class=\"content\" onclick=\"return cdmClicked(event, $id, true);\">";
+					$tmp_content .= "<div class=\"content\" onclick=\"return Headlines.click(event, $id, true);\">";
 
 					$tmp_content .= "<div id=\"POSTNOTE-$id\">";
 					if ($line['note']) {
@@ -578,7 +537,10 @@ class Feeds extends Handler_Protected {
 					if (!$line['lang']) $line['lang'] = 'en';
 
 					// this is filled from RROW data-content
-					$tmp_content .= "<div class=\"content-inner\" lang=\"".$line['lang']."\">";
+					$tmp_content .= "<div class=\"content-inner\" lang=\"".$line['lang']."\">
+                        <img src='images/indicator_white.gif'>
+                    </div>";
+					$tmp_content .= "<div class=\"intermediate\">";
 
 					if ($line["orig_feed_id"]) {
 
@@ -600,14 +562,11 @@ class Feeds extends Handler_Protected {
 							$tmp_content .= "&nbsp;";
 
 							$tmp_content .= "<a target='_blank' rel='noopener noreferrer' href='" . htmlspecialchars($tmp_line['feed_url']) . "'>";
-							$tmp_content .= "<img title='".__('Feed URL')."'class='tinyFeedIcon' src='images/pub_unset.png'></a>";
 
 							$tmp_content .= "</div>";
 						}
 					}
 
-					$tmp_content .= "</div>"; //content-inner
-					$tmp_content .= "<div class=\"intermediate\">";
 
 					$always_display_enclosures = $line["always_display_enclosures"];
 					$tmp_content .= Article::format_article_enclosures($id, $always_display_enclosures,
@@ -623,12 +582,12 @@ class Feeds extends Handler_Protected {
 
 					$tags_str = Article::format_tags_string($tags, $id);
 
-					$tmp_content .= "<span class='left'>";
+					$tmp_content .= "<div class='left'>";
 
-					$tmp_content .= "<img src='images/tag.png' alt='Tags' title='Tags'>
-                    <span id=\"ATSTR-$id\">$tags_str</span>
-                    <a title=\"".__('Edit tags for this article')."\"
-                    href=\"#\" onclick=\"editArticleTags($id)\">(+)</a>";
+					$tmp_content .= "<i class='material-icons'>label_outline</i>
+                        <span id=\"ATSTR-$id\">$tags_str</span>
+                        <a title=\"".__('Edit tags for this article')."\"
+                        href=\"#\" onclick=\"Article.editTags($id)\">(+)</a>";
 
 					$num_comments = (int) $line["num_comments"];
 					$entry_comments = "";
@@ -651,8 +610,8 @@ class Feeds extends Handler_Protected {
 
 					if ($entry_comments) $tmp_content .= "&nbsp;($entry_comments)";
 
-					$tmp_content .= "</span>";
-					$tmp_content .= "<div>";
+					$tmp_content .= "</div>";
+					$tmp_content .= "<div class='right'>";
 
 					foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_BUTTON) as $p) {
 						$tmp_content .= $p->hook_article_button($line);
@@ -722,7 +681,7 @@ class Feeds extends Handler_Protected {
 
 					if ($num_errors > 0) {
 						$reply['content'] .= "<br/>";
-						$reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">" .
+						$reply['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">" .
 							__('Some feeds have update errors (click for details)') . "</a>";
 					}
 					$reply['content'] .= "</span></p></div>";
@@ -913,7 +872,7 @@ class Feeds extends Handler_Protected {
 
 		if ($num_errors > 0) {
 			$reply['headlines']['content'] .= "<br/>";
-			$reply['headlines']['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"showFeedsWithErrors()\">".
+			$reply['headlines']['content'] .= "<a class=\"insensitive\" href=\"#\" onclick=\"CommonDialogs.showFeedsWithErrors()\">".
 				__('Some feeds have update errors (click for details)')."</a>";
 		}
 		$reply['headlines']['content'] .= "</span></p>";
@@ -1011,10 +970,10 @@ class Feeds extends Handler_Protected {
 				__('This feed requires authentication.')."</div>";
 
 		print "<div class=\"dlgButtons\">
-			<button dojoType=\"dijit.form.Button\" class=\"btn-primary\" type=\"submit\" onclick=\"return dijit.byId('feedAddDlg').execute()\">".__('Subscribe')."</button>";
+			<button dojoType=\"dijit.form.Button\" class=\"alt-primary\" type=\"submit\" onclick=\"return dijit.byId('feedAddDlg').execute()\">".__('Subscribe')."</button>";
 
 		if (!(defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER)) {
-			print "<button dojoType=\"dijit.form.Button\" onclick=\"return feedBrowser()\">".__('More feeds')."</button>";
+			print "<button dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.feedBrowser()\">".__('More feeds')."</button>";
 		}
 
 		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedAddDlg').hide()\">".__('Cancel')."</button>
@@ -1107,7 +1066,7 @@ class Feeds extends Handler_Protected {
 				</div>";
 		}
 
-		print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\" onclick=\"dijit.byId('searchDlg').execute()\">".__('Search')."</button>
+		print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"dijit.byId('searchDlg').execute()\">".__('Search')."</button>
 		<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('searchDlg').hide()\">".__('Cancel')."</button>
 		</div>";
 
@@ -1521,26 +1480,26 @@ class Feeds extends Handler_Protected {
 	static function getFeedIcon($id) {
 		switch ($id) {
 			case 0:
-				return "images/archive.png";
+				return "archive";
 				break;
 			case -1:
-				return "images/star.png";
+				return "star";
 				break;
 			case -2:
-				return "images/feed.png";
+				return "rss_feed";
 				break;
 			case -3:
-				return "images/fresh.png";
+				return "whatshot";
 				break;
 			case -4:
-				return "images/folder.png";
+				return "inbox";
 				break;
 			case -6:
-				return "images/time.png";
+				return "restore";
 				break;
 			default:
 				if ($id < LABEL_BASE_INDEX) {
-					return "images/label.png";
+					return "label";
 				} else {
 					$icon = self::getIconFile($id);
 

+ 16 - 13
classes/handler/public.php

@@ -262,23 +262,24 @@ class Handler_Public extends Handler {
 
 	function getProfiles() {
 		$login = clean($_REQUEST["login"]);
+		$rv = [];
 
-		$sth = $this->pdo->prepare("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users
+		if ($login) {
+			$sth = $this->pdo->prepare("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users
 			WHERE ttrss_users.id = ttrss_settings_profiles.owner_uid AND login = ? ORDER BY title");
-		$sth->execute([$login]);
-
-		print "<select dojoType='dijit.form.Select' style='width : 220px; margin : 0px' name='profile'>";
+			$sth->execute([$login]);
 
-		print "<option value='0'>" . __("Default profile") . "</option>";
+			$rv = [ [ "value" => 0, "label" => __("Default profile") ] ];
 
-		while ($line = $sth->fetch()) {
-			$id = $line["id"];
-			$title = $line["title"];
+			while ($line = $sth->fetch()) {
+				$id = $line["id"];
+				$title = $line["title"];
 
-			print "<option value='$id'>$title</option>";
-		}
+				array_push($rv, [ "label" => $title, "value" => $id ]);
+			}
+	    }
 
-		print "</select>";
+		print json_encode($rv);
 	}
 
 	function logout() {
@@ -508,7 +509,7 @@ class Handler_Public extends Handler {
 
 				if (clean($_POST["profile"])) {
 
-					$profile = clean($_POST["profile"]);
+					$profile = (int) clean($_POST["profile"]);
 
 					$sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles
 						WHERE id = ? AND owner_uid = ?");
@@ -516,7 +517,9 @@ class Handler_Public extends Handler {
 
 					if ($sth->fetch()) {
 						$_SESSION["profile"] = $profile;
-					}
+ 					} else {
+					    $_SESSION["profile"] = null;
+                    }
 				}
 			} else {
 

+ 2 - 2
classes/labels.php

@@ -163,8 +163,8 @@ class Labels
 			/* Remove cached data */
 
 			$sth = $pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
-				WHERE label_cache LIKE ? AND owner_uid = ?");
-			$sth->execute(["%$caption%", $owner_uid]);
+				WHERE owner_uid = ?");
+			$sth->execute([$owner_uid]);
 
 		}
 

+ 2 - 5
classes/opml.php

@@ -8,11 +8,8 @@ class Opml extends Handler_Protected {
 	}
 
 	function export() {
-		$output_name = $_REQUEST["filename"];
-		if (!$output_name) $output_name = "TinyTinyRSS.opml";
-
-		$show_settings = $_REQUEST["settings"];
-
+		$output_name = "tt-rss_".date("Y-m-d").".opml";
+		$show_settings = $_REQUEST["include_settings"];
 		$owner_uid = $_SESSION["uid"];
 
 		$rc = $this->opml_export($output_name, $owner_uid, false, ($show_settings == 1));

+ 68 - 63
classes/pref/feeds.php

@@ -492,7 +492,7 @@ class Pref_Feeds extends Handler_Protected {
 		@unlink($icon_file);
 
 		print "<script type=\"text/javascript\">";
-		print "parent.uploadIconHandler($rc);";
+		print "parent.CommonDialogs.uploadIconHandler($rc);";
 		print "</script>";
 		return;
 	}
@@ -542,9 +542,8 @@ class Pref_Feeds extends Handler_Protected {
 			$last_error = $row["last_error"];
 
 			if ($last_error) {
-				print "&nbsp;<img src=\"images/error.png\" alt=\"(error)\"
-				style=\"vertical-align : middle\"
-				title=\"".htmlspecialchars($last_error)."\">";
+				print "&nbsp;<i class=\"material-icons\" 
+					title=\"".htmlspecialchars($last_error)."\">error</i>";
 
 			}
 
@@ -745,9 +744,9 @@ class Pref_Feeds extends Handler_Protected {
 			<input type=\"hidden\" name=\"op\" value=\"pref-feeds\">
 			<input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\">
 			<input type=\"hidden\" name=\"method\" value=\"uploadicon\">
-			<button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\"
+			<button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.uploadFeedIcon();\"
 				type=\"submit\">".__('Replace')."</button>
-			<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\"
+			<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick=\"return CommonDialogs.removeFeedIcon($feed_id);\"
 				type=\"submit\">".__('Remove')."</button>
 			</form>";
 
@@ -765,7 +764,7 @@ class Pref_Feeds extends Handler_Protected {
 
 			print "<div class='dlgButtons'>
 			<div style=\"float : left\">
-			<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick='return unsubscribeFeed($feed_id, \"$title\")'>".
+			<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick='return CommonDialogs.unsubscribeFeed($feed_id, \"$title\")'>".
 				__('Unsubscribe')."</button>";
 
 			print "</div>";
@@ -1134,7 +1133,8 @@ class Pref_Feeds extends Handler_Protected {
 	function index() {
 
 		print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
-		print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Feeds')."\">";
+		print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>rss_feed</i> ".__('Feeds')."\">";
 
 		$sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors
 			FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
@@ -1149,14 +1149,14 @@ class Pref_Feeds extends Handler_Protected {
 		if ($num_errors > 0) {
 
 			$error_button = "<button dojoType=\"dijit.form.Button\"
-			  		onclick=\"showFeedsWithErrors()\" id=\"errorButton\">" .
+			  		onclick=\"CommonDialogs.showFeedsWithErrors()\" id=\"errorButton\">" .
 				__("Feeds with errors") . "</button>";
 		}
 
 		$inactive_button = "<button dojoType=\"dijit.form.Button\"
 				id=\"pref_feeds_inactive_btn\"
 				style=\"display : none\"
-				onclick=\"showInactiveFeeds()\">" .
+				onclick=\"dijit.byId('feedTree').showInactiveFeeds()\">" .
 				__("Inactive feeds") . "</button>";
 
 		$feed_search = clean($_REQUEST["search"]);
@@ -1174,7 +1174,7 @@ class Pref_Feeds extends Handler_Protected {
 		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=\"updateFeedList()\">".
+			<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('feedTree').reload()\">".
 				__('Search')."</button>
 			</div>";
 
@@ -1190,15 +1190,15 @@ class Pref_Feeds extends Handler_Protected {
 		print "<div dojoType=\"dijit.form.DropDownButton\">".
 				"<span>" . __('Feeds')."</span>";
 		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"quickAddFeed()\"
+		print "<div onclick=\"CommonDialogs.quickAddFeed()\"
 			dojoType=\"dijit.MenuItem\">".__('Subscribe to feed')."</div>";
-		print "<div onclick=\"editSelectedFeed()\"
+		print "<div onclick=\"dijit.byId('feedTree').editSelectedFeed()\"
 			dojoType=\"dijit.MenuItem\">".__('Edit selected feeds')."</div>";
-		print "<div onclick=\"resetFeedOrder()\"
+		print "<div onclick=\"dijit.byId('feedTree').resetFeedOrder()\"
 			dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
-		print "<div onclick=\"batchSubscribe()\"
+		print "<div onclick=\"dijit.byId('feedTree').batchSubscribe()\"
 			dojoType=\"dijit.MenuItem\">".__('Batch subscribe')."</div>";
-		print "<div dojoType=\"dijit.MenuItem\" onclick=\"removeSelectedFeeds()\">"
+		print "<div dojoType=\"dijit.MenuItem\" onclick=\"dijit.byId('feedTree').removeSelectedFeeds()\">"
 			.__('Unsubscribe')."</div> ";
 		print "</div></div>";
 
@@ -1206,11 +1206,11 @@ class Pref_Feeds extends Handler_Protected {
 			print "<div dojoType=\"dijit.form.DropDownButton\">".
 					"<span>" . __('Categories')."</span>";
 			print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-			print "<div onclick=\"createCategory()\"
+			print "<div onclick=\"dijit.byId('feedTree').createCategory()\"
 				dojoType=\"dijit.MenuItem\">".__('Add category')."</div>";
-			print "<div onclick=\"resetCatOrder()\"
+			print "<div onclick=\"dijit.byId('feedTree').resetCatOrder()\"
 				dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
-			print "<div onclick=\"removeSelectedCategories()\"
+			print "<div onclick=\"dijit.byId('feedTree').removeSelectedCategories()\"
 				dojoType=\"dijit.MenuItem\">".__('Remove selected')."</div>";
 			print "</div></div>";
 
@@ -1247,15 +1247,15 @@ class Pref_Feeds extends Handler_Protected {
 			var bare_id = id.substr(id.indexOf(':')+1);
 
 			if (id.match('FEED:')) {
-				editFeed(bare_id);
+				CommonDialogs.editFeed(bare_id);
 			} else if (id.match('CAT:')) {
-				editCat(bare_id, item);
+				dijit.byId('feedTree').editCategory(bare_id, item);
 			}
 		</script>
 		<script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
 			Element.hide(\"feedlistLoading\");
 
-			checkInactiveFeeds();
+			dijit.byId('feedTree').checkInactiveFeeds();
 		</script>
 		</div>";
 
@@ -1268,13 +1268,16 @@ class Pref_Feeds extends Handler_Protected {
 
 		print "</div>"; # feeds pane
 
-		print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">";
+		print "<div dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>import_export</i> ".__('OPML')."\">";
 
-		print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
-			__("Only main settings profile can be migrated using OPML.") . "</p>";
+		print __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
+			__("Only main settings profile can be migrated using OPML.");
+
+		print "<p/>";
 
 		print "<iframe id=\"upload_iframe\"
-			name=\"upload_iframe\" onload=\"opmlImportComplete(this)\"
+			name=\"upload_iframe\" onload=\"Helpers.OPML.onImportComplete(this)\"
 			style=\"width: 400px; height: 100px; display: none;\"></iframe>";
 
 		print "<form  name=\"opml_form\" style='display : block' target=\"upload_iframe\"
@@ -1285,28 +1288,35 @@ class Pref_Feeds extends Handler_Protected {
 			</label>
 			<input type=\"hidden\" name=\"op\" value=\"dlg\">
 			<input type=\"hidden\" name=\"method\" value=\"importOpml\">
-			<button dojoType=\"dijit.form.Button\" onclick=\"return opmlImport();\" type=\"submit\">" .
-			__('Import my OPML') . "</button>";
+			<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.OPML.import();\" type=\"submit\">" .
+			__('Import OPML') . "</button>";
+
+		print "</form>";
 
 		print "<hr>";
 
-		$opml_export_filename = "TinyTinyRSS_".date("Y-m-d").".opml";
+		print "<form dojoType=\"dijit.form.Form\" id=\"opmlExportForm\">";
 
-		print "<p>" . __('Filename:') .
-            " <input class=\"input input-text\" type=\"text\" id=\"filename\" value=\"$opml_export_filename\" />&nbsp;" .
-				__('Include settings') . "<input type=\"checkbox\" id=\"settings\" checked=\"1\"/>";
+		print "<button dojoType=\"dijit.form.Button\"
+			onclick=\"Helpers.OPML.export()\" >" .
+			__('Export OPML') . "</button>";
 
-		print "</p><button dojoType=\"dijit.form.Button\"
-			onclick=\"gotoExportOpml(document.opml_form.filename.value, document.opml_form.settings.checked)\" >" .
-              __('Export OPML') . "</button></p></form>";
+		print "<label>";
+		print_checkbox("include_settings", true, "1", "");
+		print "&nbsp;" . __("Include settings");
+		print "</label>";
 
-		print "<hr>";
+		print "</form>";
+
+		print "<p/>";
 
-		print "<p>" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') . "</p>";
+		print "<h2>" . __("Published OPML") . "</h2>";
 
-		print_warning("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.");
+		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>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return App.displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
 			__('Display published OPML URL')."</button> ";
 
 		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
@@ -1314,19 +1324,20 @@ class Pref_Feeds extends Handler_Protected {
 
 		print "</div>"; # pane
 
-		print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Published & shared articles / Generated feeds')."\">";
+		print "<div dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>share</i> ".__('Published & shared articles / Generated feeds')."\">";
 
-		print "<p>" . __('Published articles are exported as a public RSS feed and can be subscribed by anyone who knows the URL specified below.') . "</p>";
+		print __('Published articles can be subscribed by anyone who knows the following URL:');
 
 		$rss_url = '-2::' . htmlspecialchars(get_self_url_prefix() .
 				"/public.php?op=rss&id=-2&view-mode=all_articles");;
 
 		print "<p>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("View as RSS")."','generatedFeed', '$rss_url')\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return App.displayDlg('".__("Show as feed")."','generatedFeed', '$rss_url')\">".
 			__('Display URL')."</button> ";
 
-		print "<button class=\"warning\" dojoType=\"dijit.form.Button\" onclick=\"return clearFeedAccessKeys()\">".
+		print "<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick=\"return Helpers.clearFeedAccessKeys()\">".
 			__('Clear all generated URLs')."</button> ";
 
 		print "</p>";
@@ -1412,9 +1423,9 @@ class Pref_Feeds extends Handler_Protected {
 		print "<div dojoType=\"dijit.form.DropDownButton\">".
 				"<span>" . __('Select')."</span>";
 		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'all')\"
+		print "<div onclick=\"Tables.select('prefInactiveFeedList', true)\"
 			dojoType=\"dijit.MenuItem\">".__('All')."</div>";
-		print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'none')\"
+		print "<div onclick=\"Tables.select('prefInactiveFeedList', false)\"
 			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 		print "</div></div>";
 		print "</div>"; #toolbar
@@ -1428,20 +1439,17 @@ class Pref_Feeds extends Handler_Protected {
 		while ($line = $sth->fetch()) {
 
 			$feed_id = $line["id"];
-			$this_row_id = "id=\"FUPDD-$feed_id\"";
 
-			# class needed for selectTableRows()
-			print "<tr class=\"placeholder\" $this_row_id>";
+			print "<tr class=\"placeholder\" data-row-id='$feed_id'>";
 
-			# id needed for selectTableRows()
 			print "<td width='5%' align='center'><input
-				onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
-				type=\"checkbox\" id=\"FUPDC-$feed_id\"></td>";
+				onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\"
+				type=\"checkbox\"></td>";
 			print "<td>";
 
 			print "<a class=\"visibleLink\" href=\"#\" ".
 				"title=\"".__("Click to edit feed")."\" ".
-				"onclick=\"editFeed(".$line["id"].")\">".
+				"onclick=\"CommonDialogs.editFeed(".$line["id"].")\">".
 				htmlspecialchars($line["title"])."</a>";
 
 			print "</td><td class=\"insensitive\" align='right'>";
@@ -1457,7 +1465,7 @@ class Pref_Feeds extends Handler_Protected {
 
 		print "<div class='dlgButtons'>";
 		print "<div style='float : left'>";
-		print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').removeSelected()\">"
+		print "<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').removeSelected()\">"
 			.__('Unsubscribe from selected feeds')."</button> ";
 		print "</div>";
 
@@ -1477,9 +1485,9 @@ class Pref_Feeds extends Handler_Protected {
 		print "<div dojoType=\"dijit.form.DropDownButton\">".
 				"<span>" . __('Select')."</span>";
 		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"selectTableRows('prefErrorFeedList', 'all')\"
+		print "<div onclick=\"Tables.select('prefErrorFeedList', true)\"
 			dojoType=\"dijit.MenuItem\">".__('All')."</div>";
-		print "<div onclick=\"selectTableRows('prefErrorFeedList', 'none')\"
+		print "<div onclick=\"Tables.select('prefErrorFeedList', false)\"
 			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 		print "</div></div>";
 		print "</div>"; #toolbar
@@ -1493,20 +1501,17 @@ class Pref_Feeds extends Handler_Protected {
 		while ($line = $sth->fetch()) {
 
 			$feed_id = $line["id"];
-			$this_row_id = "id=\"FERDD-$feed_id\"";
 
-			# class needed for selectTableRows()
-			print "<tr class=\"placeholder\" $this_row_id>";
+			print "<tr class=\"placeholder\" data-row-id='$feed_id'>";
 
-			# id needed for selectTableRows()
 			print "<td width='5%' align='center'><input
-				onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
-				type=\"checkbox\" id=\"FERDC-$feed_id\"></td>";
+				onclick='Tables.onRowChecked(this);' dojoType=\"dijit.form.CheckBox\"
+				type=\"checkbox\"></td>";
 			print "<td>";
 
 			print "<a class=\"visibleLink\" href=\"#\" ".
 				"title=\"".__("Click to edit feed")."\" ".
-				"onclick=\"editFeed(".$line["id"].")\">".
+				"onclick=\"CommonDialogs.editFeed(".$line["id"].")\">".
 				htmlspecialchars($line["title"])."</a>: ";
 
 			print "<span class=\"insensitive\">";
@@ -1524,7 +1529,7 @@ class Pref_Feeds extends Handler_Protected {
 
 		print "<div class='dlgButtons'>";
 		print "<div style='float : left'>";
-		print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').removeSelected()\">"
+		print "<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').removeSelected()\">"
 			.__('Unsubscribe from selected feeds')."</button> ";
 		print "</div>";
 

+ 25 - 25
classes/pref/filters.php

@@ -320,10 +320,10 @@ class Pref_Filters extends Handler_Protected {
 				$label_sth->execute([$line['action_param'], $_SESSION['uid']]);
 
 				if ($label_row = $label_sth->fetch()) {
-					$fg_color = $label_row["fg_color"];
+					//$fg_color = $label_row["fg_color"];
 					$bg_color = $label_row["bg_color"];
 
-					$name[1] = "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-right : 4px'>&alpha;</span>" . $name[1];
+					$name[1] = "<i class=\"material-icons\" style='color : $bg_color; margin-right : 4px'>label</i>" . $name[1];
 				}
 			}
 
@@ -374,13 +374,13 @@ class Pref_Filters extends Handler_Protected {
 			print_hidden("method", "editSave");
 			print_hidden("csrf_token", $_SESSION['csrf_token']);
 
-			print "<div class=\"dlgSec\">".__("Caption")."</div>";
+			print "<div class=\"dlgSecHoriz\">".__("Caption")."</div>";
 
 			print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"$title\">";
 
 			print "</div>";
 
-			print "<div class=\"dlgSec\">".__("Match")."</div>";
+			print "<div class=\"dlgSecHoriz\">".__("Match")."</div>";
 
 			print "<div dojoType=\"dijit.Toolbar\">";
 
@@ -429,7 +429,7 @@ class Pref_Filters extends Handler_Protected {
 
 				$data = htmlspecialchars(json_encode($line));
 
-				print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
+				print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='Lists.onRowChecked(this)'>".
 					"<span onclick=\"dijit.byId('filterEditDlg').editRule(this)\">".$this->getRuleName($line)."</span>".
 					"<input type='hidden' name='rule[]' value=\"$data\"/></li>";
 			}
@@ -438,7 +438,7 @@ class Pref_Filters extends Handler_Protected {
 
 			print "</div>";
 
-			print "<div class=\"dlgSec\">".__("Apply actions")."</div>";
+			print "<div class=\"dlgSecHoriz\">".__("Apply actions")."</div>";
 
 			print "<div dojoType=\"dijit.Toolbar\">";
 
@@ -473,7 +473,7 @@ class Pref_Filters extends Handler_Protected {
 
 				$data = htmlspecialchars(json_encode($line));
 
-				print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='toggleSelectListRow2(this)'>".
+				print "<li><input dojoType='dijit.form.CheckBox' type='checkbox' onclick='Lists.onRowChecked(this)'>".
 					"<span onclick=\"dijit.byId('filterEditDlg').editAction(this)\">".$this->getActionName($line)."</span>".
 					"<input type='hidden' name='action[]' value=\"$data\"/></li>";
 			}
@@ -514,14 +514,14 @@ class Pref_Filters extends Handler_Protected {
 			print "<div class=\"dlgButtons\">";
 
 			print "<div style=\"float : left\">";
-			print "<button dojoType=\"dijit.form.Button\" class=\"btn-danger\" onclick=\"return dijit.byId('filterEditDlg').removeFilter()\">".
+			print "<button dojoType=\"dijit.form.Button\" class=\"alt-danger\" onclick=\"return dijit.byId('filterEditDlg').removeFilter()\">".
 				__('Remove')."</button>";
 			print "</div>";
 
-			print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
+			print "<button dojoType=\"dijit.form.Button\" class=\"alt-info\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
 				__('Test')."</button> ";
 
-			print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
+			print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
 				__('Save')."</button> ";
 
 			print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">".
@@ -797,20 +797,20 @@ class Pref_Filters extends Handler_Protected {
 			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 		print "</div></div>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return quickAddFilter()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return Filters.quickAddFilter()\">".
 			__('Create filter')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return joinSelectedFilters()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').joinSelectedFilters()\">".
 			__('Combine')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return editSelectedFilter()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').editSelectedFilter()\">".
 			__('Edit')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return resetFilterOrder()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').resetFilterOrder()\">".
 			__('Reset sort order')."</button> ";
 
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return removeSelectedFilters()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterTree').removeSelectedFilters()\">".
 			__('Remove')."</button> ";
 
 		print "</div>"; # toolbar
@@ -840,7 +840,7 @@ class Pref_Filters extends Handler_Protected {
 			var bare_id = id.substr(id.indexOf(':')+1);
 
 			if (id.match('FILTER:')) {
-				editFilter(bare_id);
+				dijit.byId('filterTree').editFilter(bare_id);
 			}
 		</script>
 
@@ -857,17 +857,17 @@ class Pref_Filters extends Handler_Protected {
 
 	function newfilter() {
 
-		print "<form name='filter_new_form' id='filter_new_form'>";
+		print "<form name='filter_new_form' id='filter_new_form' onsubmit='return false'>";
 
 		print_hidden("op", "pref-filters");
 		print_hidden("method", "add");
 		print_hidden("csrf_token", $_SESSION['csrf_token']);
 
-		print "<div class=\"dlgSec\">".__("Caption")."</div>";
+		print "<div class=\"dlgSecHoriz\">".__("Caption")."</div>";
 
 		print "<input required=\"true\" dojoType=\"dijit.form.ValidationTextBox\" style=\"width : 20em;\" name=\"title\" value=\"\">";
 
-		print "<div class=\"dlgSec\">".__("Match")."</div>";
+		print "<div class=\"dlgSecHoriz\">".__("Match")."</div>";
 
 		print "<div dojoType=\"dijit.Toolbar\">";
 
@@ -894,7 +894,7 @@ class Pref_Filters extends Handler_Protected {
 
 		print "</div>";
 
-		print "<div class=\"dlgSec\">".__("Apply actions")."</div>";
+		print "<div class=\"dlgSecHoriz\">".__("Apply actions")."</div>";
 
 		print "<div dojoType=\"dijit.Toolbar\">";
 
@@ -935,10 +935,10 @@ class Pref_Filters extends Handler_Protected {
 
 		print "<div class=\"dlgButtons\">";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
+		print "<button dojoType=\"dijit.form.Button\" class=\"alt-info\" onclick=\"return dijit.byId('filterEditDlg').test()\">".
 			__('Test')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
+		print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"return dijit.byId('filterEditDlg').execute()\">".
 			__('Create')."</button> ";
 
 		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterEditDlg').hide()\">".
@@ -1015,7 +1015,7 @@ class Pref_Filters extends Handler_Protected {
 		</div>";
 
 
-		print "<button dojoType=\"dijit.form.Button\" class=\"btn-primary \" type=\"submit\" onclick=\"return dijit.byId('filterNewRuleDlg').execute()\">".
+		print "<button dojoType=\"dijit.form.Button\" class=\"alt-primary \" type=\"submit\" onclick=\"return dijit.byId('filterNewRuleDlg').execute()\">".
 			($rule ? __("Save rule") : __('Add rule'))."</button> ";
 
 		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewRuleDlg').hide()\">".
@@ -1044,7 +1044,7 @@ class Pref_Filters extends Handler_Protected {
 		print "<div class=\"dlgSecCont\">";
 
 		print "<select name=\"action_id\" dojoType=\"dijit.form.Select\"
-			onchange=\"filterDlgCheckAction(this)\">";
+			onchange=\"Filters.filterDlgCheckAction(this)\">";
 
 		$res = $this->pdo->query("SELECT id,description FROM ttrss_filter_actions
 			ORDER BY name");
@@ -1108,7 +1108,7 @@ class Pref_Filters extends Handler_Protected {
 
 		print "<div class=\"dlgButtons\">";
 
-		print "<button dojoType=\"dijit.form.Button\" class=\"btn-primary\" type=\"submit\" onclick=\"return dijit.byId('filterNewActionDlg').execute()\">".
+		print "<button dojoType=\"dijit.form.Button\" class=\"alt-primary\" type=\"submit\" onclick=\"return dijit.byId('filterNewActionDlg').execute()\">".
 			($action ? __("Save action") : __('Add action'))."</button> ";
 
 		print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('filterNewActionDlg').hide()\">".

+ 17 - 24
classes/pref/labels.php

@@ -29,12 +29,9 @@ class Pref_Labels extends Handler_Protected {
 			$fg_color = $line['fg_color'];
 			$bg_color = $line['bg_color'];
 
-			print "<span class=\"labelColorIndicator\" id=\"label-editor-indicator\" style='color : $fg_color; background-color : $bg_color; margin-bottom : 4px; margin-right : 4px'>&alpha;</span>";
-
-			print "<input style=\"font-size : 16px\" name=\"caption\"
-			dojoType=\"dijit.form.ValidationTextBox\"
-			required=\"true\"
-			value=\"".htmlspecialchars($line['caption'])."\">";
+			print "<input style='font-size : 16px; color : $fg_color; background : $bg_color; transition : background 0.1s linear'
+				id='labelEdit_caption' name='caption' dojoType='dijit.form.ValidationTextBox'
+				required='true' value=\"".htmlspecialchars($line['caption'])."\">";
 
 			print "</div>";
 			print "<div class=\"dlgSec\">" . __("Colors") . "</div>";
@@ -56,8 +53,8 @@ class Pref_Labels extends Handler_Protected {
 
 			print "<div dojoType=\"dijit.ColorPalette\">
 			<script type=\"dojo/method\" event=\"onChange\" args=\"fg_color\">
-				dijit.byId(\"labelEdit_fgColor\").attr('value', fg_color);
-				$('label-editor-indicator').setStyle({color: fg_color});
+				dijit.byId('labelEdit_fgColor').attr('value', fg_color);
+				dijit.byId('labelEdit_caption').domNode.setStyle({color: fg_color});
 			</script>
 			</div>";
 			print "</div>";
@@ -66,8 +63,8 @@ class Pref_Labels extends Handler_Protected {
 
 			print "<div dojoType=\"dijit.ColorPalette\">
 			<script type=\"dojo/method\" event=\"onChange\" args=\"bg_color\">
-				dijit.byId(\"labelEdit_bgColor\").attr('value', bg_color);
-				$('label-editor-indicator').setStyle({backgroundColor: bg_color});
+				dijit.byId('labelEdit_bgColor').attr('value', bg_color);
+				dijit.byId('labelEdit_caption').domNode.setStyle({backgroundColor: bg_color});
 			</script>
 			</div>";
 			print "</div>";
@@ -78,7 +75,7 @@ class Pref_Labels extends Handler_Protected {
 #			print "</form>";
 
 			print "<div class=\"dlgButtons\">";
-			print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\" onclick=\"dijit.byId('labelEditDlg').execute()\">".
+			print "<button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\" onclick=\"dijit.byId('labelEditDlg').execute()\">".
 				__('Save')."</button>";
 			print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelEditDlg').hide()\">".
 				__('Cancel')."</button>";
@@ -147,13 +144,11 @@ class Pref_Labels extends Handler_Protected {
 				$sth->execute([$fg, $bg, $id, $_SESSION['uid']]);
 			}
 
-			$caption = Labels::find_caption($id, $_SESSION["uid"]);
-
 			/* Remove cached data */
 
 			$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
-				WHERE label_cache LIKE ? AND owner_uid = ?");
-			$sth->execute(["%$caption%", $_SESSION['uid']]);
+				WHERE owner_uid = ?");
+			$sth->execute([$_SESSION['uid']]);
 		}
 	}
 
@@ -166,13 +161,11 @@ class Pref_Labels extends Handler_Protected {
 				AND owner_uid = ?");
 			$sth->execute([$id, $_SESSION['uid']]);
 
-			$caption = Labels::find_caption($id, $_SESSION["uid"]);
-
 			/* Remove cached data */
 
 			$sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET label_cache = ''
-				WHERE label_cache LIKE ? AND owner_uid = ?");
-			$sth->execute(["%$caption%", $_SESSION['uid']]);
+				WHERE owner_uid = ?");
+			$sth->execute([$_SESSION['uid']]);
 		}
 	}
 
@@ -275,13 +268,13 @@ class Pref_Labels extends Handler_Protected {
 			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 		print "</div></div>";
 
-		print"<button dojoType=\"dijit.form.Button\" onclick=\"return addLabel()\">".
+		print"<button dojoType=\"dijit.form.Button\" onclick=\"CommonDialogs.addLabel()\">".
 			__('Create label')."</button dojoType=\"dijit.form.Button\"> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedLabels()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').removeSelected()\">".
 			__('Remove')."</button dojoType=\"dijit.form.Button\"> ";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"labelColorReset()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('labelTree').resetColors()\">".
 			__('Clear colors')."</button dojoType=\"dijit.form.Button\">";
 
 
@@ -310,7 +303,7 @@ class Pref_Labels extends Handler_Protected {
 			var bare_id = id.substr(id.indexOf(':')+1);
 
 			if (id.match('LABEL:')) {
-				editLabel(bare_id);
+				dijit.byId('labelTree').editLabel(bare_id);
 			}
 		</script>
 		</div>";
@@ -323,4 +316,4 @@ class Pref_Labels extends Handler_Protected {
 		print "</div>"; #container
 
 	}
-}
+}

+ 82 - 65
classes/pref/prefs.php

@@ -176,14 +176,15 @@ class Pref_Prefs extends Handler_Protected {
 		$_SESSION["prefs_op_result"] = "";
 
 		print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
-		print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Personal data / Authentication')."\">";
+		print "<div dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>person</i> ".__('Personal data / Authentication')."\">";
 
 		print "<form dojoType=\"dijit.form.Form\" id=\"changeUserdataForm\">";
 
 		print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
 		evt.preventDefault();
 		if (this.validate()) {
-			notify_progress('Saving data...', true);
+			Notify.progress('Saving data...', true);
 
 			new Ajax.Request('backend.php', {
 				parameters: dojo.objectToQuery(this.getValues()),
@@ -227,7 +228,7 @@ class Pref_Prefs extends Handler_Protected {
 		print_hidden("op", "pref-prefs");
 		print_hidden("method", "changeemail");
 
-		print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\">".
+		print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\">".
 			__("Save data")."</button>";
 
 		print "</form>";
@@ -249,12 +250,12 @@ class Pref_Prefs extends Handler_Protected {
 			print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
 			evt.preventDefault();
 			if (this.validate()) {
-				notify_progress('Changing password...', true);
+				Notify.progress('Changing password...', true);
 
 				new Ajax.Request('backend.php', {
 					parameters: dojo.objectToQuery(this.getValues()),
 					onComplete: function(transport) {
-						notify('');
+						Notify.close();
 						if (transport.responseText.indexOf('ERROR: ') == 0) {
 
 							$('pwd_change_infobox').innerHTML =
@@ -298,7 +299,7 @@ class Pref_Prefs extends Handler_Protected {
 			print_hidden("op", "pref-prefs");
 			print_hidden("method", "changepassword");
 
-			print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\">".
+			print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\">".
 				__("Change password")."</button>";
 
 			print "</form>";
@@ -316,14 +317,14 @@ class Pref_Prefs extends Handler_Protected {
 				print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
 				evt.preventDefault();
 				if (this.validate()) {
-					notify_progress('Disabling OTP', true);
+					Notify.progress('Disabling OTP', true);
 
 					new Ajax.Request('backend.php', {
 						parameters: dojo.objectToQuery(this.getValues()),
 						onComplete: function(transport) {
-							notify('');
+							Notify.close();
 							if (transport.responseText.indexOf('ERROR: ') == 0) {
-								notify_error(transport.responseText.replace('ERROR: ', ''));
+								Notify.error(transport.responseText.replace('ERROR: ', ''));
 							} else {
 								window.location.reload();
 							}
@@ -351,7 +352,7 @@ class Pref_Prefs extends Handler_Protected {
 
 				} else if (function_exists("imagecreatefromstring")) {
 
-					print_warning(__("You will need a compatible Authenticator to use this. Changing your password would automatically disable OTP."));
+					print "<p>" . __("You will need a compatible Authenticator to use this. Changing your password would automatically disable OTP.") . "</p>";
 
 					print "<p>".__("Scan the following code by the Authenticator application:")."</p>";
 
@@ -367,14 +368,14 @@ class Pref_Prefs extends Handler_Protected {
 					print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
 					evt.preventDefault();
 					if (this.validate()) {
-						notify_progress('Saving data...', true);
+						Notify.progress('Saving data...', true);
 
 						new Ajax.Request('backend.php', {
 							parameters: dojo.objectToQuery(this.getValues()),
 							onComplete: function(transport) {
-								notify('');
+								Notify.close();
 								if (transport.responseText.indexOf('ERROR:') == 0) {
-									notify_error(transport.responseText.replace('ERROR:', ''));
+									Notify.error(transport.responseText.replace('ERROR:', ''));
 								} else {
 									window.location.reload();
 								}
@@ -403,7 +404,7 @@ class Pref_Prefs extends Handler_Protected {
 					print "</td></tr>";
 					print "</table>";
 
-					print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"btn-primary\">".
+					print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\" class=\"alt-primary\">".
 						__("Enable OTP")."</button>";
 
 					print "</form>";
@@ -422,7 +423,8 @@ class Pref_Prefs extends Handler_Protected {
 
 		print "</div>"; #pane
 
-		print "<div dojoType=\"dijit.layout.AccordionPane\" selected=\"true\" title=\"".__('Preferences')."\">";
+		print "<div dojoType=\"dijit.layout.AccordionPane\" selected=\"true\" 
+			title=\"<i class='material-icons'>settings</i> ".__('Preferences')."\">";
 
 		print "<form dojoType=\"dijit.form.Form\" id=\"changeSettingsForm\">";
 
@@ -436,12 +438,12 @@ class Pref_Prefs extends Handler_Protected {
 				onComplete: function(transport) {
 					var msg = transport.responseText;
 					if (quit) {
-						gotoMain();
+						document.location.href = 'index.php';
 					} else {
 						if (msg == 'PREFS_NEED_RELOAD') {
 							window.location.reload();
 						} else {
-							notify_info(msg);
+							Notify.info(msg);
 						}
 					}
 			} });
@@ -452,12 +454,12 @@ class Pref_Prefs extends Handler_Protected {
 
 		print '<div dojoType="dijit.layout.ContentPane" region="center" style="overflow-y : auto">';
 
-		if ($_SESSION["profile"]) {
+		$profile = $_SESSION["profile"];
+
+		if ($profile) {
 			print_notice(__("Some preferences are only available in default profile."));
-		}
 
-		if ($_SESSION["profile"]) {
-			initialize_user_prefs($_SESSION["uid"], $_SESSION["profile"]);
+			initialize_user_prefs($_SESSION["uid"], $profile);
 		} else {
 			initialize_user_prefs($_SESSION["uid"]);
 		}
@@ -473,7 +475,7 @@ class Pref_Prefs extends Handler_Protected {
 				ttrss_user_prefs.pref_name = ttrss_prefs.pref_name AND
 				owner_uid = :uid
 			ORDER BY ttrss_prefs_sections.order_id,pref_name");
-		$sth->execute([":uid" => $_SESSION['uid'], ":profile" => $_SESSION['profile']]);
+		$sth->execute([":uid" => $_SESSION['uid'], ":profile" => $profile]);
 
 		$lnum = 0;
 
@@ -497,8 +499,7 @@ class Pref_Prefs extends Handler_Protected {
 
 			if (!$short_desc) continue;
 
-			if ($_SESSION["profile"] && in_array($line["pref_name"],
-					$profile_blacklist)) {
+			if ($profile && in_array($line["pref_name"], $profile_blacklist)) {
 				continue;
 			}
 
@@ -512,7 +513,7 @@ class Pref_Prefs extends Handler_Protected {
 
 				$active_section = $line["section_id"];
 
-				print "<tr><td colspan=\"3\"><h3>".$section_name."</h3></td></tr>";
+				print "<tr><td colspan=\"3\"><h2>".$section_name."</h2></td></tr>";
 
 				$lnum = 0;
 			}
@@ -541,8 +542,8 @@ class Pref_Prefs extends Handler_Protected {
 				print_select($pref_name, $value, $timezones, 'dojoType="dijit.form.FilteringSelect"');
 			} else if ($pref_name == "USER_STYLESHEET") {
 
-				print "<button dojoType=\"dijit.form.Button\"
-					onclick=\"customizeCSS()\">" . __('Customize') . "</button>";
+				print "<button dojoType=\"dijit.form.Button\" class='alt-info'
+					onclick=\"Helpers.customizeCSS()\">" . __('Customize') . "</button>";
 
 			} else if ($pref_name == "USER_CSS_THEME") {
 
@@ -553,8 +554,17 @@ class Pref_Prefs extends Handler_Protected {
 
 				if (!theme_valid($value)) $value = "default.php";
 
-				print_select($pref_name, $value, $themes,
-					'dojoType="dijit.form.Select"');
+				print "<select name='$pref_name' id='$pref_name' dojoType='dijit.form.Select'>";
+
+				$issel = $value == "default.php" ? "selected='selected'" : "";
+				print "<option $issel value='default.php'>".__("default")."</option>";
+
+				foreach ($themes as $theme) {
+					$issel = $value == $theme ? "selected='selected'" : "";
+					print "<option $issel value='$theme'>$theme</option>";
+				}
+
+				print "</select>";
 
 
 			} else if ($pref_name == "DEFAULT_UPDATE_INTERVAL") {
@@ -608,11 +618,11 @@ class Pref_Prefs extends Handler_Protected {
 				print "<br/>";
 
 				print " <button dojoType=\"dijit.form.Button\" disabled=\"$has_serial\"
-					onclick=\"insertSSLserial('$cert_serial')\">" .
+					onclick=\"dijit.byId('SSL_CERT_SERIAL').attr('value', '$cert_serial')\">" .
 					__('Register') . "</button>";
 
 				print " <button dojoType=\"dijit.form.Button\"
-					onclick=\"insertSSLserial('')\">" .
+					onclick=\"dijit.byId('SSL_CERT_SERIAL').attr('value', '')\">" .
 					__('Clear') . "</button>";
 
 			} else if ($pref_name == 'DIGEST_PREFERRED_TIME') {
@@ -650,7 +660,7 @@ class Pref_Prefs extends Handler_Protected {
 		print_hidden("op", "pref-prefs");
 		print_hidden("method", "saveconfig");
 
-		print "<div dojoType=\"dijit.form.ComboButton\" type=\"submit\" class=\"btn-primary\">
+		print "<div dojoType=\"dijit.form.ComboButton\" type=\"submit\" class=\"alt-primary\">
 			<span>".__('Save configuration')."</span>
 			<div dojoType=\"dijit.DropDownMenu\">
 				<div dojoType=\"dijit.MenuItem\"
@@ -659,10 +669,10 @@ class Pref_Prefs extends Handler_Protected {
 			</div>
 			</div>";
 
-		print "<button dojoType=\"dijit.form.Button\" onclick=\"return editProfiles()\">".
+		print "<button dojoType=\"dijit.form.Button\" onclick=\"return Helpers.editProfiles()\">".
 			__('Manage profiles')."</button> ";
 
-		print "<button dojoType=\"dijit.form.Button\" class=\"btn-danger\" onclick=\"return validatePrefsReset()\">".
+		print "<button dojoType=\"dijit.form.Button\" class=\"alt-danger\" onclick=\"return Helpers.confirmReset()\">".
 			__('Reset to defaults')."</button>";
 
 		print "&nbsp;";
@@ -676,25 +686,20 @@ class Pref_Prefs extends Handler_Protected {
 
 		print "</div>"; #pane
 
-		print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Plugins')."\">";
-
-		print_notice(__("You will need to reload Tiny Tiny RSS for plugin changes to take effect."));
-
-		if (ini_get("open_basedir") && function_exists("curl_init") && !defined("NO_CURL")) {
-			print_warning("Your PHP configuration has open_basedir restrictions enabled. Some plugins relying on CURL for functionality may not work correctly.");
-		}
+		print "<div dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>extension</i> ".__('Plugins')."\">";
 
 		print "<form dojoType=\"dijit.form.Form\" id=\"changePluginsForm\">";
 
 		print "<script type=\"dojo/method\" event=\"onSubmit\" args=\"evt\">
 		evt.preventDefault();
 		if (this.validate()) {
-			notify_progress('Saving data...', true);
+			Notify.progress('Saving data...', true);
 
 			new Ajax.Request('backend.php', {
 				parameters: dojo.objectToQuery(this.getValues()),
 				onComplete: function(transport) {
-					notify('');
+					Notify.close();
 					if (confirm(__('Selected plugins have been enabled. Reload?'))) {
 						window.location.reload();
 					}
@@ -706,9 +711,16 @@ class Pref_Prefs extends Handler_Protected {
 		print_hidden("op", "pref-prefs");
 		print_hidden("method", "setplugins");
 
+		print '<div dojoType="dijit.layout.BorderContainer" gutters="false">';
+		print '<div dojoType="dijit.layout.ContentPane" region="center" style="overflow-y : auto">';
+
+		if (ini_get("open_basedir") && function_exists("curl_init") && !defined("NO_CURL")) {
+			print_warning("Your PHP configuration has open_basedir restrictions enabled. Some plugins relying on CURL for functionality may not work correctly.");
+		}
+
 		print "<table width='100%' class='prefPluginsList'>";
 
-		print "<tr><td colspan='5'><h3>".__("System plugins")."</h3>".
+		print "<tr><td colspan='5'><h2>".__("System plugins")."</h2>".
             format_notice(__("System plugins are enabled in <strong>config.php</strong> for all users.")).
             "</td></tr>";
 
@@ -742,9 +754,9 @@ class Pref_Prefs extends Handler_Protected {
 						dojoType=\"dijit.form.CheckBox\" $checked
 						type=\"checkbox\"></td>";
 
-				$plugin_icon = $checked ? "plugin.png" : "plugin_disabled.png";
+				$icon_class = $checked ? "plugin-enabled" : "plugin-disabled";
 
-				print "<td><label><img src='images/$plugin_icon' alt=''> $name</label></td>";
+				print "<td><label><i class='material-icons $icon_class'>extension</i> $name</label></td>";
 				print "<td>" . htmlspecialchars($about[1]);
 				if (@$about[4]) {
 					print " &mdash; <a target=\"_blank\" rel=\"noopener noreferrer\" class=\"visibleLink\"
@@ -756,7 +768,7 @@ class Pref_Prefs extends Handler_Protected {
 
 				if (count($tmppluginhost->get_all($plugin)) > 0) {
 					if (in_array($name, $system_enabled)) {
-						print "<td><a href='#' onclick=\"clearPluginData('$name')\"
+						print "<td><a href='#' onclick=\"Helpers.clearPluginData('$name')\"
 							class='visibleLink'>".__("Clear data")."</a></td>";
 					}
 				}
@@ -766,7 +778,7 @@ class Pref_Prefs extends Handler_Protected {
 			}
 		}
 
-		print "<tr><td colspan='4'><h3>".__("User plugins")."</h3></td></tr>";
+		print "<tr><td colspan='4'><h2>".__("User plugins")."</h2></td></tr>";
 
 		print "<tr class=\"title\">
 				<td width=\"5%\">&nbsp;</td>
@@ -797,13 +809,13 @@ class Pref_Prefs extends Handler_Protected {
 
 				print "<tr class='$rowclass'>";
 
-				$plugin_icon = $checked ? "plugin.png" : "plugin_disabled.png";
+				$icon_class = $checked ? "plugin-enabled" : "plugin-disabled";
 
-				print "<td align='center'><input id='FPCHK-$name' name='plugins[]' value='$name' onclick='toggleSelectRow2(this);'
+				print "<td align='center'><input id='FPCHK-$name' name='plugins[]' value='$name' onclick='Tables.onRowChecked(this);'
 					dojoType=\"dijit.form.CheckBox\" $checked $disabled
 					type=\"checkbox\"></td>";
 
-				print "<td><label for='FPCHK-$name'><img src='images/$plugin_icon' alt=''> $name</label></td>";
+				print "<td><label for='FPCHK-$name'><i class='material-icons $icon_class'>extension</i> $name</label></td>";
 				print "<td><label for='FPCHK-$name'>" . htmlspecialchars($about[1]) . "</label>";
 				if (@$about[4]) {
 					print " &mdash; <a target=\"_blank\" rel=\"noopener noreferrer\" class=\"visibleLink\"
@@ -816,7 +828,7 @@ class Pref_Prefs extends Handler_Protected {
 
 				if (count($tmppluginhost->get_all($plugin)) > 0) {
 					if (in_array($name, $system_enabled) || in_array($name, $user_enabled)) {
-						print "<td><a href='#' onclick=\"clearPluginData('$name')\" class='visibleLink'>".__("Clear data")."</a></td>";
+						print "<td><a href='#' onclick=\"Helpers.clearPluginData('$name')\" class='visibleLink'>".__("Clear data")."</a></td>";
 					}
 				}
 
@@ -830,17 +842,25 @@ class Pref_Prefs extends Handler_Protected {
 
 		print "</table>";
 
-		print "<p><button dojoType=\"dijit.form.Button\" type=\"submit\">".
-			__("Enable selected plugins")."</button></p>";
+		//print "<p>" . __("You will need to reload Tiny Tiny RSS for plugin changes to take effect.") . "</p>";
 
-		print "</form>";
+		print "</div>"; #content-pane
+		print '<div dojoType="dijit.layout.ContentPane" region="bottom">';
+		print "<button dojoType=\"dijit.form.Button\" type=\"submit\">".
+			__("Enable selected plugins")."</button>";
+		print "</div>"; #pane
 
 		print "</div>"; #pane
+		print "</div>"; #border-container
+
+		print "</form>";
+
 
 		PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
 			"hook_prefs_tab", "prefPrefs");
 
 		print "</div>"; #container
+
 	}
 
 	function toggleAdvanced() {
@@ -992,9 +1012,9 @@ class Pref_Prefs extends Handler_Protected {
 		print "<div dojoType=\"dijit.form.DropDownButton\">".
 				"<span>" . __('Select')."</span>";
 		print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-		print "<div onclick=\"selectTableRows('prefFeedProfileList', 'all')\"
+		print "<div onclick=\"Tables.select('prefFeedProfileList', true)\"
 			dojoType=\"dijit.MenuItem\">".__('All')."</div>";
-		print "<div onclick=\"selectTableRows('prefFeedProfileList', 'none')\"
+		print "<div onclick=\"Tables.select('prefFeedProfileList', false)\"
 			dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 		print "</div></div>";
 
@@ -1019,11 +1039,10 @@ class Pref_Prefs extends Handler_Protected {
 		print "<table width=\"100%\" class=\"prefFeedProfileList\"
 			cellspacing=\"0\" id=\"prefFeedProfileList\">";
 
-		print "<tr class=\"placeholder\" id=\"FCATR-0\">"; #odd
+		print "<tr class=\"placeholder\">"; # data-row-id='0' <-- no point, shouldn't be removed
 
 		print "<td width='5%' align='center'><input
-			id='FCATC-0'
-			onclick='toggleSelectRow2(this);'
+			onclick='Tables.onRowChecked(this);'
 			dojoType=\"dijit.form.CheckBox\"
 			type=\"checkbox\"></td>";
 
@@ -1043,15 +1062,13 @@ class Pref_Prefs extends Handler_Protected {
 		while ($line = $sth->fetch()) {
 
 			$profile_id = $line["id"];
-			$this_row_id = "id=\"FCATR-$profile_id\"";
 
-			print "<tr class=\"placeholder\" $this_row_id>";
+			print "<tr class=\"placeholder\" data-row-id='$profile_id'>";
 
 			$edit_title = htmlspecialchars($line["title"]);
 
 			print "<td width='5%' align='center'><input
-				onclick='toggleSelectRow2(this);'
-				id='FCATC-$profile_id'
+				onclick='Tables.onRowChecked(this);'
 				dojoType=\"dijit.form.CheckBox\"
 				type=\"checkbox\"></td>";
 
@@ -1089,7 +1106,7 @@ class Pref_Prefs extends Handler_Protected {
 
 		print "<div class='dlgButtons'>
 			<div style='float : left'>
-			<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').removeSelected()\">".
+			<button class=\"alt-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').removeSelected()\">".
 			__('Remove selected profiles')."</button>
 			<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('profileEditDlg').activateProfile()\">".
 			__('Activate profile')."</button>

+ 4 - 3
classes/pref/system.php

@@ -26,7 +26,8 @@ class Pref_System extends Handler_Protected {
 	function index() {
 
 		print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
-		print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Error Log')."\">";
+		print "<div dojoType=\"dijit.layout.AccordionPane\" 
+			title=\"<i class='material-icons'>report</i> ".__('Event Log')."\">";
 
 		if (LOG_DESTINATION == "sql") {
 
@@ -37,10 +38,10 @@ class Pref_System extends Handler_Protected {
 				LIMIT 100");
 
 			print "<button dojoType=\"dijit.form.Button\"
-				onclick=\"updateSystemList()\">".__('Refresh')."</button> ";
+				onclick=\"Helpers.updateEventLog()\">".__('Refresh')."</button> ";
 
 			print "&nbsp;<button dojoType=\"dijit.form.Button\"
-				onclick=\"clearSqlLog()\">".__('Clear log')."</button> ";
+				class=\"alt-danger\" onclick=\"Helpers.clearEventLog()\">".__('Clear')."</button> ";
 
 			print "<p><table width=\"100%\" cellspacing=\"10\" class=\"prefErrorLog\">";
 

+ 31 - 36
classes/pref/users.php

@@ -99,7 +99,7 @@ class Pref_Users extends Handler_Protected {
 			print '</div>';
 
 			print "<div class=\"dlgButtons\">
-				<button dojoType=\"dijit.form.Button\" class=\"btn-primary\" type=\"submit\" onclick=\"dijit.byId('userEditDlg').execute()\">".
+				<button dojoType=\"dijit.form.Button\" class=\"alt-primary\" type=\"submit\" onclick=\"dijit.byId('userEditDlg').execute()\">".
 				__('Save')."</button>
 				<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('userEditDlg').hide()\">".
 				__('Cancel')."</button></div>";
@@ -160,9 +160,9 @@ class Pref_Users extends Handler_Protected {
 					$icon_file = ICONS_URL."/".$line["id"].".ico";
 
 					if (file_exists($icon_file) && filesize($icon_file) > 0) {
-						$feed_icon = "<img class=\"tinyFeedIcon\" src=\"$icon_file\">";
+						$feed_icon = "<img class=\"icon\" src=\"$icon_file\">";
 					} else {
-						$feed_icon = "<img class=\"tinyFeedIcon\" src=\"images/blank_icon.gif\">";
+						$feed_icon = "<img class=\"icon\" src=\"images/blank_icon.gif\">";
 					}
 
 					print "<li>$feed_icon&nbsp;<a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>";
@@ -218,12 +218,13 @@ class Pref_Users extends Handler_Protected {
 		}
 
 		function add() {
-
 			$login = trim(clean($_REQUEST["login"]));
 			$tmp_user_pwd = make_password(8);
 			$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
 			$pwd_hash = encrypt_password($tmp_user_pwd, $salt, true);
 
+			if (!$login) return; // no blank usernames
+
 			$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
 				login = ?");
 			$sth->execute([$login]);
@@ -243,18 +244,18 @@ class Pref_Users extends Handler_Protected {
 
 					$new_uid = $row['id'];
 
-					print format_notice(T_sprintf("Added user <b>%s</b> with password <b>%s</b>",
-						$login, $tmp_user_pwd));
+					print T_sprintf("Added user %s with password %s",
+						$login, $tmp_user_pwd);
 
 					initialize_user($new_uid);
 
 				} else {
 
-					print format_warning(T_sprintf("Could not create user <b>%s</b>", $login));
+					print T_sprintf("Could not create user %s", $login);
 
 				}
 			} else {
-				print format_warning(T_sprintf("User <b>%s</b> already exists.", $login));
+				print T_sprintf("User %s already exists.", $login);
 			}
 		}
 
@@ -282,9 +283,9 @@ class Pref_Users extends Handler_Protected {
 				$sth->execute([$pwd_hash, $new_salt, $uid]);
 
 				if ($show_password) {
-					print T_sprintf("Changed password of user <b>%s</b> to <b>%s</b>", $login, $tmp_user_pwd);
+					print T_sprintf("Changed password of user %s to %s", $login, $tmp_user_pwd);
 				} else {
-					print_notice(T_sprintf("Sending new password of user <b>%s</b> to <b>%s</b>", $login, $email));
+					print_notice(T_sprintf("Sending new password of user %s to %s", $login, $email));
 				}
 
 				if ($email) {
@@ -341,7 +342,7 @@ class Pref_Users extends Handler_Protected {
 			print "<div style='float : right; padding-right : 4px;'>
 				<input dojoType=\"dijit.form.TextBox\" id=\"user_search\" size=\"20\" type=\"search\"
 					value=\"$user_search\">
-				<button dojoType=\"dijit.form.Button\" onclick=\"updateUsersList()\">".
+				<button dojoType=\"dijit.form.Button\" oncl1ick=\"Users.reload()\">".
 					__('Search')."</button>
 				</div>";
 
@@ -354,20 +355,20 @@ class Pref_Users extends Handler_Protected {
 			print "<div dojoType=\"dijit.form.DropDownButton\">".
 					"<span>" . __('Select')."</span>";
 			print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
-			print "<div onclick=\"selectTableRows('prefUserList', 'all')\"
+			print "<div onclick=\"Tables.select('prefUserList', true)\"
 				dojoType=\"dijit.MenuItem\">".__('All')."</div>";
-			print "<div onclick=\"selectTableRows('prefUserList', 'none')\"
+			print "<div onclick=\"Tables.select('prefUserList', false)\"
 				dojoType=\"dijit.MenuItem\">".__('None')."</div>";
 			print "</div></div>";
 
-			print "<button dojoType=\"dijit.form.Button\" onclick=\"addUser()\">".__('Create user')."</button>";
+			print "<button dojoType=\"dijit.form.Button\" onclick=\"Users.add()\">".__('Create user')."</button>";
 
 			print "
-				<button dojoType=\"dijit.form.Button\" onclick=\"editSelectedUser()\">".
+				<button dojoType=\"dijit.form.Button\" onclick=\"Users.editSelected()\">".
 				__('Edit')."</button dojoType=\"dijit.form.Button\">
-				<button dojoType=\"dijit.form.Button\" onclick=\"removeSelectedUsers()\">".
+				<button dojoType=\"dijit.form.Button\" onclick=\"Users.removeSelected()\">".
 				__('Remove')."</button dojoType=\"dijit.form.Button\">
-				<button dojoType=\"dijit.form.Button\" onclick=\"resetSelectedUserPass()\">".
+				<button dojoType=\"dijit.form.Button\" onclick=\"Users.resetSelected()\">".
 				__('Reset password')."</button dojoType=\"dijit.form.Button\">";
 
 			PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
@@ -400,11 +401,11 @@ class Pref_Users extends Handler_Protected {
 
 			print "<tr class=\"title\">
 						<td align='center' width=\"5%\">&nbsp;</td>
-						<td width='20%'><a href=\"#\" onclick=\"updateUsersList('login')\">".__('Login')."</a></td>
-						<td width='20%'><a href=\"#\" onclick=\"updateUsersList('access_level')\">".__('Access Level')."</a></td>
-						<td width='10%'><a href=\"#\" onclick=\"updateUsersList('num_feeds')\">".__('Subscribed feeds')."</a></td>
-						<td width='20%'><a href=\"#\" onclick=\"updateUsersList('created')\">".__('Registered')."</a></td>
-						<td width='20%'><a href=\"#\" onclick=\"updateUsersList('last_login')\">".__('Last login')."</a></td></tr>";
+						<td width='20%'><a href=\"#\" onclick=\"Users.reload('login')\">".__('Login')."</a></td>
+						<td width='20%'><a href=\"#\" onclick=\"Users.reload('access_level')\">".__('Access Level')."</a></td>
+						<td width='10%'><a href=\"#\" onclick=\"Users.reload('num_feeds')\">".__('Subscribed feeds')."</a></td>
+						<td width='20%'><a href=\"#\" onclick=\"Users.reload('created')\">".__('Registered')."</a></td>
+						<td width='20%'><a href=\"#\" onclick=\"Users.reload('last_login')\">".__('Last login')."</a></td></tr>";
 
 			$lnum = 0;
 
@@ -412,27 +413,21 @@ class Pref_Users extends Handler_Protected {
 
 				$uid = $line["id"];
 
-				print "<tr id=\"UMRR-$uid\">";
+				print "<tr data-row-id=\"$uid\" onclick='Users.edit($uid)'>";
 
 				$line["login"] = htmlspecialchars($line["login"]);
-
 				$line["created"] = make_local_datetime($line["created"], false);
 				$line["last_login"] = make_local_datetime($line["last_login"], false);
 
-				print "<td align='center'><input onclick='toggleSelectRow2(this);'
-					dojoType=\"dijit.form.CheckBox\" type=\"checkbox\"
-					id=\"UMCHK-$uid\"></td>";
-
-				$onclick = "onclick='editUser($uid, event)' title='".__('Click to edit')."'";
-
-				print "<td $onclick><img src='images/user.png' class='marked-pic' alt=''> " . $line["login"] . "</td>";
+				print "<td align='center'><input onclick='Tables.onRowChecked(this); event.stopPropagation();'
+					dojoType=\"dijit.form.CheckBox\" type=\"checkbox\"></td>";
 
-				if (!$line["email"]) $line["email"] = "&nbsp;";
+				print "<td title='".__('Click to edit')."'><i class='material-icons'>person</i> " . $line["login"] . "</td>";
 
-				print "<td $onclick>" .	$access_level_names[$line["access_level"]] . "</td>";
-				print "<td $onclick>" . $line["num_feeds"] . "</td>";
-				print "<td $onclick>" . $line["created"] . "</td>";
-				print "<td $onclick>" . $line["last_login"] . "</td>";
+				print "<td>" .	$access_level_names[$line["access_level"]] . "</td>";
+				print "<td>" . $line["num_feeds"] . "</td>";
+				print "<td>" . $line["created"] . "</td>";
+				print "<td>" . $line["last_login"] . "</td>";
 
 				print "</tr>";
 

+ 1 - 1
classes/rpc.php

@@ -8,7 +8,7 @@ class RPC extends Handler_Protected {
 	}
 
 	function setprofile() {
-		$_SESSION["profile"] = clean($_REQUEST["id"]);
+		$_SESSION["profile"] = (int) clean($_REQUEST["id"]);
 
 		// default value
 		if (!$_SESSION["profile"]) $_SESSION["profile"] = null;

+ 0 - 4
css/Makefile

@@ -1,4 +0,0 @@
-
-default.css: *.less
-	lessc -x default.less > [email protected]
-

+ 95 - 98
css/cdm.less

@@ -1,57 +1,52 @@
 .cdm {
-	margin-right : 4px;
-
 	.header, .footer {
-		display : table;
-	}
-
-	.header img, .header input, .footer img {
-		vertical-align : middle;
-	}
-
-	.header > div, .footer > div {
-		white-space : nowrap;
+		display : flex;
+		flex-direction : row;
+		flex-wrap : nowrap;
 	}
 
-	.header > span, .footer > span.left {
-		width : 100%;
-	}
-
-	.header img, .footer img {
+	.header img, .footer img,
+	.footer i {
 		margin : 0px 4px;
+		vertical-align: middle;
 	}
 
 	.header {
 		> * {
-			display : table-cell;
-			padding : 5px;
-		}
-
-		span.updated {
-			color : @default-text;
-			font-weight : normal;
-			font-size : 11px;
+			align-self : center;
+			padding : 4px;
 			white-space : nowrap;
-			vertical-align : middle;
 		}
 
-		input {
-			margin-right : 5px;
+		.left, .right {
+			display : flex;
+
+			> * {
+				align-self : center;
+			}
+
+			i.material-icons {
+				margin-left : 2px;
+				padding : 2px;
+				transition : color 0.2s linear;
+				user-select: none;
+				font-size : 21px;
+			}
 		}
 
-		div.updPic {
-			width : 25px;
-			display : inline-block;
-			text-align : center;
+		.titleWrap {
+			flex-grow : 2;
 		}
 
-		div.updPic img {
-			vertical-align : middle;
+		span.updated {
+			color : @default-text;
+			font-weight : normal;
+			font-size : 11px;
+			white-space : nowrap;
 		}
 
 		input {
-			margin-left : 4px;
-			margin-right : 4px;
+			margin : 0px 4px;
 		}
 	}
 
@@ -63,8 +58,11 @@
 		clear : both;
 
 		> * {
-			display : table-cell;
-			vertical-align : middle;
+			align-self : center;
+		}
+
+		.left {
+			flex-grow : 2;
 		}
 	}
 
@@ -76,17 +74,6 @@
 		margin : 10px;
 		line-height : 1.5;
 		font-size : 16px;
-
-		h1 {
-			font-size : 16px;
-		}
-
-		h2,
-		h3,
-		h4 {
-			font-size : 15px;
-		}
-
 	}
 
 	.intermediate img,
@@ -100,13 +87,17 @@
 }
 
 .cdm.expanded {
-	margin-top : 4px;
-	margin-bottom : 4px;
+	/*margin-top : 4px;
+	margin-bottom : 4px;*/
 
-	.collapse {
+	.collapse, .excerpt {
 		display : none;
 	}
 
+	.titleWrap {
+		white-space : normal;
+	}
+
 	.footer {
 		border: 0px solid #ddd;
 		border-bottom-width: 1px;
@@ -157,11 +148,6 @@ div.cdm.active div.content {
 }
 
 .cdm {
-	div.content div.postEnclosures {
-		margin-top: 1em;
-		color: @default-text;
-	}
-
 	div.feed-title {
 		border: 0px solid @color-link;
 		border-bottom-width: 1px;
@@ -231,18 +217,36 @@ div#floatingTitle {
 	border-bottom-width: 1px;
 	background : white;
 	color : @default-text;
+	display : flex;
+	flex-direction : row;
+	flex-wrap : nowrap;
 	box-shadow : 0px 1px 1px -1px rgba(0,0,0,0.1);
 
 	> * {
-		display : table-cell;
+		align-self: center;
 		white-space : nowrap;
-		vertical-align : middle;
-		padding : 9px 5px;
+		padding : 4px;
 	}
 
-	img {
-		margin-right : 4px;
-		margin-left : 4px;
+	.left, .right {
+		display : flex;
+
+		> * {
+			align-self : center;
+		}
+
+		i.material-icons {
+			margin-left : 2px;
+			font-size : 20px;
+			padding : 2px;
+			user-select: none;
+		}
+
+		i.anchor {
+			margin-left : 0px;
+			padding : 0px;
+			color : #ccc;
+		}
 	}
 
 	span.author {
@@ -290,7 +294,7 @@ div#floatingTitle {
 		color : @default-text;
 	}
 
-	.collapse {
+	.collapse, .excerpt {
 		display : none;
 	}
 
@@ -299,7 +303,6 @@ div#floatingTitle {
 		white-space : normal;
 	}
 
-	.dijit,
 	img.score-pic {
 		display : none;
 	}
@@ -333,33 +336,8 @@ div#floatingTitle.Unread a.title {
 	color : black;
 }
 
-.cdm.high .header {
-	a.title.high,
-	.excerpt,
-	span.author {
-		color : #00aa00;
-	}
-}
-
-.cdm.Unread.high .header {
-	a.title.high,
-	.excerpt,
-	span.author {
-		color : #00dd00;
-	}