diff options
Diffstat (limited to 'js/PrefHelpers.js')
-rw-r--r-- | js/PrefHelpers.js | 589 |
1 files changed, 526 insertions, 63 deletions
diff --git a/js/PrefHelpers.js b/js/PrefHelpers.js index 62f6d91b1..3f738aa95 100644 --- a/js/PrefHelpers.js +++ b/js/PrefHelpers.js @@ -1,7 +1,7 @@ 'use strict'; /* eslint-disable no-new */ -/* global __, dijit, dojo, Tables, xhrPost, Notify, xhr, App, fox */ +/* global __, dijit, dojo, Tables, Notify, xhr, App, fox */ const Helpers = { AppPasswords: { @@ -19,7 +19,7 @@ const Helpers = { alert("No passwords selected."); } else if (confirm(__("Remove selected app passwords?"))) { - xhr.post("backend.php", {op: "pref-prefs", method: "deleteAppPassword", ids: rows.toString()}, (reply) => { + xhr.post("backend.php", {op: "pref-prefs", method: "deleteAppPasswords", "ids[]": rows}, (reply) => { this.updateContent(reply); Notify.close(); }); @@ -53,6 +53,33 @@ const Helpers = { return false; }, }, + Digest: { + preview: function() { + const dialog = new fox.SingleUseDialog({ + title: __("Digest preview"), + content: ` + <div class='panel panel-scrollable digest-preview'> + <div class='text-center'>${__("Loading, please wait...")}</div> + </div> + + <footer class='text-center'> + ${App.FormFields.submit_tag(__('Close this window'))} + </footer> + ` + }); + + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); + + xhr.json("backend.php", {op: "pref-prefs", method: "previewDigest"}, (reply) => { + dialog.domNode.querySelector('.digest-preview').innerHTML = reply[0]; + }); + }); + + dialog.show(); + + } + }, System: { // }, @@ -97,7 +124,7 @@ const Helpers = { edit: function() { const dialog = new fox.SingleUseDialog({ id: "profileEditDlg", - title: __("Settings Profiles"), + title: __("Manage profiles"), getSelectedProfiles: function () { return Tables.getSelected("pref-profiles-list"); }, @@ -108,12 +135,7 @@ const Helpers = { if (confirm(__("Remove selected profiles? Active and default profiles will not be removed."))) { Notify.progress("Removing selected profiles...", true); - const query = { - op: "pref-prefs", method: "remprofiles", - ids: sel_rows.toString() - }; - - xhr.post("backend.php", query, () => { + xhr.post("backend.php", {op: "pref-prefs", method: "remprofiles", "ids[]": sel_rows}, () => { Notify.close(); dialog.refresh(); }); @@ -161,7 +183,7 @@ const Helpers = { <table width='100%' id='pref-profiles-list'> ${reply.map((profile) => ` <tr data-row-id="${profile.id}"> - <td width='5%'> + <td class='checkbox'> ${App.FormFields.checkbox_tag("", false, "", {onclick: 'Tables.onRowChecked(this)'})} </td> <td> @@ -183,9 +205,9 @@ const Helpers = { </div> <footer> - ${App.FormFields.button_tag(__('Remove selected profiles'), "", + ${App.FormFields.button_tag(App.FormFields.icon("delete") + " " +__('Remove selected profiles'), "", {class: 'pull-left alt-danger', onclick: 'App.dialogOf(this).removeSelected()'})} - ${App.FormFields.submit_tag(__('Activate profile'), {onclick: 'App.dialogOf(this).execute()'})} + ${App.FormFields.submit_tag(App.FormFields.icon("check") + " " + __('Activate profile'), {onclick: 'App.dialogOf(this).execute()'})} ${App.FormFields.cancel_dialog_tag(__('Cancel'))} </footer> </form> @@ -217,58 +239,70 @@ const Helpers = { }, Prefs: { customizeCSS: function() { - xhr.json("backend.php", {op: "pref-prefs", method: "customizeCSS"}, (reply) => { - - const dialog = new fox.SingleUseDialog({ - title: __("Customize stylesheet"), - apply: function() { - xhr.post("backend.php", this.attr('value'), () => { - Element.show("css_edit_apply_msg"); - App.byId("user_css_style").innerText = this.attr('value'); - }); - }, - execute: function () { - Notify.progress('Saving data...', true); + const dialog = new fox.SingleUseDialog({ + title: __("Customize stylesheet"), + apply: function() { + xhr.post("backend.php", this.attr('value'), () => { + Element.show("css_edit_apply_msg"); + App.byId("user_css_style").innerText = this.attr('value'); + }); + }, + execute: function () { + Notify.progress('Saving data...', true); - xhr.post("backend.php", this.attr('value'), () => { - window.location.reload(); - }); - }, - content: ` - <div class='alert alert-info'> - ${__("You can override colors, fonts and layout of your currently selected theme with custom CSS declarations here.")} + xhr.post("backend.php", this.attr('value'), () => { + window.location.reload(); + }); + }, + content: ` + <div class='alert alert-info'> + ${__("You can override colors, fonts and layout of your currently selected theme with custom CSS declarations here.")} + </div> + + ${App.FormFields.hidden_tag('op', 'rpc')} + ${App.FormFields.hidden_tag('method', 'setpref')} + ${App.FormFields.hidden_tag('key', 'USER_STYLESHEET')} + + <div id='css_edit_apply_msg' style='display : none'> + <div class='alert alert-warning'> + ${__("User CSS has been applied, you might need to reload the page to see all changes.")} </div> + </div> + + <textarea class='panel user-css-editor' disabled='true' dojoType='dijit.form.SimpleTextarea' + style='font-size : 12px;' name='value'>${__("Loading, please wait...")}</textarea> + + <footer> + <button dojoType='dijit.form.Button' class='alt-success' onclick="App.dialogOf(this).apply()"> + ${App.FormFields.icon("check")} + ${__('Apply')} + </button> + <button dojoType='dijit.form.Button' class='alt-primary' type='submit'> + ${App.FormFields.icon("refresh")} + ${__('Save and reload')} + </button> + <button dojoType='dijit.form.Button' onclick="App.dialogOf(this).hide()"> + ${__('Cancel')} + </button> + </footer> + ` + }); - ${App.FormFields.hidden_tag('op', 'rpc')} - ${App.FormFields.hidden_tag('method', 'setpref')} - ${App.FormFields.hidden_tag('key', 'USER_STYLESHEET')} + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); - <div id='css_edit_apply_msg' style='display : none'> - <div class='alert alert-warning'> - ${__("User CSS has been applied, you might need to reload the page to see all changes.")} - </div> - </div> + xhr.json("backend.php", {op: "pref-prefs", method: "customizeCSS"}, (reply) => { - <textarea class='panel user-css-editor' dojoType='dijit.form.SimpleTextarea' - style='font-size : 12px;' name='value'>${reply.value}</textarea> - - <footer> - <button dojoType='dijit.form.Button' class='alt-success' onclick="App.dialogOf(this).apply()"> - ${__('Apply')} - </button> - <button dojoType='dijit.form.Button' class='alt-primary' type='submit'> - ${__('Save and reload')} - </button> - <button dojoType='dijit.form.Button' onclick="App.dialogOf(this).hide()"> - ${__('Cancel')} - </button> - </footer> - ` - }); + const editor = dijit.getEnclosingWidget(dialog.domNode.querySelector(".user-css-editor")); - dialog.show(); + editor.attr('value', reply.value); + editor.attr('disabled', false); + }); }); + + dialog.show(); + }, confirmReset: function() { if (confirm(__("Reset to defaults?"))) { @@ -278,20 +312,448 @@ const Helpers = { }); } }, - clearPluginData: function(name) { - if (confirm(__("Clear stored data for this plugin?"))) { + refresh: function() { + xhr.post("backend.php", { op: "pref-prefs" }, (reply) => { + dijit.byId('prefsTab').attr('content', reply); + Notify.close(); + }); + }, + }, + Plugins: { + _list_of_plugins: [], + _search_query: "", + enableSelected: function() { + const form = dijit.byId("changePluginsForm"); + + if (form.validate()) { + xhr.post("backend.php", form.getValues(), () => { + Notify.close(); + if (confirm(__('Selected plugins have been enabled. Reload?'))) { + window.location.reload(); + } + }) + } + }, + search: function() { + this._search_query = dijit.byId("changePluginsForm").getValues().search; + this.render_contents(); + }, + reload: function() { + xhr.json("backend.php", {op: "pref-prefs", method: "getPluginsList"}, (reply) => { + this._list_of_plugins = reply; + this.render_contents(); + }); + }, + render_contents: function() { + const container = document.querySelector(".prefs-plugin-list"); + + container.innerHTML = ""; + let results_rendered = 0; + + const is_admin = this._list_of_plugins.is_admin; + + const search_tokens = this._search_query + .split(/ {1,}/) + .filter((stoken) => (stoken.length > 0 ? stoken : null)); + + this._list_of_plugins.plugins.forEach((plugin) => { + + if (search_tokens.length == 0 || + Object.values(plugin).filter((pval) => + search_tokens.filter((stoken) => + (pval.toString().indexOf(stoken) != -1 ? stoken : null) + ).length == search_tokens.length).length > 0) { + + ++results_rendered; + + // only user-enabled actually counts in the checkbox when saving because system plugin checkboxes are disabled (see below) + container.innerHTML += ` + <li data-row-value="${App.escapeHtml(plugin.name)}" data-plugin-local="${plugin.is_local}" data-plugin-name="${App.escapeHtml(plugin.name)}" title="${plugin.is_system ? __("System plugins are enabled using global configuration.") : ""}"> + <label class="checkbox ${plugin.is_system ? "system text-info" : ""}"> + ${App.FormFields.checkbox_tag("plugins[]", plugin.user_enabled || plugin.system_enabled, plugin.name, + {disabled: plugin.is_system})}</div> + <span class='name'>${plugin.name}:</span> + </label> + <div class="description ${plugin.is_system ? "text-info" : ""}"> + ${plugin.description} + </div> + <div class='actions'> + ${plugin.is_system ? + App.FormFields.button_tag(App.FormFields.icon("security"), "", + {disabled: true}) : ''} + ${plugin.more_info ? + App.FormFields.button_tag(App.FormFields.icon("help"), "", + {class: 'alt-info', onclick: `window.open("${App.escapeHtml(plugin.more_info)}")`}) : ''} + ${is_admin && plugin.is_local ? + App.FormFields.button_tag(App.FormFields.icon("update"), "", + {title: __("Update"), class: 'alt-warning', "data-update-btn-for-plugin": plugin.name, style: 'display : none', + onclick: `Helpers.Plugins.update("${App.escapeHtml(plugin.name)}")`}) : ''} + ${is_admin && plugin.has_data ? + App.FormFields.button_tag(App.FormFields.icon("clear"), "", + {title: __("Clear data"), onclick: `Helpers.Plugins.clearData("${App.escapeHtml(plugin.name)}")`}) : ''} + ${is_admin && plugin.is_local ? + App.FormFields.button_tag(App.FormFields.icon("delete"), "", + {title: __("Uninstall"), onclick: `Helpers.Plugins.uninstall("${App.escapeHtml(plugin.name)}")`}) : ''} + </div> + <div class='version text-muted'>${plugin.version}</div> + </li> + `; + } else { + // if plugin is outside of search scope, keep current value in case of saving (only user-enabled is needed) + container.innerHTML += App.FormFields.checkbox_tag("plugins[]", plugin.user_enabled, plugin.name, {style: 'display : none'}); + } + }); + + if (results_rendered == 0) { + container.innerHTML += `<li class='text-center text-info'>${__("Could not find any plugins for this search query.")}</li>`; + } + + dojo.parser.parse(container); + + }, + clearData: function(name) { + if (confirm(__("Clear stored data for %s?").replace("%s", name))) { Notify.progress("Loading, please wait..."); - xhr.post("backend.php", {op: "pref-prefs", method: "clearplugindata", name: name}, () => { + xhr.post("backend.php", {op: "pref-prefs", method: "clearPluginData", name: name}, () => { Helpers.Prefs.refresh(); }); } }, - refresh: function() { - xhr.post("backend.php", { op: "pref-prefs" }, (reply) => { - dijit.byId('prefsTab').attr('content', reply); - Notify.close(); + uninstall: function(plugin) { + const msg = __("Uninstall plugin %s?").replace("%s", plugin); + + if (confirm(msg)) { + Notify.progress("Loading, please wait..."); + + xhr.json("backend.php", {op: "pref-prefs", method: "uninstallPlugin", plugin: plugin}, (reply) => { + if (reply && reply.status == 1) + Helpers.Prefs.refresh(); + else { + Notify.error("Plugin uninstallation failed."); + } + }); + + } + }, + install: function() { + const dialog = new fox.SingleUseDialog({ + PI_RES_ALREADY_INSTALLED: "PI_RES_ALREADY_INSTALLED", + PI_RES_SUCCESS: "PI_RES_SUCCESS", + PI_ERR_NO_CLASS: "PI_ERR_NO_CLASS", + PI_ERR_NO_INIT_PHP: "PI_ERR_NO_INIT_PHP", + PI_ERR_EXEC_FAILED: "PI_ERR_EXEC_FAILED", + PI_ERR_NO_TEMPDIR: "PI_ERR_NO_TEMPDIR", + PI_ERR_PLUGIN_NOT_FOUND: "PI_ERR_PLUGIN_NOT_FOUND", + PI_ERR_NO_WORKDIR: "PI_ERR_NO_WORKDIR", + title: __("Available plugins"), + need_refresh: false, + entries: false, + search_query: "", + installed_plugins: [], + onHide: function() { + if (this.need_refresh) { + Helpers.Prefs.refresh(); + } + }, + performInstall: function(plugin) { + + const install_dialog = new fox.SingleUseDialog({ + title: __("Plugin installer"), + content: ` + <ul class="panel panel-scrollable contents"> + <li class='text-center'>${__("Installing %s, please wait...").replace("%s", plugin)}</li> + </ul> + + <footer class='text-center'> + ${App.FormFields.submit_tag(__("Close this window"))} + </footer>` + }); + + const tmph = dojo.connect(install_dialog, 'onShow', function () { + dojo.disconnect(tmph); + + const container = install_dialog.domNode.querySelector(".contents"); + + xhr.json("backend.php", {op: "pref-prefs", method: "installPlugin", plugin: plugin}, (reply) => { + if (!reply) { + container.innerHTML = `<li class='text-center text-error'>${__("Operation failed: check event log.")}</li>`; + } else { + switch (reply.result) { + case dialog.PI_RES_SUCCESS: + container.innerHTML = `<li class='text-success text-center'>${__("Plugin has been installed.")}</li>` + dialog.need_refresh = true; + break; + case dialog.PI_RES_ALREADY_INSTALLED: + container.innerHTML = `<li class='text-success text-center'>${__("Plugin is already installed.")}</li>` + break; + default: + container.innerHTML = ` + <li> + <h3 style="margin-top: 0">${plugin}</h3> + <div class='text-error'>${reply.result}</div> + ${reply.stderr ? `<pre class="small text-error pre-wrap">${reply.stderr}</pre>` : ''} + ${reply.stdour ? `<pre class="small text-success pre-wrap">${reply.stdout}</pre>` : ''} + <p class="small"> + ${App.FormFields.icon("error_outline") + " " + __("Exited with RC: %d").replace("%d", reply.git_status)} + </p> + </li> + `; + } + } + }); + }); + + install_dialog.show(); + + }, + search: function() { + this.search_query = this.attr('value').search.toLowerCase(); + + if ('requestIdleCallback' in window) + window.requestIdleCallback(() => { + this.render_contents(); + }); + else + this.render_contents(); + }, + render_contents: function() { + const container = dialog.domNode.querySelector(".contents"); + + if (!dialog.entries) { + container.innerHTML = `<li class='text-center text-error'>${__("Operation failed: check event log.")}</li>`; + } else { + container.innerHTML = ""; + + let results_rendered = 0; + + const search_tokens = dialog.search_query + .split(/ {1,}/) + .filter((stoken) => (stoken.length > 0 ? stoken : null)); + + dialog.entries.forEach((plugin) => { + const is_installed = (dialog.installed_plugins + .filter((p) => plugin.topics.map((t) => t.replace(/-/g, "_")).includes(p))).length > 0; + + if (search_tokens.length == 0 || + Object.values(plugin).filter((pval) => + search_tokens.filter((stoken) => + (pval.indexOf(stoken) != -1 ? stoken : null) + ).length == search_tokens.length).length > 0) { + + ++results_rendered; + + container.innerHTML += ` + <li data-row-value="${App.escapeHtml(plugin.name)}" class="${is_installed ? "plugin-installed" : ""}"> + ${App.FormFields.button_tag((is_installed ? + App.FormFields.icon("check") + " " +__("Already installed") : + App.FormFields.icon("file_download") + " " +__('Install')), "", {class: 'alt-primary pull-right', + disabled: is_installed, + onclick: `App.dialogOf(this).performInstall("${App.escapeHtml(plugin.name)}")`})} + + <h3>${plugin.name} + <a target="_blank" href="${App.escapeHtml(plugin.html_url)}"> + ${App.FormFields.icon("open_in_new_window")} + </a> + </h3> + + <div class='small text-muted'>${__("Updated: %s").replace("%s", plugin.last_update)}</div> + + <div class='description'>${plugin.description}</div> + </li> + ` + } + }); + + if (results_rendered == 0) { + container.innerHTML = `<li class='text-center text-info'>${__("Could not find any plugins for this search query.")}</li>`; + } + + dojo.parser.parse(container); + } + }, + reload: function() { + const container = dialog.domNode.querySelector(".contents"); + container.innerHTML = `<li class='text-center'>${__("Looking for plugins...")}</li>`; + + xhr.json("backend.php", {op: "pref-prefs", method: "getAvailablePlugins"}, (reply) => { + dialog.entries = reply; + dialog.render_contents(); + }); + }, + content: ` + <div dojoType='fox.Toolbar'> + <div class='pull-right'> + <input name="search" placeholder="${__("Search...")}" type="search" dojoType="dijit.form.TextBox" onkeyup="App.dialogOf(this).search()"> + </div> + <div style='height : 16px'> </div> <!-- disgusting --> + </div> + + <ul style='clear : both' class="panel panel-scrollable-400px contents plugin-installer-list"> </ul> + + <footer> + ${App.FormFields.button_tag(App.FormFields.icon("refresh") + " " +__("Refresh"), "", {class: 'alt-primary', onclick: 'App.dialogOf(this).reload()'})} + ${App.FormFields.cancel_dialog_tag(__("Close"))} + </footer> + `, + }); + + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); + + dialog.installed_plugins = [...document.querySelectorAll('*[data-plugin-name]')].map((p) => p.getAttribute('data-plugin-name')); + + dialog.reload(); + }); + + dialog.show(); + }, + update: function(name = null) { + + const dialog = new fox.SingleUseDialog({ + title: __("Update plugins"), + need_refresh: false, + plugins_to_update: [], + plugins_to_check: [], + onHide: function() { + if (this.need_refresh) { + Helpers.Prefs.refresh(); + } + }, + performUpdate: function() { + const container = dialog.domNode.querySelector(".update-results"); + + console.log('updating', dialog.plugins_to_update); + dialog.attr('title', __('Updating...')); + + container.innerHTML = `<li class='text-center'>${__("Updating, please wait...")}</li>`; + let enable_update_btn = false; + + xhr.json("backend.php", {op: "pref-prefs", method: "updateLocalPlugins", plugins: dialog.plugins_to_update.join(",")}, (reply) => { + + if (!reply) { + container.innerHTML = `<li class='text-center text-error'>${__("Operation failed: check event log.")}</li>`; + } else { + container.innerHTML = ""; + + reply.forEach((p) => { + if (p.rv.git_status == 0) + dialog.need_refresh = true; + else + enable_update_btn = true; + + container.innerHTML += + ` + <li> + <h3>${p.plugin}</h3> + ${p.rv.stderr ? `<pre class="small text-error pre-wrap">${p.rv.stderr}</pre>` : ''} + ${p.rv.stdout ? `<pre class="small text-success pre-wrap">${p.rv.stdout}</pre>` : ''} + <div class="small"> + ${p.rv.git_status ? App.FormFields.icon("error_outline") + " " + __("Exited with RC: %d").replace("%d", p.rv.git_status) : + App.FormFields.icon("check") + " " + __("Update done.")} + </div> + </li> + ` + }); + } + + dialog.attr('title', __('Updates complete')); + dijit.getEnclosingWidget(dialog.domNode.querySelector(".update-btn")).attr('disabled', !enable_update_btn); + }); + }, + checkNextPlugin: function() { + const name = dialog.plugins_to_check.shift(); + + if (name) { + this.checkUpdates(name); + } else { + const num_updated = dialog.plugins_to_update.length; + + if (num_updated > 0) + dialog.attr('title', + App.l10n.ngettext('Updates pending for %d plugin', 'Updates pending for %d plugins', num_updated) + .replace("%d", num_updated)); + else + dialog.attr('title', __("No updates available")); + + dijit.getEnclosingWidget(dialog.domNode.querySelector(".update-btn")) + .attr('disabled', num_updated == 0); + + } + }, + checkUpdates: function(name) { + console.log('checkUpdates', name); + + const container = dialog.domNode.querySelector(".update-results"); + + dialog.attr('title', __("Checking: %s").replace("%s", name)); + + //container.innerHTML = `<li class='text-center'>${__("Checking: %s...").replace("%s", name)}</li>`; + + xhr.json("backend.php", {op: "pref-prefs", method: "checkForPluginUpdates", name: name}, (reply) => { + + if (!reply) { + container.innerHTML += `<li class='text-error'>${__("%s: Operation failed: check event log.").replace("%s", name)}</li>`; + } else { + + reply.forEach((p) => { + if (p.rv) { + if (p.rv.need_update) { + dialog.plugins_to_update.push(p.plugin); + + const update_button = dijit.getEnclosingWidget( + App.find(`*[data-update-btn-for-plugin="${p.plugin}"]`)); + + if (update_button) + update_button.domNode.show(); + } + + if (p.rv.need_update || p.rv.git_status != 0) { + container.innerHTML += + ` + <li><h3>${p.plugin}</h3> + ${p.rv.stderr ? `<pre class="small text-error pre-wrap">${p.rv.stderr}</pre>` : ''} + ${p.rv.stdout ? `<pre class="small text-success pre-wrap">${p.rv.stdout}</pre>` : ''} + <div class="small"> + ${p.rv.git_status ? App.FormFields.icon("error_outline") + " " + __("Exited with RC: %d").replace("%d", p.rv.git_status) : + App.FormFields.icon("check") + " " + __("Ready to update")} + </div> + </li> + ` + } + } + dialog.checkNextPlugin(); + }); + } + + }); + + }, + content: ` + <ul class="panel panel-scrollable plugin-updater-list update-results"> + </ul> + + <footer> + ${App.FormFields.button_tag(App.FormFields.icon("update") + " " + __("Update"), "", {disabled: true, class: "update-btn alt-primary", onclick: "App.dialogOf(this).performUpdate()"})} + ${App.FormFields.cancel_dialog_tag(__("Close"))} + </footer> + `, }); + + const tmph = dojo.connect(dialog, 'onShow', function () { + dojo.disconnect(tmph); + + dialog.plugins_to_update = []; + + if (name) { + dialog.checkUpdates(name); + } else { + dialog.plugins_to_check = [...document.querySelectorAll('*[data-plugin-name][data-plugin-local=true]')].map((p) => p.getAttribute('data-plugin-name')); + dialog.checkNextPlugin(); + } + }); + + dialog.show(); }, }, OPML: { @@ -386,6 +848,7 @@ const Helpers = { </section> <footer class='text-center'> <button dojoType='dijit.form.Button' onclick="return App.dialogOf(this).regenOPMLKey()"> + ${App.FormFields.icon("refresh")} ${__('Generate new URL')} </button> <button dojoType='dijit.form.Button' type='submit' class='alt-primary'> |