@oxygen-cms/ui 1.5.2 → 1.6.2
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.
- package/.gitattributes +3 -0
- package/assets/bg/autumn-min.jpg +0 -0
- package/assets/bg/city-min.jpg +0 -0
- package/assets/bg/clouds-min.jpg +0 -0
- package/assets/bg/coast-min.jpg +0 -0
- package/assets/bg/speckles-min.jpg +0 -0
- package/assets/bg/trees-min.jpg +0 -0
- package/assets/bg/waves.jpg +0 -0
- package/assets/bg/yosemite-falls-min.jpg +0 -0
- package/assets/icon/apple-touch-icon-114x114.png +0 -0
- package/assets/icon/apple-touch-icon-120x120.png +0 -0
- package/assets/icon/apple-touch-icon-144x144.png +0 -0
- package/assets/icon/apple-touch-icon-152x152.png +0 -0
- package/assets/icon/apple-touch-icon-180x180.png +0 -0
- package/assets/icon/apple-touch-icon-57x57.png +0 -0
- package/assets/icon/apple-touch-icon-60x60.png +0 -0
- package/assets/icon/apple-touch-icon-72x72.png +0 -0
- package/assets/icon/apple-touch-icon-76x76.png +0 -0
- package/assets/icon/apple-touch-icon-precomposed.png +0 -0
- package/assets/icon/apple-touch-icon.png +0 -0
- package/assets/icon/browserconfig.xml +12 -0
- package/assets/icon/favicon-160x160.png +0 -0
- package/assets/icon/favicon-16x16.png +0 -0
- package/assets/icon/favicon-192x192.png +0 -0
- package/assets/icon/favicon-32x32.png +0 -0
- package/assets/icon/favicon-96x96.png +0 -0
- package/assets/icon/favicon.ico +0 -0
- package/assets/icon/mstile-144x144.png +0 -0
- package/assets/icon/mstile-150x150.png +0 -0
- package/assets/icon/mstile-310x150.png +0 -0
- package/assets/icon/mstile-310x310.png +0 -0
- package/assets/icon/mstile-70x70.png +0 -0
- package/package.json +3 -11
- package/src/AuthApi.js +12 -12
- package/src/CrudApi.js +9 -7
- package/src/PreferencesApi.js +4 -4
- package/src/UserPermissions.js +2 -1
- package/src/UserPreferences.test.js +3 -9
- package/src/UsersApi.js +4 -4
- package/src/api.js +17 -3
- package/src/components/App.vue +4 -3
- package/src/components/AuthenticationLog.vue +4 -3
- package/src/components/CodeEditor.vue +8 -6
- package/src/components/ImportExport.vue +2 -2
- package/src/components/LegacyPage.vue +5 -5
- package/src/components/MainMenu.vue +102 -0
- package/src/components/auth/WelcomeFloat.vue +8 -8
- package/src/components/dashboard/EventsPanel.vue +20 -0
- package/src/components/dashboard/MainDashboard.vue +88 -0
- package/src/components/dashboard/MediaPanel.vue +21 -0
- package/src/components/dashboard/PagesPartialsPanel.vue +24 -0
- package/src/components/dashboard/panel.scss +8 -0
- package/src/components/media/MediaList.vue +1 -1
- package/src/components/media/MediaResponsiveImages.vue +2 -2
- package/src/components/media/media.scss +1 -1
- package/src/components/preferences/Preferences.vue +82 -0
- package/src/icons.js +3 -2
- package/src/main.js +34 -8
- package/src/modules/LegacyPages.js +34 -0
- package/src/modules/Media.js +15 -0
- package/src/styles/_variables.scss +1 -16
- package/src/styles/app.scss +2 -2
package/.gitattributes
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<browserconfig>
|
|
3
|
+
<msapplication>
|
|
4
|
+
<tile>
|
|
5
|
+
<square70x70logo src="/packages/oxygen/ui/img/icon/mstile-70x70.png"/>
|
|
6
|
+
<square150x150logo src="/packages/oxygen/ui/img/icon/mstile-150x150.png"/>
|
|
7
|
+
<square310x310logo src="/packages/oxygen/ui/img/icon/mstile-310x310.png"/>
|
|
8
|
+
<wide310x150logo src="/packages/oxygen/ui/img/icon/mstile-310x150.png"/>
|
|
9
|
+
<TileColor>#2b5797</TileColor>
|
|
10
|
+
</tile>
|
|
11
|
+
</msapplication>
|
|
12
|
+
</browserconfig>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxygen-cms/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Various utilities for UI-building in Vue.js",
|
|
5
5
|
"main": "none",
|
|
6
6
|
"repository": {
|
|
@@ -16,13 +16,11 @@
|
|
|
16
16
|
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
|
17
17
|
"@fortawesome/vue-fontawesome": "^0.1.10",
|
|
18
18
|
"autoprefixer": "^9.8.5",
|
|
19
|
-
"babel-loader": "^8.1.0",
|
|
20
19
|
"brace": "^0.11.1",
|
|
21
20
|
"buefy": "^0.9.10",
|
|
22
21
|
"bulma": "~0.9.3",
|
|
23
22
|
"copy-webpack-plugin": "^5.1.1",
|
|
24
23
|
"downloadjs": "^1.4.7",
|
|
25
|
-
"file-loader": "^6.1.1",
|
|
26
24
|
"libphonenumber-js": "^1.9.11",
|
|
27
25
|
"lodash": "^4.17.21",
|
|
28
26
|
"title-case": "^3.0.3",
|
|
@@ -31,7 +29,6 @@
|
|
|
31
29
|
"vex-js": "~4.1.0",
|
|
32
30
|
"vue": "^2.6.11",
|
|
33
31
|
"vue-async-computed": "^3.9.0",
|
|
34
|
-
"vue-loader": "^15.9.6",
|
|
35
32
|
"vue-router": "^3.5.1",
|
|
36
33
|
"vue-template-compiler": "^2.6.11",
|
|
37
34
|
"vue2-ace-editor": "^0.0.15",
|
|
@@ -42,18 +39,13 @@
|
|
|
42
39
|
"@babel/preset-env": "^7.13.5",
|
|
43
40
|
"babel-jest": "^26.6.3",
|
|
44
41
|
"babel-polyfill": "^6.26.0",
|
|
45
|
-
"css-loader": "^3.5.1",
|
|
46
42
|
"eslint": "^7.32.0",
|
|
47
43
|
"eslint-config-prettier": "^8.3.0",
|
|
48
44
|
"eslint-plugin-jest": "^24.4.0",
|
|
49
45
|
"eslint-plugin-vue": "^7.17.0",
|
|
50
46
|
"jest": "^26.6.3",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"regenerator-runtime": "^0.13.7",
|
|
54
|
-
"sass-loader": "^8.0.2",
|
|
55
|
-
"webpack": "^4.46.0",
|
|
56
|
-
"webpack-cli": "^3.3.11"
|
|
47
|
+
"sass-embedded": "^1.49.9",
|
|
48
|
+
"regenerator-runtime": "^0.13.7"
|
|
57
49
|
},
|
|
58
50
|
"scripts": {
|
|
59
51
|
"test": "jest",
|
package/src/AuthApi.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getApiRoot} from "./CrudApi";
|
|
2
2
|
import {FetchBuilder, initCsrfCookie} from "./api";
|
|
3
3
|
import UserPermissions from "./UserPermissions";
|
|
4
4
|
|
|
@@ -19,16 +19,16 @@ export default class AuthApi {
|
|
|
19
19
|
password,
|
|
20
20
|
'2fa_code': code
|
|
21
21
|
})
|
|
22
|
-
.fetch(
|
|
22
|
+
.fetch(getApiRoot() + 'auth/login');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async getLoginPreferences() {
|
|
26
|
-
return await this.request('get').fetch(
|
|
26
|
+
return await this.request('get').fetch(getApiRoot() + 'auth/preferences');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async setupTwoFactorAuth() {
|
|
30
30
|
return await this.request('post')
|
|
31
|
-
.fetch(
|
|
31
|
+
.fetch(getApiRoot() + 'auth/two-factor-setup');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async confirmTwoFactorAuth(code) {
|
|
@@ -36,7 +36,7 @@ export default class AuthApi {
|
|
|
36
36
|
.withJson({
|
|
37
37
|
'2fa_code': code
|
|
38
38
|
})
|
|
39
|
-
.fetch(
|
|
39
|
+
.fetch(getApiRoot() + 'auth/two-factor-confirm');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async sendReminderEmail(email) {
|
|
@@ -44,23 +44,23 @@ export default class AuthApi {
|
|
|
44
44
|
.withJson({
|
|
45
45
|
'email': email
|
|
46
46
|
})
|
|
47
|
-
.fetch(
|
|
47
|
+
.fetch(getApiRoot() + 'auth/send-reminder-email');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async sendEmailVerification() {
|
|
51
51
|
return await this.request('post')
|
|
52
|
-
.fetch(
|
|
52
|
+
.fetch(getApiRoot() + 'auth/verify-email');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
async resetPassword(params) {
|
|
56
56
|
return await this.request('post')
|
|
57
57
|
.withJson(params)
|
|
58
|
-
.fetch(
|
|
58
|
+
.fetch(getApiRoot() + 'auth/reset-password');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async logout() {
|
|
62
62
|
let response = await this.request('post')
|
|
63
|
-
.fetch(
|
|
63
|
+
.fetch(getApiRoot() + 'auth/logout');
|
|
64
64
|
this.$buefy.notification.open({
|
|
65
65
|
message: 'You have been logged out',
|
|
66
66
|
type: 'is-info',
|
|
@@ -80,17 +80,17 @@ export default class AuthApi {
|
|
|
80
80
|
console.log(params);
|
|
81
81
|
return this.request('post')
|
|
82
82
|
.withJson(params)
|
|
83
|
-
.fetch(
|
|
83
|
+
.fetch(getApiRoot() + 'auth/change-password');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
async listUserSessions() {
|
|
87
87
|
return this.request('get')
|
|
88
|
-
.fetch(
|
|
88
|
+
.fetch(getApiRoot() + 'auth/sessions')
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
async deleteUserSession(id) {
|
|
92
92
|
return this.request('delete')
|
|
93
|
-
.fetch(
|
|
93
|
+
.fetch(getApiRoot() + 'auth/sessions/' + id)
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
package/src/CrudApi.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {FetchBuilder} from './api.js';
|
|
1
|
+
import {FetchBuilder, getApiHost} from './api.js';
|
|
2
2
|
import {morphToNotification} from "./api";
|
|
3
3
|
|
|
4
|
-
const API_ROOT = '
|
|
4
|
+
const API_ROOT = 'oxygen/api/';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
export const getApiRoot = () => {
|
|
7
|
+
return getApiHost() + API_ROOT;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CrudApi {
|
|
7
11
|
|
|
8
12
|
constructor($buefy) {
|
|
9
13
|
this.$buefy = $buefy;
|
|
@@ -18,7 +22,7 @@ class CrudApi {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
static getResourceRoot() {
|
|
21
|
-
return
|
|
25
|
+
return getApiRoot() + this.getResourceName();
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
static prepareModelForAPI(data) {
|
|
@@ -107,6 +111,4 @@ class CrudApi {
|
|
|
107
111
|
return this.request('post')
|
|
108
112
|
.fetch(this.constructor.getResourceRoot() + '/' + id + '/make-head');
|
|
109
113
|
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export { CrudApi, API_ROOT };
|
|
114
|
+
}
|
package/src/PreferencesApi.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getApiRoot} from "./CrudApi";
|
|
2
2
|
import {FetchBuilder} from "./api";
|
|
3
3
|
|
|
4
4
|
export const canAccessPrefs = ($buefy, userPermissions, keys) => {
|
|
@@ -26,7 +26,7 @@ export default class PreferencesApi {
|
|
|
26
26
|
|
|
27
27
|
async getValue(key) {
|
|
28
28
|
return await this.request('get')
|
|
29
|
-
.fetch(
|
|
29
|
+
.fetch(getApiRoot() + 'preferences/' + key);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async checkValid(key, value) {
|
|
@@ -34,7 +34,7 @@ export default class PreferencesApi {
|
|
|
34
34
|
.withJson({
|
|
35
35
|
value: value
|
|
36
36
|
})
|
|
37
|
-
.fetch(
|
|
37
|
+
.fetch(getApiRoot() + 'preferences/' + key + '/validate');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async setValue(key, value) {
|
|
@@ -42,6 +42,6 @@ export default class PreferencesApi {
|
|
|
42
42
|
.withJson({
|
|
43
43
|
value: value
|
|
44
44
|
})
|
|
45
|
-
.fetch(
|
|
45
|
+
.fetch(getApiRoot() + 'preferences/' + key);
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/UserPermissions.js
CHANGED
|
@@ -23,10 +23,11 @@ export default class UserPermissions {
|
|
|
23
23
|
let keyParts = key.split('.');
|
|
24
24
|
|
|
25
25
|
if(keyParts.length !== 2) {
|
|
26
|
-
throw new Error('TreePermissionsSystem Requires a Dot-Seperated Permissions Key');
|
|
26
|
+
throw new Error('TreePermissionsSystem Requires a Dot-Seperated Permissions Key: ' + key);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// check for the specific key
|
|
30
|
+
// console.log(key + " = " + result);
|
|
30
31
|
return this.hasKey(keyParts[0], keyParts[1]);
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -4,17 +4,11 @@ jest.mock('./AuthApi');
|
|
|
4
4
|
|
|
5
5
|
test('gets and sets preferences', async () => {
|
|
6
6
|
UserPreferences.setBuefy({});
|
|
7
|
-
UserPreferences
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
foo: 'bar',
|
|
11
|
-
baz: { qux: 'fub '}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
7
|
+
let prefs = new UserPreferences({
|
|
8
|
+
foo: 'bar',
|
|
9
|
+
baz: { qux: 'fub '}
|
|
14
10
|
});
|
|
15
11
|
|
|
16
|
-
let prefs = await UserPreferences.load();
|
|
17
|
-
|
|
18
12
|
expect(prefs.get('foo')).toBe('bar');
|
|
19
13
|
expect(prefs.has('fob')).toBe(false);
|
|
20
14
|
expect(prefs.has('baz.qux')).toBe(true);
|
package/src/UsersApi.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getApiRoot, CrudApi} from './CrudApi';
|
|
2
2
|
|
|
3
3
|
export default class UsersApi extends CrudApi {
|
|
4
4
|
|
|
@@ -20,12 +20,12 @@ export default class UsersApi extends CrudApi {
|
|
|
20
20
|
.withJson({
|
|
21
21
|
fullName: name
|
|
22
22
|
})
|
|
23
|
-
.fetch(
|
|
23
|
+
.fetch(getApiRoot() + 'users/' + id + '/fullName');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async impersonate(id) {
|
|
27
27
|
return await this.request('post')
|
|
28
|
-
.fetch(
|
|
28
|
+
.fetch(getApiRoot() + 'users/' + id + '/impersonate');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async forceDelete(id) {
|
|
@@ -35,7 +35,7 @@ export default class UsersApi extends CrudApi {
|
|
|
35
35
|
|
|
36
36
|
async stopImpersonating() {
|
|
37
37
|
return await this.request('post')
|
|
38
|
-
.fetch(
|
|
38
|
+
.fetch(getApiRoot() + 'users/stop-impersonating');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
}
|
package/src/api.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
import {getApiRoot} from "./CrudApi";
|
|
2
|
+
|
|
3
|
+
export const getApiHost = () => {
|
|
4
|
+
if (parseInt(window.location.port) >= 3000) {
|
|
5
|
+
// for local development, Laravel should be running on port 80,
|
|
6
|
+
// whereas the frontend (`npm run dev`) is running on a higher port
|
|
7
|
+
return "http://localhost/";
|
|
8
|
+
} else {
|
|
9
|
+
return '/';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
var xsrfToken = null;
|
|
2
14
|
|
|
3
15
|
export const initCsrfCookie = async () => {
|
|
4
16
|
await window.fetch(
|
|
5
|
-
'
|
|
17
|
+
getApiRoot() + 'csrf-cookie',
|
|
6
18
|
{
|
|
7
19
|
credentials: 'same-origin'
|
|
8
20
|
});
|
|
@@ -46,7 +58,7 @@ export class FetchBuilder {
|
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
cookies() {
|
|
49
|
-
this.credentials = 'same-origin';
|
|
61
|
+
this.credentials = getApiHost() === '/' ? 'same-origin' : 'include';
|
|
50
62
|
return this;
|
|
51
63
|
}
|
|
52
64
|
|
|
@@ -90,7 +102,7 @@ export class FetchBuilder {
|
|
|
90
102
|
queue: false
|
|
91
103
|
});
|
|
92
104
|
return {};
|
|
93
|
-
} else if(response.status === 204) {
|
|
105
|
+
} else if(response.status === 204 || response.status === 404) {
|
|
94
106
|
// no content, we're okay
|
|
95
107
|
} else {
|
|
96
108
|
console.error('Response did not contain valid JSON: ', e);
|
|
@@ -158,6 +170,8 @@ const handleAPIError = function(content, $buefy, $router, response) {
|
|
|
158
170
|
} else if(response.status === 403 && content.code === 'email_unverified') {
|
|
159
171
|
$router.push({ path: '/auth/needs-verified-email', query: {redirect: $router.currentRoute.fullPath } });
|
|
160
172
|
return;
|
|
173
|
+
} else if(response.status === 404) {
|
|
174
|
+
$router.push({ name: 'error404' });
|
|
161
175
|
} else if(response.status === 429) {
|
|
162
176
|
$buefy.notification.open({
|
|
163
177
|
message: 'Too many requests within a short timeframe. Please wait.',
|
package/src/components/App.vue
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div style="height: 100%;">
|
|
3
|
-
<!-- <transition name="slide-left" mode="out-in">-->
|
|
4
3
|
<router-view @logout="signOut">
|
|
5
4
|
<template #main-navigation>
|
|
6
|
-
<
|
|
5
|
+
<MainMenu :items="mainMenuItems" />
|
|
7
6
|
</template>
|
|
8
7
|
</router-view>
|
|
9
|
-
<!-- </transition>-->
|
|
10
8
|
</div>
|
|
11
9
|
</template>
|
|
12
10
|
|
|
13
11
|
<script>
|
|
14
12
|
import AuthApi from "../AuthApi";
|
|
13
|
+
import MainMenu from "./MainMenu.vue";
|
|
15
14
|
export default {
|
|
16
15
|
name: "App",
|
|
16
|
+
components: {MainMenu},
|
|
17
17
|
props: {
|
|
18
18
|
appTitle: { type: String, required: true },
|
|
19
19
|
defaultRouteTitle: { type: String, required: true },
|
|
20
|
+
mainMenuItems: { type: Object, required: true },
|
|
20
21
|
impersonating: {
|
|
21
22
|
type: Boolean,
|
|
22
23
|
default: false
|
|
@@ -84,8 +84,9 @@
|
|
|
84
84
|
<script>
|
|
85
85
|
import {FetchBuilder} from "../api";
|
|
86
86
|
import Internationalize from "../Internationalize";
|
|
87
|
-
import UAParser from 'ua-parser-js';
|
|
87
|
+
import { UAParser } from 'ua-parser-js';
|
|
88
88
|
import AuthApi from "../AuthApi";
|
|
89
|
+
import {getApiRoot} from "../CrudApi";
|
|
89
90
|
|
|
90
91
|
const IP_INFO_LOADING = 'loading';
|
|
91
92
|
|
|
@@ -121,7 +122,7 @@ export default {
|
|
|
121
122
|
let data = await FetchBuilder
|
|
122
123
|
.default(this.$buefy, 'post')
|
|
123
124
|
.withQueryParams({ page: this.paginatedItems.currentPage })
|
|
124
|
-
.fetch('
|
|
125
|
+
.fetch(getApiRoot() + 'auth/login-log-entries');
|
|
125
126
|
|
|
126
127
|
this.paginatedItems.items = data.items.map((item) => { return { geolocationInfo: this.ipInfo.get(item.ipAddress), ...item } });
|
|
127
128
|
this.paginatedItems.totalItems = data.totalItems;
|
|
@@ -143,7 +144,7 @@ export default {
|
|
|
143
144
|
}
|
|
144
145
|
this.ipInfo.set(ip, IP_INFO_LOADING);
|
|
145
146
|
FetchBuilder.default(this.$buefy, 'post')
|
|
146
|
-
.fetch('
|
|
147
|
+
.fetch(getApiRoot() + 'auth/ip-location/' + ip, (data) => data)
|
|
147
148
|
.then((data) => {
|
|
148
149
|
this.ipInfo.set(ip, data);
|
|
149
150
|
this.updateInfoForIp(ip);
|
|
@@ -31,9 +31,16 @@
|
|
|
31
31
|
|
|
32
32
|
<script>
|
|
33
33
|
|
|
34
|
+
import AceEditor from 'vue2-ace-editor';
|
|
35
|
+
import 'brace/ext/language_tools'; //language extension prerequsite...
|
|
36
|
+
import 'brace/mode/html';
|
|
37
|
+
import 'brace/mode/twig';
|
|
38
|
+
import 'brace/theme/tomorrow_night_eighties';
|
|
39
|
+
import 'brace/theme/tomorrow_night';
|
|
40
|
+
|
|
34
41
|
export default {
|
|
35
42
|
name: "CodeEditor",
|
|
36
|
-
components: { AceEditor
|
|
43
|
+
components: { AceEditor },
|
|
37
44
|
props: {
|
|
38
45
|
value: { type: String, default: null },
|
|
39
46
|
height: { type: String, required: true },
|
|
@@ -65,11 +72,6 @@ export default {
|
|
|
65
72
|
},
|
|
66
73
|
methods: {
|
|
67
74
|
editorInit() {
|
|
68
|
-
require('brace/ext/language_tools') //language extension prerequsite...
|
|
69
|
-
require(`brace/mode/${this.lang}`);
|
|
70
|
-
require(`brace/theme/${this.theme}`);
|
|
71
|
-
require(`brace/snippets/${this.lang}`);
|
|
72
|
-
|
|
73
75
|
// ignore first missing DOCTYPE warning
|
|
74
76
|
let session = this.$refs.ace.editor.getSession();
|
|
75
77
|
session.on("changeAnnotation", () => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
<script>
|
|
16
16
|
import {FetchBuilder} from "../api";
|
|
17
|
-
import {
|
|
17
|
+
import {getApiRoot} from "../CrudApi";
|
|
18
18
|
import download from "downloadjs";
|
|
19
19
|
|
|
20
20
|
export default {
|
|
@@ -29,7 +29,7 @@ export default {
|
|
|
29
29
|
this.exporting = true;
|
|
30
30
|
let response = await (new FetchBuilder(this.$buefy, 'post'))
|
|
31
31
|
.cookies()
|
|
32
|
-
.fetchRaw(
|
|
32
|
+
.fetchRaw(getApiRoot() + "import-export/export");
|
|
33
33
|
this.$buefy.notification.open({ message: 'Export successful', type: 'is-success', queue: false });
|
|
34
34
|
let blob = await response.blob();
|
|
35
35
|
download(blob, 'database ' + (new Date()).toLocaleString() + '.zip');
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script>
|
|
12
12
|
import MediaInsertModal from "./media/MediaInsertModal.vue";
|
|
13
13
|
|
|
14
|
-
import { morphToNotification
|
|
14
|
+
import {getApiHost, morphToNotification} from "../api";
|
|
15
15
|
import MediaApi from "../MediaApi";
|
|
16
16
|
|
|
17
17
|
// from https://gist.github.com/hdodov/a87c097216718655ead6cf2969b0dcfa
|
|
@@ -134,7 +134,7 @@ export default {
|
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
vuePathToURL(path) {
|
|
137
|
-
return this.legacyPrefix + path;
|
|
137
|
+
return getApiHost() + this.legacyPrefix.replace(/^\//, '') + path;
|
|
138
138
|
},
|
|
139
139
|
// We detect when the iframe url changes, and update our window accordingly...
|
|
140
140
|
onNavigated(newURL) {
|
|
@@ -162,7 +162,7 @@ export default {
|
|
|
162
162
|
|
|
163
163
|
if(path !== this.currentPath) {
|
|
164
164
|
let { loadInside, location } = this.fullURLToVuePath(path);
|
|
165
|
-
console.log('[LegacyPage] ', loadInside, location);
|
|
165
|
+
console.log('[LegacyPage] ', 'loadInside:', loadInside, 'location:', location);
|
|
166
166
|
if(loadInside === 'iframe') {
|
|
167
167
|
window.history.pushState({}, "", location);
|
|
168
168
|
} else if(loadInside === 'vue') {
|
|
@@ -182,9 +182,9 @@ export default {
|
|
|
182
182
|
},
|
|
183
183
|
loadPath(routePath) {
|
|
184
184
|
let path = this.vuePathToURL(routePath);
|
|
185
|
-
if(path
|
|
185
|
+
if(path.endsWith('/oxygen/view/auth/login')) {
|
|
186
186
|
console.log('[LegacyPage] Need to login again, redirecting...');
|
|
187
|
-
window.location.replace('/oxygen/
|
|
187
|
+
window.location.replace('/oxygen/auth/login');
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
console.log('[LegacyPage] Loading', path);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="userPermissions">
|
|
3
|
+
<b-menu-list>
|
|
4
|
+
<b-menu-item icon="home" tag="router-link" to="/dashboard" label="Dashboard"></b-menu-item>
|
|
5
|
+
</b-menu-list>
|
|
6
|
+
|
|
7
|
+
<b-menu-list v-for="(category, label) in categoriesWithPermission(items)"
|
|
8
|
+
:key="label"
|
|
9
|
+
:label="label">
|
|
10
|
+
<b-menu-item v-for="(group, groupLabel) in groupsWithPermission(category)"
|
|
11
|
+
:key="groupLabel"
|
|
12
|
+
:icon="group.icon"
|
|
13
|
+
:tag="userPermissions.has(group.listPermission) ? 'router-link' : null"
|
|
14
|
+
:expanded="group.groupPrefix ? $route.fullPath.startsWith(group.groupPrefix) : null"
|
|
15
|
+
:to="group.listAction">
|
|
16
|
+
<template #label>
|
|
17
|
+
{{ groupLabel }}
|
|
18
|
+
<b-button v-if="userPermissions.has(group.addPermission)"
|
|
19
|
+
tag="router-link"
|
|
20
|
+
type="is-text"
|
|
21
|
+
class="is-pulled-right show-if-active"
|
|
22
|
+
:icon-right="group.addIcon"
|
|
23
|
+
:to="group.addAction"></b-button>
|
|
24
|
+
</template>
|
|
25
|
+
<b-menu-item v-for="(item, itemLabel) in itemsWithPermission(group.items)" :key="itemLabel" :label="itemLabel" tag="router-link" :to="item.to"></b-menu-item>
|
|
26
|
+
</b-menu-item>
|
|
27
|
+
</b-menu-list>
|
|
28
|
+
|
|
29
|
+
<b-menu-list v-if="userPermissions && userPermissions.hasOneOf(['preferences.getValue', 'users.getList', 'groups.getList', 'importExport.getList'])" label="System">
|
|
30
|
+
<b-menu-item v-if="userPermissions && userPermissions.has('preferences.getValue')" icon="cogs" tag="router-link" to="/preferences" label="Preferences"></b-menu-item>
|
|
31
|
+
<b-menu-item v-if="userPermissions && userPermissions.has('users.getList')" icon="users" tag="router-link" to="/users" label="Users and Permissions"></b-menu-item>
|
|
32
|
+
</b-menu-list>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
export default {
|
|
39
|
+
name: "MainMenu",
|
|
40
|
+
props: {
|
|
41
|
+
items: {
|
|
42
|
+
type: Object,
|
|
43
|
+
required: true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
computed: {
|
|
47
|
+
userPermissions() { return this.$store.getters.userPermissions; }
|
|
48
|
+
},
|
|
49
|
+
methods: {
|
|
50
|
+
categoriesWithPermission(categories) {
|
|
51
|
+
return Object.fromEntries(Object.entries(categories).filter(([,category]) => this.userPermissions.hasOneOf(Object.values(category).flatMap((group) => this.getPermissionsForGroup(group))) ));
|
|
52
|
+
},
|
|
53
|
+
getPermissionsForGroup(group) {
|
|
54
|
+
let values = Object.values(group.items).map(s => s.permission);
|
|
55
|
+
if(group.addPermission) { values.push(group.addPermission); }
|
|
56
|
+
if(group.listPermission) { values.push(group.listPermission); }
|
|
57
|
+
return values;
|
|
58
|
+
},
|
|
59
|
+
itemsWithPermission(items) {
|
|
60
|
+
return Object.fromEntries(Object.entries(items).filter(([, item]) => this.userPermissions.has(item.permission)));
|
|
61
|
+
},
|
|
62
|
+
groupsWithPermission(groups) {
|
|
63
|
+
return Object.fromEntries(Object.entries(groups)
|
|
64
|
+
.filter(([, group]) => this.userPermissions.hasOneOf(this.getPermissionsForGroup(group)))
|
|
65
|
+
.sort(([,groupA], [,groupB]) => (groupB.order ?? 0) - (groupA.order ?? 0))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<style scoped lang="scss">
|
|
73
|
+
.show-if-active {
|
|
74
|
+
visibility: hidden;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
padding: 0;
|
|
77
|
+
transition: visibility 0.2s ease, opacity 0.2s ease;
|
|
78
|
+
height: auto;
|
|
79
|
+
|
|
80
|
+
.router-link-active & {
|
|
81
|
+
visibility: visible;
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.menu-list li:hover & {
|
|
86
|
+
visibility: visible;
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.left-navigation-container.is-collapsed .show-if-active {
|
|
92
|
+
visibility: hidden !important;
|
|
93
|
+
opacity: 0 !important;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
|
|
97
|
+
<style>
|
|
98
|
+
.show-if-active .icon:first-child:last-child {
|
|
99
|
+
margin-left: 0;
|
|
100
|
+
margin-right: 0;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -49,35 +49,35 @@ export default {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
.login-theme-autumn {
|
|
52
|
-
background-image: url('/
|
|
52
|
+
background-image: url('../../../assets/bg/autumn-min.jpg');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
.login-theme-city {
|
|
56
|
-
background-image: url('/
|
|
56
|
+
background-image: url('../../../assets/bg/city-min.jpg');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.login-theme-clouds {
|
|
60
|
-
background-image: url('/
|
|
60
|
+
background-image: url('../../../assets/bg/clouds-min.jpg');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
.login-theme-coast {
|
|
64
|
-
background-image: url('/
|
|
64
|
+
background-image: url('../../../assets/bg/coast-min.jpg');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
.login-theme-speckles {
|
|
68
|
-
background-image: url('/
|
|
68
|
+
background-image: url('../../../assets/bg/speckles-min.jpg');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.login-theme-trees {
|
|
72
|
-
background-image: url('/
|
|
72
|
+
background-image: url('../../../assets/bg/trees-min.jpg');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.login-theme-waves {
|
|
76
|
-
background-image: url('/
|
|
76
|
+
background-image: url('../../../assets/bg/waves.jpg');
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
.login-theme-yosemite {
|
|
80
|
-
background-image: url('/
|
|
80
|
+
background-image: url('../../../assets/bg/yosemite-falls-min.jpg');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
.login-theme-white {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<p class="title">Events<b-icon icon="calendar-alt" size="is-medium"></b-icon></p>
|
|
4
|
+
<p class="subtitle">Promotion for upcoming concerts and events</p>
|
|
5
|
+
<div class="buttons">
|
|
6
|
+
<b-button tag="router-link" to="/upcoming-events" icon-left="list">Manage Events</b-button>
|
|
7
|
+
<b-button tag="router-link" to="/upcoming-events/create" icon-left="plus" type="is-success">Create New Event</b-button>
|
|
8
|
+
</div>
|
|
9
|
+
</article>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
export default {
|
|
14
|
+
name: "EventsPanel"
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<style scoped lang="scss">
|
|
19
|
+
@import './panel.scss';
|
|
20
|
+
</style>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="full-height scroll-container">
|
|
3
|
+
<div class="has-background-white-ter">
|
|
4
|
+
<div class="container hero is-medium">
|
|
5
|
+
<div class="hero-body">
|
|
6
|
+
<h1 class="title" style="font-size: 3rem;">
|
|
7
|
+
<transition name="fade">
|
|
8
|
+
<span v-if="user">Welcome, {{ user.fullName }}</span>
|
|
9
|
+
</transition>
|
|
10
|
+
</h1>
|
|
11
|
+
<br>
|
|
12
|
+
<h2 class="subtitle">The Oxygen CMS administration panel allows you to edit the site content, manage events and more.</h2>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="has-background-white">
|
|
18
|
+
<div class="container hero">
|
|
19
|
+
<div class="hero-body">
|
|
20
|
+
<section class="tile is-ancestor">
|
|
21
|
+
<div class="tile is-parent">
|
|
22
|
+
<div class="tile is-child heading-box">
|
|
23
|
+
<h2 class="title">Manage site</h2>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<div class="grid-loading">
|
|
29
|
+
<b-loading :active="!userPermissions" :is-full-page="false"></b-loading>
|
|
30
|
+
<transition name="fade">
|
|
31
|
+
<section v-if="userPermissions" class="tile is-ancestor">
|
|
32
|
+
<div v-for="(group, i) in panels" :key="i" class="tile is-vertical">
|
|
33
|
+
<div v-if="userPermissions && userPermissions.hasOneOf(group.map(panel => panel.permission))" class="tile is-parent is-vertical">
|
|
34
|
+
<div v-for="panel in group" :key="panel.name" class="tile is-child notification">
|
|
35
|
+
<component :is="panel.component"></component>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</section>
|
|
40
|
+
</transition>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<component :is="item" v-for="(item, i) in extraRows" :key="i"></component>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
export default {
|
|
53
|
+
name: "MainDashboard",
|
|
54
|
+
props: {
|
|
55
|
+
panels: {
|
|
56
|
+
type: Array,
|
|
57
|
+
required: true
|
|
58
|
+
},
|
|
59
|
+
extraRows: {
|
|
60
|
+
type: Array,
|
|
61
|
+
default: () => { return []; }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
data() {
|
|
65
|
+
return {
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
computed: {
|
|
69
|
+
userPermissions() { return this.$store.getters.userPermissions; },
|
|
70
|
+
user() { return this.$store.state.user; }
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style scoped>
|
|
76
|
+
.title .icon {
|
|
77
|
+
margin-left: 1rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.grid-loading {
|
|
81
|
+
position: relative;
|
|
82
|
+
min-height: 30rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.notification {
|
|
86
|
+
text-align: center;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<p class="title">Photos & Files <b-icon icon="photo-video" size="is-medium"></b-icon></p>
|
|
4
|
+
<p class="subtitle">Photos, videos, audio, PDFs.</p>
|
|
5
|
+
<div class="buttons">
|
|
6
|
+
<b-button tag="router-link" to="/media/list" icon-left="list">Manage Photos & Files</b-button>
|
|
7
|
+
<b-button tag="router-link" type="is-success" to="/media/list?upload=true" icon-left="upload">Upload</b-button>
|
|
8
|
+
<b-button tag="router-link" to="/media/responsive-images" type="is-light" icon-left="mail-bulk">Generate Responsive Images</b-button>
|
|
9
|
+
</div>
|
|
10
|
+
</article>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
export default {
|
|
15
|
+
name: "MediaPanel"
|
|
16
|
+
}
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped lang="scss">
|
|
20
|
+
@import './panel.scss';
|
|
21
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article>
|
|
3
|
+
<p class="title">Pages<b-icon icon="file-alt" size="is-medium"></b-icon></p>
|
|
4
|
+
<p class="subtitle">Manage the content of the website</p>
|
|
5
|
+
<div class="buttons">
|
|
6
|
+
<b-button tag="router-link" to="/pages" icon-left="list">Manage Pages</b-button>
|
|
7
|
+
<b-button tag="router-link" to="/partials" icon-left="puzzle-piece">Manage Partials</b-button>
|
|
8
|
+
</div>
|
|
9
|
+
<b-button tag="a" href="/" icon-left="eye" type="is-primary" rounded>View Live Site</b-button>
|
|
10
|
+
</article>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
export default {
|
|
15
|
+
name: "PagesPartialsPanel",
|
|
16
|
+
computed: {
|
|
17
|
+
userPermissions() { return this.$store.getters.userPermissions; },
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style scoped lang="scss">
|
|
23
|
+
@import './panel.scss';
|
|
24
|
+
</style>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</template>
|
|
19
19
|
|
|
20
20
|
<script>
|
|
21
|
-
import {
|
|
21
|
+
import {getApiRoot} from "../../CrudApi";
|
|
22
22
|
import {FetchBuilder, morphToNotification} from "../../api";
|
|
23
23
|
|
|
24
24
|
export default {
|
|
@@ -35,7 +35,7 @@ export default {
|
|
|
35
35
|
this.requestInFlight = true;
|
|
36
36
|
this.hasServerLog = false;
|
|
37
37
|
this.serverLog = '[generating...]';
|
|
38
|
-
let result = await (FetchBuilder.default(this.$buefy, 'post')).fetch(
|
|
38
|
+
let result = await (FetchBuilder.default(this.$buefy, 'post')).fetch(getApiRoot() + 'media/make-responsive');
|
|
39
39
|
this.$buefy.toast.open(morphToNotification(result));
|
|
40
40
|
this.requestInFlight = false;
|
|
41
41
|
this.serverLog = result.log;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PreferencesList>
|
|
3
|
+
<template #default="slotProps">
|
|
4
|
+
<b-tab-item v-if="slotProps.canAccessPrefs(['appearance.themes', 'appearance.pages', 'appearance.events'].concat(getExtraPrefsPermissions('appearance')))" label="Website Theme">
|
|
5
|
+
<PreferencesThemeChooser @theme-changed="onThemeChanged" />
|
|
6
|
+
<PreferencesPageTemplates :current-theme="currentTheme" />
|
|
7
|
+
<PreferencesEventTemplates :current-theme="currentTheme" />
|
|
8
|
+
<PreferencesSiteAppearance :current-theme="currentTheme" />
|
|
9
|
+
<component :is="pref.component" v-for="pref in getExtraPrefs('appearance')" :key="pref.key" :current-theme="currentTheme" />
|
|
10
|
+
</b-tab-item>
|
|
11
|
+
<b-tab-item v-if="slotProps.canAccessPrefs(getExtraPrefsPermissions('external'))" label="External Integrations">
|
|
12
|
+
<component :is="pref.component" v-for="pref in getExtraPrefs('external')" :key="pref.key" :current-theme="currentTheme" />
|
|
13
|
+
</b-tab-item>
|
|
14
|
+
<b-tab-item v-if="slotProps.canAccessPrefs(['modules.auth'])" label="Authentication & Security">
|
|
15
|
+
<PreferencesAuthentication :current-theme="currentTheme" />
|
|
16
|
+
</b-tab-item>
|
|
17
|
+
<b-tab-item v-if="slotProps.canAccessPrefs(['appearance.auth'])" label="Admin Look & Feel">
|
|
18
|
+
<PreferencesAdminAppearance :current-theme="currentTheme" />
|
|
19
|
+
</b-tab-item>
|
|
20
|
+
<b-tab-item v-if="userPermissions.has('importExport.getExport')" label="Website Data">
|
|
21
|
+
<ImportExport />
|
|
22
|
+
</b-tab-item>
|
|
23
|
+
</template>
|
|
24
|
+
</PreferencesList>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
|
|
29
|
+
import PreferencesList from './PreferencesList.vue';
|
|
30
|
+
import PreferencesPageTemplates from './PreferencesPageTemplates.vue';
|
|
31
|
+
import PreferencesEventTemplates from './PreferencesEventTemplates.vue';
|
|
32
|
+
import PreferencesThemeChooser from './PreferencesThemeChooser.vue';
|
|
33
|
+
import PreferencesAuthentication from './PreferencesAuthentication.vue';
|
|
34
|
+
import PreferencesAdminAppearance from './PreferencesAdminAppearance.vue';
|
|
35
|
+
import PreferencesSiteAppearance from './PreferencesSiteAppearance.vue';
|
|
36
|
+
import ImportExport from '../ImportExport.vue';
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
name: "Preferences",
|
|
40
|
+
components: {
|
|
41
|
+
PreferencesList,
|
|
42
|
+
PreferencesPageTemplates,
|
|
43
|
+
PreferencesEventTemplates,
|
|
44
|
+
PreferencesThemeChooser,
|
|
45
|
+
PreferencesAuthentication,
|
|
46
|
+
PreferencesAdminAppearance,
|
|
47
|
+
PreferencesSiteAppearance,
|
|
48
|
+
ImportExport
|
|
49
|
+
},
|
|
50
|
+
props: {
|
|
51
|
+
extraPrefs: {
|
|
52
|
+
type: Object,
|
|
53
|
+
default: () => { return {}; }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
data() {
|
|
57
|
+
return {
|
|
58
|
+
currentTheme: null,
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
computed: {
|
|
62
|
+
userPermissions() {
|
|
63
|
+
return this.$store.getters.userPermissions;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
methods: {
|
|
67
|
+
onThemeChanged(currentTheme) {
|
|
68
|
+
this.currentTheme = currentTheme;
|
|
69
|
+
},
|
|
70
|
+
getExtraPrefsPermissions(category) {
|
|
71
|
+
return (this.extraPrefs[category] ?? []).map(pref => pref.key);
|
|
72
|
+
},
|
|
73
|
+
getExtraPrefs(category) {
|
|
74
|
+
return (this.extraPrefs[category] ?? []);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<style scoped>
|
|
81
|
+
|
|
82
|
+
</style>
|
package/src/icons.js
CHANGED
|
@@ -73,7 +73,8 @@ import {
|
|
|
73
73
|
faLandmark,
|
|
74
74
|
faFolderOpen,
|
|
75
75
|
faImages,
|
|
76
|
-
faMinusCircle
|
|
76
|
+
faMinusCircle,
|
|
77
|
+
faCalendarPlus, faPaperPlane
|
|
77
78
|
} from "@fortawesome/free-solid-svg-icons";
|
|
78
79
|
|
|
79
80
|
export const addIconsToLibrary = () => {
|
|
@@ -86,5 +87,5 @@ export const addIconsToLibrary = () => {
|
|
|
86
87
|
faFileExcel, faFileCsv, faChevronCircleDown, faChevronCircleUp, faTrash,
|
|
87
88
|
faEye, faEyeSlash, faCaretDown, faCaretUp, faUpload, faUser, faFolder, faHome, faFilePdf, faSignOutAlt, faTag,
|
|
88
89
|
faFolderPlus, faTimes, faQuestionCircle, faFileUpload, faLandmark,
|
|
89
|
-
faFolderOpen, faFile, faFileAudio, faFileImage, faShare, faImages);
|
|
90
|
+
faFolderOpen, faFile, faFileAudio, faFileImage, faShare, faImages, faCalendarPlus, faPaperPlane);
|
|
90
91
|
};
|
package/src/main.js
CHANGED
|
@@ -12,6 +12,7 @@ import { AuthRoutes, makeAuthenticatedRoute } from "./routes";
|
|
|
12
12
|
import createStore from "./store/index";
|
|
13
13
|
import { checkAuthenticated } from "./AuthApi";
|
|
14
14
|
import Error404 from "./components/Error404.vue";
|
|
15
|
+
import MainMenu from "./components/MainMenu.vue";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Creates the Vue.js Oxygen application, allowing for a few points of customization (i.e.: adding modules)
|
|
@@ -26,7 +27,7 @@ export default class OxygenUI {
|
|
|
26
27
|
this.Vue = Vue;
|
|
27
28
|
this.authenticatedRoutes = []
|
|
28
29
|
this.unauthenticatedRoutes = []
|
|
29
|
-
this.
|
|
30
|
+
this.mainMenuItems = {}
|
|
30
31
|
this.beforeMountHooks = []
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -40,6 +41,17 @@ export default class OxygenUI {
|
|
|
40
41
|
this.authenticatedRoutes.push(route);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
addMainMenuGroup(category, group) {
|
|
45
|
+
if(!this.mainMenuItems[category]) {
|
|
46
|
+
this.mainMenuItems[category] = {};
|
|
47
|
+
}
|
|
48
|
+
if(!group.items) {
|
|
49
|
+
group.items = {};
|
|
50
|
+
}
|
|
51
|
+
this.mainMenuItems[category][group.name] = group;
|
|
52
|
+
return group;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
addUnauthenticatedRoutes(routes) {
|
|
44
56
|
for (let route of routes) {
|
|
45
57
|
this.unauthenticatedRoutes.push(route);
|
|
@@ -74,12 +86,18 @@ export default class OxygenUI {
|
|
|
74
86
|
makeAuthenticatedRoute(this.authenticatedRoutes)
|
|
75
87
|
])
|
|
76
88
|
.concat(this.unauthenticatedRoutes)
|
|
77
|
-
.concat([
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
.concat([
|
|
90
|
+
{
|
|
91
|
+
path: '/not-found',
|
|
92
|
+
name: 'error404',
|
|
93
|
+
component: Error404,
|
|
94
|
+
meta: {title: 'Not found'}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
path: '/*',
|
|
98
|
+
redirect: { name: 'error404' }
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
83
101
|
|
|
84
102
|
const router = new Router({
|
|
85
103
|
routes: routes,
|
|
@@ -91,7 +109,13 @@ export default class OxygenUI {
|
|
|
91
109
|
|
|
92
110
|
this.app = new this.Vue({
|
|
93
111
|
router: router,
|
|
94
|
-
|
|
112
|
+
render: (h) => h(App, {
|
|
113
|
+
props: {
|
|
114
|
+
appTitle: document.title,
|
|
115
|
+
defaultRouteTitle: 'Administration Panel',
|
|
116
|
+
mainMenuItems: this.mainMenuItems
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
95
119
|
store
|
|
96
120
|
});
|
|
97
121
|
|
|
@@ -109,3 +133,5 @@ export default class OxygenUI {
|
|
|
109
133
|
}
|
|
110
134
|
}
|
|
111
135
|
|
|
136
|
+
export const WEB_CONTENT = 'Web Content';
|
|
137
|
+
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import LegacyPage from "../components/LegacyPage.vue";
|
|
2
|
+
import { WEB_CONTENT } from "../main.js";
|
|
2
3
|
|
|
3
4
|
export default function(ui) {
|
|
5
|
+
ui.addMainMenuGroup(WEB_CONTENT, {
|
|
6
|
+
name: 'Pages',
|
|
7
|
+
icon: 'file-alt',
|
|
8
|
+
listAction: '/pages',
|
|
9
|
+
listPermission: 'pages.getList',
|
|
10
|
+
addIcon: 'plus',
|
|
11
|
+
addPermission: 'pages.postCreate',
|
|
12
|
+
addAction: '/pages/create',
|
|
13
|
+
items: {
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
ui.addMainMenuGroup(WEB_CONTENT, {
|
|
17
|
+
name: 'Partials',
|
|
18
|
+
icon: 'puzzle-piece',
|
|
19
|
+
listAction: '/partials',
|
|
20
|
+
listPermission: 'partials.getList',
|
|
21
|
+
addIcon: 'plus',
|
|
22
|
+
addPermission: 'partials.postCreate',
|
|
23
|
+
addAction: '/partials/create',
|
|
24
|
+
items: {
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
ui.addMainMenuGroup(WEB_CONTENT, {
|
|
28
|
+
name: 'Events',
|
|
29
|
+
icon: 'calendar-alt',
|
|
30
|
+
listAction: '/upcoming-events',
|
|
31
|
+
listPermission: 'upcomingEvents.getList',
|
|
32
|
+
addIcon: 'calendar-plus',
|
|
33
|
+
addPermission: 'upcomingEvents.postCreate',
|
|
34
|
+
addAction: '/upcoming-events/create',
|
|
35
|
+
items: {
|
|
36
|
+
}
|
|
37
|
+
});
|
|
4
38
|
ui.addAuthenticatedRoutes([
|
|
5
39
|
{
|
|
6
40
|
// will match everything, try to render a legacy Oxygen page...
|
package/src/modules/Media.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import MediaPage from "../components/media/MediaPage.vue";
|
|
2
2
|
import MediaResponsiveImages from "../components/media/MediaResponsiveImages.vue";
|
|
3
|
+
import {WEB_CONTENT} from "../main";
|
|
3
4
|
|
|
4
5
|
export default function(ui) {
|
|
6
|
+
ui.addMainMenuGroup(WEB_CONTENT, {
|
|
7
|
+
name: 'Photos & Files',
|
|
8
|
+
icon: 'photo-video',
|
|
9
|
+
groupPrefix: '/media',
|
|
10
|
+
addAction: '/media/list/?upload=true',
|
|
11
|
+
addIcon: 'upload',
|
|
12
|
+
addPermission: 'media.postCreate',
|
|
13
|
+
listAction: '/media/list',
|
|
14
|
+
listPermission: 'media.getList',
|
|
15
|
+
items: {
|
|
16
|
+
'Deleted Photos & Files': { to: '/media/trash', permission: 'media.getList' },
|
|
17
|
+
'Responsive Images': { to: '/media/responsive-images', permission: 'media.postMakeResponsive' }
|
|
18
|
+
}
|
|
19
|
+
});
|
|
5
20
|
ui.addAuthenticatedRoutes([
|
|
6
21
|
{
|
|
7
22
|
path: 'media/list/:currentPath(.*)?',
|
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
// Set your colors
|
|
2
2
|
$primary: #0053b1;
|
|
3
|
-
//$twitter: #4099FF;
|
|
4
3
|
|
|
5
|
-
@import "~bulma/sass/utilities/_all";
|
|
4
|
+
@import "~bulma/sass/utilities/_all.sass";
|
|
6
5
|
|
|
7
6
|
$menu-item-color: darken($grey, 5%);
|
|
8
7
|
$menu-item-active-background-color: transparent;
|
|
9
8
|
$menu-item-active-color: lighten($grey, 4%);
|
|
10
|
-
|
|
11
|
-
// $menu-item-hover-background-color: rgba(255, 255, 255, 0.1);
|
|
12
|
-
// $menu-item-hover-color: $white-bis;
|
|
13
|
-
|
|
14
|
-
//$navbar-item-active-color: #fff;
|
|
15
|
-
//$navbar-item-hover-background-color: #f3f3f9;
|
|
16
|
-
//
|
|
17
|
-
//// Links
|
|
18
|
-
//$link: $primary;
|
|
19
|
-
//$link-focus-border: $primary;
|
|
20
|
-
|
|
21
|
-
// Import Bulma's core
|
|
22
|
-
// TODO: wtf is going on here??
|
|
23
|
-
@import "~bulma/sass/utilities/_all";
|
package/src/styles/app.scss
CHANGED