aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2016-09-08 09:13:59 +0200
committerMorris Jobke <hey@morrisjobke.de>2016-09-08 18:44:27 +0200
commit6bb95de1c50208d6cbaf1242f86a2fdab09c2246 (patch)
tree47fa17b753a672e6d93c2186c517df2fd5b355b2
parentb3d3a95bf3e11a4689884f72f6d6021d4dcff97c (diff)
downloadnextcloud-server-6bb95de1c50208d6cbaf1242f86a2fdab09c2246.tar.gz
nextcloud-server-6bb95de1c50208d6cbaf1242f86a2fdab09c2246.zip
Adding a optional disclaimer to the anonymous upload page
-rw-r--r--apps/files_sharing/css/public.css11
-rw-r--r--apps/files_sharing/lib/Controllers/ShareController.php1
-rw-r--r--apps/files_sharing/templates/public.php5
-rw-r--r--lib/private/Settings/Admin/Sharing.php1
-rw-r--r--settings/css/settings.css8
-rw-r--r--settings/js/admin.js41
-rw-r--r--settings/templates/admin/sharing.php10
-rw-r--r--tests/lib/Settings/Admin/SharingTest.php12
8 files changed, 86 insertions, 3 deletions
diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css
index 4c5f847f9ff..3e3d13cebf0 100644
--- a/apps/files_sharing/css/public.css
+++ b/apps/files_sharing/css/public.css
@@ -168,6 +168,10 @@ thead {
margin: 0 auto;
}
+#emptycontent.has-disclaimer {
+ margin-top: 10vh;
+}
+
#public-upload #emptycontent h2 {
margin: 10px 0 5px 0;
}
@@ -222,3 +226,10 @@ thead {
padding-left: 18px;
margin-right: 7px;
}
+
+
+.disclaimer {
+ margin: -20px auto 30px;
+ max-width: 400px;
+ text-align: left;
+} \ No newline at end of file
diff --git a/apps/files_sharing/lib/Controllers/ShareController.php b/apps/files_sharing/lib/Controllers/ShareController.php
index cd0d40048cf..5ccaab746fc 100644
--- a/apps/files_sharing/lib/Controllers/ShareController.php
+++ b/apps/files_sharing/lib/Controllers/ShareController.php
@@ -357,6 +357,7 @@ class ShareController extends Controller {
$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
$shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
$shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
+ $shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null);
// Load files we need
\OCP\Util::addScript('files', 'file-upload');
diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php
index 1b282f04a7a..bdedb04d244 100644
--- a/apps/files_sharing/templates/public.php
+++ b/apps/files_sharing/templates/public.php
@@ -104,10 +104,13 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
<?php } else { ?>
<input type="hidden" id="upload-only-interface" value="1"/>
<div id="public-upload">
- <div id="emptycontent" class="">
+ <div id="emptycontent" class="<?php if (!empty($_['disclaimer'])) { ?>has-disclaimer<?php } ?>">
<div id="displayavatar"><div class="avatardiv"></div></div>
<h2><?php p($l->t('Upload files to %s', [$_['shareOwner']])) ?></h2>
<p><span class="icon-folder"></span> <?php p($_['filename']) ?></p>
+ <?php if (!empty($_['disclaimer'])) { ?>
+ <p class="disclaimer"><?php p($_['disclaimer']); ?></p>
+ <?php } ?>
<input type="file" name="files[]" class="hidden" multiple>
<a href="#" class="button icon-upload"><?php p($l->t('Select or drop files')) ?></a>
diff --git a/lib/private/Settings/Admin/Sharing.php b/lib/private/Settings/Admin/Sharing.php
index 8d3ddc9b3b5..59821044605 100644
--- a/lib/private/Settings/Admin/Sharing.php
+++ b/lib/private/Settings/Admin/Sharing.php
@@ -65,6 +65,7 @@ class Sharing implements ISettings {
'shareEnforceExpireDate' => $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no'),
'shareExcludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes' ? true : false,
'shareExcludedGroupsList' => $excludeGroupsList,
+ 'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', null),
];
return new TemplateResponse('settings', 'admin/sharing', $parameters, '');
diff --git a/settings/css/settings.css b/settings/css/settings.css
index 6ed707f7c45..d66b0f82b55 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -499,6 +499,14 @@ table.grid td.date{
display: inline-block;
}
+#publicShareDisclaimerText {
+ width: calc(100% - 23px); /* 20 px left margin, 3 px right margin */
+ max-width: 600px;
+ height: 150px;
+ margin-left: 20px;
+ box-sizing: border-box;
+}
+
/* correctly display help icons next to headings */
.icon-info {
padding: 11px 20px;
diff --git a/settings/js/admin.js b/settings/js/admin.js
index fb59b3ba026..a2a1cef531c 100644
--- a/settings/js/admin.js
+++ b/settings/js/admin.js
@@ -90,7 +90,7 @@ $(document).ready(function(){
}
});
- $('#shareAPI input:not(#excludedGroups)').change(function() {
+ $('#shareAPI input:not(.noJSAutoUpdate)').change(function() {
var value = $(this).val();
if ($(this).attr('type') === 'checkbox') {
if (this.checked) {
@@ -106,6 +106,45 @@ $(document).ready(function(){
$("#setDefaultExpireDate").toggleClass('hidden', !this.checked);
});
+ $('#publicShareDisclaimer').change(function() {
+ $("#publicShareDisclaimerText").toggleClass('hidden', !this.checked);
+ if(!this.checked) {
+ savePublicShareDisclaimerText('');
+ }
+ });
+
+ var savePublicShareDisclaimerText = _.debounce(function(value) {
+ var data = {
+ app:'core',
+ key:'shareapi_public_link_disclaimertext'
+ };
+ if (_.isString(value) && value !== '') {
+ data['action'] = 'setValue';
+ data['value'] = value;
+ } else {
+ data['action'] = 'deleteKey';
+ $('#publicShareDisclaimerText').val('');
+ }
+
+ OC.msg.startSaving('#publicShareDisclaimerStatus');
+ $.post(
+ OC.AppConfig.url,
+ data,
+ function(result){
+ if(result.status === 'success'){
+ OC.msg.finishedSuccess('#publicShareDisclaimerStatus', t('core', 'Saved'))
+ } else {
+ OC.msg.finishedError('#publicShareDisclaimerStatus', t('core', 'Not saved'))
+ }
+ },
+ 'json'
+ );
+ }, 500);
+
+ $('#publicShareDisclaimerText').on('change, keyup', function() {
+ savePublicShareDisclaimerText(this.value);
+ });
+
$('#allowLinks').change(function() {
$("#publicLinkSettings").toggleClass('hidden', !this.checked);
$('#setDefaultExpireDate').toggleClass('hidden', !(this.checked && $('#shareapiDefaultExpireDate')[0].checked));
diff --git a/settings/templates/admin/sharing.php b/settings/templates/admin/sharing.php
index b8f8e920246..1c47d74770f 100644
--- a/settings/templates/admin/sharing.php
+++ b/settings/templates/admin/sharing.php
@@ -95,7 +95,7 @@
<label for="shareapiExcludeGroups"><?php p($l->t('Exclude groups from sharing'));?></label><br/>
</p>
<p id="selectExcludedGroups" class="indent <?php if (!$_['shareExcludeGroups'] || $_['shareAPIEnabled'] === 'no') p('hidden'); ?>">
- <input name="shareapi_exclude_groups_list" type="hidden" id="excludedGroups" value="<?php p($_['shareExcludedGroupsList']) ?>" style="width: 400px"/>
+ <input name="shareapi_exclude_groups_list" type="hidden" id="excludedGroups" value="<?php p($_['shareExcludedGroupsList']) ?>" style="width: 400px" class="noJSAutoUpdate"/>
<br />
<em><?php p($l->t('These groups will still be able to receive shares, but not to initiate them.')); ?></em>
</p>
@@ -104,4 +104,12 @@
<?php if ($_['allowShareDialogUserEnumeration'] === 'yes') print_unescaped('checked="checked"'); ?> />
<label for="shareapi_allow_share_dialog_user_enumeration"><?php p($l->t('Allow username autocompletion in share dialog. If this is disabled the full username needs to be entered.'));?></label><br />
</p>
+ <p>
+ <input type="checkbox" id="publicShareDisclaimer" class="checkbox noJSAutoUpdate"
+ <?php if ($_['publicShareDisclaimerText'] !== null) print_unescaped('checked="checked"'); ?> />
+ <label for="publicShareDisclaimer"><?php p($l->t('Show disclaimer text on the public link upload page. (Only shown when the file list is hidden.)'));?></label>
+ <span id="publicShareDisclaimerStatus" class="msg" style="display:none"></span>
+ <br/>
+ <textarea placeholder="This text will be shown on the public link upload page when the file list is hidden." id="publicShareDisclaimerText" <?php if ($_['publicShareDisclaimerText'] === null) { print_unescaped('class="hidden"'); } ?>><?php p($_['publicShareDisclaimerText']) ?></textarea>
+ </p>
</div>
diff --git a/tests/lib/Settings/Admin/SharingTest.php b/tests/lib/Settings/Admin/SharingTest.php
index 38ab7614d1c..86bbdcbbfb0 100644
--- a/tests/lib/Settings/Admin/SharingTest.php
+++ b/tests/lib/Settings/Admin/SharingTest.php
@@ -109,6 +109,11 @@ class SharingTest extends TestCase {
->method('getAppValue')
->with('core', 'shareapi_exclude_groups', 'no')
->willReturn('no');
+ $this->config
+ ->expects($this->at(13))
+ ->method('getAppValue')
+ ->with('core', 'shareapi_public_link_disclaimertext', null)
+ ->willReturn('Lorem ipsum');
$expected = new TemplateResponse(
'settings',
@@ -129,6 +134,7 @@ class SharingTest extends TestCase {
'shareEnforceExpireDate' => 'no',
'shareExcludeGroups' => false,
'shareExcludedGroupsList' => '',
+ 'publicShareDisclaimer' => 'Lorem ipsum',
],
''
);
@@ -202,6 +208,11 @@ class SharingTest extends TestCase {
->method('getAppValue')
->with('core', 'shareapi_exclude_groups', 'no')
->willReturn('yes');
+ $this->config
+ ->expects($this->at(13))
+ ->method('getAppValue')
+ ->with('core', 'shareapi_public_link_disclaimertext', null)
+ ->willReturn('Lorem ipsum');
$expected = new TemplateResponse(
'settings',
@@ -222,6 +233,7 @@ class SharingTest extends TestCase {
'shareEnforceExpireDate' => 'no',
'shareExcludeGroups' => true,
'shareExcludedGroupsList' => 'NoSharers|OtherNoSharers',
+ 'publicShareDisclaimer' => 'Lorem ipsum',
],
''
);
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","sources":["webpack:///nextcloud/webpack/runtime/chunk loaded","webpack:///nextcloud/webpack/runtime/load script","webpack:///nextcloud/apps/files_external/src/settings.js","webpack:///nextcloud/webpack/bootstrap","webpack:///nextcloud/webpack/runtime/compat get default export","webpack:///nextcloud/webpack/runtime/define property getters","webpack:///nextcloud/webpack/runtime/ensure chunk","webpack:///nextcloud/webpack/runtime/get javascript chunk filename","webpack:///nextcloud/webpack/runtime/global","webpack:///nextcloud/webpack/runtime/hasOwnProperty shorthand","webpack:///nextcloud/webpack/runtime/make namespace object","webpack:///nextcloud/webpack/runtime/node module decorator","webpack:///nextcloud/webpack/runtime/runtimeId","webpack:///nextcloud/webpack/runtime/publicPath","webpack:///nextcloud/webpack/runtime/jsonp chunk loading","webpack:///nextcloud/webpack/runtime/nonce","webpack:///nextcloud/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var inProgress = {};\nvar dataWebpackPrefix = \"nextcloud:\";\n// loadScript function to load a script via script tag\n__webpack_require__.l = (url, done, key, chunkId) => {\n\tif(inProgress[url]) { inProgress[url].push(done); return; }\n\tvar script, needAttach;\n\tif(key !== undefined) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tfor(var i = 0; i < scripts.length; i++) {\n\t\t\tvar s = scripts[i];\n\t\t\tif(s.getAttribute(\"src\") == url || s.getAttribute(\"data-webpack\") == dataWebpackPrefix + key) { script = s; break; }\n\t\t}\n\t}\n\tif(!script) {\n\t\tneedAttach = true;\n\t\tscript = document.createElement('script');\n\n\t\tscript.charset = 'utf-8';\n\t\tscript.timeout = 120;\n\t\tif (__webpack_require__.nc) {\n\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n\t\t}\n\t\tscript.setAttribute(\"data-webpack\", dataWebpackPrefix + key);\n\n\t\tscript.src = url;\n\t}\n\tinProgress[url] = [done];\n\tvar onScriptComplete = (prev, event) => {\n\t\t// avoid mem leaks in IE.\n\t\tscript.onerror = script.onload = null;\n\t\tclearTimeout(timeout);\n\t\tvar doneFns = inProgress[url];\n\t\tdelete inProgress[url];\n\t\tscript.parentNode && script.parentNode.removeChild(script);\n\t\tdoneFns && doneFns.forEach((fn) => (fn(event)));\n\t\tif(prev) return prev(event);\n\t}\n\tvar timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);\n\tscript.onerror = onScriptComplete.bind(null, script.onerror);\n\tscript.onload = onScriptComplete.bind(null, script.onload);\n\tneedAttach && document.head.appendChild(script);\n};","/**\n * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors\n * SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc.\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport axios from '@nextcloud/axios'\nimport { t } from '@nextcloud/l10n'\nimport { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'\n\nimport jQuery from 'jquery'\n\naddPasswordConfirmationInterceptors(axios)\n\n/**\n * Returns the selection of applicable users in the given configuration row\n *\n * @param $row configuration row\n * @return array array of user names\n */\nfunction getSelection($row) {\n\tlet values = $row.find('.applicableUsers').select2('val')\n\tif (!values || values.length === 0) {\n\t\tvalues = []\n\t}\n\treturn values\n}\n\n/**\n *\n * @param $row\n */\nfunction getSelectedApplicable($row) {\n\tconst users = []\n\tconst groups = []\n\tconst multiselect = getSelection($row)\n\t$.each(multiselect, function(index, value) {\n\t\t// FIXME: don't rely on string parts to detect groups...\n\t\tconst pos = (value.indexOf) ? value.indexOf('(group)') : -1\n\t\tif (pos !== -1) {\n\t\t\tgroups.push(value.substr(0, pos))\n\t\t} else {\n\t\t\tusers.push(value)\n\t\t}\n\t})\n\n\t// FIXME: this should be done in the multiselect change event instead\n\t$row.find('.applicable')\n\t\t.data('applicable-groups', groups)\n\t\t.data('applicable-users', users)\n\n\treturn { users, groups }\n}\n\n/**\n *\n * @param $element\n * @param highlight\n */\nfunction highlightBorder($element, highlight) {\n\t$element.toggleClass('warning-input', highlight)\n\treturn highlight\n}\n\n/**\n *\n * @param $input\n */\nfunction isInputValid($input) {\n\tconst optional = $input.hasClass('optional')\n\tswitch ($input.attr('type')) {\n\tcase 'text':\n\tcase 'password':\n\t\tif ($input.val() === '' && !optional) {\n\t\t\treturn false\n\t\t}\n\t\tbreak\n\t}\n\treturn true\n}\n\n/**\n *\n * @param $input\n */\nfunction highlightInput($input) {\n\tswitch ($input.attr('type')) {\n\tcase 'text':\n\tcase 'password':\n\t\treturn highlightBorder($input, !isInputValid($input))\n\t}\n}\n\n/**\n * Initialize select2 plugin on the given elements\n *\n * @param {Array<object>} array of jQuery elements\n * @param $elements\n * @param {number} userListLimit page size for result list\n */\nfunction initApplicableUsersMultiselect($elements, userListLimit) {\n\tconst escapeHTML = function(text) {\n\t\treturn text.toString()\n\t\t\t.split('&').join('&amp;')\n\t\t\t.split('<').join('&lt;')\n\t\t\t.split('>').join('&gt;')\n\t\t\t.split('\"').join('&quot;')\n\t\t\t.split('\\'').join('&#039;')\n\t}\n\tif (!$elements.length) {\n\t\treturn\n\t}\n\treturn $elements.select2({\n\t\tplaceholder: t('files_external', 'Type to select account or group.'),\n\t\tallowClear: true,\n\t\tmultiple: true,\n\t\ttoggleSelect: true,\n\t\tdropdownCssClass: 'files-external-select2',\n\t\t// minimumInputLength: 1,\n\t\tajax: {\n\t\t\turl: OC.generateUrl('apps/files_external/applicable'),\n\t\t\tdataType: 'json',\n\t\t\tquietMillis: 100,\n\t\t\tdata(term, page) { // page is the one-based page number tracked by Select2\n\t\t\t\treturn {\n\t\t\t\t\tpattern: term, // search term\n\t\t\t\t\tlimit: userListLimit, // page size\n\t\t\t\t\toffset: userListLimit * (page - 1), // page number starts with 0\n\t\t\t\t}\n\t\t\t},\n\t\t\tresults(data) {\n\t\t\t\tif (data.status === 'success') {\n\n\t\t\t\t\tconst results = []\n\t\t\t\t\tlet userCount = 0 // users is an object\n\n\t\t\t\t\t// add groups\n\t\t\t\t\t$.each(data.groups, function(gid, group) {\n\t\t\t\t\t\tresults.push({ name: gid + '(group)', displayname: group, type: 'group' })\n\t\t\t\t\t})\n\t\t\t\t\t// add users\n\t\t\t\t\t$.each(data.users, function(id, user) {\n\t\t\t\t\t\tuserCount++\n\t\t\t\t\t\tresults.push({ name: id, displayname: user, type: 'user' })\n\t\t\t\t\t})\n\n\t\t\t\t\tconst more = (userCount >= userListLimit) || (data.groups.length >= userListLimit)\n\t\t\t\t\treturn { results, more }\n\t\t\t\t} else {\n\t\t\t\t\t// FIXME add error handling\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tinitSelection(element, callback) {\n\t\t\tconst users = {}\n\t\t\tusers.users = []\n\t\t\tconst toSplit = element.val().split(',')\n\t\t\tfor (let i = 0; i < toSplit.length; i++) {\n\t\t\t\tusers.users.push(toSplit[i])\n\t\t\t}\n\n\t\t\t$.ajax(OC.generateUrl('displaynames'), {\n\t\t\t\ttype: 'POST',\n\t\t\t\tcontentType: 'application/json',\n\t\t\t\tdata: JSON.stringify(users),\n\t\t\t\tdataType: 'json',\n\t\t\t}).done(function(data) {\n\t\t\t\tconst results = []\n\t\t\t\tif (data.status === 'success') {\n\t\t\t\t\t$.each(data.users, function(user, displayname) {\n\t\t\t\t\t\tif (displayname !== false) {\n\t\t\t\t\t\t\tresults.push({ name: user, displayname, type: 'user' })\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tcallback(results)\n\t\t\t\t} else {\n\t\t\t\t\t// FIXME add error handling\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tid(element) {\n\t\t\treturn element.name\n\t\t},\n\t\tformatResult(element) {\n\t\t\tconst $result = $('<span><div class=\"avatardiv\"></div><span>' + escapeHTML(element.displayname) + '</span></span>')\n\t\t\tconst $div = $result.find('.avatardiv')\n\t\t\t\t.attr('data-type', element.type)\n\t\t\t\t.attr('data-name', element.name)\n\t\t\t\t.attr('data-displayname', element.displayname)\n\t\t\tif (element.type === 'group') {\n\t\t\t\tconst url = OC.imagePath('core', 'actions/group')\n\t\t\t\t$div.html('<img width=\"32\" height=\"32\" src=\"' + url + '\">')\n\t\t\t}\n\t\t\treturn $result.get(0).outerHTML\n\t\t},\n\t\tformatSelection(element) {\n\t\t\tif (element.type === 'group') {\n\t\t\t\treturn '<span title=\"' + escapeHTML(element.name) + '\" class=\"group\">' + escapeHTML(element.displayname + ' ' + t('files_external', '(Group)')) + '</span>'\n\t\t\t} else {\n\t\t\t\treturn '<span title=\"' + escapeHTML(element.name) + '\" class=\"user\">' + escapeHTML(element.displayname) + '</span>'\n\t\t\t}\n\t\t},\n\t\tescapeMarkup(m) { return m }, // we escape the markup in formatResult and formatSelection\n\t}).on('select2-loaded', function() {\n\t\t$.each($('.avatardiv'), function(i, div) {\n\t\t\tconst $div = $(div)\n\t\t\tif ($div.data('type') === 'user') {\n\t\t\t\t$div.avatar($div.data('name'), 32)\n\t\t\t}\n\t\t})\n\t}).on('change', function(event) {\n\t\thighlightBorder($(event.target).closest('.applicableUsersContainer').find('.select2-choices'), !event.val.length)\n\t})\n}\n\n/**\n * @param id\n * @class OCA.Files_External.Settings.StorageConfig\n *\n * @classdesc External storage config\n */\nconst StorageConfig = function(id) {\n\tthis.id = id\n\tthis.backendOptions = {}\n}\n// Keep this in sync with \\OCA\\Files_External\\MountConfig::STATUS_*\nStorageConfig.Status = {\n\tIN_PROGRESS: -1,\n\tSUCCESS: 0,\n\tERROR: 1,\n\tINDETERMINATE: 2,\n}\nStorageConfig.Visibility = {\n\tNONE: 0,\n\tPERSONAL: 1,\n\tADMIN: 2,\n\tDEFAULT: 3,\n}\n/**\n * @memberof OCA.Files_External.Settings\n */\nStorageConfig.prototype = {\n\t_url: null,\n\n\t/**\n\t * Storage id\n\t *\n\t * @type int\n\t */\n\tid: null,\n\n\t/**\n\t * Mount point\n\t *\n\t * @type string\n\t */\n\tmountPoint: '',\n\n\t/**\n\t * Backend\n\t *\n\t * @type string\n\t */\n\tbackend: null,\n\n\t/**\n\t * Authentication mechanism\n\t *\n\t * @type string\n\t */\n\tauthMechanism: null,\n\n\t/**\n\t * Backend-specific configuration\n\t *\n\t * @type Object.<string,object>\n\t */\n\tbackendOptions: null,\n\n\t/**\n\t * Mount-specific options\n\t *\n\t * @type Object.<string,object>\n\t */\n\tmountOptions: null,\n\n\t/**\n\t * Creates or saves the storage.\n\t *\n\t * @param {Function} [options.success] success callback, receives result as argument\n\t * @param {Function} [options.error] error callback\n\t * @param options\n\t */\n\tsave(options) {\n\t\tlet url = OC.generateUrl(this._url)\n\t\tlet method = 'POST'\n\t\tif (_.isNumber(this.id)) {\n\t\t\tmethod = 'PUT'\n\t\t\turl = OC.generateUrl(this._url + '/{id}', { id: this.id })\n\t\t}\n\n\t\tthis._save(method, url, options)\n\t},\n\n\t/**\n\t * Private implementation of the save function (called after potential password confirmation)\n\t * @param {string} method\n\t * @param {string} url\n\t * @param {{success: Function, error: Function}} options\n\t */\n\tasync _save(method, url, options) {\n\t\ttry {\n\t\t\tconst response = await axios.request({\n\t\t\t\tconfirmPassword: PwdConfirmationMode.Strict,\n\t\t\t\tmethod,\n\t\t\t\turl,\n\t\t\t\tdata: this.getData(),\n\t\t\t})\n\t\t\tconst result = response.data\n\t\t\tthis.id = result.id\n\t\t\toptions.success(result)\n\t\t} catch (error) {\n\t\t\toptions.error(error)\n\t\t}\n\t},\n\n\t/**\n\t * Returns the data from this object\n\t *\n\t * @return {Array} JSON array of the data\n\t */\n\tgetData() {\n\t\tconst data = {\n\t\t\tmountPoint: this.mountPoint,\n\t\t\tbackend: this.backend,\n\t\t\tauthMechanism: this.authMechanism,\n\t\t\tbackendOptions: this.backendOptions,\n\t\t\ttestOnly: true,\n\t\t}\n\t\tif (this.id) {\n\t\t\tdata.id = this.id\n\t\t}\n\t\tif (this.mountOptions) {\n\t\t\tdata.mountOptions = this.mountOptions\n\t\t}\n\t\treturn data\n\t},\n\n\t/**\n\t * Recheck the storage\n\t *\n\t * @param {Function} [options.success] success callback, receives result as argument\n\t * @param {Function} [options.error] error callback\n\t * @param options\n\t */\n\trecheck(options) {\n\t\tif (!_.isNumber(this.id)) {\n\t\t\tif (_.isFunction(options.error)) {\n\t\t\t\toptions.error()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\t$.ajax({\n\t\t\ttype: 'GET',\n\t\t\turl: OC.generateUrl(this._url + '/{id}', { id: this.id }),\n\t\t\tdata: { testOnly: true },\n\t\t\tsuccess: options.success,\n\t\t\terror: options.error,\n\t\t})\n\t},\n\n\t/**\n\t * Deletes the storage\n\t *\n\t * @param {Function} [options.success] success callback\n\t * @param {Function} [options.error] error callback\n\t * @param options\n\t */\n\tasync destroy(options) {\n\t\tif (!_.isNumber(this.id)) {\n\t\t\t// the storage hasn't even been created => success\n\t\t\tif (_.isFunction(options.success)) {\n\t\t\t\toptions.success()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tawait axios.request({\n\t\t\t\tmethod: 'DELETE',\n\t\t\t\turl: OC.generateUrl(this._url + '/{id}', { id: this.id }),\n\t\t\t\tconfirmPassword: PwdConfirmationMode.Strict,\n\t\t\t})\n\t\t\toptions.success()\n\t\t} catch (e) {\n\t\t\toptions.error(e)\n\t\t}\n\t},\n\n\t/**\n\t * Validate this model\n\t *\n\t * @return {boolean} false if errors exist, true otherwise\n\t */\n\tvalidate() {\n\t\tif (this.mountPoint === '') {\n\t\t\treturn false\n\t\t}\n\t\tif (!this.backend) {\n\t\t\treturn false\n\t\t}\n\t\tif (this.errors) {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t},\n}\n\n/**\n * @param id\n * @class OCA.Files_External.Settings.GlobalStorageConfig\n * @augments OCA.Files_External.Settings.StorageConfig\n *\n * @classdesc Global external storage config\n */\nconst GlobalStorageConfig = function(id) {\n\tthis.id = id\n\tthis.applicableUsers = []\n\tthis.applicableGroups = []\n}\n/**\n * @memberOf OCA.Files_External.Settings\n */\nGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,\n\t/** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ {\n\t\t_url: 'apps/files_external/globalstorages',\n\n\t\t/**\n\t\t * Applicable users\n\t\t *\n\t\t * @type Array.<string>\n\t\t */\n\t\tapplicableUsers: null,\n\n\t\t/**\n\t\t * Applicable groups\n\t\t *\n\t\t * @type Array.<string>\n\t\t */\n\t\tapplicableGroups: null,\n\n\t\t/**\n\t\t * Storage priority\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tpriority: null,\n\n\t\t/**\n\t\t * Returns the data from this object\n\t\t *\n\t\t * @return {Array} JSON array of the data\n\t\t */\n\t\tgetData() {\n\t\t\tconst data = StorageConfig.prototype.getData.apply(this, arguments)\n\t\t\treturn _.extend(data, {\n\t\t\t\tapplicableUsers: this.applicableUsers,\n\t\t\t\tapplicableGroups: this.applicableGroups,\n\t\t\t\tpriority: this.priority,\n\t\t\t})\n\t\t},\n\t})\n\n/**\n * @param id\n * @class OCA.Files_External.Settings.UserStorageConfig\n * @augments OCA.Files_External.Settings.StorageConfig\n *\n * @classdesc User external storage config\n */\nconst UserStorageConfig = function(id) {\n\tthis.id = id\n}\nUserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,\n\t/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {\n\t\t_url: 'apps/files_external/userstorages',\n\t})\n\n/**\n * @param id\n * @class OCA.Files_External.Settings.UserGlobalStorageConfig\n * @augments OCA.Files_External.Settings.StorageConfig\n *\n * @classdesc User external storage config\n */\nconst UserGlobalStorageConfig = function(id) {\n\tthis.id = id\n}\nUserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,\n\t/** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ {\n\n\t\t_url: 'apps/files_external/userglobalstorages',\n\t})\n\n/**\n * @class OCA.Files_External.Settings.MountOptionsDropdown\n *\n * @classdesc Dropdown for mount options\n *\n * @param {object} $container container DOM object\n */\nconst MountOptionsDropdown = function() {\n}\n/**\n * @memberof OCA.Files_External.Settings\n */\nMountOptionsDropdown.prototype = {\n\t/**\n\t * Dropdown element\n\t *\n\t * @member Object\n\t */\n\t$el: null,\n\n\t/**\n\t * Show dropdown\n\t *\n\t * @param {object} $container container\n\t * @param {object} mountOptions mount options\n\t * @param {Array} visibleOptions enabled mount options\n\t */\n\tshow($container, mountOptions, visibleOptions) {\n\t\tif (MountOptionsDropdown._last) {\n\t\t\tMountOptionsDropdown._last.hide()\n\t\t}\n\n\t\tconst $el = $(OCA.Files_External.Templates.mountOptionsDropDown({\n\t\t\tmountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'),\n\t\t\tmountOptionsEncryptLabel: t('files_external', 'Enable encryption'),\n\t\t\tmountOptionsPreviewsLabel: t('files_external', 'Enable previews'),\n\t\t\tmountOptionsSharingLabel: t('files_external', 'Enable sharing'),\n\t\t\tmountOptionsFilesystemCheckLabel: t('files_external', 'Check for changes'),\n\t\t\tmountOptionsFilesystemCheckOnce: t('files_external', 'Never'),\n\t\t\tmountOptionsFilesystemCheckDA: t('files_external', 'Once every direct access'),\n\t\t\tmountOptionsReadOnlyLabel: t('files_external', 'Read only'),\n\t\t\tdeleteLabel: t('files_external', 'Disconnect'),\n\t\t}))\n\t\tthis.$el = $el\n\n\t\tconst storage = $container[0].parentNode.className\n\n\t\tthis.setOptions(mountOptions, visibleOptions, storage)\n\n\t\tthis.$el.appendTo($container)\n\t\tMountOptionsDropdown._last = this\n\n\t\tthis.$el.trigger('show')\n\t},\n\n\thide() {\n\t\tif (this.$el) {\n\t\t\tthis.$el.trigger('hide')\n\t\t\tthis.$el.remove()\n\t\t\tthis.$el = null\n\t\t\tMountOptionsDropdown._last = null\n\t\t}\n\t},\n\n\t/**\n\t * Returns the mount options from the dropdown controls\n\t *\n\t * @return {object} options mount options\n\t */\n\tgetOptions() {\n\t\tconst options = {}\n\n\t\tthis.$el.find('input, select').each(function() {\n\t\t\tconst $this = $(this)\n\t\t\tconst key = $this.attr('name')\n\t\t\tlet value = null\n\t\t\tif ($this.attr('type') === 'checkbox') {\n\t\t\t\tvalue = $this.prop('checked')\n\t\t\t} else {\n\t\t\t\tvalue = $this.val()\n\t\t\t}\n\t\t\tif ($this.attr('data-type') === 'int') {\n\t\t\t\tvalue = parseInt(value, 10)\n\t\t\t}\n\t\t\toptions[key] = value\n\t\t})\n\t\treturn options\n\t},\n\n\t/**\n\t * Sets the mount options to the dropdown controls\n\t *\n\t * @param {object} options mount options\n\t * @param {Array} visibleOptions enabled mount options\n\t * @param storage\n\t */\n\tsetOptions(options, visibleOptions, storage) {\n\t\tif (storage === 'owncloud') {\n\t\t\tconst ind = visibleOptions.indexOf('encrypt')\n\t\t\tif (ind > 0) {\n\t\t\t\tvisibleOptions.splice(ind, 1)\n\t\t\t}\n\t\t}\n\t\tconst $el = this.$el\n\t\t_.each(options, function(value, key) {\n\t\t\tconst $optionEl = $el.find('input, select').filterAttr('name', key)\n\t\t\tif ($optionEl.attr('type') === 'checkbox') {\n\t\t\t\tif (_.isString(value)) {\n\t\t\t\t\tvalue = (value === 'true')\n\t\t\t\t}\n\t\t\t\t$optionEl.prop('checked', !!value)\n\t\t\t} else {\n\t\t\t\t$optionEl.val(value)\n\t\t\t}\n\t\t})\n\t\t$el.find('.optionRow').each(function(i, row) {\n\t\t\tconst $row = $(row)\n\t\t\tconst optionId = $row.find('input, select').attr('name')\n\t\t\tif (visibleOptions.indexOf(optionId) === -1 && !$row.hasClass('persistent')) {\n\t\t\t\t$row.hide()\n\t\t\t} else {\n\t\t\t\t$row.show()\n\t\t\t}\n\t\t})\n\t},\n}\n\n/**\n * @class OCA.Files_External.Settings.MountConfigListView\n *\n * @classdesc Mount configuration list view\n *\n * @param {object} $el DOM object containing the list\n * @param {object} [options]\n * @param {number} [options.userListLimit] page size in applicable users dropdown\n */\nconst MountConfigListView = function($el, options) {\n\tthis.initialize($el, options)\n}\n\nMountConfigListView.ParameterFlags = {\n\tOPTIONAL: 1,\n\tUSER_PROVIDED: 2,\n}\n\nMountConfigListView.ParameterTypes = {\n\tTEXT: 0,\n\tBOOLEAN: 1,\n\tPASSWORD: 2,\n\tHIDDEN: 3,\n}\n\n/**\n * @memberOf OCA.Files_External.Settings\n */\nMountConfigListView.prototype = _.extend({\n\n\t/**\n\t * jQuery element containing the config list\n\t *\n\t * @type Object\n\t */\n\t$el: null,\n\n\t/**\n\t * Storage config class\n\t *\n\t * @type Class\n\t */\n\t_storageConfigClass: null,\n\n\t/**\n\t * Flag whether the list is about user storage configs (true)\n\t * or global storage configs (false)\n\t *\n\t * @type bool\n\t */\n\t_isPersonal: false,\n\n\t/**\n\t * Page size in applicable users dropdown\n\t *\n\t * @type int\n\t */\n\t_userListLimit: 30,\n\n\t/**\n\t * List of supported backends\n\t *\n\t * @type Object.<string,Object>\n\t */\n\t_allBackends: null,\n\n\t/**\n\t * List of all supported authentication mechanisms\n\t *\n\t * @type Object.<string,Object>\n\t */\n\t_allAuthMechanisms: null,\n\n\t_encryptionEnabled: false,\n\n\t/**\n\t * @param {object} $el DOM object containing the list\n\t * @param {object} [options]\n\t * @param {number} [options.userListLimit] page size in applicable users dropdown\n\t */\n\tinitialize($el, options) {\n\t\tthis.$el = $el\n\t\tthis._isPersonal = ($el.data('admin') !== true)\n\t\tif (this._isPersonal) {\n\t\t\tthis._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig\n\t\t} else {\n\t\t\tthis._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig\n\t\t}\n\n\t\tif (options && !_.isUndefined(options.userListLimit)) {\n\t\t\tthis._userListLimit = options.userListLimit\n\t\t}\n\n\t\tthis._encryptionEnabled = options.encryptionEnabled\n\t\tthis._canCreateLocal = options.canCreateLocal\n\n\t\t// read the backend config that was carefully crammed\n\t\t// into the data-configurations attribute of the select\n\t\tthis._allBackends = this.$el.find('.selectBackend').data('configurations')\n\t\tthis._allAuthMechanisms = this.$el.find('#addMountPoint .authentication').data('mechanisms')\n\n\t\tthis._initEvents()\n\t},\n\n\t/**\n\t * Custom JS event handlers\n\t * Trigger callback for all existing configurations\n\t * @param callback\n\t */\n\twhenSelectBackend(callback) {\n\t\tthis.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {\n\t\t\tconst backend = $(tr).find('.backend').data('identifier')\n\t\t\tcallback($(tr), backend)\n\t\t})\n\t\tthis.on('selectBackend', callback)\n\t},\n\twhenSelectAuthMechanism(callback) {\n\t\tconst self = this\n\t\tthis.$el.find('tbody tr:not(#addMountPoint):not(.externalStorageLoading)').each(function(i, tr) {\n\t\t\tconst authMechanism = $(tr).find('.selectAuthMechanism').val()\n\t\t\tcallback($(tr), authMechanism, self._allAuthMechanisms[authMechanism].scheme)\n\t\t})\n\t\tthis.on('selectAuthMechanism', callback)\n\t},\n\n\t/**\n\t * Initialize DOM event handlers\n\t */\n\t_initEvents() {\n\t\tconst self = this\n\n\t\tconst onChangeHandler = _.bind(this._onChange, this)\n\t\t// this.$el.on('input', 'td input', onChangeHandler);\n\t\tthis.$el.on('keyup', 'td input', onChangeHandler)\n\t\tthis.$el.on('paste', 'td input', onChangeHandler)\n\t\tthis.$el.on('change', 'td input:checkbox', onChangeHandler)\n\t\tthis.$el.on('change', '.applicable', onChangeHandler)\n\n\t\tthis.$el.on('click', '.status>span', function() {\n\t\t\tself.recheckStorageConfig($(this).closest('tr'))\n\t\t})\n\n\t\tthis.$el.on('click', 'td.mountOptionsToggle .icon-delete', function() {\n\t\t\tself.deleteStorageConfig($(this).closest('tr'))\n\t\t})\n\n\t\tthis.$el.on('click', 'td.save>.icon-checkmark', function() {\n\t\t\tself.saveStorageConfig($(this).closest('tr'))\n\t\t})\n\n\t\tthis.$el.on('click', 'td.mountOptionsToggle>.icon-more', function() {\n\t\t\t$(this).attr('aria-expanded', 'true')\n\t\t\tself._showMountOptionsDropdown($(this).closest('tr'))\n\t\t})\n\n\t\tthis.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this))\n\t\tthis.$el.on('change', '.selectAuthMechanism', _.bind(this._onSelectAuthMechanism, this))\n\n\t\tthis.$el.on('change', '.applicableToAllUsers', _.bind(this._onChangeApplicableToAllUsers, this))\n\t},\n\n\t_onChange(event) {\n\t\tconst $target = $(event.target)\n\t\tif ($target.closest('.dropdown').length) {\n\t\t\t// ignore dropdown events\n\t\t\treturn\n\t\t}\n\t\thighlightInput($target)\n\t\tconst $tr = $target.closest('tr')\n\t\tthis.updateStatus($tr, null)\n\t},\n\n\t_onSelectBackend(event) {\n\t\tconst $target = $(event.target)\n\t\tlet $tr = $target.closest('tr')\n\n\t\tconst storageConfig = new this._storageConfigClass()\n\t\tstorageConfig.mountPoint = $tr.find('.mountPoint input').val()\n\t\tstorageConfig.backend = $target.val()\n\t\t$tr.find('.mountPoint input').val('')\n\n\t\t$tr.find('.selectBackend').prop('selectedIndex', 0)\n\n\t\tconst onCompletion = jQuery.Deferred()\n\t\t$tr = this.newStorage(storageConfig, onCompletion)\n\t\t$tr.find('.applicableToAllUsers').prop('checked', false).trigger('change')\n\t\tonCompletion.resolve()\n\n\t\t$tr.find('td.configuration').children().not('[type=hidden]').first().focus()\n\t\tthis.saveStorageConfig($tr)\n\t},\n\n\t_onSelectAuthMechanism(event) {\n\t\tconst $target = $(event.target)\n\t\tconst $tr = $target.closest('tr')\n\t\tconst authMechanism = $target.val()\n\n\t\tconst onCompletion = jQuery.Deferred()\n\t\tthis.configureAuthMechanism($tr, authMechanism, onCompletion)\n\t\tonCompletion.resolve()\n\n\t\tthis.saveStorageConfig($tr)\n\t},\n\n\t_onChangeApplicableToAllUsers(event) {\n\t\tconst $target = $(event.target)\n\t\tconst $tr = $target.closest('tr')\n\t\tconst checked = $target.is(':checked')\n\n\t\t$tr.find('.applicableUsersContainer').toggleClass('hidden', checked)\n\t\tif (!checked) {\n\t\t\t$tr.find('.applicableUsers').select2('val', '', true)\n\t\t}\n\n\t\tthis.saveStorageConfig($tr)\n\t},\n\n\t/**\n\t * Configure the storage config with a new authentication mechanism\n\t *\n\t * @param {jQuery} $tr config row\n\t * @param {string} authMechanism\n\t * @param {jQuery.Deferred} onCompletion\n\t */\n\tconfigureAuthMechanism($tr, authMechanism, onCompletion) {\n\t\tconst authMechanismConfiguration = this._allAuthMechanisms[authMechanism]\n\t\tconst $td = $tr.find('td.configuration')\n\t\t$td.find('.auth-param').remove()\n\n\t\t$.each(authMechanismConfiguration.configuration, _.partial(\n\t\t\tthis.writeParameterInput, $td, _, _, ['auth-param'],\n\t\t).bind(this))\n\n\t\tthis.trigger('selectAuthMechanism',\n\t\t\t$tr, authMechanism, authMechanismConfiguration.scheme, onCompletion,\n\t\t)\n\t},\n\n\t/**\n\t * Create a config row for a new storage\n\t *\n\t * @param {StorageConfig} storageConfig storage config to pull values from\n\t * @param {jQuery.Deferred} onCompletion\n\t * @param {boolean} deferAppend\n\t * @return {jQuery} created row\n\t */\n\tnewStorage(storageConfig, onCompletion, deferAppend) {\n\t\tlet mountPoint = storageConfig.mountPoint\n\t\tlet backend = this._allBackends[storageConfig.backend]\n\n\t\tif (!backend) {\n\t\t\tbackend = {\n\t\t\t\tname: 'Unknown: ' + storageConfig.backend,\n\t\t\t\tinvalid: true,\n\t\t\t}\n\t\t}\n\n\t\t// FIXME: Replace with a proper Handlebar template\n\t\tconst $template = this.$el.find('tr#addMountPoint')\n\t\tconst $tr = $template.clone()\n\t\tif (!deferAppend) {\n\t\t\t$tr.insertBefore($template)\n\t\t}\n\n\t\t$tr.data('storageConfig', storageConfig)\n\t\t$tr.show()\n\t\t$tr.find('td.mountOptionsToggle, td.save, td.remove').removeClass('hidden')\n\t\t$tr.find('td').last().removeAttr('style')\n\t\t$tr.removeAttr('id')\n\t\t$tr.find('select#selectBackend')\n\t\tif (!deferAppend) {\n\t\t\tinitApplicableUsersMultiselect($tr.find('.applicableUsers'), this._userListLimit)\n\t\t}\n\n\t\tif (storageConfig.id) {\n\t\t\t$tr.data('id', storageConfig.id)\n\t\t}\n\n\t\t$tr.find('.backend').text(backend.name)\n\t\tif (mountPoint === '') {\n\t\t\tmountPoint = this._suggestMountPoint(backend.name)\n\t\t}\n\t\t$tr.find('.mountPoint input').val(mountPoint)\n\t\t$tr.addClass(backend.identifier)\n\t\t$tr.find('.backend').data('identifier', backend.identifier)\n\n\t\tif (backend.invalid || (backend.identifier === 'local' && !this._canCreateLocal)) {\n\t\t\t$tr.find('[name=mountPoint]').prop('disabled', true)\n\t\t\t$tr.find('.applicable,.mountOptionsToggle').empty()\n\t\t\t$tr.find('.save').empty()\n\t\t\tif (backend.invalid) {\n\t\t\t\tthis.updateStatus($tr, false, t('files_external', 'Unknown backend: {backendName}', { backendName: backend.name }))\n\t\t\t}\n\t\t\treturn $tr\n\t\t}\n\n\t\tconst selectAuthMechanism = $('<select class=\"selectAuthMechanism\"></select>')\n\t\tconst neededVisibility = (this._isPersonal) ? StorageConfig.Visibility.PERSONAL : StorageConfig.Visibility.ADMIN\n\t\t$.each(this._allAuthMechanisms, function(authIdentifier, authMechanism) {\n\t\t\tif (backend.authSchemes[authMechanism.scheme] && (authMechanism.visibility & neededVisibility)) {\n\t\t\t\tselectAuthMechanism.append(\n\t\t\t\t\t$('<option value=\"' + authMechanism.identifier + '\" data-scheme=\"' + authMechanism.scheme + '\">' + authMechanism.name + '</option>'),\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t\tif (storageConfig.authMechanism) {\n\t\t\tselectAuthMechanism.val(storageConfig.authMechanism)\n\t\t} else {\n\t\t\tstorageConfig.authMechanism = selectAuthMechanism.val()\n\t\t}\n\t\t$tr.find('td.authentication').append(selectAuthMechanism)\n\n\t\tconst $td = $tr.find('td.configuration')\n\t\t$.each(backend.configuration, _.partial(this.writeParameterInput, $td).bind(this))\n\n\t\tthis.trigger('selectBackend', $tr, backend.identifier, onCompletion)\n\t\tthis.configureAuthMechanism($tr, storageConfig.authMechanism, onCompletion)\n\n\t\tif (storageConfig.backendOptions) {\n\t\t\t$td.find('input, select').each(function() {\n\t\t\t\tconst input = $(this)\n\t\t\t\tconst val = storageConfig.backendOptions[input.data('parameter')]\n\t\t\t\tif (val !== undefined) {\n\t\t\t\t\tif (input.is('input:checkbox')) {\n\t\t\t\t\t\tinput.prop('checked', val)\n\t\t\t\t\t}\n\t\t\t\t\tinput.val(storageConfig.backendOptions[input.data('parameter')])\n\t\t\t\t\thighlightInput(input)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tlet applicable = []\n\t\tif (storageConfig.applicableUsers) {\n\t\t\tapplicable = applicable.concat(storageConfig.applicableUsers)\n\t\t}\n\t\tif (storageConfig.applicableGroups) {\n\t\t\tapplicable = applicable.concat(\n\t\t\t\t_.map(storageConfig.applicableGroups, function(group) {\n\t\t\t\t\treturn group + '(group)'\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\tif (applicable.length) {\n\t\t\t$tr.find('.applicableUsers').val(applicable).trigger('change')\n\t\t\t$tr.find('.applicableUsersContainer').removeClass('hidden')\n\t\t} else {\n\t\t\t// applicable to all\n\t\t\t$tr.find('.applicableUsersContainer').addClass('hidden')\n\t\t}\n\t\t$tr.find('.applicableToAllUsers').prop('checked', !applicable.length)\n\n\t\tconst priorityEl = $('<input type=\"hidden\" class=\"priority\" value=\"' + backend.priority + '\" />')\n\t\t$tr.append(priorityEl)\n\n\t\tif (storageConfig.mountOptions) {\n\t\t\t$tr.find('input.mountOptions').val(JSON.stringify(storageConfig.mountOptions))\n\t\t} else {\n\t\t\t// FIXME default backend mount options\n\t\t\t$tr.find('input.mountOptions').val(JSON.stringify({\n\t\t\t\tencrypt: true,\n\t\t\t\tpreviews: true,\n\t\t\t\tenable_sharing: false,\n\t\t\t\tfilesystem_check_changes: 1,\n\t\t\t\tencoding_compatibility: false,\n\t\t\t\treadonly: false,\n\t\t\t}))\n\t\t}\n\n\t\treturn $tr\n\t},\n\n\t/**\n\t * Load storages into config rows\n\t */\n\tloadStorages() {\n\t\tconst self = this\n\n\t\tconst onLoaded1 = $.Deferred()\n\t\tconst onLoaded2 = $.Deferred()\n\n\t\tthis.$el.find('.externalStorageLoading').removeClass('hidden')\n\t\t$.when(onLoaded1, onLoaded2).always(() => {\n\t\t\tself.$el.find('.externalStorageLoading').addClass('hidden')\n\t\t})\n\n\t\tif (this._isPersonal) {\n\t\t\t// load userglobal storages\n\t\t\t$.ajax({\n\t\t\t\ttype: 'GET',\n\t\t\t\turl: OC.generateUrl('apps/files_external/userglobalstorages'),\n\t\t\t\tdata: { testOnly: true },\n\t\t\t\tcontentType: 'application/json',\n\t\t\t\tsuccess(result) {\n\t\t\t\t\tresult = Object.values(result)\n\t\t\t\t\tconst onCompletion = jQuery.Deferred()\n\t\t\t\t\tlet $rows = $()\n\t\t\t\t\tresult.forEach(function(storageParams) {\n\t\t\t\t\t\tlet storageConfig\n\t\t\t\t\t\tconst isUserGlobal = storageParams.type === 'system' && self._isPersonal\n\t\t\t\t\t\tstorageParams.mountPoint = storageParams.mountPoint.substr(1) // trim leading slash\n\t\t\t\t\t\tif (isUserGlobal) {\n\t\t\t\t\t\t\tstorageConfig = new UserGlobalStorageConfig()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstorageConfig = new self._storageConfigClass()\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_.extend(storageConfig, storageParams)\n\t\t\t\t\t\tconst $tr = self.newStorage(storageConfig, onCompletion, true)\n\n\t\t\t\t\t\t// userglobal storages must be at the top of the list\n\t\t\t\t\t\t$tr.detach()\n\t\t\t\t\t\tself.$el.prepend($tr)\n\n\t\t\t\t\t\tconst $authentication = $tr.find('.authentication')\n\t\t\t\t\t\t$authentication.text($authentication.find('select option:selected').text())\n\n\t\t\t\t\t\t// disable any other inputs\n\t\t\t\t\t\t$tr.find('.mountOptionsToggle, .remove').empty()\n\t\t\t\t\t\t$tr.find('input:not(.user_provided), select:not(.user_provided)').attr('disabled', 'disabled')\n\n\t\t\t\t\t\tif (isUserGlobal) {\n\t\t\t\t\t\t\t$tr.find('.configuration').find(':not(.user_provided)').remove()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// userglobal storages do not expose configuration data\n\t\t\t\t\t\t\t$tr.find('.configuration').text(t('files_external', 'Admin defined'))\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// don't recheck config automatically when there are a large number of storages\n\t\t\t\t\t\tif (result.length < 20) {\n\t\t\t\t\t\t\tself.recheckStorageConfig($tr)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tself.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'))\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$rows = $rows.add($tr)\n\t\t\t\t\t})\n\t\t\t\t\tinitApplicableUsersMultiselect(self.$el.find('.applicableUsers'), this._userListLimit)\n\t\t\t\t\tself.$el.find('tr#addMountPoint').before($rows)\n\t\t\t\t\tconst mainForm = $('#files_external')\n\t\t\t\t\tif (result.length === 0 && mainForm.attr('data-can-create') === 'false') {\n\t\t\t\t\t\tmainForm.hide()\n\t\t\t\t\t\t$('a[href=\"#external-storage\"]').parent().hide()\n\t\t\t\t\t\t$('.emptycontent').show()\n\t\t\t\t\t}\n\t\t\t\t\tonCompletion.resolve()\n\t\t\t\t\tonLoaded1.resolve()\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\tonLoaded1.resolve()\n\t\t}\n\n\t\tconst url = this._storageConfigClass.prototype._url\n\n\t\t$.ajax({\n\t\t\ttype: 'GET',\n\t\t\turl: OC.generateUrl(url),\n\t\t\tcontentType: 'application/json',\n\t\t\tsuccess(result) {\n\t\t\t\tresult = Object.values(result)\n\t\t\t\tconst onCompletion = jQuery.Deferred()\n\t\t\t\tlet $rows = $()\n\t\t\t\tresult.forEach(function(storageParams) {\n\t\t\t\t\tstorageParams.mountPoint = (storageParams.mountPoint === '/') ? '/' : storageParams.mountPoint.substr(1) // trim leading slash\n\t\t\t\t\tconst storageConfig = new self._storageConfigClass()\n\t\t\t\t\t_.extend(storageConfig, storageParams)\n\t\t\t\t\tconst $tr = self.newStorage(storageConfig, onCompletion, true)\n\n\t\t\t\t\t// don't recheck config automatically when there are a large number of storages\n\t\t\t\t\tif (result.length < 20) {\n\t\t\t\t\t\tself.recheckStorageConfig($tr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'))\n\t\t\t\t\t}\n\t\t\t\t\t$rows = $rows.add($tr)\n\t\t\t\t})\n\t\t\t\tinitApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit)\n\t\t\t\tself.$el.find('tr#addMountPoint').before($rows)\n\t\t\t\tonCompletion.resolve()\n\t\t\t\tonLoaded2.resolve()\n\t\t\t},\n\t\t})\n\t},\n\n\t/**\n\t * @param {jQuery} $td\n\t * @param {string} parameter\n\t * @param {string} placeholder\n\t * @param {Array} classes\n\t * @return {jQuery} newly created input\n\t */\n\twriteParameterInput($td, parameter, placeholder, classes) {\n\t\tconst hasFlag = function(flag) {\n\t\t\treturn (placeholder.flags & flag) === flag\n\t\t}\n\t\tclasses = $.isArray(classes) ? classes : []\n\t\tclasses.push('added')\n\t\tif (hasFlag(MountConfigListView.ParameterFlags.OPTIONAL)) {\n\t\t\tclasses.push('optional')\n\t\t}\n\n\t\tif (hasFlag(MountConfigListView.ParameterFlags.USER_PROVIDED)) {\n\t\t\tif (this._isPersonal) {\n\t\t\t\tclasses.push('user_provided')\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tlet newElement\n\n\t\tconst trimmedPlaceholder = placeholder.value\n\t\tif (placeholder.type === MountConfigListView.ParameterTypes.PASSWORD) {\n\t\t\tnewElement = $('<input type=\"password\" class=\"' + classes.join(' ') + '\" data-parameter=\"' + parameter + '\" placeholder=\"' + trimmedPlaceholder + '\" />')\n\t\t} else if (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {\n\t\t\tconst checkboxId = _.uniqueId('checkbox_')\n\t\t\tnewElement = $('<div><label><input type=\"checkbox\" id=\"' + checkboxId + '\" class=\"' + classes.join(' ') + '\" data-parameter=\"' + parameter + '\" />' + trimmedPlaceholder + '</label></div>')\n\t\t} else if (placeholder.type === MountConfigListView.ParameterTypes.HIDDEN) {\n\t\t\tnewElement = $('<input type=\"hidden\" class=\"' + classes.join(' ') + '\" data-parameter=\"' + parameter + '\" />')\n\t\t} else {\n\t\t\tnewElement = $('<input type=\"text\" class=\"' + classes.join(' ') + '\" data-parameter=\"' + parameter + '\" placeholder=\"' + trimmedPlaceholder + '\" />')\n\t\t}\n\n\t\tif (placeholder.defaultValue) {\n\t\t\tif (placeholder.type === MountConfigListView.ParameterTypes.BOOLEAN) {\n\t\t\t\tnewElement.find('input').prop('checked', placeholder.defaultValue)\n\t\t\t} else {\n\t\t\t\tnewElement.val(placeholder.defaultValue)\n\t\t\t}\n\t\t}\n\n\t\tif (placeholder.tooltip) {\n\t\t\tnewElement.attr('title', placeholder.tooltip)\n\t\t}\n\n\t\thighlightInput(newElement)\n\t\t$td.append(newElement)\n\t\treturn newElement\n\t},\n\n\t/**\n\t * Gets the storage model from the given row\n\t *\n\t * @param $tr row element\n\t * @return {OCA.Files_External.StorageConfig} storage model instance\n\t */\n\tgetStorageConfig($tr) {\n\t\tlet storageId = $tr.data('id')\n\t\tif (!storageId) {\n\t\t\t// new entry\n\t\t\tstorageId = null\n\t\t}\n\n\t\tlet storage = $tr.data('storageConfig')\n\t\tif (!storage) {\n\t\t\tstorage = new this._storageConfigClass(storageId)\n\t\t}\n\t\tstorage.errors = null\n\t\tstorage.mountPoint = $tr.find('.mountPoint input').val()\n\t\tstorage.backend = $tr.find('.backend').data('identifier')\n\t\tstorage.authMechanism = $tr.find('.selectAuthMechanism').val()\n\n\t\tconst classOptions = {}\n\t\tconst configuration = $tr.find('.configuration input')\n\t\tconst missingOptions = []\n\t\t$.each(configuration, function(index, input) {\n\t\t\tconst $input = $(input)\n\t\t\tconst parameter = $input.data('parameter')\n\t\t\tif ($input.attr('type') === 'button') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!isInputValid($input) && !$input.hasClass('optional')) {\n\t\t\t\tmissingOptions.push(parameter)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif ($(input).is(':checkbox')) {\n\t\t\t\tif ($(input).is(':checked')) {\n\t\t\t\t\tclassOptions[parameter] = true\n\t\t\t\t} else {\n\t\t\t\t\tclassOptions[parameter] = false\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclassOptions[parameter] = $(input).val()\n\t\t\t}\n\t\t})\n\n\t\tstorage.backendOptions = classOptions\n\t\tif (missingOptions.length) {\n\t\t\tstorage.errors = {\n\t\t\t\tbackendOptions: missingOptions,\n\t\t\t}\n\t\t}\n\n\t\t// gather selected users and groups\n\t\tif (!this._isPersonal) {\n\t\t\tconst multiselect = getSelectedApplicable($tr)\n\t\t\tconst users = multiselect.users || []\n\t\t\tconst groups = multiselect.groups || []\n\t\t\tconst isApplicableToAllUsers = $tr.find('.applicableToAllUsers').is(':checked')\n\n\t\t\tif (isApplicableToAllUsers) {\n\t\t\t\tstorage.applicableUsers = []\n\t\t\t\tstorage.applicableGroups = []\n\t\t\t} else {\n\t\t\t\tstorage.applicableUsers = users\n\t\t\t\tstorage.applicableGroups = groups\n\n\t\t\t\tif (!storage.applicableUsers.length && !storage.applicableGroups.length) {\n\t\t\t\t\tif (!storage.errors) {\n\t\t\t\t\t\tstorage.errors = {}\n\t\t\t\t\t}\n\t\t\t\t\tstorage.errors.requiredApplicable = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstorage.priority = parseInt($tr.find('input.priority').val() || '100', 10)\n\t\t}\n\n\t\tconst mountOptions = $tr.find('input.mountOptions').val()\n\t\tif (mountOptions) {\n\t\t\tstorage.mountOptions = JSON.parse(mountOptions)\n\t\t}\n\n\t\treturn storage\n\t},\n\n\t/**\n\t * Deletes the storage from the given tr\n\t *\n\t * @param $tr storage row\n\t * @param Function callback callback to call after save\n\t */\n\tdeleteStorageConfig($tr) {\n\t\tconst self = this\n\t\tconst configId = $tr.data('id')\n\t\tif (!_.isNumber(configId)) {\n\t\t\t// deleting unsaved storage\n\t\t\t$tr.remove()\n\t\t\treturn\n\t\t}\n\t\tconst storage = new this._storageConfigClass(configId)\n\n\t\tOC.dialogs.confirm(t('files_external', 'Are you sure you want to disconnect this external storage? It will make the storage unavailable in Nextcloud and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself.', {\n\t\t\tstorage: this.mountPoint,\n\t\t}), t('files_external', 'Delete storage?'), function(confirm) {\n\t\t\tif (confirm) {\n\t\t\t\tself.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)\n\n\t\t\t\tstorage.destroy({\n\t\t\t\t\tsuccess() {\n\t\t\t\t\t\t$tr.remove()\n\t\t\t\t\t},\n\t\t\t\t\terror(result) {\n\t\t\t\t\t\tconst statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined\n\t\t\t\t\t\tself.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t},\n\n\t/**\n\t * Saves the storage from the given tr\n\t *\n\t * @param $tr storage row\n\t * @param Function callback callback to call after save\n\t * @param callback\n\t * @param concurrentTimer only update if the timer matches this\n\t */\n\tsaveStorageConfig($tr, callback, concurrentTimer) {\n\t\tconst self = this\n\t\tconst storage = this.getStorageConfig($tr)\n\t\tif (!storage || !storage.validate()) {\n\t\t\treturn false\n\t\t}\n\n\t\tthis.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)\n\t\tstorage.save({\n\t\t\tsuccess(result) {\n\t\t\t\tif (concurrentTimer === undefined\n\t\t\t\t\t|| $tr.data('save-timer') === concurrentTimer\n\t\t\t\t) {\n\t\t\t\t\tself.updateStatus($tr, result.status, result.statusMessage)\n\t\t\t\t\t$tr.data('id', result.id)\n\n\t\t\t\t\tif (_.isFunction(callback)) {\n\t\t\t\t\t\tcallback(storage)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\terror(result) {\n\t\t\t\tif (concurrentTimer === undefined\n\t\t\t\t\t|| $tr.data('save-timer') === concurrentTimer\n\t\t\t\t) {\n\t\t\t\t\tconst statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined\n\t\t\t\t\tself.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t},\n\n\t/**\n\t * Recheck storage availability\n\t *\n\t * @param {jQuery} $tr storage row\n\t * @return {boolean} success\n\t */\n\trecheckStorageConfig($tr) {\n\t\tconst self = this\n\t\tconst storage = this.getStorageConfig($tr)\n\t\tif (!storage.validate()) {\n\t\t\treturn false\n\t\t}\n\n\t\tthis.updateStatus($tr, StorageConfig.Status.IN_PROGRESS)\n\t\tstorage.recheck({\n\t\t\tsuccess(result) {\n\t\t\t\tself.updateStatus($tr, result.status, result.statusMessage)\n\t\t\t},\n\t\t\terror(result) {\n\t\t\t\tconst statusMessage = (result && result.responseJSON) ? result.responseJSON.message : undefined\n\t\t\t\tself.updateStatus($tr, StorageConfig.Status.ERROR, statusMessage)\n\t\t\t},\n\t\t})\n\t},\n\n\t/**\n\t * Update status display\n\t *\n\t * @param {jQuery} $tr\n\t * @param {number} status\n\t * @param {string} message\n\t */\n\tupdateStatus($tr, status, message) {\n\t\tconst $statusSpan = $tr.find('.status span')\n\t\tswitch (status) {\n\t\tcase null:\n\t\t\t// remove status\n\t\t\t$statusSpan.hide()\n\t\t\tbreak\n\t\tcase StorageConfig.Status.IN_PROGRESS:\n\t\t\t$statusSpan.attr('class', 'icon-loading-small')\n\t\t\tbreak\n\t\tcase StorageConfig.Status.SUCCESS:\n\t\t\t$statusSpan.attr('class', 'success icon-checkmark-white')\n\t\t\tbreak\n\t\tcase StorageConfig.Status.INDETERMINATE:\n\t\t\t$statusSpan.attr('class', 'indeterminate icon-info-white')\n\t\t\tbreak\n\t\tdefault:\n\t\t\t$statusSpan.attr('class', 'error icon-error-white')\n\t\t}\n\t\tif (status !== null) {\n\t\t\t$statusSpan.show()\n\t\t}\n\t\tif (typeof message !== 'string') {\n\t\t\tmessage = t('files_external', 'Click to recheck the configuration')\n\t\t}\n\t\t$statusSpan.attr('title', message)\n\t},\n\n\t/**\n\t * Suggest mount point name that doesn't conflict with the existing names in the list\n\t *\n\t * @param {string} defaultMountPoint default name\n\t */\n\t_suggestMountPoint(defaultMountPoint) {\n\t\tconst $el = this.$el\n\t\tconst pos = defaultMountPoint.indexOf('/')\n\t\tif (pos !== -1) {\n\t\t\tdefaultMountPoint = defaultMountPoint.substring(0, pos)\n\t\t}\n\t\tdefaultMountPoint = defaultMountPoint.replace(/\\s+/g, '')\n\t\tlet i = 1\n\t\tlet append = ''\n\t\tlet match = true\n\t\twhile (match && i < 20) {\n\t\t\tmatch = false\n\t\t\t$el.find('tbody td.mountPoint input').each(function(index, mountPoint) {\n\t\t\t\tif ($(mountPoint).val() === defaultMountPoint + append) {\n\t\t\t\t\tmatch = true\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t})\n\t\t\tif (match) {\n\t\t\t\tappend = i\n\t\t\t\ti++\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn defaultMountPoint + append\n\t},\n\n\t/**\n\t * Toggles the mount options dropdown\n\t *\n\t * @param {object} $tr configuration row\n\t */\n\t_showMountOptionsDropdown($tr) {\n\t\tconst self = this\n\t\tconst storage = this.getStorageConfig($tr)\n\t\tconst $toggle = $tr.find('.mountOptionsToggle')\n\t\tconst dropDown = new MountOptionsDropdown()\n\t\tconst visibleOptions = [\n\t\t\t'previews',\n\t\t\t'filesystem_check_changes',\n\t\t\t'enable_sharing',\n\t\t\t'encoding_compatibility',\n\t\t\t'readonly',\n\t\t\t'delete',\n\t\t]\n\t\tif (this._encryptionEnabled) {\n\t\t\tvisibleOptions.push('encrypt')\n\t\t}\n\t\tdropDown.show($toggle, storage.mountOptions || [], visibleOptions)\n\t\t$('body').on('mouseup.mountOptionsDropdown', function(event) {\n\t\t\tconst $target = $(event.target)\n\t\t\tif ($target.closest('.popovermenu').length) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdropDown.hide()\n\t\t})\n\n\t\tdropDown.$el.on('hide', function() {\n\t\t\tconst mountOptions = dropDown.getOptions()\n\t\t\t$('body').off('mouseup.mountOptionsDropdown')\n\t\t\t$tr.find('input.mountOptions').val(JSON.stringify(mountOptions))\n\t\t\t$tr.find('td.mountOptionsToggle>.icon-more').attr('aria-expanded', 'false')\n\t\t\tself.saveStorageConfig($tr)\n\t\t})\n\t},\n}, OC.Backbone.Events)\n\nwindow.addEventListener('DOMContentLoaded', function() {\n\tconst enabled = $('#files_external').attr('data-encryption-enabled')\n\tconst canCreateLocal = $('#files_external').attr('data-can-create-local')\n\tconst encryptionEnabled = (enabled === 'true')\n\tconst mountConfigListView = new MountConfigListView($('#externalStorage'), {\n\t\tencryptionEnabled,\n\t\tcanCreateLocal: (canCreateLocal === 'true'),\n\t})\n\tmountConfigListView.loadStorages()\n\n\t// TODO: move this into its own View class\n\tconst $allowUserMounting = $('#allowUserMounting')\n\t$allowUserMounting.bind('change', function() {\n\t\tOC.msg.startSaving('#userMountingMsg')\n\t\tif (this.checked) {\n\t\t\tOCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'yes')\n\t\t\t$('input[name=\"allowUserMountingBackends\\\\[\\\\]\"]').prop('checked', true)\n\t\t\t$('#userMountingBackends').removeClass('hidden')\n\t\t\t$('input[name=\"allowUserMountingBackends\\\\[\\\\]\"]').eq(0).trigger('change')\n\t\t} else {\n\t\t\tOCP.AppConfig.setValue('files_external', 'allow_user_mounting', 'no')\n\t\t\t$('#userMountingBackends').addClass('hidden')\n\t\t}\n\t\tOC.msg.finishedSaving('#userMountingMsg', { status: 'success', data: { message: t('files_external', 'Saved') } })\n\t})\n\n\t$('input[name=\"allowUserMountingBackends\\\\[\\\\]\"]').bind('change', function() {\n\t\tOC.msg.startSaving('#userMountingMsg')\n\n\t\tlet userMountingBackends = $('input[name=\"allowUserMountingBackends\\\\[\\\\]\"]:checked').map(function() {\n\t\t\treturn $(this).val()\n\t\t}).get()\n\t\tconst deprecatedBackends = $('input[name=\"allowUserMountingBackends\\\\[\\\\]\"][data-deprecate-to]').map(function() {\n\t\t\tif ($.inArray($(this).data('deprecate-to'), userMountingBackends) !== -1) {\n\t\t\t\treturn $(this).val()\n\t\t\t}\n\t\t\treturn null\n\t\t}).get()\n\t\tuserMountingBackends = userMountingBackends.concat(deprecatedBackends)\n\n\t\tOCP.AppConfig.setValue('files_external', 'user_mounting_backends', userMountingBackends.join())\n\t\tOC.msg.finishedSaving('#userMountingMsg', { status: 'success', data: { message: t('files_external', 'Saved') } })\n\n\t\t// disable allowUserMounting\n\t\tif (userMountingBackends.length === 0) {\n\t\t\t$allowUserMounting.prop('checked', false)\n\t\t\t$allowUserMounting.trigger('change')\n\n\t\t}\n\t})\n\n\t$('#global_credentials').on('submit', async function(event) {\n\t\tevent.preventDefault()\n\t\tconst $form = $(this)\n\t\tconst $submit = $form.find('[type=submit]')\n\t\t$submit.val(t('files_external', 'Saving …'))\n\n\t\tconst uid = $form.find('[name=uid]').val()\n\t\tconst user = $form.find('[name=username]').val()\n\t\tconst password = $form.find('[name=password]').val()\n\t\tawait axios.request({\n\t\t\tmethod: 'POST',\n\t\t\tdata: JSON.stringify({\n\t\t\t\tuid,\n\t\t\t\tuser,\n\t\t\t\tpassword,\n\t\t\t}),\n\t\t\turl: OC.generateUrl('apps/files_external/globalcredentials'),\n\t\t\tconfirmPassword: PwdConfirmationMode.Strict,\n\t\t})\n\n\t\t$submit.val(t('files_external', 'Saved'))\n\t\tsetTimeout(function() {\n\t\t\t$submit.val(t('files_external', 'Save'))\n\t\t}, 2500)\n\n\t\treturn false\n\t})\n\n\t// global instance\n\tOCA.Files_External.Settings.mountConfig = mountConfigListView\n\n\t/**\n\t * Legacy\n\t *\n\t * @namespace\n\t * @deprecated use OCA.Files_External.Settings.mountConfig instead\n\t */\n\tOC.MountConfig = {\n\t\tsaveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView),\n\t}\n})\n\n// export\n\nOCA.Files_External = OCA.Files_External || {}\n/**\n * @namespace\n */\nOCA.Files_External.Settings = OCA.Files_External.Settings || {}\n\nOCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig\nOCA.Files_External.Settings.UserStorageConfig = UserStorageConfig\nOCA.Files_External.Settings.MountConfigListView = MountConfigListView\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.f = {};\n// This file contains only the entry chunk.\n// The chunk loading function for additional chunks\n__webpack_require__.e = (chunkId) => {\n\treturn Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {\n\t\t__webpack_require__.f[key](chunkId, promises);\n\t\treturn promises;\n\t}, []));\n};","// This function allow to reference async chunks\n__webpack_require__.u = (chunkId) => {\n\t// return url for filenames based on template\n\treturn \"\" + chunkId + \"-\" + chunkId + \".js?v=\" + {\"2441\":\"44b85e4901c485417f88\",\"5862\":\"142cd48ca8ec32e57725\",\"6146\":\"5f2015343db7411125d5\",\"8289\":\"8f098190dce9305dab1e\"}[chunkId] + \"\";\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 5808;","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')\n\t\tscriptUrl = document.currentScript.src;\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) {\n\t\t\tvar i = scripts.length - 1;\n\t\t\twhile (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;\n\t\t}\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t5808: 0\n};\n\n__webpack_require__.f.j = (chunkId, promises) => {\n\t\t// JSONP chunk loading for javascript\n\t\tvar installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;\n\t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n\t\t\t// a Promise means \"currently loading\".\n\t\t\tif(installedChunkData) {\n\t\t\t\tpromises.push(installedChunkData[2]);\n\t\t\t} else {\n\t\t\t\tif(true) { // all chunks have JS\n\t\t\t\t\t// setup Promise in chunk cache\n\t\t\t\t\tvar promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));\n\t\t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n\t\t\t\t\t// start chunk loading\n\t\t\t\t\tvar url = __webpack_require__.p + __webpack_require__.u(chunkId);\n\t\t\t\t\t// create error before stack unwound to get useful stacktrace later\n\t\t\t\t\tvar error = new Error();\n\t\t\t\t\tvar loadingEnded = (event) => {\n\t\t\t\t\t\tif(__webpack_require__.o(installedChunks, chunkId)) {\n\t\t\t\t\t\t\tinstalledChunkData = installedChunks[chunkId];\n\t\t\t\t\t\t\tif(installedChunkData !== 0) installedChunks[chunkId] = undefined;\n\t\t\t\t\t\t\tif(installedChunkData) {\n\t\t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n\t\t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n\t\t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n\t\t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n\t\t\t\t\t\t\t\terror.type = errorType;\n\t\t\t\t\t\t\t\terror.request = realSrc;\n\t\t\t\t\t\t\t\tinstalledChunkData[1](error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\t__webpack_require__.l(url, loadingEnded, \"chunk-\" + chunkId, chunkId);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n};\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [4208], () => (__webpack_require__(80655)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","inProgress","dataWebpackPrefix","highlightBorder","$element","highlight","toggleClass","isInputValid","$input","optional","hasClass","attr","val","highlightInput","initApplicableUsersMultiselect","$elements","userListLimit","escapeHTML","text","toString","split","join","length","select2","placeholder","t","allowClear","multiple","toggleSelect","dropdownCssClass","ajax","url","OC","generateUrl","dataType","quietMillis","data","term","page","pattern","limit","offset","results","status","userCount","$","each","groups","gid","group","push","name","displayname","type","users","id","user","more","initSelection","element","callback","toSplit","i","contentType","JSON","stringify","done","formatResult","$result","$div","find","imagePath","html","get","outerHTML","formatSelection","escapeMarkup","m","on","div","avatar","event","target","closest","addPasswordConfirmationInterceptors","axios","StorageConfig","this","backendOptions","Status","IN_PROGRESS","SUCCESS","ERROR","INDETERMINATE","Visibility","NONE","PERSONAL","ADMIN","DEFAULT","prototype","_url","mountPoint","backend","authMechanism","mountOptions","save","options","method","_","isNumber","_save","result","request","confirmPassword","PwdConfirmationMode","Strict","getData","success","error","testOnly","recheck","isFunction","destroy","e","validate","errors","GlobalStorageConfig","applicableUsers","applicableGroups","extend","priority","apply","arguments","UserStorageConfig","UserGlobalStorageConfig","MountOptionsDropdown","$el","show","$container","visibleOptions","_last","hide","OCA","Files_External","Templates","mountOptionsDropDown","mountOptionsEncodingLabel","mountOptionsEncryptLabel","mountOptionsPreviewsLabel","mountOptionsSharingLabel","mountOptionsFilesystemCheckLabel","mountOptionsFilesystemCheckOnce","mountOptionsFilesystemCheckDA","mountOptionsReadOnlyLabel","deleteLabel","storage","parentNode","className","setOptions","appendTo","trigger","remove","getOptions","$this","key","value","prop","parseInt","ind","indexOf","splice","$optionEl","filterAttr","isString","row","$row","optionId","MountConfigListView","initialize","ParameterFlags","OPTIONAL","USER_PROVIDED","ParameterTypes","TEXT","BOOLEAN","PASSWORD","HIDDEN","_storageConfigClass","_isPersonal","_userListLimit","_allBackends","_allAuthMechanisms","_encryptionEnabled","Settings","isUndefined","encryptionEnabled","_canCreateLocal","canCreateLocal","_initEvents","whenSelectBackend","tr","whenSelectAuthMechanism","self","scheme","onChangeHandler","bind","_onChange","recheckStorageConfig","deleteStorageConfig","saveStorageConfig","_showMountOptionsDropdown","_onSelectBackend","_onSelectAuthMechanism","_onChangeApplicableToAllUsers","$target","$tr","updateStatus","storageConfig","onCompletion","jQuery","newStorage","resolve","children","not","first","focus","configureAuthMechanism","checked","is","authMechanismConfiguration","$td","configuration","partial","writeParameterInput","deferAppend","invalid","$template","clone","insertBefore","removeClass","last","removeAttr","_suggestMountPoint","addClass","identifier","empty","backendName","selectAuthMechanism","neededVisibility","authIdentifier","authSchemes","visibility","append","input","undefined","applicable","concat","map","priorityEl","encrypt","previews","enable_sharing","filesystem_check_changes","encoding_compatibility","readonly","loadStorages","onLoaded1","Deferred","onLoaded2","when","always","Object","values","$rows","forEach","storageParams","isUserGlobal","substr","detach","prepend","$authentication","add","before","mainForm","parent","parameter","classes","hasFlag","flag","flags","isArray","newElement","trimmedPlaceholder","checkboxId","uniqueId","defaultValue","tooltip","getStorageConfig","storageId","classOptions","missingOptions","index","multiselect","getSelection","pos","getSelectedApplicable","requiredApplicable","parse","configId","dialogs","confirm","statusMessage","responseJSON","message","concurrentTimer","$statusSpan","defaultMountPoint","substring","replace","match","$toggle","dropDown","off","Backbone","Events","window","addEventListener","enabled","mountConfigListView","$allowUserMounting","msg","startSaving","OCP","AppConfig","setValue","eq","finishedSaving","userMountingBackends","deprecatedBackends","inArray","async","preventDefault","$form","$submit","uid","password","setTimeout","mountConfig","MountConfig","saveStorage","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","loaded","__webpack_modules__","call","O","chunkIds","fn","notFulfilled","Infinity","fulfilled","j","keys","every","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","f","chunkId","Promise","all","reduce","promises","u","g","globalThis","Function","obj","hasOwnProperty","l","script","needAttach","scripts","document","getElementsByTagName","s","getAttribute","createElement","charset","timeout","nc","setAttribute","src","onScriptComplete","prev","onerror","onload","clearTimeout","doneFns","removeChild","head","appendChild","Symbol","toStringTag","nmd","paths","scriptUrl","importScripts","location","currentScript","tagName","toUpperCase","test","Error","p","b","baseURI","href","installedChunks","installedChunkData","promise","reject","errorType","realSrc","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","__webpack_exports__"],"sourceRoot":""}