@oxygen-cms/ui 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/{.eslintrc.js → .eslintrc.json} +3 -3
  2. package/package.json +14 -4
  3. package/src/CrudApi.js +23 -9
  4. package/src/MediaApi.js +3 -3
  5. package/src/PagesApi.js +48 -0
  6. package/src/PartialsApi.js +30 -0
  7. package/src/components/AuthenticatedLayout.vue +3 -1
  8. package/src/components/CodeEditor.vue +1 -1
  9. package/src/components/GroupsChooser.vue +2 -2
  10. package/src/components/GroupsList.vue +2 -2
  11. package/src/components/PageActions.vue +28 -0
  12. package/src/components/PageEdit.vue +164 -0
  13. package/src/components/PageNestedPagination.vue +27 -0
  14. package/src/components/PageNestedRow.vue +52 -0
  15. package/src/components/PageStatusIcon.vue +33 -0
  16. package/src/components/PageTable.vue +156 -0
  17. package/src/components/PartialActions.vue +28 -0
  18. package/src/components/PartialList.vue +74 -0
  19. package/src/components/PartialStatusIcon.vue +29 -0
  20. package/src/components/PartialTable.vue +65 -0
  21. package/src/components/ResourceList.vue +132 -0
  22. package/src/components/UserManagement.vue +2 -2
  23. package/src/components/UserProfileForm.vue +1 -1
  24. package/src/components/UserProfilePage.vue +1 -1
  25. package/src/components/content/CommandsList.vue +108 -0
  26. package/src/components/content/ContentEditor.vue +489 -0
  27. package/src/components/content/GridCellNodeView.vue +82 -0
  28. package/src/components/content/GridRowNodeView.vue +53 -0
  29. package/src/components/content/HtmlNodeView.vue +89 -0
  30. package/src/components/content/MarkMenu.vue +116 -0
  31. package/src/components/content/MediaNodeView.vue +83 -0
  32. package/src/components/content/ObjectLinkNodeView.vue +181 -0
  33. package/src/components/content/PartialNodeView.vue +217 -0
  34. package/src/components/content/commands.js +72 -0
  35. package/src/components/content/suggestion.js +211 -0
  36. package/src/components/media/MediaChooseDirectory.vue +2 -2
  37. package/src/components/media/MediaDirectory.vue +1 -1
  38. package/src/components/media/MediaInsertModal.vue +11 -2
  39. package/src/components/media/MediaItem.vue +1 -1
  40. package/src/components/media/MediaItemPreview.vue +18 -2
  41. package/src/components/media/MediaList.vue +4 -5
  42. package/src/components/media/MediaUpload.vue +1 -1
  43. package/src/components/media/media.scss +1 -0
  44. package/src/components/pages/PageList.vue +65 -0
  45. package/src/components/users/CreateUserModal.vue +1 -1
  46. package/src/components/util.css +1 -1
  47. package/src/icons.js +33 -5
  48. package/src/main.js +4 -0
  49. package/src/modules/PagesPartials.js +74 -2
  50. package/src/styles/pages-table.scss +34 -0
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <b-table
3
+ ref="table"
4
+ :data="pagesByParent[0].items"
5
+ :loading="pagesByParent[0].loading"
6
+ custom-row-key="id"
7
+ :paginated="pagesByParent[0].totalItems > pagesByParent[0].itemsPerPage"
8
+ backend-pagination
9
+ :total="pagesByParent[0].totalItems"
10
+ :per-page="pagesByParent[0].itemsPerPage"
11
+ :current-page="pagesByParent[0].currentPage"
12
+ detailed
13
+ :has-detailed-visible="rowHasChildren"
14
+ custom-detail-row
15
+ :backend-sorting="!!onSort"
16
+ default-sort-direction="asc"
17
+ :default-sort="[sortField, sortOrder]"
18
+ sticky-header
19
+ detail-key="id"
20
+ aria-next-label="Next page"
21
+ aria-previous-label="Previous page"
22
+ aria-page-label="Page"
23
+ aria-current-label="Current page"
24
+ class="full-height-flex full-height-container"
25
+ @page-change="onPageChange"
26
+ @details-open="item => setExpanded(item, true)"
27
+ @details-close="item => setExpanded(item, false)"
28
+ @sort="onSort">
29
+ <b-table-column v-slot="props" label="Title" :sortable="!!onSort" field="title">{{ props.row.title }} <PageStatusIcon :item="props.row"></PageStatusIcon></b-table-column>
30
+ <b-table-column v-slot="props" label="URL" :sortable="!!onSort" field="slugPart"><a :href="PagesApi.slugToUrl(props.row.slug)" class="is-size-7" target="_blank">{{ PagesApi.slugToUrl(props.row.slug) }} <b-icon icon="external-link-alt"></b-icon></a></b-table-column>
31
+ <b-table-column v-slot="props" label="Description" width="30%" :sortable="!!onSort" field="description"><div class="is-size-7">{{ props.row.description }}</div></b-table-column>
32
+ <b-table-column v-slot="props" label="Last Updated" field="updatedAt" :sortable="!!onSort">
33
+ <div v-if="props.row.updatedAt" class="is-size-7"><Updated :model="props.row"></Updated></div>
34
+ </b-table-column>
35
+ <b-table-column v-slot="props" class="toolbar">
36
+ <slot name="actions" :row="props.row"></slot>
37
+ </b-table-column>
38
+ <!-- TODO: this is a massive hack, only supports up to a certain level of nesting -->
39
+ <template #detail="slot">
40
+ <template v-if="expanded[slot.row.id]">
41
+ <template v-for="(item1, i) in pagesByParent[slot.row.id].items">
42
+ <PageNestedRow :key="item1.id" :item="item1" :is-first="i === 0" :depth="1" :expanded="expanded[item1.id]" @toggle-expand="item => setExpanded(item, !expanded[item.id])">
43
+ <template #actions="props"><slot name="actions" :row="props.row"></slot></template>
44
+ </PageNestedRow>
45
+ <template v-if="expanded[item1.id]">
46
+ <template v-for="(item2, j) in pagesByParent[item1.id].items">
47
+ <PageNestedRow :key="item2.id" :item="item2" :is-first="j === 0" :depth="2" :expanded="expanded[item2.id]" @toggle-expand="item => setExpanded(item, !expanded[item.id])">
48
+ <template #actions="props"><slot name="actions" :row="props.row"></slot></template>
49
+ </PageNestedRow>
50
+ <template v-if="expanded[item2.id]">
51
+ <template v-for="(item3, k) in pagesByParent[item2.id].items">
52
+ <PageNestedRow :key="item3.id" :item="item3" :is-first="k === 0" :depth="3" :expanded="expanded[item3.id]" @toggle-expand="item => setExpanded(item, !expanded[item.id])">
53
+ <template #actions="props"><slot name="actions" :row="props.row"></slot></template>
54
+ </PageNestedRow>
55
+ </template></template>
56
+ <PageNestedPagination v-if="expanded[item2.id]" :key="item2.key" :item="item2" :pages-by-parent="pagesByParent" :depth="3" :paginate="paginate"></PageNestedPagination>
57
+ </template>
58
+ </template>
59
+ <PageNestedPagination v-if="expanded[item1.id]" :key="item1.key" :item="item1" :pages-by-parent="pagesByParent" :depth="2" :paginate="paginate"></PageNestedPagination>
60
+ </template>
61
+ <PageNestedPagination :item="slot.row" :pages-by-parent="pagesByParent" :depth="1" :paginate="paginate"></PageNestedPagination>
62
+ </template>
63
+ </template>
64
+ </b-table>
65
+ </template>
66
+
67
+ <script>
68
+ import PageStatusIcon from "./PageStatusIcon.vue";
69
+ import Updated from "./Updated.vue";
70
+ import PagesApi from "../PagesApi.js";
71
+ import Vue from "vue";
72
+ import PageNestedRow from "./PageNestedRow.vue";
73
+ import PageNestedPagination from "./PageNestedPagination.vue";
74
+
75
+ export default {
76
+ name: "PageTable",
77
+ components: {PageNestedPagination, PageNestedRow, PageStatusIcon, Updated},
78
+ props: {
79
+ sortField: String,
80
+ sortOrder: String,
81
+ onSort: { type: Function, default: () => {} },
82
+ paginatedItems: { type: Object },
83
+ onPageChange: Function,
84
+ },
85
+ data() {
86
+ return {
87
+ pagesApi: new PagesApi(),
88
+ PagesApi: PagesApi,
89
+ expanded: {},
90
+ // `0` denotes the "root" level
91
+ pagesByParent: {0: {items: [], loading: true, totalItems: 0, itemsPerPage: 0, currentPage: 1}},
92
+ }
93
+ },
94
+ watch: {
95
+ 'paginatedItems.items': 'loadAllDetails'
96
+ },
97
+ beforeMount() {
98
+ this.loadAllDetails();
99
+ },
100
+ methods: {
101
+ async loadAllDetails() {
102
+ this.pagesByParent[0].loading = true;
103
+ this.pagesByParent[0].items = await Promise.all(this.paginatedItems.items.map(this.loadChildren));
104
+ this.pagesByParent[0].totalItems = this.paginatedItems.totalItems;
105
+ this.pagesByParent[0].itemsPerPage = this.paginatedItems.itemsPerPage;
106
+ this.pagesByParent[0].currentPage = this.paginatedItems.currentPage;
107
+ this.pagesByParent[0].loading = false;
108
+ },
109
+ rowHasChildren(row) {
110
+ return row.numChildren > 0;
111
+ },
112
+ async loadChildren(page) {
113
+ return await this.paginate(page, 1);
114
+ },
115
+ async paginate(page, pageNum) {
116
+ if (!this.pagesByParent[page.id]) {
117
+ Vue.set(this.pagesByParent, page.id, {items: [], itemsPerPage: 1, totalItems: 0, currentPage: pageNum});
118
+ }
119
+
120
+ // if the slug is the root ( "/" ) then we don't show any child pages, since they're displayed as sibling pages.
121
+ if (page.slug !== '/' && page.numChildren > 0) {
122
+ console.log('loading children for', page.slug, 'page #', pageNum);
123
+ let result = await this.pagesApi.list({ inTrash: false, page: pageNum, q: null, path: page.slug, sortField: this.sortField, sortOrder: this.sortOrder });
124
+ // TODO: this currently recursively grabs all children ==> as many as N http requests where N is the number of pages in the site.
125
+ // Can we be more efficient?
126
+ this.pagesByParent[page.id].items = await Promise.all(result.items.map(this.loadChildren));
127
+ this.pagesByParent[page.id].itemsPerPage = result.itemsPerPage;
128
+ this.pagesByParent[page.id].totalItems = result.totalItems;
129
+ this.pagesByParent[page.id].currentPage = pageNum;
130
+ } else {
131
+ this.pagesByParent[page.id].items = [];
132
+ this.pagesByParent[page.id].itemsPerPage = 0;
133
+ this.pagesByParent[page.id].totalItems = 0;
134
+ this.pagesByParent[page.id].currentPage = 1;
135
+ }
136
+
137
+ return page;
138
+ },
139
+ async setExpanded(item, expanded) {
140
+ let o = {};
141
+ o[item.id] = expanded;
142
+ this.expanded = Object.assign({}, this.expanded, o);
143
+ }
144
+ }
145
+ }
146
+ </script>
147
+
148
+ <style scoped lang="scss">
149
+ @import "./util.css";
150
+
151
+ ::v-deep .table-wrapper.has-sticky-header {
152
+ flex: 1 1 auto;
153
+ }
154
+
155
+ @import "../styles/pages-table";
156
+ </style>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <div>
3
+ <b-button v-if="item.stage !== STAGE_PUBLISHED" class="mr-2" rounded size="is-small" icon-left="globe-asia" @click="publish">Publish</b-button>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+ import PartialsApi from "../PartialsApi.js";
9
+
10
+ export default {
11
+ name: "PartialActions",
12
+ props: {
13
+ item: Object
14
+ },
15
+ data() {
16
+ return {
17
+ STAGE_PUBLISHED: PartialsApi.STAGE_PUBLISHED,
18
+ partialsApi: new PartialsApi()
19
+ }
20
+ },
21
+ methods: {
22
+ async publish() {
23
+ let item = await this.partialsApi.publish(this.item.id);
24
+ this.$emit('update', item);
25
+ }
26
+ }
27
+ }
28
+ </script>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div class="full-height-flex scroll-container">
3
+
4
+ <b-loading v-model="paginatedItems.loading" :is-full-page="false" :can-cancel="false"></b-loading>
5
+
6
+ <h2 v-if="!paginatedItems.loading && paginatedItems.items.length === 0" class="subtitle">
7
+ No items found.
8
+ </h2>
9
+
10
+ <div
11
+ v-for="item in paginatedItems.items"
12
+ :key="item.id"
13
+ class="card"
14
+ @click="$emit('choose', item)">
15
+ <span>{{ item.title }} ( <code>{{ item.key }}</code> )</span>
16
+ <ContentEditor :editable="false" :content="item.richContent" />
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+ import PartialsApi from "../PartialsApi.js";
23
+ import ContentEditor from "./content/ContentEditor.vue";
24
+
25
+ export default {
26
+ name: "PartialList",
27
+ components: {ContentEditor},
28
+ props: {
29
+ inTrash: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ searchQuery: {
34
+ type: String,
35
+ required: true
36
+ }
37
+ },
38
+ data() {
39
+ return {
40
+ partialsApi: new PartialsApi(),
41
+ paginatedItems: {items: [], totalItems: 0, itemsPerPage: 0, loading: false, currentPage: 1},
42
+ }
43
+ },
44
+ created() {
45
+ this.fetchData()
46
+ },
47
+ methods: {
48
+ async fetchData() {
49
+ if(this.paginatedItems.loading) {
50
+ return;
51
+ }
52
+ this.paginatedItems.loading = true;
53
+ this.paginatedItems.items = [];
54
+
55
+ let data = await this.partialsApi.list({ inTrash: this.inTrash, page: this.paginatedItems.currentPage, q: this.searchQuery });
56
+
57
+ this.paginatedItems.items = data.items;
58
+ this.paginatedItems.loading = false;
59
+ this.paginatedItems.itemsPerPage = data.itemsPerPage;
60
+ this.paginatedItems.totalItems = data.totalItems;
61
+
62
+ console.log(data);
63
+ }
64
+ }
65
+ }
66
+ </script>
67
+
68
+ <style scoped>
69
+ @import "./util.css";
70
+
71
+ .card {
72
+ cursor: pointer;
73
+ }
74
+ </style>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <b-icon :icon="statusIcon"></b-icon>
3
+ </template>
4
+
5
+ <script>
6
+ import PartialsApi from "../PartialsApi.js";
7
+
8
+ export default {
9
+ name: "PartialStatusIcon",
10
+ props: {
11
+ item: Object
12
+ },
13
+ computed: {
14
+ statusIcon() {
15
+ if(this.item.stage === PartialsApi.STAGE_DRAFT) {
16
+ return 'pen-square';
17
+ } else if(this.item.stage === PartialsApi.STAGE_PUBLISHED) {
18
+ return 'globe-asia';
19
+ } else {
20
+ throw new Error('unknown stage ' + this.item.stage);
21
+ }
22
+ }
23
+ }
24
+ }
25
+ </script>
26
+
27
+ <style scoped>
28
+
29
+ </style>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <b-table
3
+ ref="table"
4
+ :data="(paginatedItems === null || paginatedItems.items === null) ? [] : paginatedItems.items"
5
+ :loading="paginatedItems === null || paginatedItems.items === null || paginatedItems.loading"
6
+ custom-row-key="id"
7
+ :paginated="paginatedItems !== null && (paginatedItems.totalItems > paginatedItems.itemsPerPage)"
8
+ backend-pagination
9
+ :total="paginatedItems !== null ? paginatedItems.totalItems : 0"
10
+ :per-page="paginatedItems !== null ? paginatedItems.itemsPerPage : 0"
11
+ :current-page="paginatedItems !== null ? paginatedItems.currentPage : 1"
12
+ :detailed="false"
13
+ :backend-sorting="!!onSort"
14
+ default-sort-direction="asc"
15
+ :default-sort="[sortField, sortOrder]"
16
+ sticky-header
17
+ aria-next-label="Next page"
18
+ aria-previous-label="Previous page"
19
+ aria-page-label="Page"
20
+ aria-current-label="Current page"
21
+ class="full-height-flex full-height-container"
22
+ @page-change="onPageChange"
23
+ @sort="onSort">
24
+ <b-table-column v-slot="props" label="Title" :sortable="!!onSort" field="title">{{ props.row.title }} <PartialStatusIcon :item="props.row"></PartialStatusIcon></b-table-column>
25
+ <b-table-column v-slot="props" label="Key" :sortable="!!onSort" field="key">{{ props.row.key }}</b-table-column>
26
+ <b-table-column v-slot="props" label="Last Updated" field="updatedAt" :sortable="!!onSort">
27
+ <div v-if="props.row.updatedAt" class="is-size-7"><Updated :model="props.row"></Updated></div>
28
+ </b-table-column>
29
+ <b-table-column v-slot="props">
30
+ <slot name="actions" :row="props.row"></slot>
31
+ </b-table-column>
32
+ </b-table>
33
+ </template>
34
+
35
+ <script>
36
+ import Updated from "./Updated.vue";
37
+ import PartialStatusIcon from "./PartialStatusIcon.vue";
38
+
39
+ export default {
40
+ name: "PartialTable",
41
+ components: {PartialStatusIcon, Updated},
42
+ props: {
43
+ detailed: Boolean,
44
+ sortField: String,
45
+ sortOrder: String,
46
+ onSort: { type: Function, default: () => {} },
47
+ paginatedItems: { type: Object, default: null },
48
+ onPageChange: Function,
49
+ },
50
+ data() {
51
+ return {
52
+ }
53
+ },
54
+ watch: {
55
+ },
56
+ methods: {
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <style scoped lang="scss">
62
+ ::v-deep .table-wrapper.has-sticky-header {
63
+ flex: 1 1 auto;
64
+ }
65
+ </style>
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <div class="full-height full-height-container pad">
3
+ <div class="top-bar">
4
+ <h1 v-if="!inTrash" class="title">{{ displayName }}</h1>
5
+ <b-button v-if="inTrash" tag="router-link" :to="'/' + routePrefix + '/'" outlined rounded icon-left="arrow-left">All {{ displayName }}</b-button>
6
+ <h1 v-if="inTrash" class="title">
7
+ Deleted {{ displayName }}
8
+ </h1>
9
+ <div class="is-flex-grow-1">
10
+ </div>
11
+ <b-input v-model.lazy="searchQuery" rounded :placeholder="'Search ' + displayName" icon="search" icon-pack="fas" class="mr-3"></b-input>
12
+ <b-button v-if="!inTrash" tag="router-link" :to="'/' + routePrefix + '/create'" type="is-success" icon-left="pencil-alt" class="mr-3">Create
13
+ {{ singularDisplayName }}</b-button>
14
+ <b-button v-if="!inTrash" tag="router-link" :to="'/' + routePrefix + '/trash'" type="is-danger" outlined icon-left="trash">Deleted
15
+ {{ displayName }}</b-button>
16
+ </div>
17
+
18
+ <div class="full-height full-height-container box">
19
+ <component :is="tableComponent" :paginated-items="paginatedItems" :on-page-change="page => paginatedItems.currentPage = page" :detailed="!searchQuery" :on-sort="onSort">
20
+ <template #actions="slotProps">
21
+ <div class="buttons" style="min-width: 18rem">
22
+ <component :is="actionsComponent" :item="slotProps.row" @update="updateItem"></component>
23
+ <b-button rounded icon-left="pencil-alt" tag="router-link" :to="'/' + routePrefix + '/' + slotProps.row.id" size="is-small">Edit</b-button>
24
+ <b-button
25
+ v-if="inTrash" rounded outlined icon-left="recycle"
26
+ size="is-small" @click="restoreItem(slotProps.row.id)">Restore
27
+ </b-button>
28
+ <b-button
29
+ v-if="inTrash" rounded type="is-danger" outlined icon-left="trash"
30
+ size="is-small" @click="forceDeleteItem(slotProps.row.id)">Delete Forever
31
+ </b-button>
32
+ <b-button
33
+ v-if="!inTrash" rounded icon-left="trash"
34
+ size="is-small" @click="deleteItem(slotProps.row.id)">Delete</b-button>
35
+ </div>
36
+ </template>
37
+ </component>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <script>
43
+ import ContentEditor from "./content/ContentEditor.vue";
44
+ import {debounce} from "lodash";
45
+
46
+ export default {
47
+ name: "ResourceList",
48
+ components: {ContentEditor},
49
+ props: {
50
+ routePrefix: String,
51
+ displayName: String,
52
+ singularDisplayName: String,
53
+ inTrash: {
54
+ type: Boolean,
55
+ default: false
56
+ },
57
+ defaultSortField: String,
58
+ defaultSortOrder: String,
59
+ tableComponent: Object,
60
+ actionsComponent: Object,
61
+ resourceApi: Object
62
+ },
63
+ data() {
64
+ return {
65
+ searchQuery: '',
66
+ sortField: this.defaultSortField,
67
+ sortOrder: this.defaultSortOrder,
68
+ paginatedItems: {items: [], totalItems: 0, itemsPerPage: 0, loading: false, currentPage: 1},
69
+ }
70
+ },
71
+ watch: {
72
+ 'resourceApi': 'fetchData',
73
+ 'inTrash': 'fetchData',
74
+ 'sortField': 'fetchData',
75
+ 'sortOrder': 'fetchData',
76
+ 'searchQuery': 'debouncedFetchData',
77
+ 'paginatedItems.currentPage': 'fetchData'
78
+ },
79
+ created() {
80
+ this.fetchData()
81
+ },
82
+ methods: {
83
+ debouncedFetchData: debounce(async function() {
84
+ await this.fetchData()
85
+ }, 1000),
86
+ async fetchData() {
87
+ if(this.paginatedItems.loading) {
88
+ return;
89
+ }
90
+ this.paginatedItems.loading = true;
91
+ this.paginatedItems.items = [];
92
+
93
+ let data = await this.resourceApi.list({
94
+ q: this.searchQuery,
95
+ inTrash: this.inTrash,
96
+ page: this.paginatedItems.currentPage,
97
+ sortField: this.sortField,
98
+ sortOrder: this.sortOrder
99
+ });
100
+
101
+ this.paginatedItems.items = data.items;
102
+ this.paginatedItems.loading = false;
103
+ this.paginatedItems.itemsPerPage = data.itemsPerPage;
104
+ this.paginatedItems.totalItems = data.totalItems;
105
+ },
106
+ // updates a single item
107
+ updateItem(item) {
108
+ this.paginatedItems.items = this.paginatedItems.items.map(i => { return i.id === item.id ? item : i; });
109
+ },
110
+ onSort(field, order) {
111
+ this.sortField = field;
112
+ this.sortOrder = order;
113
+ },
114
+ async restoreItem(id) {
115
+ await this.resourceApi.restoreAndNotify(id);
116
+ await this.fetchData();
117
+ },
118
+ async forceDeleteItem(id) {
119
+ await this.resourceApi.confirmForceDelete(id);
120
+ await this.fetchData();
121
+ },
122
+ async deleteItem(id) {
123
+ await this.resourceApi.deleteAndNotify(id);
124
+ await this.fetchData();
125
+ },
126
+ }
127
+ }
128
+ </script>
129
+
130
+ <style scoped>
131
+ @import "./util.css";
132
+ </style>
@@ -100,7 +100,7 @@ export default {
100
100
  EditButtonOnRowHover, GroupsChooser, GenericEditableField, UserJoined, GroupsList},
101
101
  data() {
102
102
  return {
103
- usersApi: new UsersApi(this.$buefy),
103
+ usersApi: new UsersApi(),
104
104
  selectedUser: null,
105
105
  searchQuery: null,
106
106
  isCreateUserModalActive: false,
@@ -116,7 +116,7 @@ export default {
116
116
  methods: {
117
117
  async fetchData() {
118
118
  this.paginatedItems.loading = true;
119
- let data = await this.usersApi.list(false, this.paginatedItems.currentPage, this.searchQuery);
119
+ let data = await this.usersApi.list({ inTrash: false, page: this.paginatedItems.currentPage, q: this.searchQuery });
120
120
 
121
121
  this.paginatedItems.items = data.items;
122
122
  this.paginatedItems.totalItems = data.totalItems;
@@ -144,7 +144,7 @@ export default {
144
144
  isChangePasswordModalActive: false,
145
145
  isUserPreferencesModalActive: false,
146
146
  authApi: new AuthApi(this.$buefy),
147
- usersApi: new UsersApi(this.$buefy)
147
+ usersApi: new UsersApi()
148
148
  }
149
149
  },
150
150
  methods: {
@@ -45,7 +45,7 @@ export default {
45
45
  return {
46
46
  model: null,
47
47
  loading: true,
48
- usersApi: new UsersApi(this.$buefy)
48
+ usersApi: new UsersApi()
49
49
  }
50
50
  },
51
51
  computed: {
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <b-dropdown ref="dropdown" v-model="selectedIndex" :inline="true" :scrollable="true" class="items" :style="{ top: (top + 30) + 'px', left: (left + 4) + 'px', display: !visible ? 'none !important' : 'inherit' }" @change="index => selectItem(items[index])">
3
+ <b-dropdown-item
4
+ v-for="(item, index) in items"
5
+ :key="index"
6
+ :value="index"
7
+ @click="selectItem(item)"
8
+ >
9
+ <b-icon :icon="item.icon"></b-icon>
10
+ {{ item.title }}
11
+ </b-dropdown-item>
12
+ <!-- </b-dropdown-item>-->
13
+ <b-dropdown-item v-if="items.length === 0">
14
+ No result
15
+ </b-dropdown-item>
16
+ </b-dropdown>
17
+ </template>
18
+
19
+ <script>
20
+ export default {
21
+ props: {
22
+ items: {
23
+ type: Array,
24
+ required: true,
25
+ },
26
+ command: {
27
+ type: Function,
28
+ required: true,
29
+ },
30
+ top: {
31
+ type: Number,
32
+ required: true
33
+ },
34
+ left: {
35
+ type: Number,
36
+ required: true
37
+ },
38
+ visible: {
39
+ type: Boolean,
40
+ required: true
41
+ }
42
+ },
43
+
44
+ data() {
45
+ return {
46
+ selectedIndex: 0,
47
+ }
48
+ },
49
+
50
+ watch: {
51
+ items() {
52
+ this.selectedIndex = 0
53
+ },
54
+ },
55
+
56
+ methods: {
57
+ onKeyDown({ event }) {
58
+ if (event.key === 'ArrowUp') {
59
+ this.upHandler()
60
+ return true
61
+ }
62
+
63
+ if (event.key === 'ArrowDown') {
64
+ this.downHandler()
65
+ return true
66
+ }
67
+
68
+ if (event.key === 'Enter') {
69
+ this.enterHandler()
70
+ return true
71
+ }
72
+
73
+ return false
74
+ },
75
+ upHandler() {
76
+ this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length;
77
+ let el = this.$refs.dropdown.$children[this.selectedIndex].$el;
78
+ el.scrollIntoView({
79
+ block: 'nearest'
80
+ });
81
+ },
82
+ downHandler() {
83
+ this.selectedIndex = (this.selectedIndex + 1) % this.items.length;
84
+ let el = this.$refs.dropdown.$children[this.selectedIndex].$el;
85
+ el.scrollIntoView({
86
+ block: 'nearest'
87
+ });
88
+ },
89
+ enterHandler() {
90
+ const item = this.items[this.selectedIndex];
91
+ this.selectItem(item)
92
+ },
93
+ selectItem(item) {
94
+ if (item) {
95
+ this.command(item)
96
+ }
97
+ },
98
+ },
99
+ }
100
+ </script>
101
+
102
+ <style lang="scss">
103
+ .items {
104
+ left: 10;
105
+ position: absolute;
106
+ z-index: 9999;
107
+ }
108
+ </style>