@oxygen-cms/ui 1.5.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 (84) hide show
  1. package/.babelrc +1 -0
  2. package/.eslintrc.js +22 -0
  3. package/.github/workflows/node.js.yml +29 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/ui.iml +10 -0
  6. package/.jshintrc +3 -0
  7. package/README.md +7 -0
  8. package/assets/oxygen-icon.png +0 -0
  9. package/jest.init.js +1 -0
  10. package/package.json +72 -0
  11. package/src/AuthApi.js +116 -0
  12. package/src/CrudApi.js +112 -0
  13. package/src/EventsApi.js +16 -0
  14. package/src/GroupsApi.js +9 -0
  15. package/src/Internationalize.js +31 -0
  16. package/src/MediaApi.js +52 -0
  17. package/src/MediaDirectoryApi.js +62 -0
  18. package/src/PreferencesApi.js +47 -0
  19. package/src/UserPermissions.js +66 -0
  20. package/src/UserPreferences.js +69 -0
  21. package/src/UserPreferences.test.js +23 -0
  22. package/src/UsersApi.js +41 -0
  23. package/src/api.js +209 -0
  24. package/src/components/App.vue +61 -0
  25. package/src/components/AuthenticatedLayout.vue +254 -0
  26. package/src/components/AuthenticationLog.vue +196 -0
  27. package/src/components/CodeEditor.vue +90 -0
  28. package/src/components/EditButtonOnRowHover.vue +21 -0
  29. package/src/components/Error404.vue +25 -0
  30. package/src/components/EventsChooser.vue +88 -0
  31. package/src/components/EventsTable.vue +82 -0
  32. package/src/components/GenericEditableField.vue +74 -0
  33. package/src/components/GroupsChooser.vue +58 -0
  34. package/src/components/GroupsList.vue +129 -0
  35. package/src/components/ImportExport.vue +45 -0
  36. package/src/components/LegacyPage.vue +256 -0
  37. package/src/components/UserJoined.vue +35 -0
  38. package/src/components/UserManagement.vue +168 -0
  39. package/src/components/UserProfileForm.vue +214 -0
  40. package/src/components/ViewProfile.vue +32 -0
  41. package/src/components/auth/Auth404.vue +16 -0
  42. package/src/components/auth/Login.vue +135 -0
  43. package/src/components/auth/LoginLogo.vue +30 -0
  44. package/src/components/auth/Logout.vue +26 -0
  45. package/src/components/auth/PasswordRemind.vue +71 -0
  46. package/src/components/auth/PasswordReset.vue +97 -0
  47. package/src/components/auth/TwoFactorSetup.vue +115 -0
  48. package/src/components/auth/VerifyEmail.vue +71 -0
  49. package/src/components/auth/WelcomeFloat.vue +87 -0
  50. package/src/components/auth/login.scss +17 -0
  51. package/src/components/media/MediaChooseDirectory.vue +129 -0
  52. package/src/components/media/MediaDirectory.vue +109 -0
  53. package/src/components/media/MediaInsertModal.vue +88 -0
  54. package/src/components/media/MediaItem.vue +282 -0
  55. package/src/components/media/MediaItemPreview.vue +45 -0
  56. package/src/components/media/MediaList.vue +305 -0
  57. package/src/components/media/MediaPage.vue +44 -0
  58. package/src/components/media/MediaResponsiveImages.vue +51 -0
  59. package/src/components/media/MediaUpload.vue +133 -0
  60. package/src/components/media/media.scss +51 -0
  61. package/src/components/preferences/PreferencesAdminAppearance.vue +22 -0
  62. package/src/components/preferences/PreferencesAuthentication.vue +27 -0
  63. package/src/components/preferences/PreferencesEventTemplates.vue +22 -0
  64. package/src/components/preferences/PreferencesField.vue +215 -0
  65. package/src/components/preferences/PreferencesList.vue +50 -0
  66. package/src/components/preferences/PreferencesPageTemplates.vue +23 -0
  67. package/src/components/preferences/PreferencesSiteAppearance.vue +22 -0
  68. package/src/components/preferences/PreferencesThemeChooser.vue +73 -0
  69. package/src/components/preferences/ShowIfPermitted.vue +37 -0
  70. package/src/components/preferences/UserPreferences.vue +30 -0
  71. package/src/components/users/CreateUserModal.vue +73 -0
  72. package/src/components/util.css +47 -0
  73. package/src/icons.js +90 -0
  74. package/src/main.js +112 -0
  75. package/src/modules/LegacyPages.js +18 -0
  76. package/src/modules/Media.js +45 -0
  77. package/src/modules/UserManagement.js +24 -0
  78. package/src/routes/index.js +92 -0
  79. package/src/store/index.js +70 -0
  80. package/src/styles/_variables.scss +23 -0
  81. package/src/styles/app.scss +76 -0
  82. package/src/unsavedChanges.js +16 -0
  83. package/src/util.js +65 -0
  84. package/src/util.test.js +39 -0
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <div class="box container is-wider two-factor-setup">
3
+ <LoginLogo />
4
+
5
+ <transition name="fade" mode="out-in">
6
+ <div v-if="twoFactorQRCode">
7
+ <h1 class="subtitle has-text-centered">
8
+ You need to set up two-factor authentication for this account.
9
+ </h1>
10
+
11
+ <div class="content">
12
+ <p>Two-factor authentication uses once-off codes from another device (e.g.: your phone) for additional security. To use two-factor authentication with Oxygen CMS, you need to download an authenticator app onto your phone.</p>
13
+ <p>Supported apps include:</p>
14
+ <ul>
15
+ <li>Google Authenticator</li>
16
+ <li><a href="https://lastpass.com/auth/" target="_blank">LastPass Authenticator</a></li>
17
+ <li><a href="https://www.microsoft.com/en-us/account/authenticator" target="_blank">Microsoft Authenticator</a></li>
18
+ <li><a href="https://authy.com/features/" target="_blank">Authy</a></li>
19
+ </ul>
20
+ </div>
21
+
22
+ <h2 class="subtitle">Getting started</h2>
23
+ <p>Scan the QR code below on your phone to begin setting up two-factor authentication, or follow <a :href="twoFactorUri">this link</a>.</p>
24
+
25
+ <div class="qr-code">
26
+ <div v-html="twoFactorQRCode"></div>
27
+ </div>
28
+
29
+ <p>Or, manually enter this secret key into your chosen authenticator app:<br><br>
30
+ <pre>{{ twoFactorString }}</pre></p>
31
+
32
+ <br>
33
+ <h2 class="subtitle">Confirm a two-factor code to continue</h2>
34
+ <p>Once you've successfully configured your authenticator app, enter a valid code below to continue.</p>
35
+ <br>
36
+
37
+ <div class="level">
38
+ <div class="level-left">
39
+ <b-field label="2FA Code" label-position="inside" :type="failedValidation ? 'is-danger' : ''">
40
+ <b-input v-model="totpCode" name="2fa_code" type="number" autofocus minlength="6" required placeholder="enter code here" @keyup.enter.native="confirm2FA"></b-input>
41
+ </b-field>
42
+ </div>
43
+ <div class="level-right">
44
+ <b-button type="is-primary" :loading="submitting" @click="confirm2FA">Confirm</b-button>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div v-else style="height=20rem;"></div>
49
+ </transition>
50
+
51
+ <b-loading :active="!twoFactorQRCode" :is-full-page="false"></b-loading>
52
+
53
+ </div>
54
+ </template>
55
+
56
+ <script>
57
+ import AuthApi from "../../AuthApi";
58
+ import LoginLogo from "./LoginLogo.vue";
59
+ import {morphToNotification} from "../../api";
60
+
61
+ export default {
62
+ name: "TwoFactorSetup",
63
+ components: { LoginLogo },
64
+ data() {
65
+ return {
66
+ twoFactorUri: null,
67
+ twoFactorString: null,
68
+ twoFactorQRCode: null,
69
+ totpCode: '',
70
+ failedValidation: false,
71
+ submitting: false,
72
+ authApi: new AuthApi(this.$buefy)
73
+ }
74
+ },
75
+ async created() {
76
+ try {
77
+ let data = await this.authApi.setupTwoFactorAuth();
78
+ this.twoFactorUri = data.as_uri;
79
+ this.twoFactorString = data.as_string;
80
+ this.twoFactorQRCode = data.as_qr_code;
81
+ } catch(e) {
82
+ console.log('Already set up?');
83
+ }
84
+ },
85
+ methods: {
86
+ async confirm2FA() {
87
+ try {
88
+ this.submitting = true;
89
+ let response = await this.authApi.confirmTwoFactorAuth(this.totpCode);
90
+ this.$buefy.notification.open(morphToNotification(response));
91
+ this.$router.push('/');
92
+ } catch(e) {
93
+ this.failedValidation = true;
94
+ }
95
+ this.submitting = false;
96
+ }
97
+ }
98
+ }
99
+ </script>
100
+
101
+ <style scoped lang="scss">
102
+ @import './login.scss';
103
+
104
+ .qr-code {
105
+ padding: 1rem 0;
106
+ }
107
+
108
+ .qr-code ::v-deep .b-skeleton {
109
+ max-width: 200px;
110
+ }
111
+
112
+ .two-factor-setup {
113
+ position: relative;
114
+ }
115
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div class="box container">
3
+ <LoginLogo />
4
+
5
+ <h1 class="subtitle has-text-centered">
6
+ You need to verify your email address to continue
7
+ </h1>
8
+
9
+ <transition name="fade">
10
+ <div v-if="sent" class="content">
11
+ <p>An email has been sent to your inbox with a confirmation link. You need to open the link to continue.</p>
12
+ <p>If you don't receive the email in the next few minutes, we can try and send it again.</p>
13
+ <b-button type="is-link" @click="send">Retry</b-button>
14
+ </div>
15
+ <div v-else-if="sendFailure" class="content">
16
+ <p>The email failed to send.</p>
17
+ <b-button type="is-link" @click="send">Retry</b-button>
18
+ </div>
19
+ <div v-else class="content has-text-centered">
20
+ <p>Sending verification email...</p>
21
+ <b-progress ></b-progress>
22
+ </div>
23
+ </transition>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ import LoginLogo from './LoginLogo.vue';
29
+ import AuthApi from "../../AuthApi";
30
+
31
+ export default {
32
+ name: "VerifyEmail",
33
+ components: { LoginLogo },
34
+ data() {
35
+ return {
36
+ authApi: new AuthApi(this.$buefy),
37
+ sent: false,
38
+ sendFailure: false
39
+ }
40
+ },
41
+ async created() {
42
+ await this.send()
43
+ },
44
+ methods: {
45
+ async send() {
46
+ this.sent = false;
47
+ try {
48
+ await this.authApi.sendEmailVerification();
49
+ this.sent = true;
50
+ } catch(e) {
51
+ if(e.response && e.response.code === 'already_verified') {
52
+ this.$buefy.notification.open({
53
+ message: 'Email address already verified',
54
+ type: 'is-info',
55
+ queue: false,
56
+ duration: 4000
57
+ });
58
+ await this.$router.push({path: '/'});
59
+ } else {
60
+ this.sendFailure = true;
61
+ throw e;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <style scoped>
70
+ @import './login.scss';
71
+ </style>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <!-- {{ Preferencesimport Vuex from 'vuex';::get('appearance.auth::theme', 'autumn') }}-->
3
+ <div class="login-fullscreen">
4
+ <b-loading :active="theme === null"></b-loading>
5
+ <transition name="fade">
6
+ <div v-if="theme !== null" :class="'login-background login-theme-' + theme">
7
+ <transition name="slide-left" mode="out-in">
8
+ <router-view></router-view>
9
+ </transition>
10
+ </div>
11
+ </transition>
12
+
13
+
14
+ </div>
15
+ </template>
16
+
17
+ <script>
18
+ import AuthApi from "../../AuthApi";
19
+
20
+ export default {
21
+ name: "WelcomeFloat",
22
+ data() {
23
+ return {
24
+ theme: null,
25
+ authApi: new AuthApi(this.$buefy)
26
+ }
27
+ },
28
+ async created() {
29
+ let preferences = await this.authApi.getLoginPreferences();
30
+ this.theme = preferences.theme;
31
+ }
32
+ }
33
+ </script>
34
+
35
+ <style scoped>
36
+ .login-fullscreen {
37
+ height: 100%;
38
+ overflow: auto;
39
+ }
40
+
41
+ .login-background {
42
+ min-height: 100%;
43
+ background-attachment: fixed;
44
+ background-size: cover;
45
+ display: flex;
46
+ flex-direction: column;
47
+ justify-content: center;
48
+ overflow-y: auto;
49
+ }
50
+
51
+ .login-theme-autumn {
52
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/autumn-min.jpg');
53
+ }
54
+
55
+ .login-theme-city {
56
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/city-min.jpg');
57
+ }
58
+
59
+ .login-theme-clouds {
60
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/clouds-min.jpg');
61
+ }
62
+
63
+ .login-theme-coast {
64
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/coast-min.jpg');
65
+ }
66
+
67
+ .login-theme-speckles {
68
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/speckles-min.jpg');
69
+ }
70
+
71
+ .login-theme-trees {
72
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/trees-min.jpg');
73
+ }
74
+
75
+ .login-theme-waves {
76
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/waves.jpg');
77
+ }
78
+
79
+ .login-theme-yosemite {
80
+ background-image: url('/vendor/oxygen/ui-theme/img/bg/yosemite-falls-min.jpg');
81
+ }
82
+
83
+ .login-theme-white {
84
+ background-color: white;
85
+ }
86
+
87
+ </style>
@@ -0,0 +1,17 @@
1
+ .login-justify-content {
2
+ display: flex;
3
+ flex-direction: row-reverse;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+ }
7
+
8
+ .login-fullscreen .container {
9
+ flex-grow: 0;
10
+ margin: 2rem auto;
11
+ width: 25em;
12
+ }
13
+
14
+ .login-fullscreen .container.is-wider {
15
+ width: 90%;
16
+ max-width: 55em;
17
+ }
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <b-dropdown
3
+ ref="dropdown"
4
+ position="is-top-left"
5
+ append-to-body
6
+ aria-role="menu"
7
+ trap-focus
8
+ class="choose-directory-dropdown"
9
+ >
10
+ <template #trigger>
11
+ <b-button size="is-small" rounded icon-left="folder-open">
12
+ <span v-if="buttonText">{{ buttonText }}</span>
13
+ </b-button>
14
+ </template>
15
+
16
+
17
+ <b-dropdown-item
18
+ aria-role="menu-item"
19
+ :focusable="false"
20
+ custom
21
+ paddingless>
22
+ <div class="modal-card" style="width: 30rem; overflow: visible;">
23
+ <header class="modal-card-head">
24
+ <p class="modal-card-title">Choose a directory</p>
25
+ </header>
26
+ <section class="modal-card-body" style="overflow: visible;">
27
+ <b-field>
28
+ <b-autocomplete
29
+ v-model="searchQuery"
30
+ :disabled="isLoading"
31
+ open-on-focus
32
+ :data="filteredDirectoriesList"
33
+ :custom-formatter="data => getDirectoryPathString(data)"
34
+ placeholder="Search for directories..."
35
+ clearable
36
+ @select="selectDirectory">
37
+ <template #empty>No results found</template>
38
+ </b-autocomplete>
39
+ </b-field>
40
+ </section>
41
+ <footer class="modal-card-foot is-flex">
42
+ <div class="is-flex-grow-1"></div>
43
+ <b-button
44
+ label="Close"
45
+ @click="close"/>
46
+ <b-button
47
+ :disabled="selectedDirectory === null"
48
+ label="Move"
49
+ type="is-primary"
50
+ @click="submit"/>
51
+ </footer>
52
+ </div>
53
+ </b-dropdown-item>
54
+ </b-dropdown>
55
+ </template>
56
+
57
+ <script>
58
+ import MediaDirectoryApi, {getDirectoryPathString} from "../../MediaDirectoryApi";
59
+
60
+ export default {
61
+ name: "MediaChooseDirectory",
62
+ props: {
63
+ buttonText: {
64
+ type: String,
65
+ default: "Move to directory"
66
+ }
67
+ },
68
+ data() {
69
+ return {
70
+ mediaDirectoryApi: new MediaDirectoryApi(this.$buefy),
71
+ newDirectory: null,
72
+ isLoading: false,
73
+ searchQuery: '',
74
+ directoriesList: [],
75
+ selectedDirectory: null,
76
+ getDirectoryPathString: getDirectoryPathString
77
+ }
78
+ },
79
+ computed: {
80
+ filteredDirectoriesList() {
81
+ let query = this.searchQuery ? this.searchQuery.toLowerCase() : '';
82
+ if(!query) { return this.directoriesList; }
83
+ return this.directoriesList.filter((option) => {
84
+ return option.name.toLowerCase().indexOf(query) >= 0
85
+ || option.fullPath.toLowerCase().indexOf(query) >= 0;
86
+ })
87
+ }
88
+ },
89
+ created() {
90
+ this.fetchData()
91
+ },
92
+ methods: {
93
+ async fetchData() {
94
+ this.isLoading = true;
95
+ let data = await this.mediaDirectoryApi.list(false, 1, null);
96
+ this.directoriesList = data.items;
97
+ this.directoriesList.sort((a, b) => {
98
+ return getDirectoryPathString(a).localeCompare(getDirectoryPathString(b))
99
+ });
100
+ this.isLoading = false;
101
+ },
102
+ debounceFetchData() {
103
+ clearTimeout(this.searchDebounce)
104
+ this.searchDebounce = setTimeout(() => {
105
+ this.fetchData();
106
+ }, 400);
107
+ },
108
+ submit() {
109
+ this.$emit('submit', this.selectedDirectory);
110
+ this.close();
111
+ },
112
+ selectDirectory(option) {
113
+ this.selectedDirectory = option;
114
+ },
115
+ close() {
116
+ this.selectedDirectory = null;
117
+ this.searchQuery = '';
118
+ this.$refs.dropdown.toggle();
119
+ }
120
+ }
121
+ }
122
+ </script>
123
+
124
+ <style>
125
+ .dropdown-content {
126
+ padding-top: 0;
127
+ padding-bottom: 0;
128
+ }
129
+ </style>
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <div class="media-item card" :class="directory.selected ? 'media-item-selected' : ''">
3
+ <div class="card-image media-icon-container cursor-pointer" @click.exact="select(true)" @click.shift.exact="select(false)">
4
+ <b-icon icon="folder" size="is-large" class="media-icon"></b-icon>
5
+ </div>
6
+ <div class="card-content">
7
+ <p class="title is-4 cursor-pointer has-text-centered" @click.exact="select(true)" @click.shift.exact="select(false)">{{ directory.name }}</p>
8
+ <p v-if="displayFullPath" class="subtitle is-6 cursor-pointer has-text-centered" @click.exact="select(true)" @click.shift.exact="select(false)">inside '{{ directoryPath }}'</p>
9
+ <div v-if="directory.selected" class="content media-item-toolbar">
10
+ <b-button v-if="directory.selected" icon-left="pencil-alt" size="is-small" rounded @click.stop="renameDirectory">Rename</b-button>
11
+ <MediaChooseDirectory v-if="directory.selected" button-text="Move" @submit="moveToDirectory">
12
+ </MediaChooseDirectory>
13
+ <b-button v-if="directory.selected" icon-left="trash" size="is-small" type="is-danger" rounded outlined @click.stop="confirmDeleteDirectory">Delete</b-button>
14
+ </div>
15
+ </div>
16
+
17
+ <b-modal :active.sync="isEditModalActive" trap-focus has-modal-card width="80%">
18
+ <div class="modal-card" style="width: auto">
19
+ <header class="modal-card-head">
20
+ <p class="modal-card-title">Rename Directory</p>
21
+ </header>
22
+ <section class="modal-card-body">
23
+ <b-field label="Name" label-position="inside">
24
+ <b-input v-model="newName"></b-input>
25
+ </b-field>
26
+ <b-field label="Slug" label-position="inside">
27
+ <b-input v-model="newSlug"></b-input>
28
+ </b-field>
29
+ </section>
30
+ <footer class="modal-card-foot is-flex">
31
+ <div class="is-flex-grow-1"></div>
32
+ <b-button @click="isEditModalActive = false">Cancel</b-button>
33
+ <b-button type="is-primary" @click="doRenameDirectory">Rename</b-button>
34
+ </footer>
35
+ </div>
36
+ </b-modal>
37
+
38
+ <!-- <b-button type="is-light" expanded @click="currentPath = dir.fullPath"></b-button>-->
39
+ </div>
40
+
41
+ </template>
42
+
43
+ <script>
44
+ import MediaDirectoryApi, {getDirectoryPathString} from "../../MediaDirectoryApi";
45
+ import {morphToNotification} from "../../api";
46
+ import MediaChooseDirectory from "./MediaChooseDirectory.vue";
47
+
48
+ export default {
49
+ name: "MediaDirectory",
50
+ components: { MediaChooseDirectory },
51
+ props: {
52
+ directory: { type: Object, required: true },
53
+ displayFullPath: Boolean
54
+ },
55
+ data() {
56
+ return {
57
+ isEditModalActive: false,
58
+ newName: '',
59
+ newSlug: '',
60
+ mediaDirectoryApi: new MediaDirectoryApi(this.$buefy)
61
+ }
62
+ },
63
+ computed: {
64
+ directoryPath() {
65
+ return getDirectoryPathString(this.directory.parentDirectory);
66
+ }
67
+ },
68
+ methods: {
69
+ select(toggle) {
70
+ // if already selected, then navigate into a directory
71
+ if(this.directory.selected) {
72
+ this.$emit('navigate', { currentPath: this.directory.fullPath })
73
+ return;
74
+ }
75
+ this.$emit('select', this.directory, toggle);
76
+ },
77
+ renameDirectory() {
78
+ this.newName = this.directory.name;
79
+ this.newSlug = this.directory.slug;
80
+ this.isEditModalActive = true;
81
+ },
82
+ doRenameDirectory() {
83
+ this.$emit('rename', this.directory, this.newName);
84
+ this.isRenaming = false;
85
+ },
86
+ async confirmDeleteDirectory() {
87
+ await this.mediaDirectoryApi.confirmForceDelete(this.directory.id);
88
+ this.$emit('delete');
89
+ },
90
+ async moveToDirectory(directory) {
91
+ console.log('move to directory', directory);
92
+ let data = await this.mediaDirectoryApi.update({
93
+ id: this.directory.id,
94
+ parentDirectory: directory
95
+ });
96
+ this.$buefy.toast.open(morphToNotification(data));
97
+ this.$emit('move', this.directory);
98
+ }
99
+ }
100
+ }
101
+ </script>
102
+
103
+ <style scoped lang="scss">
104
+ @import "./media.scss";
105
+
106
+ .media-item-selected .title {
107
+ margin-bottom: 1.5rem;
108
+ }
109
+ </style>
@@ -0,0 +1,88 @@
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">
3
+ <div class="modal-card">
4
+ <header class="modal-card-head">
5
+ <slot name="title"><p class="modal-card-title">Choose an item to insert</p></slot>
6
+ </header>
7
+ <section class="modal-card-body">
8
+ <MediaList :current-path="currentPath" :in-trash="inTrash" :search-query="searchQuery" @navigate="onNavigate" @double-click-action="doInsert" @select-files="items => selectedFiles = items" />
9
+ </section>
10
+ <footer class="modal-card-foot is-flex">
11
+ <div class="is-flex-grow-1"></div>
12
+ <b-button @click="emitClose">Close</b-button>
13
+ <b-button :disabled="selectedFiles.length === 0 || (!multiselectAllowed && selectedFiles.length > 1)" type="is-primary" @click="doInsert">
14
+ <span v-if="selectedFiles.length === 0">{{ actionVerb }}</span>
15
+ <span v-else-if="selectedFiles.length === 1">{{actionVerb }} item</span>
16
+ <span v-else-if="multiselectAllowed">{{ actionVerb }} {{ selectedFiles }} items</span>
17
+ <span v-else>Select only a single item</span>
18
+ </b-button>
19
+ </footer>
20
+ </div>
21
+ </b-modal>
22
+ </template>
23
+
24
+ <script>
25
+ import MediaList from './MediaList.vue';
26
+
27
+ export default {
28
+ name: "MediaInsertModal",
29
+ components: { MediaList },
30
+ props: {
31
+ active: Boolean,
32
+ multiselectAllowed: {
33
+ type: Boolean,
34
+ default: true
35
+ },
36
+ actionVerb: {
37
+ type: String,
38
+ default: 'Insert'
39
+ }
40
+ },
41
+ data() {
42
+ return {
43
+ currentPath: '',
44
+ inTrash: false,
45
+ searchQuery: null,
46
+ selectedFiles: []
47
+ }
48
+ },
49
+ watch: {
50
+ active: function(active) {
51
+ this.$emit('update:active', active);
52
+ }
53
+ },
54
+ methods: {
55
+ onNavigate(options) {
56
+ this.currentPath = options.currentPath ? options.currentPath : '';
57
+ this.inTrash = !!options.inTrash;
58
+ this.searchQuery = options.searchQuery ? options.searchQuery : null;
59
+ },
60
+ emitClose() {
61
+ this.$emit('close');
62
+ },
63
+ doInsert() {
64
+ this.$emit('select', this.selectedFiles);
65
+ }
66
+ }
67
+ }
68
+ </script>
69
+
70
+ <style scoped>
71
+ .modal-card {
72
+ width: 100%;
73
+ height: 100%;
74
+ max-width: 1600px;
75
+ }
76
+
77
+ .modal-card-body {
78
+ padding: 0;
79
+ }
80
+
81
+ .media-insert-modal ::v-deep .animation-content {
82
+ width: 80%;
83
+ height: 80%;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ }
88
+ </style>