@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,217 @@
1
+ <template>
2
+ <NodeViewWrapper class="wrapper" :class="{editable: isEditable, selected: isEditable && selectedOrEditorFocused}">
3
+ <div v-if="isEditable" class="toolbar">
4
+ <div class="my-field has-addons" style="width: 100%">
5
+ <p v-if="!partial" class="control">
6
+ <b-button size="is-small" @click="partialChooserActive = true">Choose an existing partial</b-button>
7
+ </p>
8
+ <p v-if="!partial" class="control">
9
+ <b-button size="is-small" @click="createPartialModalActive = true">Create from scratch</b-button>
10
+ </p>
11
+ <div class="is-flex-grow-1"></div>
12
+ <p v-if="partial" class="control">
13
+ <b-button size="is-small" type="is-dark" @click="partialChooserActive = true">{{ partial.key }}</b-button>
14
+ </p>
15
+ <!-- <p class="control">-->
16
+ <!-- <b-button size="is-small" data-drag-handle type="is-dark" icon-left="grip-vertical"></b-button>-->
17
+ <!-- </p>-->
18
+ <p v-if="partial" class="control">
19
+ <b-button size="is-small" :disabled="!saveButtonEnabled" icon-left="save" type="is-dark" @click="savePartial"></b-button>
20
+ </p>
21
+ <p v-if="partial" class="control">
22
+ <b-dropdown position="is-bottom-left">
23
+ <template #trigger>
24
+ <b-button icon-left="sliders-h" size="is-small" type="is-dark"></b-button>
25
+ </template>
26
+ <div class="modal-card">
27
+ <header class="modal-card-head">
28
+ <p class="modal-card-title">Edit Partial Settings</p>
29
+ </header>
30
+ <div v-if="partial" class="modal-card-body">
31
+ <!-- <h2></h2>-->
32
+ <b-field label="Title" label-position="on-border">
33
+ <b-input :value="partial.title" @input="v => partial.title = v"></b-input>
34
+ </b-field>
35
+ <br>
36
+ <b-field label="Slug" label-position="on-border">
37
+ <b-input :value="partial.key" @input="v => partial.key = v"></b-input>
38
+ </b-field>
39
+ </div>
40
+ </div>
41
+ </b-dropdown>
42
+ </p>
43
+ <p class="control"><b-button icon-left="trash" size="is-small" type="is-danger" @click="removeSelf"></b-button></p>
44
+ </div>
45
+ </div>
46
+ <b-modal :active="partialChooserActive" has-modal-card aria-role="dialog" aria-modal auto-focus @update:active="(v) => partialChooserActive = v">
47
+ <div class="modal-card">
48
+ <header class="modal-card-head">
49
+ <p class="modal-card-title">Choose a partial to use</p>
50
+ </header>
51
+ <section class="modal-card-body">
52
+ <PartialList :in-trash="false" search-query="" @choose="selectPartial" />
53
+ </section>
54
+ <footer class="modal-card-foot"></footer>
55
+ </div>
56
+ </b-modal>
57
+ <b-modal :active="createPartialModalActive" has-modal-card aria-role="dialog" aria-modal auto-focus @update:active="(v) => createPartialModalActive = v">
58
+ <div class="modal-card">
59
+ <header class="modal-card-head">
60
+ <p class="modal-card-title">Partial</p>
61
+ </header>
62
+ <section class="modal-card-body">
63
+ <p>A partial represents a fragment of reusable content which can be included in multiple places and/or on multiple pages.</p>
64
+ <hr>
65
+ <b-field label="Title" label-position="on-border">
66
+ <b-input v-model="title" />
67
+ </b-field>
68
+ <br>
69
+ <b-field label="Slug" label-position="on-border">
70
+ <b-input v-model="slug" />
71
+ </b-field>
72
+ </section>
73
+ <footer class="modal-card-foot is-flex">
74
+ <div class="is-flex-grow-1"></div>
75
+ <b-button @click="createPartialModalActive = false">Close</b-button>
76
+ <b-button type="is-success" @click="createPartial">
77
+ Create Partial
78
+ </b-button>
79
+ </footer>
80
+ </div>
81
+ </b-modal>
82
+ <ContentEditor v-if="partial !== null" ref="editor" :editable="isEditable" :content="partial.richContent" class="editor-content" @update:content="updateContent" />
83
+ <em v-else-if="!isEditable">Unlinked partial block...</em>
84
+ </NodeViewWrapper>
85
+ </template>
86
+
87
+ <script>
88
+ import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-2'
89
+ import ContentEditor from "./ContentEditor.vue";
90
+ import PartialsApi from "../../PartialsApi.js";
91
+ import PartialList from "../PartialList.vue";
92
+
93
+ export default {
94
+ name: "PartialNodeView",
95
+ components: {ContentEditor, NodeViewWrapper, PartialList },
96
+ props: nodeViewProps,
97
+ data() {
98
+ return {
99
+ partialsApi: new PartialsApi(),
100
+ partialChooserActive: false,
101
+ createPartialModalActive: false,
102
+ partial: null,
103
+ serverPartial: null,
104
+ slug: 'layout.nav',
105
+ title: 'Navigation',
106
+ }
107
+ },
108
+ computed: {
109
+ isEditable() {
110
+ return this.editor.isEditable;
111
+ },
112
+ saveButtonEnabled() {
113
+ return this.partial && (JSON.stringify(this.partial) !== JSON.stringify(this.serverPartial));
114
+ },
115
+ selectedOrEditorFocused() {
116
+ return this.selected || (this.$refs.editor && this.$refs.editor.editor.isFocused);
117
+ }
118
+ },
119
+ watch: {
120
+ 'node': 'loadPartial'
121
+ },
122
+ mounted() {
123
+ this.loadPartial()
124
+ },
125
+ methods: {
126
+ async loadPartial() {
127
+ if(this.node.attrs.id === null) {
128
+ return;
129
+ }
130
+ let response = await this.partialsApi.get(this.node.attrs.id);
131
+ this.selectPartial(response.item);
132
+ },
133
+ removeSelf() {
134
+ this.deleteNode();
135
+ },
136
+ async createPartial() {
137
+ let response = await this.partialsApi.create({
138
+ key: this.slug,
139
+ title: this.title
140
+ });
141
+ if(!response.item) {
142
+ return;
143
+ }
144
+ this.createPartialModalActive = false;
145
+ this.selectPartial(response.item);
146
+ },
147
+ selectPartial(partial) {
148
+ console.log('select partial', partial);
149
+ this.partial = { ...partial };
150
+ this.serverPartial = { ... partial };
151
+ this.partialChooserActive = false;
152
+ this.updateAttributes({ id: this.partial.id });
153
+ // let rect = this.$el.getBoundingClientRect();
154
+ // console.log(rect);
155
+ // this.absTop = rect.top;
156
+ // this.absLeft = rect.left;
157
+ // this.absWidth = rect.width;
158
+ },
159
+ updateContent(newContent) {
160
+ this.partial.richContent = newContent;
161
+ },
162
+ async savePartial() {
163
+ // force update the content
164
+ this.$refs.editor.onUpdate();
165
+
166
+ let res = await this.partialsApi.update(this.partial);
167
+ this.partial = { ... res.item };
168
+ this.serverPartial = { ... res.item };
169
+ }
170
+ }
171
+ }
172
+ </script>
173
+
174
+ <style scoped>
175
+ .wrapper {
176
+ position: relative;
177
+ background-color: #555;
178
+ }
179
+ .wrapper.editable {
180
+ outline: 2px dashed purple;
181
+ padding-top: 30px;
182
+ }
183
+ .wrapper.selected {
184
+ outline-style: solid;
185
+ }
186
+
187
+ ::v-deep(.editor > .editor-content > .ProseMirror) {
188
+ min-height: 4rem;
189
+ }
190
+
191
+ .toolbar {
192
+ display: flex;
193
+ z-index: 11;
194
+ position: absolute;
195
+ top: 0;
196
+ left: 0;
197
+ width: 100%;
198
+ }
199
+
200
+ .editor-content {
201
+ position: relative;
202
+ }
203
+
204
+ p.control {
205
+ margin-bottom: 0;
206
+ }
207
+
208
+ .my-field {
209
+ display: flex;
210
+ }
211
+ </style>
212
+
213
+ <style>
214
+ .my-field > .control > .button {
215
+ border-radius: 0;
216
+ }
217
+ </style>
@@ -0,0 +1,72 @@
1
+ import { Extension } from '@tiptap/core'
2
+ import Suggestion from '@tiptap/suggestion'
3
+ import {items} from './suggestion'
4
+
5
+ export default Extension.create({
6
+ name: 'commands',
7
+
8
+ addOptions() {
9
+ return {
10
+ suggestion: {
11
+ char: '/',
12
+ allowSpaces: true,
13
+ command: ({editor, range, props}) => {
14
+ let command = editor.chain().focus().deleteRange(range);
15
+ command = props.command({ editor, range, command });
16
+ command.run();
17
+ },
18
+ items: items,
19
+ render: function() {
20
+ let editorComponent
21
+ return {
22
+ onStart: (props) => {
23
+ editorComponent = props.editor.editorComponent;
24
+ if(!props.clientRect)
25
+ {
26
+ editorComponent.commandsListVisible = false;
27
+ return;
28
+ }
29
+
30
+ let rect = props.clientRect();
31
+ editorComponent.commandsListVisible = true;
32
+ editorComponent.commandsListTop = rect.top;
33
+ editorComponent.commandsListLeft = rect.left;
34
+ editorComponent.commandsListItems = props.items;
35
+ editorComponent.commandsListCommand = props.command;
36
+ },
37
+
38
+ onUpdate(props) {
39
+ let rect = props.clientRect();
40
+ editorComponent.commandsListTop = rect.top;
41
+ editorComponent.commandsListLeft = rect.left;
42
+ editorComponent.commandsListItems = props.items;
43
+ editorComponent.commandsListCommand = props.command;
44
+ },
45
+
46
+ onKeyDown(props) {
47
+ if (props.event.key === 'Escape') {
48
+ editorComponent.commandsListVisible = false;
49
+ return true;
50
+ }
51
+ return editorComponent.$refs['commandsList'].onKeyDown(props);
52
+ },
53
+
54
+ onExit() {
55
+ editorComponent.commandsListVisible = false;
56
+ },
57
+ }
58
+ },
59
+ },
60
+ }
61
+ },
62
+
63
+ addProseMirrorPlugins() {
64
+ this.editor.editorComponent = this.options.editorComponent;
65
+ return [
66
+ Suggestion({
67
+ editor: this.editor,
68
+ ...this.options.suggestion,
69
+ }),
70
+ ];
71
+ },
72
+ })
@@ -0,0 +1,211 @@
1
+ export function items({ query, canConvert }) {
2
+ let items = [
3
+ {
4
+ title: 'Paragraph',
5
+ icon: 'paragraph',
6
+ canConvert: true,
7
+ node: ({editor}) => {
8
+ return editor.schema.node('paragraph');
9
+ },
10
+ command: ({ command }) => {
11
+ return command.setParagraph();
12
+ },
13
+ },
14
+ {
15
+ title: 'H1',
16
+ icon: 'heading',
17
+ canConvert: true,
18
+ node: ({editor}) => {
19
+ return editor.schema.node('heading', {level: 1});
20
+ },
21
+ command: ({ command }) => {
22
+ return command.setParagraph().setNode('heading', {level: 1});
23
+ },
24
+ },
25
+ {
26
+ title: 'H2',
27
+ icon: 'heading',
28
+ canConvert: true,
29
+ node: ({editor}) => {
30
+ return editor.schema.node('heading', {level: 2});
31
+ },
32
+ command: ({ command }) => {
33
+ return command.setParagraph().setNode('heading', {level: 2});
34
+ },
35
+ },
36
+ {
37
+ title: 'H3',
38
+ icon: 'heading',
39
+ canConvert: true,
40
+ node: ({editor}) => {
41
+ return editor.schema.node('heading', {level: 3});
42
+ },
43
+ command: ({ command }) => {
44
+ return command.setParagraph().setNode('heading', {level: 3});
45
+ },
46
+ },
47
+ {
48
+ title: 'Blockquote',
49
+ icon: 'quote-left',
50
+ canConvert: true,
51
+ node: ({editor}) => {
52
+ return editor.schema.node('blockquote', {}, [editor.schema.node('paragraph', {}, [])]);
53
+ },
54
+ command: ({ command }) => {
55
+ return command.setParagraph().setBlockquote();
56
+ },
57
+ },
58
+ {
59
+ title: 'Ordered List',
60
+ icon: 'list-ol',
61
+ canConvert: true,
62
+ node: ({editor}) => {
63
+ return editor.schema.node('orderedList', {}, [editor.schema.node('listItem', {}, [editor.schema.node('paragraph', {}, [])])]);
64
+ },
65
+ command: ({ command }) => {
66
+ return command.setParagraph().toggleOrderedList();
67
+ },
68
+ },
69
+ {
70
+ title: 'Unordered List',
71
+ icon: 'list-ul',
72
+ canConvert: true,
73
+ node: ({editor}) => {
74
+ return editor.schema.node('bulletList', {}, [editor.schema.node('listItem', {}, [editor.schema.node('paragraph', {}, [])])]);
75
+ },
76
+ command: ({ command }) => {
77
+ return command.setParagraph().toggleBulletList();
78
+ },
79
+ },
80
+ {
81
+ title: 'Horizontal Rule',
82
+ icon: 'ruler-horizontal',
83
+ canConvert: true,
84
+ node: ({editor}) => {
85
+ return editor.schema.node('horizontalRule');
86
+ },
87
+ command: ({ command }) => {
88
+ return command.setParagraph().setHorizontalRule();
89
+ },
90
+ },
91
+ // {
92
+ // title: 'Bold',
93
+ // icon: 'bold',
94
+ // command: ({ editor, range }) => {
95
+ // editor
96
+ // .chain()
97
+ // .focus()
98
+ // .deleteRange(range)
99
+ // .setMark('bold')
100
+ // .run()
101
+ // },
102
+ // },
103
+ // {
104
+ // title: 'Italic',
105
+ // icon: 'italic',
106
+ // command: ({ editor, range }) => {
107
+ // editor
108
+ // .chain()
109
+ // .focus()
110
+ // .deleteRange(range)
111
+ // .setMark('italic')
112
+ // .run()
113
+ // },
114
+ // },
115
+ {
116
+ title: 'Media',
117
+ icon: 'photo-video',
118
+ canConvert: false,
119
+ node: ({editor}) => {
120
+ return editor.schema.node('mediaItem');
121
+ },
122
+ command: ({ command }) => {
123
+ return command
124
+ .insertContent({
125
+ type: 'mediaItem'
126
+ });
127
+ },
128
+ },
129
+ {
130
+ title: 'Raw HTML',
131
+ icon: 'code',
132
+ canConvert: false,
133
+ node: ({editor}) => {
134
+ return editor.schema.node('rawHtml');
135
+ },
136
+ command: ({ command}) => {
137
+ return command
138
+ .insertContent({
139
+ type: 'rawHtml'
140
+ });
141
+ },
142
+ },
143
+ {
144
+ title: 'Grid Row',
145
+ icon: 'th-large',
146
+ canConvert: false,
147
+ node: ({editor}) => {
148
+ return editor.schema.node('gridRow');
149
+ },
150
+ command: ({ command }) => {
151
+ return command
152
+ .insertContent({
153
+ type: 'gridRow'
154
+ });
155
+ },
156
+ },
157
+ {
158
+ title: 'Partial',
159
+ icon: 'puzzle-piece',
160
+ canConvert: false,
161
+ node: ({editor}) => {
162
+ return editor.schema.node('partial');
163
+ },
164
+ command: ({ command }) => {
165
+ return command
166
+ .insertContent({
167
+ type: 'partial'
168
+ });
169
+ },
170
+ },
171
+ {
172
+ title: 'Link to Page',
173
+ icon: 'file-alt',
174
+ canConvert: false,
175
+ node: ({editor}) => {
176
+ return editor.schema.node('objectLink', { type: 'page', id: null, content: null});
177
+ },
178
+ command: ({ command }) => {
179
+ return command
180
+ .insertContent({
181
+ type: 'objectLink',
182
+ attrs: {
183
+ type: 'page', id: null, content: null
184
+ }
185
+ });
186
+ },
187
+ },
188
+ {
189
+ title: 'Link to Media',
190
+ icon: 'file-image',
191
+ canConvert: false,
192
+ node: ({editor}) => {
193
+ return editor.schema.node('objectLink', { type: 'media', id: null, content: null});
194
+ },
195
+ command: ({ command }) => {
196
+ return command
197
+ .insertContent({
198
+ type: 'objectLink',
199
+ attrs: {
200
+ type: 'media', id: null, content: null
201
+ }
202
+ });
203
+ },
204
+ },
205
+ ];
206
+ items = items.filter(item => item.title.toLowerCase().includes(query.toLowerCase()));
207
+ if(canConvert) {
208
+ items = items.filter(item => item.canConvert);
209
+ }
210
+ return items;
211
+ }
@@ -67,7 +67,7 @@ export default {
67
67
  },
68
68
  data() {
69
69
  return {
70
- mediaDirectoryApi: new MediaDirectoryApi(this.$buefy),
70
+ mediaDirectoryApi: new MediaDirectoryApi(),
71
71
  newDirectory: null,
72
72
  isLoading: false,
73
73
  searchQuery: '',
@@ -92,7 +92,7 @@ export default {
92
92
  methods: {
93
93
  async fetchData() {
94
94
  this.isLoading = true;
95
- let data = await this.mediaDirectoryApi.list(false, 1, null);
95
+ let data = await this.mediaDirectoryApi.list({ inTrash: false, page: 1, q: null });
96
96
  this.directoriesList = data.items;
97
97
  this.directoriesList.sort((a, b) => {
98
98
  return getDirectoryPathString(a).localeCompare(getDirectoryPathString(b))
@@ -57,7 +57,7 @@ export default {
57
57
  isEditModalActive: false,
58
58
  newName: '',
59
59
  newSlug: '',
60
- mediaDirectoryApi: new MediaDirectoryApi(this.$buefy)
60
+ mediaDirectoryApi: new MediaDirectoryApi()
61
61
  }
62
62
  },
63
63
  computed: {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <b-modal :active.sync="active" trap-focus has-modal-card aria-role="dialog" aria-modal auto-focus width="80%" class="media-insert-modal">
2
+ <b-modal :active="active" trap-focus has-modal-card aria-role="dialog" aria-modal auto-focus width="80%" class="media-insert-modal" @update:active="updateActive">
3
3
  <div class="modal-card">
4
4
  <header class="modal-card-head">
5
5
  <slot name="title"><p class="modal-card-title">Choose an item to insert</p></slot>
@@ -9,7 +9,7 @@
9
9
  </section>
10
10
  <footer class="modal-card-foot is-flex">
11
11
  <div class="is-flex-grow-1"></div>
12
- <b-button @click="emitClose">Close</b-button>
12
+ <b-button @click="emitClose">{{ closeVerb }}</b-button>
13
13
  <b-button :disabled="selectedFiles.length === 0 || (!multiselectAllowed && selectedFiles.length > 1)" type="is-primary" @click="doInsert">
14
14
  <span v-if="selectedFiles.length === 0">{{ actionVerb }}</span>
15
15
  <span v-else-if="selectedFiles.length === 1">{{actionVerb }} item</span>
@@ -36,6 +36,10 @@ export default {
36
36
  actionVerb: {
37
37
  type: String,
38
38
  default: 'Insert'
39
+ },
40
+ closeVerb: {
41
+ type: String,
42
+ default: 'Close'
39
43
  }
40
44
  },
41
45
  data() {
@@ -62,6 +66,11 @@ export default {
62
66
  },
63
67
  doInsert() {
64
68
  this.$emit('select', this.selectedFiles);
69
+ },
70
+ updateActive(newVal) {
71
+ if(!newVal) {
72
+ this.emitClose();
73
+ }
65
74
  }
66
75
  }
67
76
  }
@@ -185,7 +185,7 @@ export default {
185
185
  versions: [],
186
186
  caption: null,
187
187
  description: null,
188
- mediaApi: new MediaApi(this.$buefy),
188
+ mediaApi: new MediaApi(),
189
189
  Internationalize
190
190
  }
191
191
  },
@@ -1,19 +1,22 @@
1
1
  <template>
2
2
  <div class="media-icon-container">
3
- <img v-if="item.type === TYPE_IMAGE && !loadingError" :src="'/content/media/' + item.filename" :alt="item.caption ? item.caption : item.name" @error="loadingFailed">
3
+ <img v-if="item.type === TYPE_IMAGE && !loadingError" :src="getApiHost() + 'content/media/' + item.filename" :alt="item.caption ? item.caption : item.name" @error="loadingFailed">
4
4
  <b-tooltip v-else-if="item.type === TYPE_IMAGE" :label="missingMessage" type="is-dark" position="is-bottom" multilined>
5
5
  <b-icon type="is-danger" icon="file-image" size="is-large" class="media-icon"></b-icon>
6
6
  </b-tooltip>
7
- <img v-if="item.type === TYPE_DOCUMENT && !loadingError" :src="'/oxygen/api/media/' + item.id + '/preview'" alt="PDF preview" @error="loadingFailed">
7
+ <img v-if="item.type === TYPE_DOCUMENT && !loadingError" :src="getApiRoot() + 'media/' + item.id + '/preview'" alt="PDF preview" @error="loadingFailed">
8
8
  <b-tooltip v-else-if="item.type === TYPE_DOCUMENT" :label="missingMessage" type="is-dark" position="is-bottom" multilined>
9
9
  <b-icon type="is-danger" icon="file-pdf" size="is-large" class="media-icon"></b-icon>
10
10
  </b-tooltip>
11
11
  <b-icon v-else-if="item.type === TYPE_AUDIO" icon="music" size="is-large" class="media-icon"></b-icon>
12
+ <div class="media-icon-toolbar"><slot></slot></div>
12
13
  </div>
13
14
  </template>
14
15
 
15
16
  <script>
16
17
  import MediaApi from "../../MediaApi";
18
+ import {getApiRoot} from "../../CrudApi.js";
19
+ import {getApiHost} from "../../api.js";
17
20
 
18
21
  export default {
19
22
  name: "MediaItemPreview",
@@ -26,13 +29,19 @@ export default {
26
29
  },
27
30
  data() {
28
31
  return {
32
+ getApiHost: getApiHost,
33
+ getApiRoot: getApiRoot,
29
34
  TYPE_IMAGE: MediaApi.TYPE_IMAGE,
30
35
  TYPE_AUDIO: MediaApi.TYPE_AUDIO,
31
36
  TYPE_DOCUMENT: MediaApi.TYPE_DOCUMENT,
32
37
  loadingError: false
33
38
  }
34
39
  },
40
+ watch: { 'item': 'resetLoadingFailed' },
35
41
  methods: {
42
+ resetLoadingFailed() {
43
+ this.loadingError = false;
44
+ },
36
45
  loadingFailed() {
37
46
  this.loadingError = true;
38
47
  }
@@ -42,4 +51,11 @@ export default {
42
51
 
43
52
  <style scoped lang="scss">
44
53
  @import './media.scss';
54
+
55
+ .media-icon-toolbar {
56
+ z-index: 10;
57
+ position: absolute;
58
+ top: 1rem;
59
+ right: 1rem;
60
+ }
45
61
  </style>
@@ -1,5 +1,4 @@
1
1
  <template>
2
-
3
2
  <div class="full-height full-height-container media-container">
4
3
  <div class="top-bar">
5
4
  <div v-if="!paginatedItems.loading && !inTrash && !searchQuery" class="breadcrumb">
@@ -20,7 +19,7 @@
20
19
  <b-button outlined rounded icon-left="arrow-left" class="action-bar-pad" @click="navigateTo({ currentPath: '' })">All Items</b-button>
21
20
  <div class="title action-bar-pad">Search results for "{{ searchQuery }}"</div>
22
21
  </div>
23
- <ul v-else>
22
+ <ul v-else class="breadcrumb">
24
23
  <li><b-skeleton :animated="true" size="is-large" :width="100"></b-skeleton></li>
25
24
  </ul>
26
25
 
@@ -140,8 +139,8 @@ export default {
140
139
  isCreateDirectoryModalActive: false,
141
140
  newDirectoryName: '',
142
141
  searchDebounce: null,
143
- mediaApi: new MediaApi(this.$buefy),
144
- mediaDirectoryApi: new MediaDirectoryApi(this.$buefy),
142
+ mediaApi: new MediaApi(),
143
+ mediaDirectoryApi: new MediaDirectoryApi(),
145
144
  getDirectoryBreadcrumbItems: getDirectoryBreadcrumbItems
146
145
  }
147
146
  },
@@ -174,7 +173,7 @@ export default {
174
173
  this.paginatedItems.files = []
175
174
  this.paginatedItems.directories = [];
176
175
 
177
- let data = await this.mediaApi.list(this.inTrash, this.searchQuery, this.paginatedItems.currentPage, this.searchQuery ? '' : this.currentPath);
176
+ let data = await this.mediaApi.list({ inTrash: this.inTrash, q: this.searchQuery, page: this.paginatedItems.currentPage, path: this.searchQuery ? '' : this.currentPath });
178
177
 
179
178
  this.paginatedItems.currentDirectory = data.currentDirectory;
180
179
  this.paginatedItems.directories = data.directories.map(dir => { dir.selected = false; return dir; });
@@ -56,7 +56,7 @@ export default {
56
56
  return {
57
57
  filesToUpload: [],
58
58
  isUploading: false,
59
- mediaApi: new MediaApi(this.$buefy)
59
+ mediaApi: new MediaApi()
60
60
  }
61
61
  },
62
62
  computed: {