@oxygen-cms/ui 1.5.0 → 1.6.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/.eslintrc.js CHANGED
@@ -2,6 +2,7 @@ module.exports = {
2
2
  "env": {
3
3
  "browser": true,
4
4
  "es2021": true,
5
+ "node": true,
5
6
  "jest/globals": true
6
7
  },
7
8
  "extends": [
@@ -16,7 +16,7 @@ jobs:
16
16
 
17
17
  strategy:
18
18
  matrix:
19
- node-version: [10.x, 12.x, 14.x]
19
+ node-version: [16.x]
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-cms/ui",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Various utilities for UI-building in Vue.js",
5
5
  "main": "none",
6
6
  "repository": {
@@ -48,7 +48,7 @@
48
48
  "eslint-plugin-jest": "^24.4.0",
49
49
  "eslint-plugin-vue": "^7.17.0",
50
50
  "jest": "^26.6.3",
51
- "node-sass": "^4.13.1",
51
+ "sass-embedded": "^1.49.9",
52
52
  "postcss-loader": "^3.0.0",
53
53
  "regenerator-runtime": "^0.13.7",
54
54
  "sass-loader": "^8.0.2",
@@ -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.authApi.userDetails.mockResolvedValue({
8
- user: {
9
- preferences: {
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/api.js CHANGED
@@ -57,7 +57,7 @@ export class FetchBuilder {
57
57
  this.headers.set('X-XSRF-TOKEN', xsrfToken);
58
58
  }
59
59
 
60
- async fetch(url) {
60
+ async fetchRaw(url) {
61
61
  await this.setXsrfTokenHeader()
62
62
 
63
63
  let v = { ... this};
@@ -72,7 +72,11 @@ export class FetchBuilder {
72
72
  }
73
73
  }
74
74
 
75
- let response = await window.fetch(url.toString(), this);
75
+ return await window.fetch(url.toString(), this);
76
+ }
77
+
78
+ async fetch(url) {
79
+ let response = await this.fetchRaw(url);
76
80
 
77
81
  let data = {};
78
82
  try {
@@ -132,8 +132,7 @@ export default {
132
132
  }
133
133
  },
134
134
  async fetchData() {
135
- this.fetchSessions();
136
- this.fetchLogins();
135
+ await Promise.all([this.fetchSessions(), this.fetchLogins()]);
137
136
  },
138
137
  geoIP(ip) {
139
138
  if(this.ipInfo.has(ip)) {
@@ -154,12 +153,12 @@ export default {
154
153
  },
155
154
  updateInfoForIp(ip) {
156
155
  let geolocationInfo = this.ipInfo.has(ip) ? this.getGeolocationInfo(this.ipInfo.get(ip)) : '';
157
- for(let item of this.paginatedItems.items) {
156
+ for(let item of (this.paginatedItems.items || [])) {
158
157
  if(item.ipAddress === ip) {
159
158
  item.geolocationInfo = geolocationInfo;
160
159
  }
161
160
  }
162
- for(let item of this.sessions.items) {
161
+ for(let item of (this.sessions.items || [])) {
163
162
  if(item.ipAddress === ip) {
164
163
  item.geolocationInfo = geolocationInfo;
165
164
  }
@@ -169,7 +168,6 @@ export default {
169
168
  let ua = new UAParser(userAgent);
170
169
  let browser = ua.getBrowser();
171
170
  let device = ua.getDevice();
172
- // console.log(device);
173
171
  return browser.name + ' ' + browser.version + ' on ' + ua.getOS().name + ', ' + (device.vendor ? device.vendor : '(unknown device)');
174
172
  },
175
173
  getGeolocationInfo(data) {
@@ -15,6 +15,7 @@
15
15
  <script>
16
16
  import {FetchBuilder} from "../api";
17
17
  import {API_ROOT} from "../CrudApi";
18
+ import download from "downloadjs";
18
19
 
19
20
  export default {
20
21
  name: "ImportExport",
@@ -26,15 +27,13 @@ export default {
26
27
  methods: {
27
28
  async processDownload() {
28
29
  this.exporting = true;
29
- let builder = (new FetchBuilder(this.$buefy, 'post'))
30
- .cookies();
31
- await builder.setXsrfTokenHeader();
32
- let response = await window.fetch(API_ROOT + "import-export/export", { ... builder });
30
+ let response = await (new FetchBuilder(this.$buefy, 'post'))
31
+ .cookies()
32
+ .fetchRaw(API_ROOT + "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
- let file = window.URL.createObjectURL(blob);
35
+ download(blob, 'database ' + (new Date()).toLocaleString() + '.zip');
36
36
  this.exporting = false;
37
- window.location.assign(file);
38
37
  }
39
38
  }
40
39
  }
@@ -60,9 +60,6 @@ export default {
60
60
  legacyPrefix: { type: String, required: true },
61
61
  adminPrefix: { type: String, required: true }
62
62
  },
63
- 'watch': {
64
- 'fullPath': 'onFullPathChanged'
65
- },
66
63
  data() {
67
64
  return {
68
65
  loadingPath: null,
@@ -76,6 +73,9 @@ export default {
76
73
  loading() { return this.loadingPath !== null; },
77
74
  userPreferences() { return this.$store.getters.userPreferences; }
78
75
  },
76
+ 'watch': {
77
+ 'fullPath': 'onFullPathChanged'
78
+ },
79
79
  async mounted() {
80
80
  this.loadingPath = 'prefs';
81
81
 
@@ -0,0 +1,99 @@
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).filter(([, group]) => this.userPermissions.hasOneOf(this.getPermissionsForGroup(group))));
64
+ }
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <style scoped lang="scss">
70
+ .show-if-active {
71
+ visibility: hidden;
72
+ opacity: 0;
73
+ padding: 0;
74
+ transition: visibility 0.2s ease, opacity 0.2s ease;
75
+ height: auto;
76
+
77
+ .router-link-active & {
78
+ visibility: visible;
79
+ opacity: 1;
80
+ }
81
+
82
+ .menu-list li:hover & {
83
+ visibility: visible;
84
+ opacity: 1;
85
+ }
86
+ }
87
+
88
+ .left-navigation-container.is-collapsed .show-if-active {
89
+ visibility: hidden !important;
90
+ opacity: 0 !important;
91
+ }
92
+ </style>
93
+
94
+ <style>
95
+ .show-if-active .icon:first-child:last-child {
96
+ margin-left: 0;
97
+ margin-right: 0;
98
+ }
99
+ </style>
@@ -8,7 +8,7 @@
8
8
  </b-notification>
9
9
 
10
10
  <b-field label="Username" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''">
11
- <b-input v-model="username" name="username" ref="username"></b-input>
11
+ <b-input ref="username" v-model="username" name="username"></b-input>
12
12
  </b-field>
13
13
 
14
14
  <b-field label="Password" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''">
@@ -31,7 +31,7 @@
31
31
  <br>
32
32
 
33
33
  <b-field key="totpCode" label="2FA Code" label-position="inside" :type="!submitting && hasFailedLogin ? 'is-danger' : ''" :message="!submitting && hasFailedLogin ? 'Incorrect code. Try again.' : ''">
34
- <b-input v-model="totpCode" name="totpCode" type="number" placeholder="e.g.: 123456" minlength="6" required @keyup.enter.native="submitLogin" ref="totpCode" />
34
+ <b-input ref="totpCode" v-model="totpCode" name="totpCode" type="number" placeholder="e.g.: 123456" minlength="6" required @keyup.enter.native="submitLogin" />
35
35
  </b-field>
36
36
 
37
37
  <br>
@@ -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 &amp; 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>
@@ -0,0 +1,8 @@
1
+ .buttons {
2
+ justify-content: center;
3
+ }
4
+
5
+ .title .icon {
6
+ display: inline-block;
7
+ margin-left: 1rem;
8
+ }
@@ -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)
@@ -21,13 +22,13 @@ import Error404 from "./components/Error404.vue";
21
22
  * @param beforeMount
22
23
  */
23
24
  export default class OxygenUI {
24
- app;
25
-
26
25
  constructor(Vue) {
26
+ this.app = null;
27
27
  this.Vue = Vue;
28
28
  this.authenticatedRoutes = []
29
29
  this.unauthenticatedRoutes = []
30
- this.rootComponents = { App }
30
+ this.mainMenuItems = {}
31
+ this.rootComponents = { App, MainMenu }
31
32
  this.beforeMountHooks = []
32
33
  }
33
34
 
@@ -41,6 +42,17 @@ export default class OxygenUI {
41
42
  this.authenticatedRoutes.push(route);
42
43
  }
43
44
 
45
+ addMainMenuGroup(category, group) {
46
+ if(!this.mainMenuItems[category]) {
47
+ this.mainMenuItems[category] = {};
48
+ }
49
+ if(!group.items) {
50
+ group.items = {};
51
+ }
52
+ this.mainMenuItems[category][group.name] = group;
53
+ return group;
54
+ }
55
+
44
56
  addUnauthenticatedRoutes(routes) {
45
57
  for (let route of routes) {
46
58
  this.unauthenticatedRoutes.push(route);
@@ -92,6 +104,9 @@ export default class OxygenUI {
92
104
 
93
105
  this.app = new this.Vue({
94
106
  router: router,
107
+ data: {
108
+ mainMenuItems: this.mainMenuItems
109
+ },
95
110
  components: this.rootComponents,
96
111
  store
97
112
  });
@@ -110,3 +125,5 @@ export default class OxygenUI {
110
125
  }
111
126
  }
112
127
 
128
+ export const WEB_CONTENT = 'Web Content';
129
+
@@ -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...
@@ -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(.*)?',
@@ -10,7 +10,6 @@ $menu-item-active-color: lighten($grey, 4%);
10
10
 
11
11
  // $menu-item-hover-background-color: rgba(255, 255, 255, 0.1);
12
12
  // $menu-item-hover-color: $white-bis;
13
-
14
13
  //$navbar-item-active-color: #fff;
15
14
  //$navbar-item-hover-background-color: #f3f3f9;
16
15
  //
@@ -5,7 +5,7 @@
5
5
  @import "~buefy/src/scss/buefy";
6
6
 
7
7
  .navbar {
8
- box-shadow: 0px 0px 2px $dark;
8
+ box-shadow: 0 0 2px $dark;
9
9
  }
10
10
 
11
11
  .navbar-item {