Page MenuHomePhorge

D5712.1775204802.diff
No OneTemporary

Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None

D5712.1775204802.diff

diff --git a/src/app/Utils.php b/src/app/Utils.php
--- a/src/app/Utils.php
+++ b/src/app/Utils.php
@@ -450,6 +450,7 @@
'app.companion_download_link',
'app.shared_folder_types',
'app.with_signup',
+ 'app.with_user_search',
'mail.from.address',
];
diff --git a/src/package-lock.json b/src/package-lock.json
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -4,7 +4,6 @@
"requires": true,
"packages": {
"": {
- "name": "src",
"devDependencies": {
"@babel/eslint-parser": "^7.17.0",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
@@ -3900,9 +3899,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001722",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz",
- "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==",
+ "version": "1.0.30001753",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz",
+ "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==",
"dev": true,
"funding": [
{
diff --git a/src/resources/js/utils.js b/src/resources/js/utils.js
--- a/src/resources/js/utils.js
+++ b/src/resources/js/utils.js
@@ -179,11 +179,77 @@
return true
}
+/**
+ * Initializes a user email autocompletion on a form input element.
+ *
+ * @param DOMElement $input Input element
+ * @param array $params Search parameters (alias, limit)
+ */
+const userAutocomplete = (input, params = {}) => {
+ if (!window.config['app.with_user_search']) {
+ return
+ }
+
+ const listId = input.id + '-datalist'
+
+ // Note: <datalist> element is a simple solution, it does not allow
+ // styling nor structured content though. It also displays differently
+ // in different browsers.
+
+ let controller = null
+ let timeout = null
+ let datalist = $('<datalist>').attr('id', listId)
+
+ const search_request = search => {
+ controller = new AbortController()
+
+ params = Object.assign({ alias: 0, limit: 10 }, params, { search })
+
+ axios.get('api/v4/search/user', { params, signal: controller.signal })
+ .then(response => {
+ datalist.empty()
+ response.data.list.forEach(user => {
+ let label = user.name ? `${user.name} <${user.email}>` : user.email
+ datalist.append($('<option>').val(user.email).text(label))
+ })
+ })
+ .catch(() => {
+ // ignore error
+ })
+ .finally(() => {
+ controller = null
+ })
+ }
+
+ $(input).attr({ autocomplete: 'off', list: listId })
+ .after(datalist)
+ .on('input', event => {
+ const search = input.value
+
+ window.clearTimeout(timeout)
+
+ if (controller) {
+ controller.abort()
+ }
+
+ // Reset the list when user selected an autocomplete entry or the input is not long enough
+ // Note: inputType is undefined in Chrome, and 'insertReplacementText' in Firefox
+ if (!search.length || !event.inputType || event.inputType == 'insertReplacementText') {
+ datalist.empty()
+ return
+ }
+
+ // Execute the search (with a delay, to limit number of requests when the user is typing fast)
+ timeout = window.setTimeout(() => search_request(search), 250)
+ })
+}
+
export {
clearFormValidation,
downloadFile,
paymentCheckout,
pick,
startLoading,
- stopLoading
+ stopLoading,
+ userAutocomplete
}
diff --git a/src/resources/vue/Distlist/Info.vue b/src/resources/vue/Distlist/Info.vue
--- a/src/resources/vue/Distlist/Info.vue
+++ b/src/resources/vue/Distlist/Info.vue
@@ -70,6 +70,7 @@
import ListInput from '../Widgets/ListInput'
import StatusComponent from '../Widgets/Status'
import SubscriptionSelect from '../Widgets/SubscriptionSelect'
+ import { userAutocomplete } from '../../js/utils'
export default {
components: {
@@ -92,12 +93,15 @@
.then(response => {
this.list = response.data
this.status = response.data.statusInfo
+
+ this.$nextTick().then(() => { userAutocomplete($('#sender-policy-input').get(0)) })
})
.catch(this.$root.errorHandler)
}
},
mounted() {
$('#name').focus()
+ userAutocomplete($('#members-input').get(0))
},
methods: {
deleteList() {
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -309,6 +309,7 @@
import PasswordInput from '../Widgets/PasswordInput'
import StatusComponent from '../Widgets/Status'
import SubscriptionSelect from '../Widgets/SubscriptionSelect'
+ import { userAutocomplete } from '../../js/utils'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -468,6 +469,8 @@
}
})
+ userAutocomplete($('#delegation-email').get(0))
+
this.$refs.roleSelectDialog.events({
show: (event) => {
$('select', this.$refs.roleSelectDialog.$el).val(this.user.isController ? 'controller' : 'user')
diff --git a/src/resources/vue/Widgets/AclInput.vue b/src/resources/vue/Widgets/AclInput.vue
--- a/src/resources/vue/Widgets/AclInput.vue
+++ b/src/resources/vue/Widgets/AclInput.vue
@@ -26,6 +26,8 @@
</template>
<script>
+ import { userAutocomplete } from '../../js/utils'
+
const DEFAULT_TYPES = [ 'read-only', 'read-write', 'full' ]
export default {
@@ -54,6 +56,8 @@
this.updateList()
this.addItem(false)
})
+
+ userAutocomplete(this.input)
},
methods: {
aclIdent(item) {
diff --git a/src/tests/Browser/Components/UserAutocomplete.php b/src/tests/Browser/Components/UserAutocomplete.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/Components/UserAutocomplete.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Tests\Browser\Components;
+
+use Laravel\Dusk\Component as BaseComponent;
+use Tests\Browser;
+
+class UserAutocomplete extends BaseComponent
+{
+ protected $selector;
+
+ public function __construct($selector)
+ {
+ $this->selector = $selector;
+ }
+
+ /**
+ * Get the root selector for the component.
+ *
+ * @return string
+ */
+ public function selector()
+ {
+ return $this->selector;
+ }
+
+ /**
+ * Assert that the browser page contains the component.
+ *
+ * @param Browser $browser
+ */
+ public function assert($browser)
+ {
+ $browser->assertAttributeRegExp($this->selector, 'autocomplete', '/^off$/')
+ ->assertAttributeRegExp($this->selector, 'list', '/.*-datalist$/');
+ }
+
+ /**
+ * Assert list
+ *
+ * @param Browser $browser
+ * @param array<string, string> $users List of user labels indexed by email address
+ */
+ public function assertAutocompleteList($browser, $users)
+ {
+ if (empty($users)) {
+ $browser->waitUntilMissing("{$this->selector}-datalist > option");
+ } else {
+ $browser->waitFor("{$this->selector}-datalist > option")
+ ->assertElementsCount("{$this->selector}-datalist > option", count($users));
+
+ $idx = 0;
+ foreach ($users as $email => $user) {
+ $idx++;
+ $selector = "{$this->selector}-datalist > option:nth-child({$idx})";
+ $browser->assertSeeIn($selector, $user)
+ ->assertAttributeRegExp($selector, 'value', '/^' . preg_quote($email, '/') . '$/');
+ }
+ }
+ }
+
+ /**
+ * Enter text into the input
+ *
+ * @param Browser $browser
+ * @param string $text
+ */
+ public function autocomplete($browser, $text)
+ {
+ $browser->keys($this->selector, str_split($text));
+ }
+
+ /**
+ * Select a user
+ *
+ * @param Browser $browser
+ * @param string $email Email address to select
+ */
+ public function selectAutocompleteUser($browser, $email)
+ {
+ $browser->click("{$this->selector}-datalist > option[value=\"{$email}\"]")
+ ->waitUntilMissing("{$this->selector}-datalist > option")
+ ->assertValue($this->selector, $email);
+ }
+}
diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php
--- a/src/tests/Browser/UsersTest.php
+++ b/src/tests/Browser/UsersTest.php
@@ -16,6 +16,7 @@
use Tests\Browser\Components\ListInput;
use Tests\Browser\Components\QuotaInput;
use Tests\Browser\Components\Toast;
+use Tests\Browser\Components\UserAutocomplete;
use Tests\Browser\Pages\Dashboard;
use Tests\Browser\Pages\Home;
use Tests\Browser\Pages\UserInfo;
@@ -1005,6 +1006,19 @@
->assertSelectHasOptions('#delegation-contact', ['', 'read-only', 'read-write'])
->assertVisible('.row.form-text')
->type('#delegation-email', 'john@kolab.org')
+ /*
+ FIXME: For some reason assertAutocompleteList() below does not work
+ ->with(new UserAutocomplete('#delegation-email'), static function ($browser) {
+ $list = [
+ 'joe@kolab.org' => 'joe@kolab.org',
+ 'jack@kolab.org' => 'Jack Daniels <jack@kolab.org>',
+ 'john@kolab.org' => 'John Doe <john@kolab.org>',
+ ];
+ $browser->autocomplete('j')
+ ->assertAutocompleteList($list)
+ ->selectAutocompleteUser('john@kolab.org');
+ })
+ */
->select('#delegation-mail', 'read-only')
->select('#delegation-contact', 'read-write')
->assertSeeIn('@button-cancel', 'Cancel')

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 3, 8:26 AM (18 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18802017
Default Alt Text
D5712.1775204802.diff (10 KB)

Event Timeline