@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.
- package/.babelrc +1 -0
- package/.eslintrc.js +22 -0
- package/.github/workflows/node.js.yml +29 -0
- package/.idea/modules.xml +8 -0
- package/.idea/ui.iml +10 -0
- package/.jshintrc +3 -0
- package/README.md +7 -0
- package/assets/oxygen-icon.png +0 -0
- package/jest.init.js +1 -0
- package/package.json +72 -0
- package/src/AuthApi.js +116 -0
- package/src/CrudApi.js +112 -0
- package/src/EventsApi.js +16 -0
- package/src/GroupsApi.js +9 -0
- package/src/Internationalize.js +31 -0
- package/src/MediaApi.js +52 -0
- package/src/MediaDirectoryApi.js +62 -0
- package/src/PreferencesApi.js +47 -0
- package/src/UserPermissions.js +66 -0
- package/src/UserPreferences.js +69 -0
- package/src/UserPreferences.test.js +23 -0
- package/src/UsersApi.js +41 -0
- package/src/api.js +209 -0
- package/src/components/App.vue +61 -0
- package/src/components/AuthenticatedLayout.vue +254 -0
- package/src/components/AuthenticationLog.vue +196 -0
- package/src/components/CodeEditor.vue +90 -0
- package/src/components/EditButtonOnRowHover.vue +21 -0
- package/src/components/Error404.vue +25 -0
- package/src/components/EventsChooser.vue +88 -0
- package/src/components/EventsTable.vue +82 -0
- package/src/components/GenericEditableField.vue +74 -0
- package/src/components/GroupsChooser.vue +58 -0
- package/src/components/GroupsList.vue +129 -0
- package/src/components/ImportExport.vue +45 -0
- package/src/components/LegacyPage.vue +256 -0
- package/src/components/UserJoined.vue +35 -0
- package/src/components/UserManagement.vue +168 -0
- package/src/components/UserProfileForm.vue +214 -0
- package/src/components/ViewProfile.vue +32 -0
- package/src/components/auth/Auth404.vue +16 -0
- package/src/components/auth/Login.vue +135 -0
- package/src/components/auth/LoginLogo.vue +30 -0
- package/src/components/auth/Logout.vue +26 -0
- package/src/components/auth/PasswordRemind.vue +71 -0
- package/src/components/auth/PasswordReset.vue +97 -0
- package/src/components/auth/TwoFactorSetup.vue +115 -0
- package/src/components/auth/VerifyEmail.vue +71 -0
- package/src/components/auth/WelcomeFloat.vue +87 -0
- package/src/components/auth/login.scss +17 -0
- package/src/components/media/MediaChooseDirectory.vue +129 -0
- package/src/components/media/MediaDirectory.vue +109 -0
- package/src/components/media/MediaInsertModal.vue +88 -0
- package/src/components/media/MediaItem.vue +282 -0
- package/src/components/media/MediaItemPreview.vue +45 -0
- package/src/components/media/MediaList.vue +305 -0
- package/src/components/media/MediaPage.vue +44 -0
- package/src/components/media/MediaResponsiveImages.vue +51 -0
- package/src/components/media/MediaUpload.vue +133 -0
- package/src/components/media/media.scss +51 -0
- package/src/components/preferences/PreferencesAdminAppearance.vue +22 -0
- package/src/components/preferences/PreferencesAuthentication.vue +27 -0
- package/src/components/preferences/PreferencesEventTemplates.vue +22 -0
- package/src/components/preferences/PreferencesField.vue +215 -0
- package/src/components/preferences/PreferencesList.vue +50 -0
- package/src/components/preferences/PreferencesPageTemplates.vue +23 -0
- package/src/components/preferences/PreferencesSiteAppearance.vue +22 -0
- package/src/components/preferences/PreferencesThemeChooser.vue +73 -0
- package/src/components/preferences/ShowIfPermitted.vue +37 -0
- package/src/components/preferences/UserPreferences.vue +30 -0
- package/src/components/users/CreateUserModal.vue +73 -0
- package/src/components/util.css +47 -0
- package/src/icons.js +90 -0
- package/src/main.js +112 -0
- package/src/modules/LegacyPages.js +18 -0
- package/src/modules/Media.js +45 -0
- package/src/modules/UserManagement.js +24 -0
- package/src/routes/index.js +92 -0
- package/src/store/index.js +70 -0
- package/src/styles/_variables.scss +23 -0
- package/src/styles/app.scss +76 -0
- package/src/unsavedChanges.js +16 -0
- package/src/util.js +65 -0
- package/src/util.test.js +39 -0
package/.babelrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "presets": ["@babel/preset-env"] }
|
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": true,
|
|
4
|
+
"es2021": true,
|
|
5
|
+
"jest/globals": true
|
|
6
|
+
},
|
|
7
|
+
"extends": [
|
|
8
|
+
"eslint:recommended",
|
|
9
|
+
"plugin:vue/recommended",
|
|
10
|
+
"prettier"
|
|
11
|
+
],
|
|
12
|
+
"parserOptions": {
|
|
13
|
+
"ecmaVersion": 12,
|
|
14
|
+
"sourceType": "module"
|
|
15
|
+
},
|
|
16
|
+
"plugins": [
|
|
17
|
+
"vue",
|
|
18
|
+
"jest"
|
|
19
|
+
],
|
|
20
|
+
"rules": {
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ master ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ master ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
node-version: [10.x, 12.x, 14.x]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v2
|
|
23
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
24
|
+
uses: actions/setup-node@v1
|
|
25
|
+
with:
|
|
26
|
+
node-version: ${{ matrix.node-version }}
|
|
27
|
+
- run: npm ci
|
|
28
|
+
- run: npm run lint
|
|
29
|
+
- run: npm run test
|
package/.idea/ui.iml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
6
|
+
</content>
|
|
7
|
+
<orderEntry type="inheritedJdk" />
|
|
8
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
9
|
+
</component>
|
|
10
|
+
</module>
|
package/.jshintrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Oxygen CMS - Vue.js User Interface Utilities
|
|
2
|
+
|
|
3
|
+
 
|
|
4
|
+
|
|
5
|
+
Supporting files for the Vue.JS-powered next-gen Oxygen CMS user interface.
|
|
6
|
+
|
|
7
|
+
This begins the migration of Oxygen into a Single-Page Application powered by the 'Oxygen API'.
|
|
Binary file
|
package/jest.init.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "babel-polyfill";
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxygen-cms/ui",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Various utilities for UI-building in Vue.js",
|
|
5
|
+
"main": "none",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/oxygen-cms/ui.git"
|
|
9
|
+
},
|
|
10
|
+
"author": "Chris Chamberlain <chris@chamberlain.id.au>",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"private": false,
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@fortawesome/fontawesome-svg-core": "^1.2.34",
|
|
15
|
+
"@fortawesome/free-regular-svg-icons": "^5.15.2",
|
|
16
|
+
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
|
17
|
+
"@fortawesome/vue-fontawesome": "^0.1.10",
|
|
18
|
+
"autoprefixer": "^9.8.5",
|
|
19
|
+
"babel-loader": "^8.1.0",
|
|
20
|
+
"brace": "^0.11.1",
|
|
21
|
+
"buefy": "^0.9.10",
|
|
22
|
+
"bulma": "~0.9.3",
|
|
23
|
+
"copy-webpack-plugin": "^5.1.1",
|
|
24
|
+
"downloadjs": "^1.4.7",
|
|
25
|
+
"file-loader": "^6.1.1",
|
|
26
|
+
"libphonenumber-js": "^1.9.11",
|
|
27
|
+
"lodash": "^4.17.21",
|
|
28
|
+
"title-case": "^3.0.3",
|
|
29
|
+
"ua-parser-js": "^0.7.24",
|
|
30
|
+
"v-hotkey": "^0.8.0",
|
|
31
|
+
"vex-js": "~4.1.0",
|
|
32
|
+
"vue": "^2.6.11",
|
|
33
|
+
"vue-async-computed": "^3.9.0",
|
|
34
|
+
"vue-loader": "^15.9.6",
|
|
35
|
+
"vue-router": "^3.5.1",
|
|
36
|
+
"vue-template-compiler": "^2.6.11",
|
|
37
|
+
"vue2-ace-editor": "^0.0.15",
|
|
38
|
+
"vuex": "^3.6.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@babel/core": "^7.13.1",
|
|
42
|
+
"@babel/preset-env": "^7.13.5",
|
|
43
|
+
"babel-jest": "^26.6.3",
|
|
44
|
+
"babel-polyfill": "^6.26.0",
|
|
45
|
+
"css-loader": "^3.5.1",
|
|
46
|
+
"eslint": "^7.32.0",
|
|
47
|
+
"eslint-config-prettier": "^8.3.0",
|
|
48
|
+
"eslint-plugin-jest": "^24.4.0",
|
|
49
|
+
"eslint-plugin-vue": "^7.17.0",
|
|
50
|
+
"jest": "^26.6.3",
|
|
51
|
+
"node-sass": "^4.13.1",
|
|
52
|
+
"postcss-loader": "^3.0.0",
|
|
53
|
+
"regenerator-runtime": "^0.13.7",
|
|
54
|
+
"sass-loader": "^8.0.2",
|
|
55
|
+
"webpack": "^4.46.0",
|
|
56
|
+
"webpack-cli": "^3.3.11"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"test": "jest",
|
|
60
|
+
"lint": "eslint --ext js,vue src/"
|
|
61
|
+
},
|
|
62
|
+
"jest": {
|
|
63
|
+
"verbose": true,
|
|
64
|
+
"setupFiles": [
|
|
65
|
+
"./jest.init.js"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/oxygen-cms/ui/issues"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://github.com/oxygen-cms/ui#readme"
|
|
72
|
+
}
|
package/src/AuthApi.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {API_ROOT} from "./CrudApi";
|
|
2
|
+
import {FetchBuilder, initCsrfCookie} from "./api";
|
|
3
|
+
import UserPermissions from "./UserPermissions";
|
|
4
|
+
|
|
5
|
+
export default class AuthApi {
|
|
6
|
+
|
|
7
|
+
constructor($buefy) {
|
|
8
|
+
this.$buefy = $buefy;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
request(method) {
|
|
12
|
+
return FetchBuilder.default(this.$buefy, method);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async login(username, password, code) {
|
|
16
|
+
return await this.request('post')
|
|
17
|
+
.withJson({
|
|
18
|
+
username,
|
|
19
|
+
password,
|
|
20
|
+
'2fa_code': code
|
|
21
|
+
})
|
|
22
|
+
.fetch(API_ROOT + 'auth/login');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getLoginPreferences() {
|
|
26
|
+
return await this.request('get').fetch(API_ROOT + 'auth/preferences');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async setupTwoFactorAuth() {
|
|
30
|
+
return await this.request('post')
|
|
31
|
+
.fetch(API_ROOT + 'auth/two-factor-setup');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async confirmTwoFactorAuth(code) {
|
|
35
|
+
return await this.request('post')
|
|
36
|
+
.withJson({
|
|
37
|
+
'2fa_code': code
|
|
38
|
+
})
|
|
39
|
+
.fetch(API_ROOT + 'auth/two-factor-confirm');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async sendReminderEmail(email) {
|
|
43
|
+
return await this.request('post')
|
|
44
|
+
.withJson({
|
|
45
|
+
'email': email
|
|
46
|
+
})
|
|
47
|
+
.fetch(API_ROOT + 'auth/send-reminder-email');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async sendEmailVerification() {
|
|
51
|
+
return await this.request('post')
|
|
52
|
+
.fetch(API_ROOT + 'auth/verify-email');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async resetPassword(params) {
|
|
56
|
+
return await this.request('post')
|
|
57
|
+
.withJson(params)
|
|
58
|
+
.fetch(API_ROOT + 'auth/reset-password');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async logout() {
|
|
62
|
+
let response = await this.request('post')
|
|
63
|
+
.fetch(API_ROOT + 'auth/logout');
|
|
64
|
+
this.$buefy.notification.open({
|
|
65
|
+
message: 'You have been logged out',
|
|
66
|
+
type: 'is-info',
|
|
67
|
+
duration: 4000,
|
|
68
|
+
queue: false
|
|
69
|
+
});
|
|
70
|
+
await initCsrfCookie();
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async changePassword(oldPass, newPass, newPassAgain) {
|
|
75
|
+
const params = {
|
|
76
|
+
oldPassword: oldPass,
|
|
77
|
+
password: newPass,
|
|
78
|
+
passwordConfirmation: newPassAgain
|
|
79
|
+
};
|
|
80
|
+
console.log(params);
|
|
81
|
+
return this.request('post')
|
|
82
|
+
.withJson(params)
|
|
83
|
+
.fetch(API_ROOT + 'auth/change-password');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async listUserSessions() {
|
|
87
|
+
return this.request('get')
|
|
88
|
+
.fetch(API_ROOT + 'auth/sessions')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async deleteUserSession(id) {
|
|
92
|
+
return this.request('delete')
|
|
93
|
+
.fetch(API_ROOT + 'auth/sessions/' + id)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const checkAuthenticated = (store) => {
|
|
98
|
+
return (to, from, next) => {
|
|
99
|
+
store.dispatch('determineLoginStatus').then((isLoggedIn) => {
|
|
100
|
+
if(!to.path.startsWith('/auth/login') && !isLoggedIn && to.meta.allowUnauthenticated !== true) {
|
|
101
|
+
UserPermissions.$buefy.notification.open({
|
|
102
|
+
message: 'You need to be logged in to view that page',
|
|
103
|
+
type: 'is-info',
|
|
104
|
+
queue: false,
|
|
105
|
+
duration: 7000
|
|
106
|
+
});
|
|
107
|
+
next({
|
|
108
|
+
path: '/auth/login',
|
|
109
|
+
query: { redirect: to.fullPath }
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
next();
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
};
|
package/src/CrudApi.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {FetchBuilder} from './api.js';
|
|
2
|
+
import {morphToNotification} from "./api";
|
|
3
|
+
|
|
4
|
+
const API_ROOT = '/oxygen/api/';
|
|
5
|
+
|
|
6
|
+
class CrudApi {
|
|
7
|
+
|
|
8
|
+
constructor($buefy) {
|
|
9
|
+
this.$buefy = $buefy;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
request(method) {
|
|
13
|
+
return FetchBuilder.default(this.$buefy, method);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static getResourceName() {
|
|
17
|
+
throw new Error();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getResourceRoot() {
|
|
21
|
+
return API_ROOT + this.getResourceName();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static prepareModelForAPI(data) {
|
|
25
|
+
let m = { ...data };
|
|
26
|
+
delete m.id;
|
|
27
|
+
return m;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async list(inTrash, page, searchQuery) {
|
|
31
|
+
return this.request('get')
|
|
32
|
+
.withQueryParams({
|
|
33
|
+
page: (searchQuery !== null && searchQuery !== '' ) ? null : page,
|
|
34
|
+
trash: (inTrash ? 'true' : 'false'),
|
|
35
|
+
q: (searchQuery !== null && searchQuery !== '' ) ? searchQuery : null
|
|
36
|
+
})
|
|
37
|
+
.fetch(this.constructor.getResourceRoot());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async create(data) {
|
|
41
|
+
return this.request('post')
|
|
42
|
+
.withJson(this.constructor.prepareModelForAPI(data))
|
|
43
|
+
.fetch(this.constructor.getResourceRoot());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async get(id) {
|
|
47
|
+
return this.request('get')
|
|
48
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async update(data) {
|
|
52
|
+
let id = data.id;
|
|
53
|
+
return this.request('put')
|
|
54
|
+
.withJson(this.constructor.prepareModelForAPI(data))
|
|
55
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(id) {
|
|
59
|
+
return this.request('delete')
|
|
60
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async search(searchQuery) {
|
|
64
|
+
return this.request('post')
|
|
65
|
+
.withJson(searchQuery)
|
|
66
|
+
.fetch(this.constructor.getResourceRoot() + '/search');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async forceDelete(id) {
|
|
70
|
+
return this.request('delete')
|
|
71
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id + '?force=true');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async confirmForceDelete(id) {
|
|
75
|
+
const promise = new Promise((resolve) => {
|
|
76
|
+
this.$buefy.dialog.confirm({
|
|
77
|
+
message: 'Are you sure you want to delete this record forever?',
|
|
78
|
+
onConfirm: resolve
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await promise;
|
|
83
|
+
let data = await this.forceDelete(id);
|
|
84
|
+
this.$buefy.toast.open(morphToNotification(data));
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async restoreAndNotify(id) {
|
|
89
|
+
let data = await this.request('post')
|
|
90
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id + '/restore');
|
|
91
|
+
this.$buefy.toast.open(morphToNotification(data));
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async deleteAndNotify(id) {
|
|
96
|
+
let data = await this.delete(id);
|
|
97
|
+
this.$buefy.toast.open(morphToNotification(data));
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async listVersions(id) {
|
|
102
|
+
return this.request('get')
|
|
103
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id + '/versions');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async makeHeadVersion(id) {
|
|
107
|
+
return this.request('post')
|
|
108
|
+
.fetch(this.constructor.getResourceRoot() + '/' + id + '/make-head');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { CrudApi, API_ROOT };
|
package/src/EventsApi.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CrudApi } from './CrudApi';
|
|
2
|
+
|
|
3
|
+
export default class EventsApi extends CrudApi {
|
|
4
|
+
|
|
5
|
+
static prepareModelForAPI(data) {
|
|
6
|
+
let m = { ...data };
|
|
7
|
+
delete m.id;
|
|
8
|
+
delete m.bookings;
|
|
9
|
+
return m;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static getResourceName() {
|
|
13
|
+
return 'upcoming-events';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
package/src/GroupsApi.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default class Internationalize {
|
|
2
|
+
static get locale() {
|
|
3
|
+
// hardcoded locale for now
|
|
4
|
+
return 'en-AU';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
static formatDate(date) {
|
|
8
|
+
let format = new Intl.DateTimeFormat(this.locale);
|
|
9
|
+
if(typeof date === 'string') {
|
|
10
|
+
return format.format(new Date(date));
|
|
11
|
+
}
|
|
12
|
+
return format.format(date);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static formatLastUpdated(updatedAt) {
|
|
16
|
+
let d = new Date(updatedAt);
|
|
17
|
+
return d.toDateString() + ' ' + d.toLocaleTimeString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static formatDateTime(datetime) {
|
|
21
|
+
let format = new Intl.DateTimeFormat(this.locale , {
|
|
22
|
+
year: 'numeric', month: 'numeric', day: 'numeric',
|
|
23
|
+
hour: 'numeric', minute: 'numeric', second: 'numeric'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if(typeof datetime === 'string') {
|
|
27
|
+
return format.format(new Date(datetime));
|
|
28
|
+
}
|
|
29
|
+
return format.format(datetime);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/MediaApi.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CrudApi } from './CrudApi';
|
|
2
|
+
|
|
3
|
+
export default class MediaApi extends CrudApi {
|
|
4
|
+
|
|
5
|
+
static get TYPE_IMAGE() { return 0; }
|
|
6
|
+
static get TYPE_DOCUMENT() { return 1; }
|
|
7
|
+
static get TYPE_AUDIO() { return 2; }
|
|
8
|
+
|
|
9
|
+
static getResourceName() {
|
|
10
|
+
return 'media';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static prepareModelForAPI(data) {
|
|
14
|
+
let m = { ...data };
|
|
15
|
+
delete m.id;
|
|
16
|
+
delete m.filename;
|
|
17
|
+
delete m.extension;
|
|
18
|
+
m.parentDirectory = m.parentDirectory ? m.parentDirectory.id : null;
|
|
19
|
+
delete m.selected;
|
|
20
|
+
return m;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async create(data) {
|
|
24
|
+
let request = this.request('post');
|
|
25
|
+
let formData = new FormData();
|
|
26
|
+
for(let file of data.files) {
|
|
27
|
+
formData.append('file[]', file);
|
|
28
|
+
}
|
|
29
|
+
if(data.currentDirectory !== null) {
|
|
30
|
+
formData.append('parentDirectory', data.currentDirectory.id);
|
|
31
|
+
}
|
|
32
|
+
return request.setBody(formData)
|
|
33
|
+
.fetch(this.constructor.getResourceRoot());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async list(inTrash, searchQuery, page, path) {
|
|
37
|
+
return this.request('get')
|
|
38
|
+
.withQueryParams({
|
|
39
|
+
path: path,
|
|
40
|
+
q: (searchQuery !== null && searchQuery !== '' ) ? searchQuery : null,
|
|
41
|
+
page: (searchQuery !== null && searchQuery !== '' ) ? null : page,
|
|
42
|
+
trash: (inTrash ? 'true' : 'false'),
|
|
43
|
+
})
|
|
44
|
+
.fetch(this.constructor.getResourceRoot());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static generateIncludeStatement(item) {
|
|
48
|
+
let fullPathNoExt = item.fullPath.split('.')[0];
|
|
49
|
+
return '{{ media(\'' + fullPathNoExt + '\') }}';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CrudApi } from './CrudApi';
|
|
2
|
+
|
|
3
|
+
export default class MediaDirectoryApi extends CrudApi {
|
|
4
|
+
|
|
5
|
+
static getResourceName() {
|
|
6
|
+
return 'media-directory';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static prepareModelForAPI(data) {
|
|
10
|
+
let m = { ...data };
|
|
11
|
+
delete m.id;
|
|
12
|
+
delete m.fullPath;
|
|
13
|
+
m.parentDirectory = m.parentDirectory ? m.parentDirectory.id : null;
|
|
14
|
+
delete m.selected;
|
|
15
|
+
return m;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const getDirectoryBreadcrumbItems = (currentDir) => {
|
|
21
|
+
let items = [];
|
|
22
|
+
let dir = currentDir;
|
|
23
|
+
while(dir !== null && typeof dir !== 'undefined') {
|
|
24
|
+
items.push({
|
|
25
|
+
text: dir.name,
|
|
26
|
+
separator: false,
|
|
27
|
+
link: dir === currentDir ? null : dir.fullPath
|
|
28
|
+
});
|
|
29
|
+
dir = dir.parentDirectory;
|
|
30
|
+
}
|
|
31
|
+
items.push({
|
|
32
|
+
home: true,
|
|
33
|
+
separator: false
|
|
34
|
+
});
|
|
35
|
+
return items.reverse();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const getDirectoryPathString = (dir) => {
|
|
39
|
+
if(dir === null) {
|
|
40
|
+
return '/';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let path = '';
|
|
44
|
+
while(dir !== null && typeof dir !== 'undefined') {
|
|
45
|
+
path = '/' + path;
|
|
46
|
+
path = dir.name + path;
|
|
47
|
+
dir = dir.parentDirectory;
|
|
48
|
+
}
|
|
49
|
+
return path;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const getDirectoryFullSlug = (dir) => {
|
|
53
|
+
let path = '';
|
|
54
|
+
while(dir !== null && typeof dir !== 'undefined') {
|
|
55
|
+
if(path !== '') {
|
|
56
|
+
path = '/' + path;
|
|
57
|
+
}
|
|
58
|
+
path = dir.slug + path;
|
|
59
|
+
dir = dir.parentDirectory;
|
|
60
|
+
}
|
|
61
|
+
return path;
|
|
62
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {API_ROOT} from "./CrudApi";
|
|
2
|
+
import {FetchBuilder} from "./api";
|
|
3
|
+
|
|
4
|
+
export const canAccessPrefs = ($buefy, userPermissions, keys) => {
|
|
5
|
+
console.log('Checking permissions for', keys);
|
|
6
|
+
for(let key of keys) {
|
|
7
|
+
let permissionsKey = 'preferences.' + key.split('::')[0].replace('.', '_');
|
|
8
|
+
if(userPermissions.has(permissionsKey)) {
|
|
9
|
+
console.log('Granted');
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
console.log('Denied');
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default class PreferencesApi {
|
|
18
|
+
|
|
19
|
+
constructor($buefy) {
|
|
20
|
+
this.$buefy = $buefy;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
request(method) {
|
|
24
|
+
return FetchBuilder.default(this.$buefy, method);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getValue(key) {
|
|
28
|
+
return await this.request('get')
|
|
29
|
+
.fetch(API_ROOT + 'preferences/' + key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async checkValid(key, value) {
|
|
33
|
+
return await this.request('post')
|
|
34
|
+
.withJson({
|
|
35
|
+
value: value
|
|
36
|
+
})
|
|
37
|
+
.fetch(API_ROOT + 'preferences/' + key + '/validate');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async setValue(key, value) {
|
|
41
|
+
return await this.request('put')
|
|
42
|
+
.withJson({
|
|
43
|
+
value: value
|
|
44
|
+
})
|
|
45
|
+
.fetch(API_ROOT + 'preferences/' + key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// this is a straight JavaScript port of the `Oxygen\Auth\Permissions\TreePermissionsSystem` PHP class.
|
|
2
|
+
|
|
3
|
+
export default class UserPermissions {
|
|
4
|
+
|
|
5
|
+
static setBuefy($buefy) {
|
|
6
|
+
this.$buefy = $buefy;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static get ROOT_CONTENT_TYPE() { return '_root'; }
|
|
10
|
+
static get PARENT_KEY() { return '_parent'; }
|
|
11
|
+
static get MAX_INHERITANCE_DEPTH() { return 10; }
|
|
12
|
+
|
|
13
|
+
constructor(permissions) {
|
|
14
|
+
this.permissions = permissions;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
has(key) {
|
|
18
|
+
if(!this.permissions) {
|
|
19
|
+
console.warn('permissions not loaded');
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let keyParts = key.split('.');
|
|
24
|
+
|
|
25
|
+
if(keyParts.length !== 2) {
|
|
26
|
+
throw new Error('TreePermissionsSystem Requires a Dot-Seperated Permissions Key');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// check for the specific key
|
|
30
|
+
return this.hasKey(keyParts[0], keyParts[1]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
hasOneOf(keys) {
|
|
34
|
+
for(let key of keys) {
|
|
35
|
+
if(this.has(key)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
hasKey(contentType, key, depth = 0) {
|
|
43
|
+
// check we're not looping
|
|
44
|
+
if(depth > UserPermissions.MAX_INHERITANCE_DEPTH) {
|
|
45
|
+
throw new Error('Max Depth Reached Due to Inheritance Loop');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// if the key is set then we will return the value of it
|
|
49
|
+
if(contentType in this.permissions && key in this.permissions[contentType]) {
|
|
50
|
+
return this.permissions[contentType][key];
|
|
51
|
+
} else {
|
|
52
|
+
// look in the parent contentType
|
|
53
|
+
let parent = (contentType in this.permissions && UserPermissions.PARENT_KEY in this.permissions[contentType]) ?
|
|
54
|
+
this.permissions[contentType][UserPermissions.PARENT_KEY]
|
|
55
|
+
: UserPermissions.ROOT_CONTENT_TYPE;
|
|
56
|
+
|
|
57
|
+
// have already reached the root - would only lead to infinite recursion
|
|
58
|
+
if(contentType === UserPermissions.ROOT_CONTENT_TYPE) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return this.hasKey(parent, key, depth + 1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|