From 17413078a72e1298c6dc8953c40e8d83ce38c49c Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 13 Feb 2021 18:32:02 +0300 Subject: pref feeds: index cleanup, split into several methods, use tabs to maximize space for feed tree, persist feed tree state --- js/PrefFeedTree.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'js/PrefFeedTree.js') diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js index 89195e616..7684c7f9d 100644 --- a/js/PrefFeedTree.js +++ b/js/PrefFeedTree.js @@ -1,9 +1,44 @@ /* eslint-disable prefer-rest-params */ /* global __, lib, dijit, define, dojo, CommonDialogs, Notify, Tables, xhrPost, fox, App */ -define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree"], function (declare, domConstruct) { +define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_base/array", "dojo/cookie"], + function (declare, domConstruct, checkBoxTree, array, cookie) { return declare("fox.PrefFeedTree", lib.CheckBoxTree, { + // save state in localStorage instead of cookies + // reference: https://stackoverflow.com/a/27968996 + _saveExpandedNodes: function(){ + if (this.persist && this.cookieName){ + const ary = []; + for (const id in this._openedNodes){ + ary.push(id); + } + // Was: + // cookie(this.cookieName, ary.join(","), {expires: 365}); + localStorage.setItem(this.cookieName, ary.join(",")); + } + }, + _initState: function(){ + this.cookieName = 'prefs:' + this.cookieName; + // summary: + // Load in which nodes should be opened automatically + this._openedNodes = {}; + if (this.persist && this.cookieName){ + // Was: + // var oreo = cookie(this.cookieName); + let oreo = localStorage.getItem(this.cookieName); + // migrate old data if nothing in localStorage + if (oreo == null || oreo === '') { + oreo = cookie(this.cookieName); + cookie(this.cookieName, null, { expires: -1 }); + } + if (oreo){ + array.forEach(oreo.split(','), function(item){ + this._openedNodes[item] = true; + }, this); + } + } + }, _createTreeNode: function(args) { const tnode = this.inherited(arguments); -- cgit v1.2.3-54-g00ecf From 8464c619e413b4aabacb802e1afe49a7c960506a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 13 Feb 2021 21:41:38 +0300 Subject: inactive feeds: use client dialog --- classes/pref/feeds.php | 52 +++-------------------- js/PrefFeedTree.js | 109 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 83 deletions(-) (limited to 'js/PrefFeedTree.js') diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index d8495a59c..03e8b1b81 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1485,56 +1485,14 @@ class Pref_Feeds extends Handler_Protected { ORDER BY last_article"); $sth->execute([$_SESSION['uid']]); - print "
"; - print "
". - "" . __('Select').""; - print "
"; - print "
".__('All')."
"; - print "
".__('None')."
"; - print "
"; - print "
"; #toolbar - - print "
"; - print ""; - - $lnum = 1; - - while ($line = $sth->fetch()) { - - $feed_id = $line["id"]; - - print ""; - - print ""; - print ""; - print ""; + $rv = []; - ++$lnum; + while ($row = $sth->fetch(PDO::FETCH_ASSOC)) { + $row['last_article'] = TimeHelper::make_local_datetime($row['last_article'], false); + array_push($rv, $row); } - print "
"; - - print "". - htmlspecialchars($line["title"]).""; - - print ""; - print TimeHelper::make_local_datetime($line['last_article'], false); - print "
"; - print "
"; - - print "
- - -
"; - + print json_encode($rv); } function feedsWithErrors() { diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js index 7684c7f9d..3e3584a9a 100644 --- a/js/PrefFeedTree.js +++ b/js/PrefFeedTree.js @@ -1,5 +1,5 @@ /* eslint-disable prefer-rest-params */ -/* global __, lib, dijit, define, dojo, CommonDialogs, Notify, Tables, xhrPost, fox, App */ +/* global __, lib, dijit, define, dojo, CommonDialogs, Notify, Tables, xhrPost, xhrJson, fox, App */ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_base/array", "dojo/cookie"], function (declare, domConstruct, checkBoxTree, array, cookie) { @@ -409,50 +409,87 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_b dialog.show(); }, showInactiveFeeds: function() { - const dialog = new fox.SingleUseDialog({ - id: "inactiveFeedsDlg", - title: __("Feeds without recent updates"), - getSelectedFeeds: function () { - return Tables.getSelected("inactive-feeds-list"); - }, - removeSelected: function () { - const sel_rows = this.getSelectedFeeds(); - - if (sel_rows.length > 0) { - if (confirm(__("Remove selected feeds?"))) { - Notify.progress("Removing selected feeds...", true); + xhrJson("backend.php", {op: 'pref-feeds', method: 'inactivefeeds'}, function (reply) { + + const dialog = new fox.SingleUseDialog({ + id: "inactiveFeedsDlg", + title: __("Feeds without recent updates"), + getSelectedFeeds: function () { + return Tables.getSelected("inactive-feeds-list"); + }, + removeSelected: function () { + const sel_rows = this.getSelectedFeeds(); + + if (sel_rows.length > 0) { + if (confirm(__("Remove selected feeds?"))) { + Notify.progress("Removing selected feeds...", true); + + const query = { + op: "pref-feeds", method: "remove", + ids: sel_rows.toString() + }; - const query = { - op: "pref-feeds", method: "remove", - ids: sel_rows.toString() - }; + xhrPost("backend.php", query, () => { + Notify.close(); - xhrPost("backend.php", query, () => { - Notify.close(); + const tree = dijit.byId("feedTree"); + if (tree) tree.reload(); - const tree = dijit.byId("feedTree"); - if (tree) tree.reload(); + dialog.hide(); + }); + } - dialog.hide(); - }); + } else { + alert(__("No feeds selected.")); } + }, + content: ` +
+
+ ${__('Select')} +
+
${__('All')}
+
${__('None')}
+
+
+
+ +
+ + ${reply.map((row) => ` + + + + + `).join("")} +
+ + + + ${App.escapeHtml(row.title)} + + + ${row.last_article} +
+
+ +
+ + +
+ ` + }); - } else { - alert(__("No feeds selected.")); - } - }, - content: __("Loading, please wait...") - }); - - const tmph = dojo.connect(dialog, 'onShow', function () { - dojo.disconnect(tmph); + dialog.show(); - xhrPost("backend.php", {op: "pref-feeds", method: "inactivefeeds"}, (transport) => { - dialog.attr('content', transport.responseText); - }) }); - dialog.show(); } }); }); -- cgit v1.2.3-54-g00ecf From 103d30ad3f92ed03156fee400801d9a38f946b34 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 13 Feb 2021 22:16:17 +0300 Subject: batch subscribe: use client dialog --- classes/pref/feeds.php | 50 +++------------------------ include/controls.php | 26 +++++++++----- js/PrefFeedTree.js | 94 ++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 90 insertions(+), 80 deletions(-) (limited to 'js/PrefFeedTree.js') diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index f337d7f4e..2649d58a1 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1557,52 +1557,10 @@ class Pref_Feeds extends Handler_Protected { } function batchSubscribe() { - print "
"; - - print_hidden("op", "pref-feeds"); - print_hidden("method", "batchaddfeeds"); - - print "
".__("One valid feed per line (no detection is done)")."
"; - print "
"; - - print ""; - - if (get_pref('ENABLE_FEED_CATS')) { - print "
"; - print " "; - print_feed_cat_select("cat", false, 'dojoType="fox.form.Select"'); - print "
"; - } - - print "
"; - - print ""; - - print ""; - print ""; - - print "
- "; - print "
"; - - print "
- - -
"; - - print "
"; + print json_encode([ + "enable_cats" => (int)get_pref('ENABLE_FEED_CATS'), + "cat_select" => format_feed_cat_select("cat", false, 'dojoType="fox.form.Select"') + ]); } function batchAddFeeds() { diff --git a/include/controls.php b/include/controls.php index 8f49e99c5..e6678db9a 100755 --- a/include/controls.php +++ b/include/controls.php @@ -181,11 +181,19 @@ function print_feed_multi_select($id, $default_ids = [], } } -function print_feed_cat_select($id, $default_id, - $attributes, $include_all_cats = true, $root_id = null, $nest_level = 0) { +function print_feed_cat_select($id, $default_id, $attributes, $include_all_cats = true, + $root_id = null, $nest_level = 0) { + + print format_feed_cat_select($id, $default_id, $attributes, $include_all_cats, $root_id, $nest_level); +} + +function format_feed_cat_select($id, $default_id, $attributes, $include_all_cats = true, + $root_id = null, $nest_level = 0) { + + $ret = ""; if (!$root_id) { - print ""; } $pdo = Db::pdo(); @@ -215,18 +223,18 @@ function print_feed_cat_select($id, $default_id, $line["title"] = " " . $line["title"]; if ($line["title"]) - printf("", + $ret .= sprintf("", $line["id"], htmlspecialchars($line["title"])); if ($line["num_children"] > 0) - print_feed_cat_select($id, $default_id, $attributes, + $ret .= format_feed_cat_select($id, $default_id, $attributes, $include_all_cats, $line["id"], $nest_level+1); } if (!$root_id) { if ($include_all_cats) { if ($found > 0) { - print ""; + $ret .= ""; } if ($default_id == 0) { @@ -235,10 +243,12 @@ function print_feed_cat_select($id, $default_id, $is_selected = ""; } - print ""; + $ret .= ""; } - print ""; + $ret .= ""; } + + return $ret; } function stylesheet_tag($filename, $id = false) { diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js index 3e3584a9a..c3dda4187 100644 --- a/js/PrefFeedTree.js +++ b/js/PrefFeedTree.js @@ -378,35 +378,77 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_b } }, batchSubscribe: function() { - const dialog = new fox.SingleUseDialog({ - id: "batchSubDlg", - title: __("Batch subscribe"), - execute: function () { - if (this.validate()) { - Notify.progress(__("Subscribing to feeds..."), true); - - xhrPost("backend.php", this.attr('value'), () => { - Notify.close(); - - const tree = dijit.byId("feedTree"); - if (tree) tree.reload(); - - dialog.hide(); - }); - } - }, - content: __("Loading, please wait...") - }); + xhrJson("backend.php", {op: 'pref-feeds', method: 'batchSubscribe'}, (reply) => { + const dialog = new fox.SingleUseDialog({ + id: "batchSubDlg", + title: __("Batch subscribe"), + execute: function () { + if (this.validate()) { + Notify.progress(__("Subscribing to feeds..."), true); - const tmph = dojo.connect(dialog, 'onShow', function () { - dojo.disconnect(tmph); + xhrPost("backend.php", this.attr('value'), () => { + Notify.close(); - xhrPost("backend.php", {op: 'pref-feeds', method: 'batchSubscribe'}, (transport) => { - dialog.attr('content', transport.responseText); - }) - }); + const tree = dijit.byId("feedTree"); + if (tree) tree.reload(); + + dialog.hide(); + }); + } + }, + content: ` +
+ ${App.FormFields.hidden("op", "pref-feeds")} + ${App.FormFields.hidden("method", "batchaddfeeds")} + +
+ ${__("One valid feed per line (no detection is done)")} +
+ +
+ + + ${reply.enable_cats ? + `
+ + ${reply.cat_select} +
+ ` : '' + } +
+ + + + + +
+ +
+ +
+ + +
+
+ ` + }); - dialog.show(); + dialog.show(); + + }); }, showInactiveFeeds: function() { xhrJson("backend.php", {op: 'pref-feeds', method: 'inactivefeeds'}, function (reply) { -- cgit v1.2.3-54-g00ecf From 43d8a1f2fffc3dd551ce80dc506dad79a65ca052 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 13 Feb 2021 23:08:20 +0300 Subject: remove getinactivefeeds (duplicate functionality) --- classes/pref/feeds.php | 19 ------------------- js/PrefFeedTree.js | 4 ++-- 2 files changed, 2 insertions(+), 21 deletions(-) (limited to 'js/PrefFeedTree.js') diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 2649d58a1..b9967e77f 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1652,25 +1652,6 @@ class Pref_Feeds extends Handler_Protected { return $c; } - function getinactivefeeds() { - if (DB_TYPE == "pgsql") { - $interval_qpart = "NOW() - INTERVAL '3 months'"; - } else { - $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)"; - } - - $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_inactive FROM ttrss_feeds WHERE - (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE - ttrss_entries.id = ref_id AND - ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND - ttrss_feeds.owner_uid = ?"); - $sth->execute([$_SESSION['uid']]); - - if ($row = $sth->fetch()) { - print (int)$row["num_inactive"]; - } - } - static function subscribe_to_feed_url() { $url_path = get_self_url_prefix() . "/public.php?op=subscribe&feed_url=%s"; diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js index c3dda4187..e17b8744d 100644 --- a/js/PrefFeedTree.js +++ b/js/PrefFeedTree.js @@ -210,8 +210,8 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_b return false; }, checkInactiveFeeds: function() { - xhrPost("backend.php", {op: "pref-feeds", method: "getinactivefeeds"}, (transport) => { - if (parseInt(transport.responseText) > 0) { + xhrJson("backend.php", {op: "pref-feeds", method: "inactivefeeds"}, (reply) => { + if (reply.length > 0) { Element.show(dijit.byId("pref_feeds_inactive_btn").domNode); } }); -- cgit v1.2.3-54-g00ecf From d4c925819b9d85a00520e413a90cfbcd61a1c667 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 13 Feb 2021 23:12:49 +0300 Subject: pref-feeds: load error button via xhr --- classes/pref/feeds.php | 21 +++++---------------- js/PrefFeedTree.js | 7 +++++++ themes/compact.css | 2 +- themes/compact_night.css | 2 +- themes/light.css | 2 +- themes/light/prefs.less | 2 +- themes/night.css | 2 +- themes/night_blue.css | 2 +- 8 files changed, 18 insertions(+), 22 deletions(-) (limited to 'js/PrefFeedTree.js') diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index b9967e77f..72a8344ad 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -1198,22 +1198,10 @@ class Pref_Feeds extends Handler_Protected { } private function index_feeds() { - $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors - FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?"); - $sth->execute([$_SESSION['uid']]); - - if ($row = $sth->fetch()) { - $num_errors = $row["num_errors"]; - } else { - $num_errors = 0; - } - - if ($num_errors > 0) { - $error_button = ""; - } else { - $error_button = ""; - } + $error_button = ""; $inactive_button = ""; - } - - print " - "; - - print ""; - - print ""; + print json_encode([ + "show_language" => DB_TYPE == "pgsql", + "show_syntax_help" => count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0, + "all_languages" => Pref_Feeds::get_ts_languages(), + "default_language" => get_pref('DEFAULT_SEARCH_LANGUAGE') + ]); } function updatedebugger() { diff --git a/js/App.js b/js/App.js index bb861829d..9d8f6c275 100644 --- a/js/App.js +++ b/js/App.js @@ -18,14 +18,44 @@ const App = { is_prefs: false, LABEL_BASE_INDEX: -1024, FormFields: { - hidden: function(name, value, id = "") { - return `` + attributes_to_string: function(attributes) { + return Object.keys(attributes).map((k) => + `${App.escapeHtml(k)}="${App.escapeHtml(attributes[k])}"`) + .join(" "); }, - select_hash: function(name, value, values, attributes) { + hidden_tag: function(name, value, attributes = {}, id = "") { + return `` + }, + // allow html inside because of icons + button_tag: function(value, type, attributes = {}) { + return `` + + }, + icon: function(icon, attributes = {}) { + return `${icon}`; + }, + submit_tag: function(value, attributes = {}) { + return this.button_tag(value, "submit", {...{class: "alt-primary"}, ...attributes}); + }, + cancel_dialog_tag: function(value, attributes = {}) { + return this.button_tag(value, "", {...{onclick: "App.dialogOf(this).hide()"}, ...attributes}); + }, + select_tag: function(name, value, values = [], attributes = {}, id = "") { + return ` + + ` + }, + select_hash: function(name, value, values = {}, attributes = {}, id = "") { return ` - ${Object.keys(values).map((vk) => - `` + `` ).join("")} ` diff --git a/js/Article.js b/js/Article.js index 4d400e2dc..a42d3af67 100644 --- a/js/Article.js +++ b/js/Article.js @@ -314,9 +314,9 @@ const Article = { const dialog = new fox.SingleUseDialog({ title: __("Edit article Tags"), content: ` - ${App.FormFields.hidden("id", id.toString())} - ${App.FormFields.hidden("op", "article")} - ${App.FormFields.hidden("method", "setArticleTags")} + ${App.FormFields.hidden_tag("id", id.toString())} + ${App.FormFields.hidden_tag("op", "article")} + ${App.FormFields.hidden_tag("method", "setArticleTags")}
${__("Tags for this article (separated by commas):")} diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js index c16afed82..dd0d56194 100644 --- a/js/CommonDialogs.js +++ b/js/CommonDialogs.js @@ -86,8 +86,8 @@ const CommonDialogs = { content: `
- ${App.FormFields.hidden("op", "feeds")} - ${App.FormFields.hidden("method", "add")} + ${App.FormFields.hidden_tag("op", "feeds")} + ${App.FormFields.hidden_tag("method", "add")} diff --git a/js/CommonFilters.js b/js/CommonFilters.js index 5afffafdc..e3629157b 100644 --- a/js/CommonFilters.js +++ b/js/CommonFilters.js @@ -45,7 +45,7 @@ const Filters = { li.innerHTML = ` ${transport.responseText} - ${App.FormFields.hidden("rule[]", rule)}`; + ${App.FormFields.hidden_tag("rule[]", rule)}`; dojo.parser.parse(li); @@ -76,7 +76,7 @@ const Filters = { li.innerHTML = ` ${transport.responseText} - ${App.FormFields.hidden("action[]", action)}`; + ${App.FormFields.hidden_tag("action[]", action)}`; dojo.parser.parse(li); diff --git a/js/Feeds.js b/js/Feeds.js index 73f1bc338..0567cf8c5 100644 --- a/js/Feeds.js +++ b/js/Feeds.js @@ -1,6 +1,6 @@ 'use strict' -/* global __, App, Headlines, xhrPost, dojo, dijit, Form, fox, PluginHost, Notify, $$, fox */ +/* global __, App, Headlines, xhrPost, xhrJson, dojo, dijit, Form, fox, PluginHost, Notify, $$, fox */ const Feeds = { counters_last_request: 0, @@ -566,14 +566,42 @@ const Feeds = { return tree.model.store.getValue(nuf, 'bare_id'); }, search: function() { - xhrPost("backend.php", - {op: "feeds", method: "search", - param: Feeds.getActive() + ":" + Feeds.activeIsCat()}, - (transport) => { + xhrJson("backend.php", + {op: "feeds", method: "search"}, + (reply) => { try { const dialog = new fox.SingleUseDialog({ - id: "searchDlg", - content: transport.responseText, + content: ` + +
+
+ +
+ + ${reply.show_language ? + ` +
+ + ${App.FormFields.select_tag("search_language", reply.default_language, reply.all_languages, + {title: __('Used for word stemming')}, "search_language")} +
+ ` : ''} +
+ +
+ ${reply.show_syntax_help ? + `${App.FormFields.button_tag(App.FormFields.icon("help") + " " + __("Search syntax"), "", + {class: 'alt-info pull-left', onclick: "window.open('https://tt-rss.org/wiki/SearchSyntax')"})} + ` : ''} + + ${App.FormFields.submit_tag(__('Search'), {onclick: "App.dialogOf(this).execute()"})} + ${App.FormFields.cancel_dialog_tag(__('Cancel'))} +
+
+ `, title: __("Search"), execute: function () { if (this.validate()) { diff --git a/js/PrefFeedTree.js b/js/PrefFeedTree.js index e0a2dd932..e081e2e31 100644 --- a/js/PrefFeedTree.js +++ b/js/PrefFeedTree.js @@ -405,8 +405,8 @@ define(["dojo/_base/declare", "dojo/dom-construct", "lib/CheckBoxTree", "dojo/_b }, content: `
- ${App.FormFields.hidden("op", "pref-feeds")} - ${App.FormFields.hidden("method", "batchaddfeeds")} + ${App.FormFields.hidden_tag("op", "pref-feeds")} + ${App.FormFields.hidden_tag("method", "batchaddfeeds")}
${__("One valid feed per line (no detection is done)")} diff --git a/js/PrefHelpers.js b/js/PrefHelpers.js index f7eca59a0..96d524953 100644 --- a/js/PrefHelpers.js +++ b/js/PrefHelpers.js @@ -183,9 +183,9 @@ const Helpers = { ${__("You can override colors, fonts and layout of your currently selected theme with custom CSS declarations here.")} - ${App.FormFields.hidden('op', 'rpc')} - ${App.FormFields.hidden('method', 'setpref')} - ${App.FormFields.hidden('key', 'USER_STYLESHEET')} + ${App.FormFields.hidden_tag('op', 'rpc')} + ${App.FormFields.hidden_tag('method', 'setpref')} + ${App.FormFields.hidden_tag('key', 'USER_STYLESHEET')}