@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,51 @@
1
+ <template>
2
+ <div class="full-height full-height-container pad">
3
+ <h1 class="title">Responsive Images Support</h1>
4
+
5
+ <div class="box full-height-container full-height-flex" style="min-height: 0">
6
+ <div class="content" style="max-width: 80rem;">
7
+ <p><a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">Responsive images</a> are images which work well on devices of differing screen sizes, resolutions etc...
8
+ Depending on the size/pixel density of the users' screen, we serve a different image to them. This optimization helps speed up load times dramatically.</p>
9
+ <b-button type="is-primary" :disabled="requestInFlight" @click="generate">Generate responsive versions for each image</b-button>
10
+ </div>
11
+ <b-progress v-if="requestInFlight" size="is-medium" show-value>Generating...</b-progress>
12
+ <div v-if="hasServerLog" class="full-height-flex full-height-container" style="min-height: 0;">
13
+ <h4 class="subtitle">Server output log:</h4>
14
+ <pre class="scroll-container full-height-flex" style="background-color: #000;" v-html="serverLog"></pre>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script>
21
+ import {API_ROOT} from "../../CrudApi";
22
+ import {FetchBuilder, morphToNotification} from "../../api";
23
+
24
+ export default {
25
+ name: "MediaResponsiveImages",
26
+ data() {
27
+ return {
28
+ serverLog: '[no output]',
29
+ hasServerLog: false,
30
+ requestInFlight: false,
31
+ }
32
+ },
33
+ methods: {
34
+ async generate() {
35
+ this.requestInFlight = true;
36
+ this.hasServerLog = false;
37
+ this.serverLog = '[generating...]';
38
+ let result = await (FetchBuilder.default(this.$buefy, 'post')).fetch(API_ROOT + 'media/make-responsive');
39
+ this.$buefy.toast.open(morphToNotification(result));
40
+ this.requestInFlight = false;
41
+ this.serverLog = result.log;
42
+ this.hasServerLog = true;
43
+ console.log(result);
44
+ }
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <style scoped>
50
+ @import '../util.css';
51
+ </style>
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <div class="modal-card">
3
+ <header class="modal-card-head">
4
+ <p v-if="!isUploading" class="modal-card-title">Upload files to '{{ currentPath }}'</p>
5
+ <p v-else class="modal-card-title">Uploading {{ filesToUpload.length }} item(s)</p>
6
+ </header>
7
+ <section v-if="!isUploading" class="modal-card-body">
8
+ <b-upload v-model="filesToUpload"
9
+ multiple
10
+ drag-drop expanded>
11
+ <section class="section">
12
+ <div class="content has-text-centered">
13
+ <p>
14
+ <b-icon
15
+ icon="upload"
16
+ size="is-large">
17
+ </b-icon>
18
+ </p>
19
+ <p>Drop your files here or click to upload</p>
20
+ </div>
21
+ </section>
22
+ </b-upload>
23
+
24
+ <div class="tags mt-4">
25
+ <span v-for="(file, index) in filesToUpload" :key="index" class="tag is-primary image-preview">
26
+ <img :src="imagePreviews[index]" alt="file preview" />
27
+ <p class="image-preview-label">
28
+ {{ file.name }}
29
+ <button class="delete is-small" type="button" @click="deleteFileToUpload(index)"></button>
30
+ </p>
31
+ </span>
32
+ </div>
33
+ </section>
34
+ <section v-else class="modal-card-body">
35
+ <b-progress size="is-medium" type="is-info"></b-progress>
36
+ </section>
37
+ <footer class="modal-card-foot is-flex">
38
+ <div class="is-flex-grow-1"></div>
39
+ <b-button @click="closeModal">Close</b-button>
40
+ <b-button type="is-primary" :disabled="isUploading" @click="doUpload">Upload</b-button>
41
+ </footer>
42
+ </div>
43
+ </template>
44
+
45
+ <script>
46
+ import {morphToNotification} from "../../api";
47
+ import MediaApi from "../../MediaApi";
48
+ import {getDirectoryPathString} from "../../MediaDirectoryApi";
49
+
50
+ export default {
51
+ name: "MediaUpload",
52
+ props: {
53
+ currentDirectory: { type: Object, default: null }
54
+ },
55
+ data() {
56
+ return {
57
+ filesToUpload: [],
58
+ isUploading: false,
59
+ mediaApi: new MediaApi(this.$buefy)
60
+ }
61
+ },
62
+ computed: {
63
+ currentPath() {
64
+ return getDirectoryPathString(this.currentDirectory);
65
+ },
66
+ },
67
+ asyncComputed: {
68
+ async imagePreviews() {
69
+ let promises = this.filesToUpload.map(file => {
70
+ return new Promise((resolve) => {
71
+ let reader = new FileReader();
72
+ reader.onload = function (e) {
73
+ resolve(e.target.result);
74
+ };
75
+ reader.readAsDataURL(file);
76
+ })
77
+ })
78
+ return Promise.all(promises);
79
+ }
80
+ },
81
+ methods: {
82
+ deleteFileToUpload(index) {
83
+ this.filesToUpload.splice(index, 1);
84
+ },
85
+ async doUpload() {
86
+ if(this.filesToUpload.length === 0) {
87
+ return this.$buefy.toast.open({
88
+ message: 'No files selected for upload',
89
+ type: 'is-warning',
90
+ queue: false
91
+ });
92
+ }
93
+ this.isUploading = true;
94
+ try {
95
+ let data = await this.mediaApi.create({
96
+ files: this.filesToUpload,
97
+ currentDirectory: this.currentDirectory
98
+ });
99
+ this.$buefy.toast.open(morphToNotification(data));
100
+ this.$emit('uploaded');
101
+ this.closeModal();
102
+ } catch(e) {
103
+ // upload failed
104
+ console.warn(e);
105
+ this.closeModal();
106
+ }
107
+ },
108
+ closeModal() {
109
+ this.isUploading = false;
110
+ this.filesToUpload = [];
111
+ this.$emit('close');
112
+ }
113
+ }
114
+ }
115
+ </script>
116
+
117
+ <style scoped>
118
+ .image-preview {
119
+ display: flex;
120
+ flex-direction: column;
121
+ height: auto;
122
+ }
123
+
124
+ .image-preview img {
125
+ margin: 0.5rem;
126
+ }
127
+
128
+ .image-preview-label {
129
+ margin: 0.5rem;
130
+ display: inline-flex;
131
+ align-items: center;
132
+ }
133
+ </style>
@@ -0,0 +1,51 @@
1
+ @import '../../styles/variables';
2
+
3
+ .card-image {
4
+ min-height: 6rem;
5
+ }
6
+
7
+ .media-icon {
8
+ font-size: 5rem;
9
+ width: 5rem;
10
+ height: 5rem;
11
+ margin-top: 2rem;
12
+ }
13
+
14
+ .media-item {
15
+ width: 20rem;
16
+ overflow: visible;
17
+ }
18
+
19
+ .media-item .card-image,
20
+ .media-item .card-content {
21
+ user-select: none;
22
+ }
23
+
24
+ .cursor-pointer {
25
+ cursor: pointer;
26
+ }
27
+
28
+ .media-item-toolbar {
29
+ display: flex;
30
+ flex-wrap: wrap;
31
+ }
32
+
33
+ .media-item-toolbar > * {
34
+ margin-right: 0.25rem;
35
+ margin-bottom: 0.25rem;
36
+ }
37
+
38
+ .media-item.card {
39
+ margin: 12px 17px;
40
+ border: 3px solid rgba(0, 0, 0, 0);
41
+ }
42
+
43
+ .media-item-selected.card {
44
+ border: 3px solid $blue;
45
+ }
46
+
47
+ .media-icon-container {
48
+ display: flex;
49
+ align-items: center;
50
+ flex-direction: column;
51
+ }
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <ShowIfPermitted data-key="appearance.auth">
3
+ <h3 class="subtitle">Login</h3>
4
+ <PreferencesField data-key="appearance.auth::theme" label="Login Theme" type="select" :current-theme="currentTheme"></PreferencesField>
5
+ </ShowIfPermitted>
6
+ </template>
7
+
8
+ <script>
9
+ import PreferencesField from "./PreferencesField.vue";
10
+ import ShowIfPermitted from "./ShowIfPermitted.vue";
11
+
12
+ export default {
13
+ name: "PreferencesAdminAppearance",
14
+ components: {ShowIfPermitted, PreferencesField },
15
+ props: {
16
+ currentTheme: {
17
+ type: String,
18
+ default: null
19
+ }
20
+ }
21
+ }
22
+ </script>
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <ShowIfPermitted data-key="modules.auth">
3
+ <h3 class="subtitle">Views</h3>
4
+ <PreferencesField data-key="modules.auth::dashboard" label="Dashboard View" type="select" :current-theme="currentTheme"></PreferencesField>
5
+ <PreferencesField data-key="modules.auth::home" label="Home View" type="select" :current-theme="currentTheme"></PreferencesField>
6
+
7
+ <h3 class="subtitle">Logging</h3>
8
+ <PreferencesField data-key="modules.auth::notifyWhenNewDevice" label="Notify user when logging in from a new device" type="switch" :current-theme="currentTheme"></PreferencesField>
9
+ <PreferencesField data-key="modules.auth::loginLogExpiry" label="Number of days to keep authentication log entries" type="number" :current-theme="currentTheme"></PreferencesField>
10
+ </ShowIfPermitted>
11
+ </template>
12
+
13
+ <script>
14
+ import PreferencesField from "./PreferencesField.vue";
15
+ import ShowIfPermitted from "./ShowIfPermitted.vue";
16
+
17
+ export default {
18
+ name: "PreferencesAuthentication",
19
+ components: {ShowIfPermitted, PreferencesField },
20
+ props: {
21
+ currentTheme: {
22
+ type: String,
23
+ default: null
24
+ }
25
+ }
26
+ }
27
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <ShowIfPermitted data-key="appearance.events">
3
+ <h3 class="subtitle">Events</h3>
4
+ <PreferencesField data-key="appearance.events::contentView" label="Events Content View" :current-theme="currentTheme"></PreferencesField>
5
+ </ShowIfPermitted>
6
+ </template>
7
+
8
+ <script>
9
+ import PreferencesField from "./PreferencesField.vue";
10
+ import ShowIfPermitted from "./ShowIfPermitted.vue";
11
+
12
+ export default {
13
+ name: "PreferencesEventsTemplates",
14
+ components: {ShowIfPermitted, PreferencesField },
15
+ props: {
16
+ currentTheme: {
17
+ type: String,
18
+ default: null
19
+ }
20
+ }
21
+ }
22
+ </script>
@@ -0,0 +1,215 @@
1
+ <template>
2
+ <div>
3
+ <div v-if="loaded">
4
+ <slot :value="value" :options="options" :update-value="updateValue">
5
+ <div class="horizontal-row">
6
+ <b-switch v-if="type === 'switch'" :value="value" :passive-type="isFallback ? 'is-dark' : ''" @input="updateValue($event)">{{ label }}</b-switch>
7
+ <b-field v-else-if="type === 'select'" :label="label" label-position="inside" class="pref-field">
8
+ <b-select v-if="grouped" :placeholder="selectPlaceholder" :value="value" @input="updateValue($event)">
9
+ <optgroup v-for="(suboptions, groupLabel) in options" :key="groupLabel" :label="groupLabel">
10
+ <option
11
+ v-for="(display, optionValue) in suboptions"
12
+ :key="optionValue"
13
+ :value="optionValue">
14
+ {{ display }}
15
+ </option>
16
+ </optgroup>
17
+ </b-select>
18
+ <b-select v-else :placeholder="selectPlaceholder" :value="value" @input="updateValue($event)">
19
+ <option
20
+ v-for="(optionDisplay, optionValue) in options"
21
+ :key="optionValue"
22
+ :value="optionValue">
23
+ {{ optionDisplay }}
24
+ </option>
25
+ </b-select>
26
+ </b-field>
27
+ <b-field v-else :label="label" :type="fieldType" :message="validationMessage" label-position="inside">
28
+ <b-input v-model="value" class="pref-field-input" :type="type" :placeholder="fallbackValue"></b-input>
29
+ <p class="control">
30
+ <b-button :loading="validating || updating" :type="(value !== serverValue && validationMessage === null && !validating) ? 'is-success' : ''" :disabled="value === serverValue || validationMessage !== null" @click="updateValue()">Update</b-button>
31
+ </p>
32
+ </b-field>
33
+ <b-button v-if="hasFallback" :disabled="!hasFallback || isFallback" type="is-light" size="is-small" :loading="updating" @click="clearValue()">
34
+ <span v-if="!isFallback">Use default from {{ defaultText }}</span>
35
+ <span v-else>Inherited from {{ defaultText }}</span>
36
+ </b-button>
37
+ </div>
38
+ </slot>
39
+ </div>
40
+ <div v-else class="pref-field-skeleton">
41
+ <b-skeleton :animated="true" size="is-large"></b-skeleton>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <script>
47
+ import PreferencesApi from "../../PreferencesApi";
48
+ import debounce from 'lodash/debounce';
49
+ import {morphToNotification} from "../../api";
50
+
51
+ export default {
52
+ name: "PreferencesField",
53
+ props: {
54
+ dataKey: { type: String, required: true },
55
+ label: { type: String, required: true },
56
+ type: {
57
+ type: String,
58
+ default: null
59
+ },
60
+ grouped: {
61
+ type: Boolean,
62
+ default: false
63
+ },
64
+ currentTheme: {
65
+ type: String,
66
+ default: null
67
+ },
68
+ defaultText: {
69
+ type: String,
70
+ default: 'theme'
71
+ }
72
+ },
73
+ data() {
74
+ return {
75
+ preferencesApi: new PreferencesApi(this.$buefy),
76
+ loaded: false,
77
+ hidden: false,
78
+ fallbackValue: null,
79
+ serverValue: null,
80
+ value: null,
81
+ options: [],
82
+ validating: false,
83
+ updating: false,
84
+ validationMessage: null
85
+ }
86
+ },
87
+ computed: {
88
+ fieldType() {
89
+ if(this.isFallback) { return 'is-dark'; }
90
+ else if(this.validationMessage !== null) { return 'is-danger'; }
91
+ else if(!this.validating && this.value !== this.serverValue) { return 'is-success'; }
92
+ else { return ''; }
93
+ },
94
+ isFallback() {
95
+ return this.fallbackValue !== null && (this.value === null || this.value === '');
96
+ },
97
+ hasFallback() {
98
+ return this.fallbackValue !== null;
99
+ },
100
+ selectPlaceholder() {
101
+ if(this.fallbackValue !== null) {
102
+ if(this.grouped) {
103
+ let entries = Object.entries(this.options).flatMap(args => { return Object.entries(args[1]); });
104
+ console.log(entries);
105
+ return entries.filter(args => args[0] === this.fallbackValue).map(args => args[1])[0];
106
+ } else {
107
+ let entries = Object.entries(this.options);
108
+ console.log(entries);
109
+ return entries.filter(args => args[0] === this.fallbackValue).map(args => args[1])[0];
110
+ }
111
+ }
112
+ return 'Select a value...';
113
+ }
114
+ },
115
+ watch: {
116
+ 'value': 'tryValidate',
117
+ 'currentTheme': 'fetchData'
118
+ },
119
+ mounted() {
120
+ this.fetchData()
121
+ },
122
+ methods: {
123
+ async fetchData() {
124
+ let serverData = await this.preferencesApi.getValue(this.dataKey);
125
+ if(serverData.permissions === false) {
126
+ return;
127
+ }
128
+ this.serverValue = serverData.primaryValue;
129
+ this.value = this.serverValue;
130
+ this.fallbackValue = serverData.fallbackValue;
131
+ this.options = serverData.options;
132
+ this.loaded = true;
133
+ },
134
+ tryValidate() {
135
+ if(this.value === this.serverValue) {
136
+ this.validationMessage = null;
137
+ return;
138
+ }
139
+ this.validating = true;
140
+ this.validateDebounced();
141
+ },
142
+ validateDebounced: debounce(async function () {
143
+ let result = await this.preferencesApi.checkValid(this.dataKey, this.value);
144
+ this.validating = false;
145
+ console.log(
146
+ result
147
+ );
148
+ this.validationMessage = result.reason ? result.reason : null;
149
+ }, 250),
150
+ async updateValue(value) {
151
+ if(typeof value === 'undefined') {
152
+ value = this.value;
153
+ }
154
+ this.updating = true;
155
+ let result = await this.preferencesApi.setValue(this.dataKey, value);
156
+ this.handleUpdateResult(result);
157
+ },
158
+ handleUpdateResult(result) {
159
+ this.updating = false;
160
+ if(result.reason) {
161
+ this.validationMessage = result.reason ? result.reason : null;
162
+ return;
163
+ }
164
+ this.updating = false;
165
+ this.$buefy.toast.open(morphToNotification(result));
166
+ this.serverValue = result.primaryValue;
167
+ this.value = result.primaryValue;
168
+ this.fallbackValue = result.fallbackValue;
169
+ },
170
+ async clearValue() {
171
+ this.updating = true;
172
+ let result = await this.preferencesApi.setValue(this.dataKey, null);
173
+ this.handleUpdateResult(result);
174
+ }
175
+ }
176
+ }
177
+ </script>
178
+
179
+ <style scoped>
180
+ .pref-field {
181
+ margin-bottom: 0.75rem;
182
+ }
183
+
184
+ .pref-field-input {
185
+ width: 100%;
186
+ max-width: 30rem;
187
+ }
188
+
189
+ .pref-field-skeleton .b-skeleton {
190
+ margin-bottom: 1rem;
191
+ width: 100%;
192
+ max-width: 40rem;
193
+ }
194
+
195
+ .pref-field-skeleton ::v-deep .b-skeleton-item {
196
+ line-height: 2.5rem;
197
+ }
198
+
199
+ .horizontal-row {
200
+ margin-bottom: 0.75rem;
201
+ display: flex;
202
+ align-items: center;
203
+ }
204
+
205
+ .horizontal-row .field,
206
+ .horizontal-row .switch {
207
+ margin-bottom: 0;
208
+ flex: 1;
209
+ max-width: 35rem;
210
+ }
211
+
212
+ .horizontal-row > .button {
213
+ margin-left: 1rem;
214
+ }
215
+ </style>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div class="full-height full-height-container pad">
3
+
4
+ <div class="top-bar">
5
+ <h1 class="title">System Preferences</h1>
6
+ </div>
7
+
8
+ <div class="box full-height-flex scroll-container">
9
+ <b-tabs class="block">
10
+ <slot :can-access-prefs="canAccessPrefs"></slot>
11
+ <!-- If there were no tabs which are accessible, then display this tab as a fallback -->
12
+ <b-tab-item v-if="!hasAtLeastOneAccess" label="General">
13
+ <em>No preferences found</em>
14
+ </b-tab-item>
15
+ </b-tabs>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script>
21
+
22
+ import {canAccessPrefs} from "../../PreferencesApi";
23
+
24
+ export default {
25
+ name: "PreferencesList",
26
+ data() {
27
+ return {
28
+ hasAtLeastOneAccess: false
29
+ }
30
+ },
31
+ computed: {
32
+ userPermissions() {
33
+ return this.$store.getters.userPermissions;
34
+ }
35
+ },
36
+ methods: {
37
+ canAccessPrefs(keys) {
38
+ let result = canAccessPrefs(this.$buefy, this.userPermissions, keys);
39
+ if(result) {
40
+ this.hasAtLeastOneAccess = true;
41
+ }
42
+ return result;
43
+ }
44
+ }
45
+ }
46
+ </script>
47
+
48
+ <style scoped>
49
+
50
+ </style>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <ShowIfPermitted data-key="appearance.pages">
3
+ <h3 class="subtitle">Pages</h3>
4
+ <PreferencesField data-key="appearance.pages::theme" label="Page Template" :current-theme="currentTheme"></PreferencesField>
5
+ <PreferencesField data-key="appearance.pages::contentView" label="Page Preview Template" :current-theme="currentTheme"></PreferencesField>
6
+ </ShowIfPermitted>
7
+ </template>
8
+
9
+ <script>
10
+ import PreferencesField from "./PreferencesField.vue";
11
+ import ShowIfPermitted from "./ShowIfPermitted.vue";
12
+
13
+ export default {
14
+ name: "PreferencesPageTemplates",
15
+ components: {ShowIfPermitted, PreferencesField },
16
+ props: {
17
+ currentTheme: {
18
+ type: String,
19
+ default: null
20
+ }
21
+ }
22
+ }
23
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <ShowIfPermitted data-key="appearance.site">
3
+ <h3 class="subtitle">Error Pages</h3>
4
+ <PreferencesField data-key="appearance.site::errorViewPrefix" label="Error View Prefix" :current-theme="currentTheme"></PreferencesField>
5
+ </ShowIfPermitted>
6
+ </template>
7
+
8
+ <script>
9
+ import PreferencesField from "./PreferencesField.vue";
10
+ import ShowIfPermitted from "./ShowIfPermitted.vue";
11
+
12
+ export default {
13
+ name: "PreferencesSiteAppearance",
14
+ components: {ShowIfPermitted, PreferencesField },
15
+ props: {
16
+ currentTheme: {
17
+ type: String,
18
+ default: null
19
+ }
20
+ }
21
+ }
22
+ </script>