@oxygen-cms/ui 1.4.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.eslintrc.js +23 -0
  2. package/.github/workflows/node.js.yml +4 -4
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/ui.iml +10 -0
  5. package/package.json +13 -5
  6. package/src/AuthApi.js +77 -42
  7. package/src/CrudApi.js +3 -3
  8. package/src/GroupsApi.js +9 -0
  9. package/src/MediaDirectoryApi.js +1 -1
  10. package/src/PreferencesApi.js +2 -0
  11. package/src/UserPermissions.js +2 -9
  12. package/src/UserPreferences.js +0 -4
  13. package/src/UserPreferences.test.js +0 -2
  14. package/src/UsersApi.js +41 -0
  15. package/src/api.js +96 -38
  16. package/src/components/App.vue +19 -240
  17. package/src/components/AuthenticatedLayout.vue +254 -0
  18. package/src/components/AuthenticationLog.vue +86 -30
  19. package/src/components/CodeEditor.vue +16 -32
  20. package/src/components/EditButtonOnRowHover.vue +21 -0
  21. package/src/components/Error404.vue +15 -5
  22. package/src/components/EventsChooser.vue +11 -11
  23. package/src/components/EventsTable.vue +14 -8
  24. package/src/components/GenericEditableField.vue +74 -0
  25. package/src/components/GroupsChooser.vue +58 -0
  26. package/src/components/GroupsList.vue +129 -0
  27. package/src/components/ImportExport.vue +32 -1
  28. package/src/components/LegacyPage.vue +22 -23
  29. package/src/components/UserJoined.vue +35 -0
  30. package/src/components/UserManagement.vue +168 -0
  31. package/src/components/UserProfileForm.vue +214 -0
  32. package/src/components/ViewProfile.vue +7 -219
  33. package/src/components/auth/Auth404.vue +16 -0
  34. package/src/components/auth/Login.vue +135 -0
  35. package/src/components/auth/LoginLogo.vue +30 -0
  36. package/src/components/auth/Logout.vue +26 -0
  37. package/src/components/auth/PasswordRemind.vue +71 -0
  38. package/src/components/auth/PasswordReset.vue +97 -0
  39. package/src/components/auth/TwoFactorSetup.vue +115 -0
  40. package/src/components/auth/VerifyEmail.vue +71 -0
  41. package/src/components/auth/WelcomeFloat.vue +87 -0
  42. package/src/components/auth/login.scss +17 -0
  43. package/src/components/{MediaChooseDirectory.vue → media/MediaChooseDirectory.vue} +12 -12
  44. package/src/components/{MediaDirectory.vue → media/MediaDirectory.vue} +8 -8
  45. package/src/components/{MediaInsertModal.vue → media/MediaInsertModal.vue} +2 -2
  46. package/src/components/{MediaItem.vue → media/MediaItem.vue} +24 -23
  47. package/src/components/{MediaItemPreview.vue → media/MediaItemPreview.vue} +5 -5
  48. package/src/components/{MediaList.vue → media/MediaList.vue} +42 -38
  49. package/src/components/{MediaPage.vue → media/MediaPage.vue} +1 -1
  50. package/src/components/{MediaResponsiveImages.vue → media/MediaResponsiveImages.vue} +5 -5
  51. package/src/components/{MediaUpload.vue → media/MediaUpload.vue} +10 -10
  52. package/src/components/{media.scss → media/media.scss} +1 -1
  53. package/src/components/preferences/PreferencesField.vue +10 -10
  54. package/src/components/preferences/PreferencesList.vue +13 -20
  55. package/src/components/preferences/PreferencesThemeChooser.vue +9 -9
  56. package/src/components/preferences/ShowIfPermitted.vue +9 -14
  57. package/src/components/users/CreateUserModal.vue +73 -0
  58. package/src/icons.js +90 -0
  59. package/src/main.js +111 -0
  60. package/src/modules/LegacyPages.js +18 -0
  61. package/src/modules/Media.js +45 -0
  62. package/src/modules/UserManagement.js +24 -0
  63. package/src/routes/index.js +92 -0
  64. package/src/store/index.js +70 -0
  65. package/src/styles/_variables.scss +1 -0
  66. package/src/styles/app.scss +15 -2
  67. package/src/login.js +0 -17
  68. package/src/routes.js +0 -61
  69. package/src/styles/login.scss +0 -86
@@ -1,72 +1,22 @@
1
1
  <template>
2
- <div class="full-height-container app-container" style="height: 100%;">
3
- <div :class="'left-navigation-container' + (collapsed ? ' is-collapsed' : '')">
4
-
5
- <div class="app-logo-title">
6
- <router-link to="/" class="app-logo-title-link">
7
- <img src="../../assets/oxygen-icon.png" alt="Oxygen CMS" class="app-logo">
8
- <span to="/" class="app-title" v-if="!collapsed">Oxygen CMS</span>
9
- </router-link>
10
- <span class="is-flex-grow-1"></span>
11
- <b-button type="is-light" v-if="!requestedCollapsed" :icon-left="collapsed ? 'angle-right' : 'angle-left'" class="collapse-menu-button" @click="setCollapsed = !setCollapsed"></b-button>
12
- </div>
13
-
14
- <b-menu class="left-navigation">
15
-
16
- <slot name="main-navigation" v-bind:userPermissions="userPermissions" v-bind:collapsed="collapsed"></slot>
17
-
18
- </b-menu>
19
-
20
- <div :class="'user-info' + (impersonating ? ' has-background-warning' : '')">
21
- <b-dropdown aria-role="list" :position="collapsed ? 'is-top-right' : 'is-top-left'" expanded>
22
- <template #trigger>
23
- <div class="user-dropdown">
24
- <div class="user-dropdown-text" v-if="!collapsed">
25
- <strong v-if="impersonating">Temporarily logged-in as<br/></strong>
26
- <transition name="fade" mode="out-in">
27
- <span v-if="user">{{ user.fullName }}</span>
28
- <b-skeleton size="is-medium" width="10em" :animated="true" v-else></b-skeleton>
29
- </transition>
30
- <transition name="fade" mode="out-in">
31
- <p v-if="user" class="is-size-7">{{ user.email }}</p>
32
- <b-skeleton width="8em" :animated="true" v-else></b-skeleton>
33
- </transition>
34
- </div>
35
- <div class="is-flex-grow-1"></div>
36
- <div class="has-background-grey-light centered-icon" style="display: inline-block;">
37
- <b-icon icon="user" size="is-large" class="has-text-grey-lighter"></b-icon>
38
- </div>
39
- </div>
40
- </template>
41
- <b-dropdown-item aria-role="listitem" has-link><router-link to="/auth/profile"><b-icon icon="user"></b-icon>Profile</router-link></b-dropdown-item>
42
- <b-dropdown-item aria-role="listitem" has-link><router-link to="/auth/login-log"><b-icon icon="list"></b-icon>Login Log</router-link></b-dropdown-item>
43
- <b-dropdown-item separator></b-dropdown-item>
44
- <b-dropdown-item aria-role="listitem" @click="signOut"><b-icon icon="sign-out-alt"></b-icon>Sign Out</b-dropdown-item>
45
- <b-dropdown-item v-if="impersonating" aria-role="listitem" @click="stopImpersonating"><b-icon icon="times"></b-icon>Stop impersonating</b-dropdown-item>
46
- </b-dropdown>
47
- </div>
48
- </div>
49
-
50
- <div class="no-pad full-height-container content-column">
51
-
52
- <transition name="slide-left" mode="out-in">
53
- <router-view></router-view>
54
- </transition>
55
-
56
- </div>
57
-
2
+ <div style="height: 100%;">
3
+ <!-- <transition name="slide-left" mode="out-in">-->
4
+ <router-view @logout="signOut">
5
+ <template #main-navigation>
6
+ <slot name="app-navigation"></slot>
7
+ </template>
8
+ </router-view>
9
+ <!-- </transition>-->
58
10
  </div>
59
11
  </template>
60
12
 
61
13
  <script>
62
14
  import AuthApi from "../AuthApi";
63
- import UserPermissions from "../UserPermissions";
64
- import UserPreferences from "../UserPreferences";
65
15
  export default {
66
16
  name: "App",
67
17
  props: {
68
- appTitle: { type: String },
69
- defaultRouteTitle: String,
18
+ appTitle: { type: String, required: true },
19
+ defaultRouteTitle: { type: String, required: true },
70
20
  impersonating: {
71
21
  type: Boolean,
72
22
  default: false
@@ -76,48 +26,28 @@
76
26
  return {
77
27
  user: null,
78
28
  authApi: new AuthApi(this.$buefy),
79
- userPermissions: null,
80
- setCollapsed: false,
81
- requestedCollapsed: false
29
+ userPermissions: null
82
30
  }
83
31
  },
84
- computed: {
85
- collapsed() {
86
- return this.setCollapsed || this.requestedCollapsed;
87
- }
32
+ watch: {
33
+ '$route' (to) {
34
+ this.setTitle(to.meta.title);
35
+ },
88
36
  },
89
37
  created() {
90
38
  this.setTitle(this.$route.meta.title);
91
39
  this.$root.$on('update-route-title', (title) => {
92
40
  this.setTitle(title);
93
41
  });
94
- this.fetchUserDetails();
95
- this.setGlobalFontSize()
96
- },
97
- watch: {
98
- '$route' (to, from) {
99
- this.setTitle(to.meta.title);
100
- },
101
42
  },
102
43
  methods: {
103
44
  setTitle(title) {
104
45
  document.title = (title || this.defaultRouteTitle) + ' - ' + this.appTitle;
105
46
  },
106
- async fetchUserDetails() {
107
- this.user = (await this.authApi.userDetails()).user;
108
- this.userPermissions = new UserPermissions(this.user.permissions);
109
- },
110
- async setGlobalFontSize() {
111
- let userPreferences = await UserPreferences.load();
112
- let fontSize = userPreferences.get('fontSize', '100%');
113
- console.log('Setting font size to ', fontSize);
114
- if(fontSize !== '100%') {
115
- window.document.documentElement.style.fontSize = fontSize;
116
- }
117
- },
118
- signOut() {
119
- console.log('user requested logout');
120
- this.authApi.logout();
47
+ async signOut() {
48
+ await this.authApi.logout();
49
+ this.$store.commit('setUser', null);
50
+ await this.$router.push('/auth/logout');
121
51
  },
122
52
  stopImpersonating() {
123
53
  this.authApi.stopImpersonating();
@@ -126,157 +56,6 @@
126
56
  }
127
57
  </script>
128
58
 
129
- <style scoped lang="scss">
130
- @import 'util.css';
131
- @import '../styles/_variables.scss';
132
-
133
- .left-navigation-container {
134
- padding: 0 !important;
135
- display: flex;
136
- flex-direction: column;
137
- flex: 1;
138
- max-width: 550px;
139
- border-right: 1px solid $grey-lighter;
140
- transition: max-width 0.5s ease;
141
- }
142
-
143
- .left-navigation-container.is-collapsed {
144
- max-width: 5rem;
145
- }
146
-
147
- .app-logo-title {
148
- display: flex;
149
- align-items: center;
150
- padding: 1rem 0;
151
- }
152
-
153
- .app-logo {
154
- width: 3rem;
155
- margin-left: 1rem;
156
- margin-right: 1rem;
157
- }
158
-
159
- .app-logo-title-link {
160
- display: flex;
161
- align-items: center;
162
- }
163
-
164
- .app-title {
165
- color: $text;
166
- font-size: 1.1rem;
167
- }
168
-
169
- .app-container {
170
- display: flex;
171
- flex-direction: row;
172
- }
173
-
174
- .left-navigation {
175
- flex: 1;
176
- padding: 1rem 1rem 1rem 2rem;
177
- overflow-y: auto;
178
- overflow-x: hidden;
179
- }
180
-
181
- .content-column {
182
- flex: 4;
183
- }
184
-
185
- ::v-deep .router-link-exact-active {
186
- // background-color: $link-invert !important;
187
- color: $black-bis !important;
188
- font-weight: 700;
189
- }
190
-
191
- ::v-deep .menu-list .icon {
192
- margin-right: 1rem;
193
- }
194
-
195
- .user-dropdown {
196
- padding: 1rem;
197
- cursor: pointer;
198
- display: flex;
199
- align-items: center;
200
- }
201
-
202
- .columns {
203
- margin-left: 0;
204
- margin-right: 0;
205
- margin-top: 0;
206
- margin-bottom: 0;
207
- }
208
-
209
- .no-pad {
210
- padding: 0;
211
- }
212
-
213
- .content-column {
214
- background-color: $white-ter;
215
- }
216
-
217
- .dropdown-content .icon {
218
- margin-right: 0.5rem;
219
- }
220
-
221
- .app-logo-title:hover .collapse-menu-button {
222
- opacity: 1.0;
223
- }
224
-
225
- .collapse-menu-button {
226
- opacity: 0;
227
- transition: opacity 0.2s ease;
228
- margin-right: 1rem;
229
- z-index: 10;
230
- }
231
-
232
- .is-collapsed {
233
- .collapse-menu-button {
234
- margin-left: 1rem;
235
- }
236
-
237
- .left-navigation {
238
- padding: 0;
239
- text-align: center;
240
- }
241
-
242
- ::v-deep .menu-label {
243
- text-indent: -9999px;
244
- height: 0;
245
- border-bottom: 1px solid $grey-lighter;
246
- }
247
-
248
- ::v-deep .icon-text > span:not(.icon) {
249
- display: none;
250
- }
251
-
252
- ::v-deep .menu-list .icon {
253
- margin-right: 0;
254
- }
255
-
256
- ::v-deep .menu-list li ul {
257
- border-left: 0;
258
- margin: 0;
259
- padding-left: 0;
260
- }
261
-
262
- .app-logo-title-link {
263
- width: 5rem;
264
- flex-direction: column;
265
- }
266
-
267
- .app-logo-title {
268
- display: block;
269
- position: relative;
270
- }
271
-
272
- .collapse-menu-button {
273
- position: absolute;
274
- top: 1rem;
275
- left: 4.5rem;
276
- }
277
- }
278
- </style>
279
-
280
59
  <style lang="scss">
281
60
  @import '../styles/app.scss';
282
61
  </style>
@@ -0,0 +1,254 @@
1
+ <template>
2
+ <div class="is-flex is-flex-direction-row" style="height: 100%;">
3
+ <div :class="'left-navigation-container' + (collapsed ? ' is-collapsed' : '')">
4
+
5
+ <div class="app-logo-title">
6
+ <router-link to="/" class="app-logo-title-link">
7
+ <img src="../../assets/oxygen-icon.png" alt="Oxygen CMS" class="app-logo">
8
+ <router-link v-if="!collapsed" to="/" class="app-title">Oxygen CMS</router-link>
9
+ </router-link>
10
+ <span class="is-flex-grow-1"></span>
11
+ <b-button v-if="!requestedCollapsed" type="is-light" :icon-left="collapsed ? 'angle-right' : 'angle-left'" class="collapse-menu-button" @click="setCollapsed = !setCollapsed"></b-button>
12
+ </div>
13
+
14
+ <b-menu class="left-navigation">
15
+ <slot name="main-navigation" :collapsed="collapsed"></slot>
16
+ </b-menu>
17
+
18
+ <div :class="'user-info' + (impersonating ? ' has-background-warning' : '')">
19
+ <b-dropdown aria-role="list" :position="collapsed ? 'is-top-right' : 'is-top-left'" expanded>
20
+ <template #trigger>
21
+ <div class="user-dropdown">
22
+ <div v-if="!collapsed" class="user-dropdown-text">
23
+ <strong v-if="impersonating">Temporarily logged-in as<br/></strong>
24
+ <transition name="fade" mode="out-in">
25
+ <span v-if="user">{{ user.fullName }}</span>
26
+ <b-skeleton v-else size="is-medium" width="10em" :animated="true"></b-skeleton>
27
+ </transition>
28
+ <transition name="fade" mode="out-in">
29
+ <p v-if="user" class="is-size-7">{{ user.email }}</p>
30
+ <b-skeleton v-else width="8em" :animated="true"></b-skeleton>
31
+ </transition>
32
+ </div>
33
+ <div class="is-flex-grow-1"></div>
34
+ <div class="has-background-grey-light centered-icon" style="display: inline-block;">
35
+ <b-icon icon="user" size="is-large" class="has-text-grey-lighter"></b-icon>
36
+ </div>
37
+ </div>
38
+ </template>
39
+ <b-dropdown-item aria-role="listitem" has-link><router-link to="/user/profile"><b-icon icon="user-edit"></b-icon>Profile</router-link></b-dropdown-item>
40
+ <b-dropdown-item aria-role="listitem" has-link><router-link to="/user/login-log"><b-icon icon="lock"></b-icon>Account Security</router-link></b-dropdown-item>
41
+ <b-dropdown-item separator></b-dropdown-item>
42
+ <b-dropdown-item aria-role="listitem" @click="$emit('logout')"><b-icon icon="sign-out-alt"></b-icon>Sign Out</b-dropdown-item>
43
+ <b-dropdown-item v-if="impersonating" aria-role="listitem" @click="stopImpersonating"><b-icon icon="times"></b-icon>Stop impersonating</b-dropdown-item>
44
+ </b-dropdown>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="no-pad full-height-container content-column">
49
+
50
+ <transition name="slide-up" mode="out-in">
51
+ <router-view></router-view>
52
+ </transition>
53
+
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <script>
59
+ import UsersApi from "../UsersApi";
60
+ import {morphToNotification} from "../api";
61
+ import {isNavigationFailure, NavigationFailureType} from "vue-router/src/util/errors";
62
+
63
+ export default {
64
+ name: "AuthenticatedLayout",
65
+ emits: ["logout"],
66
+ data() {
67
+ return {
68
+ usersApi: new UsersApi(this.$buefy),
69
+ setCollapsed: false,
70
+ requestedCollapsed: false
71
+ }
72
+ },
73
+ computed: {
74
+ impersonating() { return this.$store.state.impersonating; },
75
+ user() { return this.$store.state.user; },
76
+ userPreferences() { return this.$store.getters.userPreferences; },
77
+ collapsed() {
78
+ return this.setCollapsed || this.requestedCollapsed;
79
+ }
80
+ },
81
+ mounted() {
82
+ console.log('mounted', this.$store);
83
+ this.setGlobalFontSize()
84
+ },
85
+ methods: {
86
+ setGlobalFontSize() {
87
+ let fontSize = this.userPreferences.get('fontSize', '100%');
88
+ console.log('Setting global font size to ', fontSize);
89
+ if(fontSize !== '100%') {
90
+ window.document.documentElement.style.fontSize = fontSize;
91
+ }
92
+ },
93
+ async stopImpersonating() {
94
+ let response = await this.usersApi.stopImpersonating();
95
+ this.$buefy.notification.open(morphToNotification(response));
96
+ this.$store.commit('stopImpersonating', response.user);
97
+ // ignore duplicated navigation failure
98
+ await this.$router.push({ path: '/' }).catch(failure => {
99
+ if(!isNavigationFailure(failure, NavigationFailureType.duplicated)) {
100
+ throw failure;
101
+ }
102
+ });
103
+ }
104
+ }
105
+ }
106
+ </script>
107
+
108
+ <style scoped lang="scss">
109
+ @import 'util.css';
110
+ @import '../styles/_variables.scss';
111
+
112
+ .left-navigation-container {
113
+ padding: 0 !important;
114
+ display: flex;
115
+ flex-direction: column;
116
+ flex: 1;
117
+ max-width: 550px;
118
+ border-right: 1px solid $grey-lighter;
119
+ transition: max-width 0.5s ease;
120
+ }
121
+
122
+ .left-navigation-container.is-collapsed {
123
+ max-width: 5rem;
124
+ }
125
+
126
+ .app-logo-title {
127
+ display: flex;
128
+ align-items: center;
129
+ padding: 1rem 0;
130
+ }
131
+
132
+ .app-logo {
133
+ width: 3rem;
134
+ margin-left: 1rem;
135
+ margin-right: 1rem;
136
+ }
137
+
138
+ .app-logo-title-link {
139
+ display: flex;
140
+ align-items: center;
141
+ }
142
+
143
+ .app-title {
144
+ color: $text;
145
+ font-size: 1.1rem;
146
+ }
147
+
148
+ .app-container {
149
+ display: flex;
150
+ flex-direction: row;
151
+ }
152
+
153
+ .left-navigation {
154
+ flex: 1;
155
+ padding: 1rem 1rem 1rem 2rem;
156
+ overflow-y: auto;
157
+ overflow-x: hidden;
158
+ }
159
+
160
+ .content-column {
161
+ flex: 4;
162
+ }
163
+
164
+ ::v-deep .router-link-exact-active {
165
+ // background-color: $link-invert !important;
166
+ color: $black-bis !important;
167
+ font-weight: 700;
168
+ }
169
+
170
+ ::v-deep .menu-list .icon {
171
+ margin-right: 1rem;
172
+ }
173
+
174
+ .user-dropdown {
175
+ padding: 1rem;
176
+ cursor: pointer;
177
+ display: flex;
178
+ align-items: center;
179
+ }
180
+
181
+ .columns {
182
+ margin: 0;
183
+ }
184
+
185
+ .no-pad {
186
+ padding: 0;
187
+ }
188
+
189
+ .content-column {
190
+ background-color: $white-ter;
191
+ }
192
+
193
+ .dropdown-content .icon {
194
+ margin-right: 0.5rem;
195
+ }
196
+
197
+ .app-logo-title:hover .collapse-menu-button {
198
+ opacity: 1.0;
199
+ }
200
+
201
+ .collapse-menu-button {
202
+ opacity: 0;
203
+ transition: opacity 0.2s ease;
204
+ margin-right: 1rem;
205
+ z-index: 10;
206
+ }
207
+
208
+ .is-collapsed {
209
+ .collapse-menu-button {
210
+ margin-left: 1rem;
211
+ }
212
+
213
+ .left-navigation {
214
+ padding: 0;
215
+ text-align: center;
216
+ }
217
+
218
+ ::v-deep .menu-label {
219
+ text-indent: -9999px;
220
+ height: 0;
221
+ border-bottom: 1px solid $grey-lighter;
222
+ }
223
+
224
+ ::v-deep .icon-text > span:not(.icon) {
225
+ display: none;
226
+ }
227
+
228
+ ::v-deep .menu-list .icon {
229
+ margin-right: 0;
230
+ }
231
+
232
+ ::v-deep .menu-list li ul {
233
+ border-left: 0;
234
+ margin: 0;
235
+ padding-left: 0;
236
+ }
237
+
238
+ .app-logo-title-link {
239
+ width: 5rem;
240
+ flex-direction: column;
241
+ }
242
+
243
+ .app-logo-title {
244
+ display: block;
245
+ position: relative;
246
+ }
247
+
248
+ .collapse-menu-button {
249
+ position: absolute;
250
+ top: 1rem;
251
+ left: 4.5rem;
252
+ }
253
+ }
254
+ </style>