@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ShowIfPermitted data-key="appearance.themes">
|
|
3
|
+
<h3 class="subtitle">Website Themes</h3>
|
|
4
|
+
<PreferencesField data-key="appearance.themes::theme" label="">
|
|
5
|
+
<template #default="slotProps">
|
|
6
|
+
<b-table
|
|
7
|
+
:data="Object.values(slotProps.options)"
|
|
8
|
+
:striped="false">
|
|
9
|
+
<b-table-column v-slot="props" label="Key">
|
|
10
|
+
<img :src="props.row.image" class="theme-logo" />
|
|
11
|
+
</b-table-column>
|
|
12
|
+
|
|
13
|
+
<b-table-column v-slot="props" field="name" label="Name">
|
|
14
|
+
{{ props.row.name }}
|
|
15
|
+
<strong v-if="getSelectedOption(slotProps.options, slotProps.value) === props.row">(current theme)</strong>
|
|
16
|
+
</b-table-column>
|
|
17
|
+
|
|
18
|
+
<b-table-column v-slot="props" field="provides" label="Provides preferences">
|
|
19
|
+
<div v-for="(provideGroup, keyGroup) in props.row.provides" :key="keyGroup" class="is-size-7">
|
|
20
|
+
<span v-for="(value, key) in provideGroup" :key="key"><code>{{ keyGroup }}::{{ key}}</code><br></span>
|
|
21
|
+
</div>
|
|
22
|
+
</b-table-column>
|
|
23
|
+
|
|
24
|
+
<b-table-column v-slot="props" label="">
|
|
25
|
+
<b-button v-if="getSelectedOption(slotProps.options, slotProps.value) !== props.row" :loading="updating" @click="switchToTheme(props.row.key, slotProps.updateValue)">Switch to this theme</b-button>
|
|
26
|
+
<b-button v-else type="is-success" disabled>Theme is already active</b-button>
|
|
27
|
+
</b-table-column>
|
|
28
|
+
</b-table>
|
|
29
|
+
</template>
|
|
30
|
+
</PreferencesField>
|
|
31
|
+
</ShowIfPermitted>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script>
|
|
35
|
+
import PreferencesField from "./PreferencesField.vue";
|
|
36
|
+
import ShowIfPermitted from "./ShowIfPermitted.vue";
|
|
37
|
+
export default {
|
|
38
|
+
name: "PreferencesThemeChooser",
|
|
39
|
+
components: {ShowIfPermitted, PreferencesField},
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
updating: false
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
methods: {
|
|
46
|
+
printTheme(theme) {
|
|
47
|
+
return theme.display.name;
|
|
48
|
+
},
|
|
49
|
+
getSelectedOption(options, value) {
|
|
50
|
+
return options[value];
|
|
51
|
+
},
|
|
52
|
+
async switchToTheme(value, updateFn) {
|
|
53
|
+
console.log(value);
|
|
54
|
+
this.updating = true;
|
|
55
|
+
await updateFn(value);
|
|
56
|
+
this.updating = false;
|
|
57
|
+
this.$emit('theme-changed', value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style scoped lang="scss">
|
|
64
|
+
.theme-logo {
|
|
65
|
+
display: block;
|
|
66
|
+
max-width: 10rem;
|
|
67
|
+
margin: 0 auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.b-table ::v-deep .table td {
|
|
71
|
+
vertical-align: middle;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-show="permitted">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import {canAccessPrefs} from "../../PreferencesApi";
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: "ShowIfPermitted",
|
|
12
|
+
props: {
|
|
13
|
+
dataKey: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: null
|
|
16
|
+
},
|
|
17
|
+
keys: {
|
|
18
|
+
type: Array,
|
|
19
|
+
default: null
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
computed: {
|
|
23
|
+
keysArray() {
|
|
24
|
+
if(this.keys === null) {
|
|
25
|
+
return [this.dataKey];
|
|
26
|
+
} else {
|
|
27
|
+
return this.keys;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
permitted() { return canAccessPrefs(this.$buefy, this.$store.getters.userPermissions, this.keysArray); }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h3 class="subtitle">General</h3>
|
|
4
|
+
<PreferencesField label="Font Size" data-key="user.general::fontSize" type="select" default-text="group"></PreferencesField>
|
|
5
|
+
<h3 class="subtitle">Editor</h3>
|
|
6
|
+
<PreferencesField label="Default Mode" data-key="user.editor::defaultMode" type="select" default-text="group"></PreferencesField>
|
|
7
|
+
<PreferencesField label="Overall Theme" data-key="user.editor::theme" type="select" default-text="group"></PreferencesField>
|
|
8
|
+
<h4 class="subtitle is-6">Code Editor</h4>
|
|
9
|
+
<PreferencesField label="Theme" data-key="user.editor::ace.theme" type="select" grouped default-text="group"></PreferencesField>
|
|
10
|
+
<PreferencesField label="Font Size" data-key="user.editor::ace.fontSize" type="select" default-text="group"></PreferencesField>
|
|
11
|
+
<PreferencesField label="Word Wrap" data-key="user.editor::ace.wordWrap" type="switch" default-text="group"></PreferencesField>
|
|
12
|
+
<PreferencesField label="Highlight Active Line" data-key="user.editor::ace.highlightActiveLine" type="switch" default-text="group"></PreferencesField>
|
|
13
|
+
<PreferencesField label="Show Print Margin" data-key="user.editor::ace.showPrintMargin" type="switch" default-text="group"></PreferencesField>
|
|
14
|
+
<PreferencesField label="Show Invisibles" data-key="user.editor::ace.showInvisibles" type="switch" default-text="group"></PreferencesField>
|
|
15
|
+
<h4 class="subtitle is-6">Design Editor</h4>
|
|
16
|
+
<PreferencesField label="Theme" data-key="user.editor::ckeditor.skin" type="select" grouped default-text="group"></PreferencesField>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import PreferencesField from "./PreferencesField.vue";
|
|
22
|
+
export default {
|
|
23
|
+
name: "UserPreferences",
|
|
24
|
+
components: {PreferencesField}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style scoped>
|
|
29
|
+
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<b-modal :closable="false" :active="active" has-modal-card trap-focus aria-role="dialog" aria-modal @update:active="a => $emit('update:active', a)">
|
|
3
|
+
<div class="modal-card" style="overflow: visible">
|
|
4
|
+
<header class="modal-card-head">
|
|
5
|
+
<p class="modal-card-title">
|
|
6
|
+
<b-icon icon="users" size="is-normal" class="push-right"></b-icon>
|
|
7
|
+
Create Account
|
|
8
|
+
</p>
|
|
9
|
+
</header>
|
|
10
|
+
<section class="modal-card-body" style="overflow: visible">
|
|
11
|
+
<b-field label="Username">
|
|
12
|
+
<b-input v-model="username" placeholder="e.g.: johndoe"></b-input>
|
|
13
|
+
</b-field>
|
|
14
|
+
<b-field label="Email Address">
|
|
15
|
+
<b-input v-model="email" type="email" placeholder="e.g.: example@example.com"></b-input>
|
|
16
|
+
</b-field>
|
|
17
|
+
<b-field label="Full Name">
|
|
18
|
+
<b-input v-model="fullName" placeholder="e.g.: John Doe"></b-input>
|
|
19
|
+
</b-field>
|
|
20
|
+
<b-field label="Group">
|
|
21
|
+
<GroupsChooser :value="group" @select="g => group = g" />
|
|
22
|
+
</b-field>
|
|
23
|
+
</section>
|
|
24
|
+
<footer class="modal-card-foot is-flex">
|
|
25
|
+
<div class="is-flex-grow-1"></div>
|
|
26
|
+
<b-button @click="$emit('update:active', false)">Cancel</b-button>
|
|
27
|
+
<b-button type="is-primary" :loading="submitting" @click="submit">Create Account</b-button>
|
|
28
|
+
</footer>
|
|
29
|
+
</div>
|
|
30
|
+
</b-modal>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
import GroupsChooser from "../GroupsChooser.vue";
|
|
35
|
+
import UsersApi from "../../UsersApi";
|
|
36
|
+
import {morphToNotification} from "../../api";
|
|
37
|
+
export default {
|
|
38
|
+
name: "CreateUserModal",
|
|
39
|
+
components: {GroupsChooser},
|
|
40
|
+
props: {
|
|
41
|
+
active: { type: Boolean, required: true }
|
|
42
|
+
},
|
|
43
|
+
data() {
|
|
44
|
+
return {
|
|
45
|
+
username: '',
|
|
46
|
+
email: '',
|
|
47
|
+
fullName: '',
|
|
48
|
+
group: null,
|
|
49
|
+
submitting: false,
|
|
50
|
+
usersApi: new UsersApi(this.$buefy)
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
methods: {
|
|
54
|
+
async submit() {
|
|
55
|
+
let data = { username: this.username, email: this.email, fullName: this.fullName, group: this.group };
|
|
56
|
+
try {
|
|
57
|
+
this.submitting = true;
|
|
58
|
+
let response = await this.usersApi.create(data);
|
|
59
|
+
this.$buefy.notification.open(morphToNotification(response));
|
|
60
|
+
this.$emit('update:active', false);
|
|
61
|
+
this.$emit('update:users');
|
|
62
|
+
} catch(e) {
|
|
63
|
+
// let the user try again
|
|
64
|
+
}
|
|
65
|
+
this.submitting = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<style scoped>
|
|
72
|
+
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.full-height-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
.column.full-height-container {
|
|
6
|
+
display: flex;
|
|
7
|
+
}
|
|
8
|
+
.box.full-height-container {
|
|
9
|
+
display: flex;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.scroll-container.pad,
|
|
13
|
+
.full-height-container.pad {
|
|
14
|
+
padding: 2em;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.full-height-flex {
|
|
18
|
+
flex: 1 1 auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.full-height {
|
|
22
|
+
height: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.scroll-container {
|
|
26
|
+
overflow-y: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.transition-element.is-loading {
|
|
30
|
+
margin: 2rem auto;
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
width: 30em;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.top-bar {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
margin-bottom: 1rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.top-bar .title {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.top-bar .title:not(:first-child) {
|
|
46
|
+
margin-left: 1rem;
|
|
47
|
+
}
|
package/src/icons.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { library } from '@fortawesome/fontawesome-svg-core';
|
|
2
|
+
|
|
3
|
+
// internal icons
|
|
4
|
+
import {
|
|
5
|
+
faCheck,
|
|
6
|
+
faCheckCircle,
|
|
7
|
+
faInfoCircle,
|
|
8
|
+
faExternalLinkAlt,
|
|
9
|
+
faExclamationTriangle,
|
|
10
|
+
faExclamationCircle,
|
|
11
|
+
faArrowUp,
|
|
12
|
+
faAngleRight,
|
|
13
|
+
faAngleLeft,
|
|
14
|
+
faAngleDown,
|
|
15
|
+
faEye,
|
|
16
|
+
faEyeSlash,
|
|
17
|
+
faCaretDown,
|
|
18
|
+
faCaretUp,
|
|
19
|
+
faUpload,
|
|
20
|
+
faPlus,
|
|
21
|
+
faCogs,
|
|
22
|
+
faPuzzlePiece,
|
|
23
|
+
faPhotoVideo,
|
|
24
|
+
faUserPlus,
|
|
25
|
+
faMailBulk,
|
|
26
|
+
faUsers,
|
|
27
|
+
faArrowLeft,
|
|
28
|
+
faRecycle,
|
|
29
|
+
faList,
|
|
30
|
+
faFileAlt,
|
|
31
|
+
faFile,
|
|
32
|
+
faFileAudio,
|
|
33
|
+
faFileImage,
|
|
34
|
+
faFilePdf,
|
|
35
|
+
faUserAlt,
|
|
36
|
+
faStamp,
|
|
37
|
+
faCalendarAlt,
|
|
38
|
+
faAddressCard,
|
|
39
|
+
faFileImport,
|
|
40
|
+
faUser,
|
|
41
|
+
faFileExport,
|
|
42
|
+
faDownload,
|
|
43
|
+
faFileExcel,
|
|
44
|
+
faFileCsv,
|
|
45
|
+
faChevronCircleDown,
|
|
46
|
+
faChevronCircleUp,
|
|
47
|
+
faTrash,
|
|
48
|
+
faSearch,
|
|
49
|
+
faBan,
|
|
50
|
+
faShare,
|
|
51
|
+
faTicketAlt,
|
|
52
|
+
faPencilAlt,
|
|
53
|
+
faRedoAlt,
|
|
54
|
+
faTags,
|
|
55
|
+
faLock,
|
|
56
|
+
faUserSlash,
|
|
57
|
+
faUsersCog,
|
|
58
|
+
faMusic,
|
|
59
|
+
faCalendarDay,
|
|
60
|
+
faTimesCircle,
|
|
61
|
+
faEnvelope,
|
|
62
|
+
faUserCog,
|
|
63
|
+
faFolder,
|
|
64
|
+
faHome,
|
|
65
|
+
faSignInAlt,
|
|
66
|
+
faUserEdit,
|
|
67
|
+
faSignOutAlt,
|
|
68
|
+
faTag,
|
|
69
|
+
faFolderPlus,
|
|
70
|
+
faTimes,
|
|
71
|
+
faQuestionCircle,
|
|
72
|
+
faFileUpload,
|
|
73
|
+
faLandmark,
|
|
74
|
+
faFolderOpen,
|
|
75
|
+
faImages,
|
|
76
|
+
faMinusCircle
|
|
77
|
+
} from "@fortawesome/free-solid-svg-icons";
|
|
78
|
+
|
|
79
|
+
export const addIconsToLibrary = () => {
|
|
80
|
+
library.add(faCheck, faLock, faUserSlash, faUserEdit, faUsersCog, faMinusCircle, faSignInAlt,
|
|
81
|
+
faCheckCircle, faInfoCircle, faExclamationTriangle, faExclamationCircle, faFileAlt,
|
|
82
|
+
faArrowUp, faAngleRight, faEnvelope, faAngleLeft, faTicketAlt, faUserCog, faTags, faCalendarDay, faTimesCircle,
|
|
83
|
+
faMusic, faPencilAlt, faRedoAlt, faBan, faExternalLinkAlt,
|
|
84
|
+
faSearch, faAngleDown, faUserAlt, faCogs, faPhotoVideo, faPuzzlePiece, faPlus, faUserPlus, faMailBulk, faUsers,
|
|
85
|
+
faArrowLeft, faRecycle, faList, faStamp, faCalendarAlt, faAddressCard, faFileImport, faFileExport, faDownload,
|
|
86
|
+
faFileExcel, faFileCsv, faChevronCircleDown, faChevronCircleUp, faTrash,
|
|
87
|
+
faEye, faEyeSlash, faCaretDown, faCaretUp, faUpload, faUser, faFolder, faHome, faFilePdf, faSignOutAlt, faTag,
|
|
88
|
+
faFolderPlus, faTimes, faQuestionCircle, faFileUpload, faLandmark,
|
|
89
|
+
faFolderOpen, faFile, faFileAudio, faFileImage, faShare, faImages);
|
|
90
|
+
};
|
package/src/main.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import Router from 'vue-router';
|
|
2
|
+
import App from './components/App.vue';
|
|
3
|
+
import UserPermissions from './UserPermissions';
|
|
4
|
+
import UserPreferences from './UserPreferences';
|
|
5
|
+
import VHotkey from 'v-hotkey';
|
|
6
|
+
import Buefy from 'buefy';
|
|
7
|
+
import AsyncComputed from 'vue-async-computed';
|
|
8
|
+
import { FetchBuilder } from './api';
|
|
9
|
+
import { addIconsToLibrary } from './icons';
|
|
10
|
+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
11
|
+
import { AuthRoutes, makeAuthenticatedRoute } from "./routes";
|
|
12
|
+
import createStore from "./store/index";
|
|
13
|
+
import { checkAuthenticated } from "./AuthApi";
|
|
14
|
+
import Error404 from "./components/Error404.vue";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates the Vue.js Oxygen application, allowing for a few points of customization (i.e.: adding modules)
|
|
18
|
+
* @param Vue
|
|
19
|
+
* @param extraRoutes
|
|
20
|
+
* @param extraComponents
|
|
21
|
+
* @param beforeMount
|
|
22
|
+
*/
|
|
23
|
+
export default class OxygenUI {
|
|
24
|
+
app;
|
|
25
|
+
|
|
26
|
+
constructor(Vue) {
|
|
27
|
+
this.Vue = Vue;
|
|
28
|
+
this.authenticatedRoutes = []
|
|
29
|
+
this.unauthenticatedRoutes = []
|
|
30
|
+
this.rootComponents = { App }
|
|
31
|
+
this.beforeMountHooks = []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
addAuthenticatedRoutes(routes) {
|
|
35
|
+
for (let route of routes) {
|
|
36
|
+
this.authenticatedRoutes.push(route);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addRoute(route) {
|
|
41
|
+
this.authenticatedRoutes.push(route);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
addUnauthenticatedRoutes(routes) {
|
|
45
|
+
for (let route of routes) {
|
|
46
|
+
this.unauthenticatedRoutes.push(route);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
registerModule(module) {
|
|
51
|
+
module(this)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
createApp() {
|
|
55
|
+
// this could occur if we encounter a bad link inside the <LegacyPage> functionality
|
|
56
|
+
// TODO: remove this once <LegacyPage> is gone
|
|
57
|
+
if (window.location !== window.parent.location) {
|
|
58
|
+
throw new Error('refusing to load application inside of an iframe');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
addIconsToLibrary();
|
|
62
|
+
this.Vue.component('vue-fontawesome', FontAwesomeIcon);
|
|
63
|
+
this.Vue.use(Buefy, {
|
|
64
|
+
defaultIconComponent: 'vue-fontawesome',
|
|
65
|
+
defaultIconPack: 'fas',
|
|
66
|
+
});
|
|
67
|
+
this.Vue.use(VHotkey);
|
|
68
|
+
this.Vue.use(AsyncComputed);
|
|
69
|
+
this.Vue.use(Router);
|
|
70
|
+
|
|
71
|
+
const store = createStore(this.Vue);
|
|
72
|
+
|
|
73
|
+
const routes = AuthRoutes
|
|
74
|
+
.concat([
|
|
75
|
+
makeAuthenticatedRoute(this.authenticatedRoutes)
|
|
76
|
+
])
|
|
77
|
+
.concat(this.unauthenticatedRoutes)
|
|
78
|
+
.concat([{
|
|
79
|
+
path: '*',
|
|
80
|
+
name: 'error404',
|
|
81
|
+
component: Error404,
|
|
82
|
+
meta: {title: 'Not found'}
|
|
83
|
+
}]);
|
|
84
|
+
|
|
85
|
+
const router = new Router({
|
|
86
|
+
routes: routes,
|
|
87
|
+
base: '/oxygen/',
|
|
88
|
+
mode: 'history'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
router.beforeEach(checkAuthenticated(store));
|
|
92
|
+
|
|
93
|
+
this.app = new this.Vue({
|
|
94
|
+
router: router,
|
|
95
|
+
components: this.rootComponents,
|
|
96
|
+
store
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
FetchBuilder.setRouter(router);
|
|
100
|
+
UserPermissions.setBuefy(this.app.$buefy);
|
|
101
|
+
UserPreferences.setBuefy(this.app.$buefy)
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
mount(selector) {
|
|
106
|
+
for(let hook of this.beforeMountHooks) {
|
|
107
|
+
hook(this.app);
|
|
108
|
+
}
|
|
109
|
+
this.app.$mount(selector);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import LegacyPage from "../components/LegacyPage.vue";
|
|
2
|
+
|
|
3
|
+
export default function(ui) {
|
|
4
|
+
ui.addAuthenticatedRoutes([
|
|
5
|
+
{
|
|
6
|
+
// will match everything, try to render a legacy Oxygen page...
|
|
7
|
+
path: '(pages|partials|upcoming-events)/:subpath*',
|
|
8
|
+
component: LegacyPage,
|
|
9
|
+
props: (route) => {
|
|
10
|
+
return {
|
|
11
|
+
fullPath: route.fullPath,
|
|
12
|
+
legacyPrefix: '/oxygen/view',
|
|
13
|
+
adminPrefix: '/oxygen'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import MediaPage from "../components/media/MediaPage.vue";
|
|
2
|
+
import MediaResponsiveImages from "../components/media/MediaResponsiveImages.vue";
|
|
3
|
+
|
|
4
|
+
export default function(ui) {
|
|
5
|
+
ui.addAuthenticatedRoutes([
|
|
6
|
+
{
|
|
7
|
+
path: 'media/list/:currentPath(.*)?',
|
|
8
|
+
name: 'media.list',
|
|
9
|
+
props: true,
|
|
10
|
+
component: MediaPage,
|
|
11
|
+
meta: { title: 'Photos & File Uploads'}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: 'media/list',
|
|
15
|
+
props: {
|
|
16
|
+
currentPath: ''
|
|
17
|
+
},
|
|
18
|
+
component: MediaPage,
|
|
19
|
+
meta: { title: 'Photos & File Uploads'}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: 'media/trash',
|
|
23
|
+
name: 'media.trash',
|
|
24
|
+
props: {
|
|
25
|
+
inTrash: true,
|
|
26
|
+
currentPath: ''
|
|
27
|
+
},
|
|
28
|
+
component: MediaPage,
|
|
29
|
+
meta: { title: 'Deleted Photos & File Uploads'}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: 'media/search/:searchQuery',
|
|
33
|
+
name: 'media.search',
|
|
34
|
+
props: true,
|
|
35
|
+
component: MediaPage,
|
|
36
|
+
meta: { title: 'Search Photos & File Uploads'}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: 'media/responsive-images',
|
|
40
|
+
name: 'media.responsiveImages',
|
|
41
|
+
component: MediaResponsiveImages,
|
|
42
|
+
meta: { title: 'Responsive Images'}
|
|
43
|
+
}
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import ViewProfile from "../components/ViewProfile.vue";
|
|
2
|
+
import AuthenticationLog from "../components/AuthenticationLog.vue";
|
|
3
|
+
import UserManagement from "../components/UserManagement.vue";
|
|
4
|
+
|
|
5
|
+
export default function(ui) {
|
|
6
|
+
ui.addAuthenticatedRoutes([
|
|
7
|
+
{
|
|
8
|
+
path: 'user/profile',
|
|
9
|
+
name: 'auth.viewProfile',
|
|
10
|
+
component: ViewProfile,
|
|
11
|
+
meta: { title: 'View Profile' }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
path: 'user/login-log',
|
|
15
|
+
component: AuthenticationLog,
|
|
16
|
+
meta: { title: 'Logins & Login Attempts' }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: 'users',
|
|
20
|
+
component: UserManagement,
|
|
21
|
+
meta: { title: 'Manage Users'}
|
|
22
|
+
}
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import AuthenticatedLayout from "../components/AuthenticatedLayout.vue";
|
|
2
|
+
import Login from "../components/auth/Login.vue";
|
|
3
|
+
import Logout from "../components/auth/Logout.vue";
|
|
4
|
+
import WelcomeFloat from "../components/auth/WelcomeFloat.vue";
|
|
5
|
+
import TwoFactorSetup from "../components/auth/TwoFactorSetup.vue";
|
|
6
|
+
import PasswordRemind from "../components/auth/PasswordRemind.vue";
|
|
7
|
+
import PasswordReset from "../components/auth/PasswordReset.vue";
|
|
8
|
+
import Auth404 from "../components/auth/Auth404.vue";
|
|
9
|
+
import VerifyEmail from "../components/auth/VerifyEmail.vue";
|
|
10
|
+
|
|
11
|
+
export const AuthRoutes = [
|
|
12
|
+
{
|
|
13
|
+
path: '/auth',
|
|
14
|
+
component: WelcomeFloat,
|
|
15
|
+
meta: {
|
|
16
|
+
allowUnauthenticated: true
|
|
17
|
+
},
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
name: 'login',
|
|
21
|
+
path: 'login',
|
|
22
|
+
component: Login,
|
|
23
|
+
meta: {
|
|
24
|
+
title: 'Login',
|
|
25
|
+
allowUnauthenticated: true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'logout',
|
|
30
|
+
path: 'logout',
|
|
31
|
+
component: Logout,
|
|
32
|
+
meta: {
|
|
33
|
+
title: 'Logout',
|
|
34
|
+
allowUnauthenticated: true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: '2fa-setup',
|
|
39
|
+
path: '2fa-setup',
|
|
40
|
+
component: TwoFactorSetup,
|
|
41
|
+
meta: {
|
|
42
|
+
title: 'Setup two factor authentication'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'forgot-password',
|
|
47
|
+
path: 'forgot-password',
|
|
48
|
+
component: PasswordRemind,
|
|
49
|
+
meta: {
|
|
50
|
+
title: 'Forgot Password',
|
|
51
|
+
allowUnauthenticated: true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'reset-password',
|
|
56
|
+
path: 'reset-password',
|
|
57
|
+
component: PasswordReset,
|
|
58
|
+
meta: {
|
|
59
|
+
title: 'Reset Password',
|
|
60
|
+
allowUnauthenticated: true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'needs-verified-email',
|
|
65
|
+
path: 'needs-verified-email',
|
|
66
|
+
component: VerifyEmail,
|
|
67
|
+
meta: {
|
|
68
|
+
title: 'Verify your email address'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
path: '',
|
|
73
|
+
component: Auth404
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
path: '*',
|
|
77
|
+
component: Auth404
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export const makeAuthenticatedRoute = (children) => {
|
|
84
|
+
return {
|
|
85
|
+
path: '/',
|
|
86
|
+
component: AuthenticatedLayout,
|
|
87
|
+
props: true,
|
|
88
|
+
redirect: '/dashboard',
|
|
89
|
+
children: children,
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|