@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
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
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/ui.iml" filepath="$PROJECT_DIR$/.idea/ui.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
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
@@ -0,0 +1,3 @@
1
+ {
2
+ "esversion":9
3
+ }
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Oxygen CMS - Vue.js User Interface Utilities
2
+
3
+ ![Node.js CI](https://github.com/oxygen-cms/ui-utils/workflows/Node.js%20CI/badge.svg) ![on NPM](https://img.shields.io/npm/v/@oxygen-cms/utils)
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 };
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ import {CrudApi} from './CrudApi';
2
+
3
+ export default class GroupsApi extends CrudApi {
4
+
5
+ static getResourceName() {
6
+ return 'groups';
7
+ }
8
+
9
+ }
@@ -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
+ }
@@ -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
+ }