diff options
Diffstat (limited to 'apps/settings/js')
-rw-r--r-- | apps/settings/js/esm-test.mjs | 8 | ||||
-rw-r--r-- | apps/settings/js/federationscopemenu.js | 143 | ||||
-rw-r--r-- | apps/settings/js/federationsettingsview.js | 278 | ||||
-rw-r--r-- | apps/settings/js/map-test.js.map | 7 | ||||
-rw-r--r-- | apps/settings/js/security_password.js | 82 | ||||
-rw-r--r-- | apps/settings/js/settings.js | 109 | ||||
-rw-r--r-- | apps/settings/js/settings/personalInfo.js | 107 | ||||
-rw-r--r-- | apps/settings/js/templates.js | 91 | ||||
-rw-r--r-- | apps/settings/js/templates/federationscopemenu.handlebars | 27 | ||||
-rw-r--r-- | apps/settings/js/usersettings.js | 60 |
10 files changed, 912 insertions, 0 deletions
diff --git a/apps/settings/js/esm-test.mjs b/apps/settings/js/esm-test.mjs new file mode 100644 index 00000000000..b76812cc8a4 --- /dev/null +++ b/apps/settings/js/esm-test.mjs @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This is a dummy file for testing webserver support of JavaScript modules. + */ + +export default 'Hello' diff --git a/apps/settings/js/federationscopemenu.js b/apps/settings/js/federationscopemenu.js new file mode 100644 index 00000000000..20974caa762 --- /dev/null +++ b/apps/settings/js/federationscopemenu.js @@ -0,0 +1,143 @@ +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/* global OC, Handlebars */ +(function() { + + /** + * Construct a new FederationScopeMenu instance + * @constructs FederationScopeMenu + * @memberof OC.Settings + * @param {object} options + * @param {array.<string>} [options.excludedScopes] array of excluded scopes + */ + var FederationScopeMenu = OC.Backbone.View.extend({ + tagName: 'div', + className: 'federationScopeMenu popovermenu bubble menu menu-center', + field: undefined, + _scopes: undefined, + _excludedScopes: [], + + initialize: function(options) { + this.field = options.field; + this._scopes = [ + { + name: 'v2-private', + displayName: t('settings', 'Private'), + tooltip: t('settings', 'Only visible to people matched via phone number integration through Talk on mobile'), + iconClass: 'icon-phone', + active: false + }, + { + name: 'v2-local', + displayName: t('settings', 'Local'), + tooltip: t('settings', 'Only visible to people on this instance and guests'), + iconClass: 'icon-password', + active: false + }, + { + name: 'v2-federated', + displayName: t('settings', 'Federated'), + tooltip: t('settings', 'Only synchronize to trusted servers'), + iconClass: 'icon-contacts-dark', + active: false + }, + { + name: 'v2-published', + displayName: t('settings', 'Published'), + tooltip: t('settings', 'Synchronize to trusted servers and the global and public address book'), + iconClass: 'icon-link', + active: false + } + ]; + + if (options.excludedScopes && options.excludedScopes.length) { + this._excludedScopes = options.excludedScopes + } + }, + + /** + * Current context + * + * @type OCA.Files.FileActionContext + */ + _context: null, + + events: { + 'click a.action': '_onSelectScope', + 'keydown a.action': '_onSelectScopeKeyboard' + }, + + /** + * Event handler whenever an action has been clicked within the menu + * + * @param {Object} event event object + */ + _onSelectScope: function(event) { + var $target = $(event.currentTarget); + if (!$target.hasClass('menuitem')) { + $target = $target.closest('.menuitem'); + } + + this.trigger('select:scope', $target.data('action')); + + OC.hideMenus(); + }, + + _onSelectScopeKeyboard: function(event) { + if (event.keyCode === 13 || event.keyCode === 32) { + // Enter and space can be used to select a scope + event.preventDefault(); + this._onSelectScope(event); + } + }, + + /** + * Renders the menu with the currently set items + */ + render: function() { + this.$el.html(OC.Settings.Templates['federationscopemenu']({ + items: this._scopes + })); + }, + + /** + * Displays the menu + */ + show: function(context) { + this._context = context; + var currentlyActiveValue = $('#'+context.target.closest('form').id).find('input[type="hidden"]')[0].value; + + for(var i in this._scopes) { + if (this._scopes[i].name === currentlyActiveValue) { + this._scopes[i].active = true; + } else { + this._scopes[i].active = false; + } + + var isExcludedScope = this._excludedScopes.includes(this._scopes[i].name) + if (isExcludedScope && !this._scopes[i].active) { + this._scopes[i].hidden = true + } else if (isExcludedScope && this._scopes[i].active) { + this._scopes[i].hidden = false + this._scopes[i].disabled = true + } else { + this._scopes[i].hidden = false + this._scopes[i].disabled = false + } + } + + this.render(); + this.$el.removeClass('hidden'); + + OC.showMenu(null, this.$el); + } + }); + + OC.Settings = OC.Settings || {}; + OC.Settings.FederationScopeMenu = FederationScopeMenu; + +})(); diff --git a/apps/settings/js/federationsettingsview.js b/apps/settings/js/federationsettingsview.js new file mode 100644 index 00000000000..a4587e1bedb --- /dev/null +++ b/apps/settings/js/federationsettingsview.js @@ -0,0 +1,278 @@ +/* global OC, result, _ */ + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +(function(_, $, OC) { + 'use strict'; + + /** + * Construct a new FederationScopeMenu instance + * @constructs FederationScopeMenu + * @memberof OC.Settings + * @param {object} options + * @param {boolean} [options.showFederatedScope=false] whether show the + * "v2-federated" scope or not + * @param {boolean} [options.showPublishedScope=false] whether show the + * "v2-published" scope or not + */ + var FederationSettingsView = OC.Backbone.View.extend({ + _inputFields: undefined, + + /** @var Backbone.Model */ + _config: undefined, + + initialize: function(options) { + options = options || {}; + + if (options.config) { + this._config = options.config; + } else { + this._config = new OC.Settings.UserSettings(); + } + this.showFederatedScope = !!options.showFederatedScope; + this.showPublishedScope = !!options.showPublishedScope; + + this._inputFields = [ + 'displayname', + 'phone', + 'email', + 'website', + 'twitter', + 'address', + 'avatar' + ]; + + var self = this; + _.each(this._inputFields, function(field) { + var scopeOnly = field === 'avatar'; + + // Initialize config model + if (!scopeOnly) { + self._config.set(field, $('#' + field).val()); + } + self._config.set(field + 'Scope', $('#' + field + 'scope').val()); + + // Set inputs whenever model values change + if (!scopeOnly) { + self.listenTo(self._config, 'change:' + field, function() { + self.$('#' + field).val(self._config.get(field)); + }); + } + self.listenTo(self._config, 'change:' + field + 'Scope', function() { + self._setFieldScopeIcon(field, self._config.get(field + 'Scope')); + }); + }); + + this._registerEvents(); + }, + + render: function() { + var self = this; + var fieldsWithV2Private = [ + 'avatar', + 'phone', + 'twitter', + 'website', + 'address', + ]; + + _.each(this._inputFields, function(field) { + var $icon = self.$('#' + field + 'form .headerbar-label > .federation-menu'); + var excludedScopes = [] + + if (fieldsWithV2Private.indexOf(field) === -1) { + excludedScopes.push('v2-private'); + } + + if (!self.showFederatedScope) { + excludedScopes.push('v2-federated'); + } + + if (!self.showPublishedScope) { + excludedScopes.push('v2-published'); + } + + var scopeMenu = new OC.Settings.FederationScopeMenu({ + field: field, + excludedScopes: excludedScopes, + }); + + self.listenTo(scopeMenu, 'select:scope', function(scope) { + self._onScopeChanged(field, scope); + }); + $icon.append(scopeMenu.$el); + $icon.attr('aria-expanded', 'false'); + $icon.on('click', _.bind(scopeMenu.show, scopeMenu)); + $icon.on('keydown', function(e) { + if (e.keyCode === 32) { + // Open the menu when the user presses the space bar + e.preventDefault(); + scopeMenu.show(e); + } else if (e.keyCode === 27) { + // Close the menu again if opened + OC.hideMenus(); + } + }.bind(this)); + + // Restore initial state + self._setFieldScopeIcon(field, self._config.get(field + 'Scope')); + }); + }, + + _registerEvents: function() { + var self = this; + _.each(this._inputFields, function(field) { + if ( + field === 'avatar' || + field === 'email' || + field === 'displayname' || + field === 'twitter' || + field === 'address' || + field === 'website' || + field === 'phone' + ) { + return; + } + self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self), true); + }); + }, + + _onInputChanged: function(e) { + var self = this; + + var $dialog = $('.oc-dialog:visible'); + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + if($dialog.length === 0) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onInputChanged, this, e)); + } + return; + } + var $target = $(e.target); + var value = $target.val(); + var field = $target.attr('id'); + this._config.set(field, value); + + var savingData = this._config.save({ + error: function(jqXHR) { + OC.msg.finishedSaving('#personal-settings-container .msg', jqXHR); + } + }); + + $.when(savingData).done(function(data) { + if (data.status === "success") { + self._showInputChangeSuccess(field); + } else { + self._showInputChangeFail(field); + } + }); + }, + + _onScopeChanged: function(field, scope) { + var $dialog = $('.oc-dialog:visible'); + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + if($dialog.length === 0) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onScopeChanged, this, field, scope)); + } + return; + } + + this._config.set(field + 'Scope', scope); + + $('#' + field + 'scope').val(scope); + + // TODO: user loading/success feedback + this._config.save(); + this._setFieldScopeIcon(field, scope); + this._updateVerifyButton(field, scope); + }, + + _updateVerifyButton: function(field, scope) { + // show verification button if the value is set and the scope is 'public' + if (field === 'twitter' || field === 'website'|| field === 'email') { + var verify = this.$('#' + field + 'form > .verify'); + var scope = this.$('#' + field + 'scope').val(); + var value = this.$('#' + field).val(); + + if (scope === 'public' && value !== '') { + verify.removeClass('hidden'); + return true; + } else { + verify.addClass('hidden'); + } + } + + return false; + }, + + _showInputChangeSuccess: function(field) { + var $icon = this.$('#' + field + 'form > .icon-checkmark'); + $icon.fadeIn(200); + setTimeout(function() { + $icon.fadeOut(300); + }, 2000); + + var scope = this.$('#' + field + 'scope').val(); + var verifyAvailable = this._updateVerifyButton(field, scope); + + // change verification buttons from 'verify' to 'verifying...' on value change + if (verifyAvailable) { + if (field === 'twitter' || field === 'website') { + var verifyStatus = this.$('#' + field + 'form > .verify > #verify-' + field); + verifyStatus.attr('data-origin-title', t('settings', 'Verify')); + verifyStatus.attr('src', OC.imagePath('core', 'actions/verify.svg')); + verifyStatus.data('status', '0'); + verifyStatus.addClass('verify-action'); + } else if (field === 'email') { + var verifyStatus = this.$('#' + field + 'form > .verify > #verify-' + field); + verifyStatus.attr('data-origin-title', t('settings', 'Verifying …')); + verifyStatus.data('status', '1'); + verifyStatus.attr('src', OC.imagePath('core', 'actions/verifying.svg')); + } + } + }, + + _showInputChangeFail: function(field) { + var $icon = this.$('#' + field + 'form > .icon-error'); + $icon.fadeIn(200); + setTimeout(function() { + $icon.fadeOut(300); + }, 2000); + }, + + _setFieldScopeIcon: function(field, scope) { + var $icon = this.$('#' + field + 'form > .headerbar-label .icon-federation-menu'); + + $icon.removeClass('icon-phone'); + $icon.removeClass('icon-password'); + $icon.removeClass('icon-contacts-dark'); + $icon.removeClass('icon-link'); + $icon.addClass('hidden'); + + switch (scope) { + case 'v2-private': + $icon.addClass('icon-phone'); + $icon.removeClass('hidden'); + break; + case 'v2-local': + $icon.addClass('icon-password'); + $icon.removeClass('hidden'); + break; + case 'v2-federated': + $icon.addClass('icon-contacts-dark'); + $icon.removeClass('hidden'); + break; + case 'v2-published': + $icon.addClass('icon-link'); + $icon.removeClass('hidden'); + break; + } + } + }); + + OC.Settings = OC.Settings || {}; + OC.Settings.FederationSettingsView = FederationSettingsView; +})(_, $, OC); diff --git a/apps/settings/js/map-test.js.map b/apps/settings/js/map-test.js.map new file mode 100644 index 00000000000..422afffa56e --- /dev/null +++ b/apps/settings/js/map-test.js.map @@ -0,0 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This is a dummy file for testing webserver support of JavaScript map files. + */ +{} diff --git a/apps/settings/js/security_password.js b/apps/settings/js/security_password.js new file mode 100644 index 00000000000..d0b2d9f2c07 --- /dev/null +++ b/apps/settings/js/security_password.js @@ -0,0 +1,82 @@ +/* global OC */ + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2011-2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +window.addEventListener('DOMContentLoaded', function () { + if($('#pass2').length) { + $('#pass2').showPassword().keyup(); + } + + var removeloader = function () { + setTimeout(function(){ + if ($('.password-state').length > 0) { + $('.password-state').remove(); + } + }, 5000) + }; + + $("#passwordbutton").click(function () { + if ($('#pass1').val() !== '' && $('#pass2').val() !== '') { + // Serialize the data + var post = $("#passwordform").serialize(); + $('#passwordchanged').hide(); + $('#passworderror').hide(); + $("#passwordbutton").attr('disabled', 'disabled'); + $("#passwordbutton").after("<span class='password-loading icon icon-loading-small-dark password-state'></span>"); + $(".personal-show-label").hide(); + // Ajax foo + $.post(OC.generateUrl('/settings/personal/changepassword'), post, function (data) { + if (data.status === "success") { + $("#passwordbutton").after("<span class='checkmark icon icon-checkmark password-state'></span>"); + removeloader(); + $('#pass1').val(''); + $('#pass2').val('').change(); + } + if (typeof(data.data) !== "undefined") { + OC.msg.finishedSaving('#password-error-msg', data); + } else { + OC.msg.finishedSaving('#password-error-msg', + { + 'status' : 'error', + 'data' : { + 'message' : t('settings', 'Unable to change password') + } + } + ); + } + $(".personal-show-label").show(); + $(".password-loading").remove(); + $("#passwordbutton").removeAttr('disabled'); + }); + return false; + } else { + OC.msg.finishedSaving('#password-error-msg', + { + 'status' : 'error', + 'data' : { + 'message' : t('settings', 'Unable to change password') + } + } + ); + return false; + } + }); + + $('#pass2').strengthify({ + zxcvbn: OC.linkTo('core','vendor/zxcvbn/dist/zxcvbn.js'), + titles: [ + t('settings', 'Very weak password'), + t('settings', 'Weak password'), + t('settings', 'So-so password'), + t('settings', 'Good password'), + t('settings', 'Strong password') + ], + drawTitles: true, + $addAfter: $('input[name="newpassword-clone"]'), + nonce: btoa(OC.requestToken), + }); +}); diff --git a/apps/settings/js/settings.js b/apps/settings/js/settings.js new file mode 100644 index 00000000000..482c7020b60 --- /dev/null +++ b/apps/settings/js/settings.js @@ -0,0 +1,109 @@ +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +OC.Settings = OC.Settings || {}; +OC.Settings = _.extend(OC.Settings, { + + _cachedGroups: null, + + escapeHTML: function (text) { + return text.toString() + .split('&').join('&') + .split('<').join('<') + .split('>').join('>') + .split('"').join('"') + .split('\'').join('''); + }, + + /** + * Setup selection box for group selection. + * + * Values need to be separated by a pipe "|" character. + * (mostly because a comma is more likely to be used + * for groups) + * + * @param $elements jQuery element (hidden input) to setup select2 on + * @param {Array} [extraOptions] extra options hash to pass to select2 + * @param {Array} [options] extra options + * @param {Array} [options.excludeAdmins=false] flag whether to exclude admin groups + */ + setupGroupsSelect: function($elements, extraOptions, options) { + var self = this; + options = options || {}; + if ($elements.length > 0) { + // Let's load the data and THEN init our select + $.ajax({ + url: OC.linkToOCS('cloud/groups', 2) + 'details', + dataType: 'json', + success: function(data) { + var results = []; + + if (data.ocs.data.groups && data.ocs.data.groups.length > 0) { + + data.ocs.data.groups.forEach(function(group) { + if (!options.excludeAdmins || group.id !== 'admin') { + results.push({ id: group.id, displayname: group.displayname }); + } + }) + + // note: settings are saved through a "change" event registered + // on all input fields + $elements.select2(_.extend({ + placeholder: t('settings', 'Groups'), + allowClear: true, + multiple: true, + toggleSelect: true, + separator: '|', + data: { results: results, text: 'displayname' }, + initSelection: function(element, callback) { + var groups = $(element).val(); + var selection; + if (groups && results.length > 0) { + selection = _.map(_.filter((groups || []).split('|').sort(), function(groupId) { + return results.find(function(group) { + return group.id === groupId + }) !== undefined + }), function(groupId) { + return { + id: groupId, + displayname: results.find(function(group) { + return group.id === groupId + }).displayname + } + }) + } else if (groups) { + selection = _.map((groups || []).split('|').sort(), function(groupId) { + return { + id: groupId, + displayname: groupId + }; + }); + } + callback(selection); + }, + formatResult: function(element) { + return self.escapeHTML(element.displayname); + }, + formatSelection: function(element) { + return self.escapeHTML(element.displayname); + }, + escapeMarkup: function(m) { + // prevent double markup escape + return m; + } + }, extraOptions || {})); + } else { + OC.Notification.show(t('settings', 'Group list is empty'), { type: 'error' }); + console.log(data); + } + }, + error: function(data) { + OC.Notification.show(t('settings', 'Unable to retrieve the group list'), { type: 'error' }); + console.log(data); + } + }); + } + } +}); diff --git a/apps/settings/js/settings/personalInfo.js b/apps/settings/js/settings/personalInfo.js new file mode 100644 index 00000000000..0498134c125 --- /dev/null +++ b/apps/settings/js/settings/personalInfo.js @@ -0,0 +1,107 @@ +/* global OC */ + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2011-2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +OC.Settings = OC.Settings || {}; + +/** + * The callback will be fired as soon as enter is pressed by the + * user or 1 second after the last data entry + * + * @param {any} callback - + * @param allowEmptyValue if this is set to true the callback is also called when the value is empty + */ +jQuery.fn.keyUpDelayedOrEnter = function (callback, allowEmptyValue) { + var cb = callback; + var that = this; + + this.on('input', _.debounce(function (event) { + // enter is already handled in keypress + if (event.keyCode === 13) { + return; + } + if (allowEmptyValue || that.val() !== '') { + cb(event); + } + }, 1000)); + + this.keypress(function (event) { + if (event.keyCode === 13 && (allowEmptyValue || that.val() !== '')) { + event.preventDefault(); + cb(event); + } + }); +}; + +window.addEventListener('DOMContentLoaded', function () { + if($('#pass2').length) { + $('#pass2').showPassword().keyup(); + } + + var showVerifyDialog = function(dialog, howToVerify, verificationCode) { + var dialogContent = dialog.children('.verification-dialog-content'); + dialogContent.children(".explainVerification").text(howToVerify); + dialogContent.children(".verificationCode").text(verificationCode); + dialog.css('display', 'block'); + }; + + $(".verify").click(function (event) { + + event.stopPropagation(); + + var verify = $(this); + var indicator = $(this).children('img'); + var accountId = indicator.attr('id'); + var status = indicator.data('status'); + + var onlyVerificationCode = false; + if (parseInt(status) === 1) { + onlyVerificationCode = true; + } + + if (indicator.hasClass('verify-action')) { + $.ajax( + OC.generateUrl('/settings/users/{account}/verify', {account: accountId}), + { + method: 'GET', + data: {onlyVerificationCode: onlyVerificationCode} + } + ).done(function (data) { + var dialog = verify.children('.verification-dialog'); + showVerifyDialog($(dialog), data.msg, data.code); + indicator.attr('data-origin-title', t('settings', 'Verifying …')); + indicator.attr('src', OC.imagePath('core', 'actions/verifying.svg')); + indicator.data('status', '1'); + }); + } + + }); + + // When the user clicks anywhere outside of the verification dialog we close it + $(document).click(function(event){ + var element = event.target; + var isDialog = $(element).hasClass('verificationCode') + || $(element).hasClass('explainVerification') + || $(element).hasClass('verification-dialog-content') + || $(element).hasClass('verification-dialog'); + if (!isDialog) { + $(document).find('.verification-dialog').css('display', 'none'); + } + }); + + + var settingsEl = $('#personal-settings') + var userSettings = new OC.Settings.UserSettings(); + var federationSettingsView = new OC.Settings.FederationSettingsView({ + el: settingsEl, + config: userSettings, + showFederatedScope: !!settingsEl.data('federation-enabled'), + showPublishedScope: !!settingsEl.data('lookup-server-upload-enabled'), + }); + + federationSettingsView.render(); +}); diff --git a/apps/settings/js/templates.js b/apps/settings/js/templates.js new file mode 100644 index 00000000000..7988a8df6a9 --- /dev/null +++ b/apps/settings/js/templates.js @@ -0,0 +1,91 @@ +(function() { + var template = Handlebars.template, templates = OC.Settings.Templates = OC.Settings.Templates || {}; +templates['federationscopemenu'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return ((stack1 = lookupProperty(helpers,"unless").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"hidden") : depth0),{"name":"unless","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":3,"column":2},"end":{"line":25,"column":13}}})) != null ? stack1 : ""); +},"2":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <li>\n" + + ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"disabled") : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.program(6, data, 0),"data":data,"loc":{"start":{"line":5,"column":3},"end":{"line":9,"column":10}}})) != null ? stack1 : "") + + ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"iconClass") : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(10, data, 0),"data":data,"loc":{"start":{"line":10,"column":4},"end":{"line":14,"column":11}}})) != null ? stack1 : "") + + " <p>\n <strong class=\"menuitem-text\">" + + alias4(((helper = (helper = lookupProperty(helpers,"displayName") || (depth0 != null ? lookupProperty(depth0,"displayName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"displayName","hash":{},"data":data,"loc":{"start":{"line":16,"column":35},"end":{"line":16,"column":50}}}) : helper))) + + "</strong><br>\n <span class=\"menuitem-text-detail\">" + + alias4(((helper = (helper = lookupProperty(helpers,"tooltip") || (depth0 != null ? lookupProperty(depth0,"tooltip") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"tooltip","hash":{},"data":data,"loc":{"start":{"line":17,"column":40},"end":{"line":17,"column":51}}}) : helper))) + + "</span>\n </p>\n" + + ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"disabled") : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(14, data, 0),"data":data,"loc":{"start":{"line":19,"column":3},"end":{"line":23,"column":10}}})) != null ? stack1 : "") + + " </li>\n"; +},"3":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <div class=\"menuitem action action-" + + alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":6,"column":38},"end":{"line":6,"column":46}}}) : helper))) + + " permanent " + + ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"active") : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":6,"column":57},"end":{"line":6,"column":84}}})) != null ? stack1 : "") + + " disabled\" data-action=\"" + + alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":6,"column":108},"end":{"line":6,"column":116}}}) : helper))) + + "\">\n"; +},"4":function(container,depth0,helpers,partials,data) { + return "active"; +},"6":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <a href=\"#\" class=\"menuitem action action-" + + alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":8,"column":45},"end":{"line":8,"column":53}}}) : helper))) + + " permanent " + + ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"active") : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":8,"column":64},"end":{"line":8,"column":91}}})) != null ? stack1 : "") + + "\" data-action=\"" + + alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":8,"column":106},"end":{"line":8,"column":114}}}) : helper))) + + "\">\n"; +},"8":function(container,depth0,helpers,partials,data) { + var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " <span class=\"icon " + + container.escapeExpression(((helper = (helper = lookupProperty(helpers,"iconClass") || (depth0 != null ? lookupProperty(depth0,"iconClass") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"iconClass","hash":{},"data":data,"loc":{"start":{"line":11,"column":23},"end":{"line":11,"column":36}}}) : helper))) + + "\"></span>\n"; +},"10":function(container,depth0,helpers,partials,data) { + return " <span class=\"no-icon\"></span>\n"; +},"12":function(container,depth0,helpers,partials,data) { + return " </div>\n"; +},"14":function(container,depth0,helpers,partials,data) { + return " </a>\n"; +},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return "<ul>\n" + + ((stack1 = lookupProperty(helpers,"each").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"items") : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":2,"column":1},"end":{"line":26,"column":10}}})) != null ? stack1 : "") + + "</ul>\n"; +},"useData":true}); +})();
\ No newline at end of file diff --git a/apps/settings/js/templates/federationscopemenu.handlebars b/apps/settings/js/templates/federationscopemenu.handlebars new file mode 100644 index 00000000000..5a2077d4fc3 --- /dev/null +++ b/apps/settings/js/templates/federationscopemenu.handlebars @@ -0,0 +1,27 @@ +<ul> + {{#each items}} + {{#unless hidden}} + <li> + {{#if disabled}} + <div class="menuitem action action-{{name}} permanent {{#if active}}active{{/if}} disabled" data-action="{{name}}"> + {{else}} + <a href="#" class="menuitem action action-{{name}} permanent {{#if active}}active{{/if}}" data-action="{{name}}"> + {{/if}} + {{#if iconClass}} + <span class="icon {{iconClass}}"></span> + {{else}} + <span class="no-icon"></span> + {{/if}} + <p> + <strong class="menuitem-text">{{displayName}}</strong><br> + <span class="menuitem-text-detail">{{tooltip}}</span> + </p> + {{#if disabled}} + </div> + {{else}} + </a> + {{/if}} + </li> + {{/unless}} + {{/each}} +</ul> diff --git a/apps/settings/js/usersettings.js b/apps/settings/js/usersettings.js new file mode 100644 index 00000000000..3dc402fbf2b --- /dev/null +++ b/apps/settings/js/usersettings.js @@ -0,0 +1,60 @@ +/* global OC */ + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +(function() { + 'use strict'; + + var errorNotification; + + /** + * Model for storing and saving user settings + * + * @class UserSettings + */ + var UserSettings = OC.Backbone.Model.extend({ + url: OC.generateUrl('/settings/users/{id}/settings', {id: OC.currentUser}), + isNew: function() { + return false; // Force PUT on .save() + }, + parse: function(data) { + if (_.isUndefined(data)) { + return null; + } + + if (errorNotification) { + errorNotification.hide(); + } + + if (data.status && data.status === 'error') { + errorNotification = OC.Notification.show(data.data.message, { type: 'error' }); + } + + if (_.isUndefined(data.data)) { + return null; + } + data = data.data; + + var ignored = [ + 'userId', + 'message' + ]; + + _.each(ignored, function(ign) { + if (!_.isUndefined(data[ign])) { + delete data[ign]; + } + }); + + return data; + } + }); + + OC.Settings = OC.Settings || {}; + + OC.Settings.UserSettings = UserSettings; +})(); |