diff --git a/app/Http/Controllers/UserSearchController.php b/app/Http/Controllers/UserSearchController.php
new file mode 100644
index 000000000..1ff056cd2
--- /dev/null
+++ b/app/Http/Controllers/UserSearchController.php
@@ -0,0 +1,31 @@
+get('search', '');
+ $query = User::query()->orderBy('name', 'desc')
+ ->take(20);
+
+ if (!empty($search)) {
+ $query->where(function(Builder $query) use ($search) {
+ $query->where('email', 'like', '%' . $search . '%')
+ ->orWhere('name', 'like', '%' . $search . '%');
+ });
+ }
+
+ $users = $query->get();
+ return view('form.user-select-list', compact('users'));
+ }
+}
diff --git a/resources/js/components/breadcrumb-listing.js b/resources/js/components/breadcrumb-listing.js
deleted file mode 100644
index 3ce4bc77e..000000000
--- a/resources/js/components/breadcrumb-listing.js
+++ /dev/null
@@ -1,55 +0,0 @@
-
-class BreadcrumbListing {
-
- setup() {
- this.elem = this.$el;
- this.searchInput = this.$refs.searchInput;
- this.loadingElem = this.$refs.loading;
- this.entityListElem = this.$refs.entityList;
-
- this.entityType = this.$opts.entityType;
- this.entityId = Number(this.$opts.entityId);
-
- this.elem.addEventListener('show', this.onShow.bind(this));
- this.searchInput.addEventListener('input', this.onSearch.bind(this));
- }
-
- onShow() {
- this.loadEntityView();
- }
-
- onSearch() {
- const input = this.searchInput.value.toLowerCase().trim();
- const listItems = this.entityListElem.querySelectorAll('.entity-list-item');
- for (let listItem of listItems) {
- const match = !input || listItem.textContent.toLowerCase().includes(input);
- listItem.style.display = match ? 'flex' : 'none';
- listItem.classList.toggle('hidden', !match);
- }
- }
-
- loadEntityView() {
- this.toggleLoading(true);
-
- const params = {
- 'entity_id': this.entityId,
- 'entity_type': this.entityType,
- };
-
- window.$http.get('/search/entity/siblings', params).then(resp => {
- this.entityListElem.innerHTML = resp.data;
- }).catch(err => {
- console.error(err);
- }).then(() => {
- this.toggleLoading(false);
- this.onSearch();
- });
- }
-
- toggleLoading(show = false) {
- this.loadingElem.style.display = show ? 'block' : 'none';
- }
-
-}
-
-export default BreadcrumbListing;
\ No newline at end of file
diff --git a/resources/js/components/dropdown-search.js b/resources/js/components/dropdown-search.js
new file mode 100644
index 000000000..8c81aae3c
--- /dev/null
+++ b/resources/js/components/dropdown-search.js
@@ -0,0 +1,79 @@
+import {debounce} from "../services/util";
+
+class DropdownSearch {
+
+ setup() {
+ this.elem = this.$el;
+ this.searchInput = this.$refs.searchInput;
+ this.loadingElem = this.$refs.loading;
+ this.listContainerElem = this.$refs.listContainer;
+
+ this.localSearchSelector = this.$opts.localSearchSelector;
+ this.url = this.$opts.url;
+
+ this.elem.addEventListener('show', this.onShow.bind(this));
+ this.searchInput.addEventListener('input', this.onSearch.bind(this));
+
+ this.runAjaxSearch = debounce(this.runAjaxSearch, 300, false);
+ }
+
+ onShow() {
+ this.loadList();
+ }
+
+ onSearch() {
+ const input = this.searchInput.value.toLowerCase().trim();
+ if (this.localSearchSelector) {
+ this.runLocalSearch(input);
+ } else {
+ this.toggleLoading(true);
+ this.runAjaxSearch(input);
+ }
+ }
+
+ runAjaxSearch(searchTerm) {
+ this.loadList(searchTerm);
+ }
+
+ runLocalSearch(searchTerm) {
+ const listItems = this.listContainerElem.querySelectorAll(this.localSearchSelector);
+ for (let listItem of listItems) {
+ const match = !searchTerm || listItem.textContent.toLowerCase().includes(searchTerm);
+ listItem.style.display = match ? 'flex' : 'none';
+ listItem.classList.toggle('hidden', !match);
+ }
+ }
+
+ async loadList(searchTerm = '') {
+ this.listContainerElem.innerHTML = '';
+ this.toggleLoading(true);
+
+ try {
+ const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
+ this.listContainerElem.innerHTML = resp.data;
+ } catch (err) {
+ console.error(err);
+ }
+
+ this.toggleLoading(false);
+ if (this.localSearchSelector) {
+ this.onSearch();
+ }
+ }
+
+ getAjaxUrl(searchTerm = null) {
+ if (!searchTerm) {
+ return this.url;
+ }
+
+ const joiner = this.url.includes('?') ? '&' : '?';
+ return `${this.url}${joiner}search=${encodeURIComponent(searchTerm)}`;
+ }
+
+ toggleLoading(show = false) {
+ this.loadingElem.style.display = show ? 'block' : 'none';
+ }
+
+}
+
+export default DropdownSearch;
\ No newline at end of file
diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js
index 7b1ce3055..22402d483 100644
--- a/resources/js/components/dropdown.js
+++ b/resources/js/components/dropdown.js
@@ -17,6 +17,7 @@ class DropDown {
this.body = document.body;
this.showing = false;
this.setupListeners();
+ this.hide = this.hide.bind(this);
}
show(event = null) {
diff --git a/resources/js/components/index.js b/resources/js/components/index.js
index 87c496c91..91ccdaf3a 100644
--- a/resources/js/components/index.js
+++ b/resources/js/components/index.js
@@ -5,7 +5,6 @@ import attachments from "./attachments.js"
import autoSuggest from "./auto-suggest.js"
import backToTop from "./back-to-top.js"
import bookSort from "./book-sort.js"
-import breadcrumbListing from "./breadcrumb-listing.js"
import chapterToggle from "./chapter-toggle.js"
import codeEditor from "./code-editor.js"
import codeHighlighter from "./code-highlighter.js"
@@ -13,6 +12,7 @@ import collapsible from "./collapsible.js"
import customCheckbox from "./custom-checkbox.js"
import detailsHighlighter from "./details-highlighter.js"
import dropdown from "./dropdown.js"
+import dropdownSearch from "./dropdown-search.js"
import dropzone from "./dropzone.js"
import editorToolbox from "./editor-toolbox.js"
import entityPermissionsEditor from "./entity-permissions-editor.js"
@@ -48,6 +48,7 @@ import tagManager from "./tag-manager.js"
import templateManager from "./template-manager.js"
import toggleSwitch from "./toggle-switch.js"
import triLayout from "./tri-layout.js"
+import userSelect from "./user-select.js"
import wysiwygEditor from "./wysiwyg-editor.js"
const componentMapping = {
@@ -58,7 +59,6 @@ const componentMapping = {
"auto-suggest": autoSuggest,
"back-to-top": backToTop,
"book-sort": bookSort,
- "breadcrumb-listing": breadcrumbListing,
"chapter-toggle": chapterToggle,
"code-editor": codeEditor,
"code-highlighter": codeHighlighter,
@@ -66,6 +66,7 @@ const componentMapping = {
"custom-checkbox": customCheckbox,
"details-highlighter": detailsHighlighter,
"dropdown": dropdown,
+ "dropdown-search": dropdownSearch,
"dropzone": dropzone,
"editor-toolbox": editorToolbox,
"entity-permissions-editor": entityPermissionsEditor,
@@ -101,6 +102,7 @@ const componentMapping = {
"template-manager": templateManager,
"toggle-switch": toggleSwitch,
"tri-layout": triLayout,
+ "user-select": userSelect,
"wysiwyg-editor": wysiwygEditor,
};
diff --git a/resources/js/components/user-select.js b/resources/js/components/user-select.js
new file mode 100644
index 000000000..477c11d6b
--- /dev/null
+++ b/resources/js/components/user-select.js
@@ -0,0 +1,24 @@
+import {onChildEvent} from "../services/dom";
+
+class UserSelect {
+
+ setup() {
+
+ this.input = this.$refs.input;
+ this.userInfoContainer = this.$refs.userInfo;
+
+ this.hide = this.$el.components.dropdown.hide;
+
+ onChildEvent(this.$el, 'a.dropdown-search-item', 'click', this.selectUser.bind(this));
+ }
+
+ selectUser(event, userEl) {
+ const id = userEl.getAttribute('data-id');
+ this.input.value = id;
+ this.userInfoContainer.innerHTML = userEl.innerHTML;
+ this.hide();
+ }
+
+}
+
+export default UserSelect;
\ No newline at end of file
diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php
index 5e6a63deb..6b0153844 100644
--- a/resources/lang/en/entities.php
+++ b/resources/lang/en/entities.php
@@ -40,6 +40,7 @@ return [
'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
'permissions_enable' => 'Enable Custom Permissions',
'permissions_save' => 'Save Permissions',
+ 'permissions_owner' => 'Owner',
// Search
'search_results' => 'Search Results',
diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss
index eb40741d1..ede26c51c 100644
--- a/resources/sass/_components.scss
+++ b/resources/sass/_components.scss
@@ -724,4 +724,65 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
.template-item-actions button:first-child {
border-top: 0;
}
+}
+
+.dropdown-search-dropdown {
+ box-shadow: $bs-med;
+ overflow: hidden;
+ min-height: 100px;
+ width: 240px;
+ display: none;
+ position: absolute;
+ z-index: 80;
+ right: -$-m;
+ @include rtl {
+ right: auto;
+ left: -$-m;
+ }
+ .dropdown-search-search .svg-icon {
+ position: absolute;
+ left: $-s;
+ @include rtl {
+ right: $-s;
+ left: auto;
+ }
+ top: 11px;
+ fill: #888;
+ pointer-events: none;
+ }
+ .dropdown-search-list {
+ max-height: 400px;
+ overflow-y: scroll;
+ text-align: start;
+ }
+ .dropdown-search-item {
+ padding: $-s $-m;
+ &:hover,&:focus {
+ background-color: #F2F2F2;
+ text-decoration: none;
+ }
+ }
+ input {
+ padding-inline-start: $-xl;
+ border-radius: 0;
+ border: 0;
+ border-bottom: 1px solid #DDD;
+ }
+}
+
+@include smaller-than($m) {
+ .dropdown-search-dropdown {
+ position: fixed;
+ right: auto;
+ left: $-m;
+ }
+ .dropdown-search-dropdown .dropdown-search-list {
+ max-height: 240px;
+ }
+}
+
+.custom-select-input {
+ max-width: 280px;
+ border: 1px solid #DDD;
+ border-radius: 4px;
}
\ No newline at end of file
diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss
index e19bb4f61..246ef4b5b 100644
--- a/resources/sass/_header.scss
+++ b/resources/sass/_header.scss
@@ -269,9 +269,9 @@ header .search-box {
}
}
-.breadcrumb-listing {
+.dropdown-search {
position: relative;
- .breadcrumb-listing-toggle {
+ .dropdown-search-toggle {
padding: 6px;
border: 1px solid transparent;
border-radius: 4px;
@@ -284,54 +284,6 @@ header .search-box {
}
}
-.breadcrumb-listing-dropdown {
- box-shadow: $bs-med;
- overflow: hidden;
- min-height: 100px;
- width: 240px;
- display: none;
- position: absolute;
- z-index: 80;
- right: -$-m;
- @include rtl {
- right: auto;
- left: -$-m;
- }
- .breadcrumb-listing-search .svg-icon {
- position: absolute;
- left: $-s;
- @include rtl {
- right: $-s;
- left: auto;
- }
- top: 11px;
- fill: #888;
- pointer-events: none;
- }
- .breadcrumb-listing-entity-list {
- max-height: 400px;
- overflow-y: scroll;
- text-align: start;
- }
- input {
- padding-inline-start: $-xl;
- border-radius: 0;
- border: 0;
- border-bottom: 1px solid #DDD;
- }
-}
-
-@include smaller-than($m) {
- .breadcrumb-listing-dropdown {
- position: fixed;
- right: auto;
- left: $-m;
- }
- .breadcrumb-listing-dropdown .breadcrumb-listing-entity-list {
- max-height: 240px;
- }
-}
-
.faded {
a, button, span, span > div {
color: #666;
diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss
index c4e412f0e..e5ed608eb 100644
--- a/resources/sass/_layout.scss
+++ b/resources/sass/_layout.scss
@@ -153,6 +153,9 @@ body.flexbox {
.justify-center {
justify-content: center;
}
+.items-center {
+ align-items: center;
+}
/**
diff --git a/resources/views/components/user-select-list.blade.php b/resources/views/components/user-select-list.blade.php
new file mode 100644
index 000000000..2c49e965d
--- /dev/null
+++ b/resources/views/components/user-select-list.blade.php
@@ -0,0 +1,6 @@
+@foreach($users as $user)
+
+
+ {{ $user->name }}
+
+@endforeach
\ No newline at end of file
diff --git a/resources/views/components/user-select.blade.php b/resources/views/components/user-select.blade.php
new file mode 100644
index 000000000..c6a30f53d
--- /dev/null
+++ b/resources/views/components/user-select.blade.php
@@ -0,0 +1,30 @@
+
{{ trans('entities.permissions_intro') }}
- -{{ trans('entities.permissions_intro') }}
+