aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/settings/js')
-rw-r--r--apps/settings/js/esm-test.mjs8
-rw-r--r--apps/settings/js/federationscopemenu.js143
-rw-r--r--apps/settings/js/federationsettingsview.js278
-rw-r--r--apps/settings/js/map-test.js.map7
-rw-r--r--apps/settings/js/security_password.js82
-rw-r--r--apps/settings/js/settings.js109
-rw-r--r--apps/settings/js/settings/personalInfo.js107
-rw-r--r--apps/settings/js/templates.js91
-rw-r--r--apps/settings/js/templates/federationscopemenu.handlebars27
-rw-r--r--apps/settings/js/usersettings.js60
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('&amp;')
+ .split('<').join('&lt;')
+ .split('>').join('&gt;')
+ .split('"').join('&quot;')
+ .split('\'').join('&#039;');
+ },
+
+ /**
+ * 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;
+})();