@oxygen-cms/ui 1.8.1 → 1.9.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/package.json +1 -1
- package/src/PagesApi.js +1 -1
- package/src/components/MainMenu.vue +15 -6
- package/src/components/MinimalDropdownButton.vue +36 -0
- package/src/components/PageActions.vue +38 -6
- package/src/components/PageTable.vue +1 -1
- package/src/components/ResourceList.vue +14 -6
- package/src/components/dashboard/MediaPanel.vue +10 -2
- package/src/components/media/MediaList.vue +3 -10
- package/src/components/media/MediaUploadDropdown.vue +182 -0
- package/src/components/pages/CreatePageDropdown.vue +209 -0
- package/src/components/partials/CreatePartialDropdown.vue +135 -0
- package/src/modules/Media.js +2 -0
- package/src/modules/PagesPartials.js +6 -13
- package/src/util.js +21 -1
- package/.idea/workspace.xml +0 -37
package/package.json
CHANGED
package/src/PagesApi.js
CHANGED
|
@@ -15,12 +15,18 @@
|
|
|
15
15
|
:to="group.listAction">
|
|
16
16
|
<template #label>
|
|
17
17
|
{{ groupLabel }}
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
<span v-if="userPermissions.has(group.addPermission) && group.addDropdownComponent"
|
|
19
|
+
class="is-pulled-right show-if-active add-dropdown-trigger"
|
|
20
|
+
@click.stop.prevent
|
|
21
|
+
@mousedown.stop.prevent>
|
|
22
|
+
<component :is="group.addDropdownComponent"
|
|
23
|
+
:minimal="true" />
|
|
24
|
+
</span>
|
|
25
|
+
<router-link v-else-if="userPermissions.has(group.addPermission)"
|
|
26
|
+
class="is-pulled-right show-if-active"
|
|
27
|
+
:to="group.addAction">
|
|
28
|
+
<MinimalDropdownButton :icon="group.addIcon" />
|
|
29
|
+
</router-link>
|
|
24
30
|
</template>
|
|
25
31
|
<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
32
|
</b-menu-item>
|
|
@@ -35,8 +41,11 @@
|
|
|
35
41
|
</template>
|
|
36
42
|
|
|
37
43
|
<script>
|
|
44
|
+
import MinimalDropdownButton from "./MinimalDropdownButton.vue";
|
|
45
|
+
|
|
38
46
|
export default {
|
|
39
47
|
name: "MainMenu",
|
|
48
|
+
components: { MinimalDropdownButton },
|
|
40
49
|
props: {
|
|
41
50
|
items: {
|
|
42
51
|
type: Object,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<b-button
|
|
3
|
+
type="is-text"
|
|
4
|
+
:icon-right="icon"
|
|
5
|
+
class="minimal-button">
|
|
6
|
+
</b-button>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
export default {
|
|
11
|
+
name: "MinimalDropdownButton",
|
|
12
|
+
props: {
|
|
13
|
+
icon: {
|
|
14
|
+
type: String,
|
|
15
|
+
required: true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<style scoped lang="scss">
|
|
22
|
+
@import '../styles/variables.scss';
|
|
23
|
+
|
|
24
|
+
.minimal-button {
|
|
25
|
+
padding: 0 0.5em;
|
|
26
|
+
height: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.button.is-text.minimal-button {
|
|
30
|
+
background-color: $grey-lighter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.button.is-text.minimal-button:hover {
|
|
34
|
+
background-color: darken($grey-lighter, 5%);
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
paddingless>
|
|
21
21
|
<div class="modal-card" style="width: auto; overflow: visible">
|
|
22
22
|
<header class="modal-card-head">
|
|
23
|
-
<p class="modal-card-title">
|
|
23
|
+
<p class="modal-card-title">Update the parent for "{{ item.title }}"</p>
|
|
24
24
|
</header>
|
|
25
25
|
<section class="modal-card-body" style="overflow: visible;">
|
|
26
26
|
<b-field>
|
|
@@ -28,11 +28,16 @@
|
|
|
28
28
|
v-model.lazy="movePageSearchQuery"
|
|
29
29
|
:disabled="isLoading"
|
|
30
30
|
open-on-focus
|
|
31
|
-
:data="
|
|
32
|
-
:custom-formatter="data => data.title + ' - ' + data.slug"
|
|
31
|
+
:data="sortedPagesList"
|
|
33
32
|
placeholder="Search for pages..."
|
|
34
33
|
clearable
|
|
35
34
|
@select="setParentPage">
|
|
35
|
+
<template #default="props">
|
|
36
|
+
<span :style="getOptionStyle(props.option)">
|
|
37
|
+
{{ props.option.title }} - {{ props.option.slug }}
|
|
38
|
+
<span v-if="isCurrentParent(props.option)" style="opacity: 0.6;"> (current parent)</span>
|
|
39
|
+
</span>
|
|
40
|
+
</template>
|
|
36
41
|
<template #empty>No results found</template>
|
|
37
42
|
</b-autocomplete>
|
|
38
43
|
</b-field>
|
|
@@ -71,6 +76,16 @@ export default {
|
|
|
71
76
|
pagesList: []
|
|
72
77
|
}
|
|
73
78
|
},
|
|
79
|
+
computed: {
|
|
80
|
+
sortedPagesList() {
|
|
81
|
+
// Sort with Home page (slug '/') at the top, then alphabetically by title
|
|
82
|
+
return [...this.pagesList].sort((a, b) => {
|
|
83
|
+
if (a.slug === '/') return -1;
|
|
84
|
+
if (b.slug === '/') return 1;
|
|
85
|
+
return a.title.localeCompare(b.title);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
},
|
|
74
89
|
watch: {
|
|
75
90
|
'movePageSearchQuery': 'fetchData'
|
|
76
91
|
},
|
|
@@ -78,9 +93,6 @@ export default {
|
|
|
78
93
|
async fetchData() {
|
|
79
94
|
let data = await this.pagesApi.list({ inTrash: false, page: 1, q: this.movePageSearchQuery });
|
|
80
95
|
this.pagesList = data.items;
|
|
81
|
-
this.pagesList.sort((a, b) => {
|
|
82
|
-
return a.title.localeCompare(b.title);
|
|
83
|
-
});
|
|
84
96
|
this.isLoading = false;
|
|
85
97
|
},
|
|
86
98
|
async publish() {
|
|
@@ -88,12 +100,32 @@ export default {
|
|
|
88
100
|
this.$emit('update', item);
|
|
89
101
|
},
|
|
90
102
|
async setParentPage(parentPage) {
|
|
103
|
+
// Don't allow moving to current parent
|
|
104
|
+
if (this.isCurrentParent(parentPage)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
91
107
|
let data = await this.pagesApi.update({id: this.item.id, parent: parentPage.id, autoConvertToDraft: 'no', version: false});
|
|
92
108
|
this.$buefy.toast.open(morphToNotification(data));
|
|
93
109
|
this.$emit('reload');
|
|
94
110
|
},
|
|
95
111
|
close() {
|
|
96
112
|
this.$refs.moveDropdown.toggle();
|
|
113
|
+
},
|
|
114
|
+
isCurrentParent(page) {
|
|
115
|
+
if (!this.item.parent) return false;
|
|
116
|
+
// Handle both cases: parent as object or parent as ID
|
|
117
|
+
const parentId = typeof this.item.parent === 'object' ? this.item.parent.id : this.item.parent;
|
|
118
|
+
return page.id === parentId;
|
|
119
|
+
},
|
|
120
|
+
getOptionStyle(option) {
|
|
121
|
+
let style = '';
|
|
122
|
+
if (option.slug === '/') {
|
|
123
|
+
style += 'font-weight: bold;';
|
|
124
|
+
}
|
|
125
|
+
if (this.isCurrentParent(option)) {
|
|
126
|
+
style += ' opacity: 0.5; cursor: not-allowed;';
|
|
127
|
+
}
|
|
128
|
+
return style;
|
|
97
129
|
}
|
|
98
130
|
}
|
|
99
131
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
@details-close="item => setExpanded(item, false)"
|
|
28
28
|
@sort="onSort">
|
|
29
29
|
<b-table-column v-slot="props" label="Title" :sortable="!!onSort" field="title">{{ props.row.title }} <PageStatusIcon :item="props.row"></PageStatusIcon></b-table-column>
|
|
30
|
-
<b-table-column v-slot="props" label="URL" :sortable="!!onSort" field="slugPart"><a :href="PagesApi.slugToUrl(props.row.slug)" class="is-size-7" target="_blank">{{ PagesApi.slugToUrl(props.row.slug) }} <b-icon icon="external-link-alt"></b-icon></a></b-table-column>
|
|
30
|
+
<b-table-column v-slot="props" label="URL" :sortable="!!onSort" field="slugPart"><a :href="PagesApi.slugToUrl(props.row.slug)" class="is-size-7" target="_blank" v-if="props.row.stage === PagesApi.STAGE_PUBLISHED">{{ PagesApi.slugToUrl(props.row.slug) }} <b-icon icon="external-link-alt"></b-icon></a><em v-else class="is-size-7">(unpublished)</em></b-table-column>
|
|
31
31
|
<b-table-column v-slot="props" label="Description" width="30%" :sortable="!!onSort" field="description"><div class="is-size-7">{{ props.row.description }}</div></b-table-column>
|
|
32
32
|
<b-table-column v-slot="props" label="Last Updated" field="updatedAt" :sortable="!!onSort">
|
|
33
33
|
<div v-if="props.row.updatedAt" class="is-size-7"><Updated :model="props.row"></Updated></div>
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
<div class="is-flex-grow-1">
|
|
10
10
|
</div>
|
|
11
11
|
<b-input v-model.lazy="searchQuery" rounded :placeholder="'Search ' + displayName" icon="search" icon-pack="fas" class="mr-3"></b-input>
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
<component v-if="!inTrash"
|
|
14
|
+
:is="createDropdownComponent"
|
|
15
|
+
class="mr-3"
|
|
16
|
+
@created="fetchData" />
|
|
17
|
+
|
|
14
18
|
<b-button v-if="!inTrash" tag="router-link" :to="'/' + routePrefix + '/trash'" type="is-danger" outlined icon-left="trash">Deleted
|
|
15
19
|
{{ displayName }}</b-button>
|
|
16
20
|
</div>
|
|
@@ -58,7 +62,11 @@ export default {
|
|
|
58
62
|
defaultSortOrder: String,
|
|
59
63
|
tableComponent: Object,
|
|
60
64
|
actionsComponent: Object,
|
|
61
|
-
resourceApi: Object
|
|
65
|
+
resourceApi: Object,
|
|
66
|
+
createDropdownComponent: {
|
|
67
|
+
type: Object,
|
|
68
|
+
required: true
|
|
69
|
+
}
|
|
62
70
|
},
|
|
63
71
|
data() {
|
|
64
72
|
return {
|
|
@@ -69,12 +77,12 @@ export default {
|
|
|
69
77
|
}
|
|
70
78
|
},
|
|
71
79
|
watch: {
|
|
72
|
-
'resourceApi': '
|
|
73
|
-
'inTrash': '
|
|
80
|
+
'resourceApi': 'fetchData',
|
|
81
|
+
'inTrash': 'fetchData',
|
|
74
82
|
'sortField': 'debouncedFetchData',
|
|
75
83
|
'sortOrder': 'debouncedFetchData',
|
|
76
84
|
'searchQuery': 'debouncedFetchData',
|
|
77
|
-
'paginatedItems.currentPage': 'debouncedFetchData'
|
|
85
|
+
'paginatedItems.currentPage': 'debouncedFetchData',
|
|
78
86
|
},
|
|
79
87
|
created() {
|
|
80
88
|
this.fetchData()
|
|
@@ -4,15 +4,23 @@
|
|
|
4
4
|
<p class="subtitle">Photos, videos, audio, PDFs.</p>
|
|
5
5
|
<div class="buttons">
|
|
6
6
|
<b-button tag="router-link" to="/media/list" icon-left="list">Manage Photos & Files</b-button>
|
|
7
|
-
<
|
|
7
|
+
<MediaUploadDropdown :minimal="false" :current-directory="null" @uploaded="handleUploaded" />
|
|
8
8
|
<b-button tag="router-link" to="/media/responsive-images" type="is-light" icon-left="mail-bulk">Generate Responsive Images</b-button>
|
|
9
9
|
</div>
|
|
10
10
|
</article>
|
|
11
11
|
</template>
|
|
12
12
|
|
|
13
13
|
<script>
|
|
14
|
+
import MediaUploadDropdown from "../media/MediaUploadDropdown.vue";
|
|
15
|
+
|
|
14
16
|
export default {
|
|
15
|
-
name: "MediaPanel"
|
|
17
|
+
name: "MediaPanel",
|
|
18
|
+
components: { MediaUploadDropdown },
|
|
19
|
+
methods: {
|
|
20
|
+
handleUploaded() {
|
|
21
|
+
this.$router.push('/media/list');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
25
|
</script>
|
|
18
26
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
</b-field>
|
|
36
36
|
|
|
37
37
|
<b-button v-if="!inTrash && !searchQuery" icon-left="folder-plus" class="action-bar-pad" @click="isCreateDirectoryModalActive = true">New Directory</b-button>
|
|
38
|
-
<
|
|
38
|
+
<MediaUploadDropdown v-if="!inTrash && !searchQuery" class="action-bar-pad" :current-directory="paginatedItems.currentDirectory" @uploaded="fetchData" />
|
|
39
39
|
<b-input v-if="!inTrash" rounded placeholder="Search photos and files..." icon="search"
|
|
40
40
|
icon-pack="fas" :value="searchQuery" class="action-bar-pad" @input="value => navigateTo({searchQuery: value})"></b-input>
|
|
41
41
|
<b-button v-if="!inTrash" icon-left="trash" type="is-danger" outlined class="action-bar-pad" @click="navigateTo({inTrash: true})">Deleted Items</b-button>
|
|
@@ -101,10 +101,6 @@
|
|
|
101
101
|
</div>
|
|
102
102
|
</b-modal>
|
|
103
103
|
|
|
104
|
-
<b-modal :active.sync="isUploadModalActive" trap-focus has-modal-card aria-role="dialog" aria-modal auto-focus>
|
|
105
|
-
<MediaUpload :current-directory="paginatedItems.currentDirectory" @close="$router.push({ query: { }})" @uploaded="fetchData"></MediaUpload>
|
|
106
|
-
</b-modal>
|
|
107
|
-
|
|
108
104
|
</div>
|
|
109
105
|
|
|
110
106
|
</template>
|
|
@@ -115,11 +111,11 @@ import MediaDirectory from "./MediaDirectory.vue";
|
|
|
115
111
|
import MediaItem from "./MediaItem.vue";
|
|
116
112
|
import MediaDirectoryApi, {getDirectoryBreadcrumbItems, getDirectoryPathString} from "../../MediaDirectoryApi";
|
|
117
113
|
import {morphToNotification} from "../../api";
|
|
118
|
-
import
|
|
114
|
+
import MediaUploadDropdown from "./MediaUploadDropdown.vue";
|
|
119
115
|
|
|
120
116
|
export default {
|
|
121
117
|
name: "MediaList",
|
|
122
|
-
components: { MediaDirectory, MediaItem,
|
|
118
|
+
components: { MediaDirectory, MediaItem, MediaUploadDropdown },
|
|
123
119
|
props: {
|
|
124
120
|
currentPath: {
|
|
125
121
|
type: String,
|
|
@@ -145,9 +141,6 @@ export default {
|
|
|
145
141
|
}
|
|
146
142
|
},
|
|
147
143
|
computed: {
|
|
148
|
-
isUploadModalActive() {
|
|
149
|
-
return this.$route.query.upload === 'true';
|
|
150
|
-
},
|
|
151
144
|
displayPath() {
|
|
152
145
|
return getDirectoryPathString(this.paginatedItems.currentDirectory);
|
|
153
146
|
},
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<b-dropdown
|
|
3
|
+
ref="dropdown"
|
|
4
|
+
:position="minimal ? 'is-bottom-right' : 'is-bottom-left'"
|
|
5
|
+
aria-role="menu"
|
|
6
|
+
trap-focus
|
|
7
|
+
append-to-body
|
|
8
|
+
class="media-upload-dropdown">
|
|
9
|
+
<template #trigger="{ active }">
|
|
10
|
+
<MinimalDropdownButton v-if="minimal" icon="upload"/>
|
|
11
|
+
<b-button v-else
|
|
12
|
+
:type="active ? '' : 'is-success'"
|
|
13
|
+
icon-left="file-upload"
|
|
14
|
+
:disabled="active">
|
|
15
|
+
Upload Files
|
|
16
|
+
</b-button>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<b-dropdown-item aria-role="menu-item" custom paddingless>
|
|
20
|
+
<div class="modal-card" style="width: auto; overflow: visible">
|
|
21
|
+
<header class="modal-card-head has-background-success-light">
|
|
22
|
+
<p v-if="!isUploading" class="modal-card-title">
|
|
23
|
+
<b-icon icon="upload" size="is-normal" class="push-right"></b-icon>
|
|
24
|
+
Upload files to '{{ currentPath }}'
|
|
25
|
+
</p>
|
|
26
|
+
<p v-else class="modal-card-title">
|
|
27
|
+
<b-icon icon="upload" size="is-normal" class="push-right"></b-icon>
|
|
28
|
+
Uploading {{ filesToUpload.length }} item(s)
|
|
29
|
+
</p>
|
|
30
|
+
</header>
|
|
31
|
+
<section v-if="!isUploading" class="modal-card-body" style="overflow: visible;">
|
|
32
|
+
<b-upload v-model="filesToUpload"
|
|
33
|
+
multiple
|
|
34
|
+
drag-drop expanded>
|
|
35
|
+
<section class="section">
|
|
36
|
+
<div class="content has-text-centered">
|
|
37
|
+
<p>
|
|
38
|
+
<b-icon
|
|
39
|
+
icon="upload"
|
|
40
|
+
size="is-large">
|
|
41
|
+
</b-icon>
|
|
42
|
+
</p>
|
|
43
|
+
<p>Drop your files here or click to upload</p>
|
|
44
|
+
</div>
|
|
45
|
+
</section>
|
|
46
|
+
</b-upload>
|
|
47
|
+
|
|
48
|
+
<div class="tags mt-4">
|
|
49
|
+
<span v-for="(file, index) in filesToUpload" :key="index" class="tag is-primary image-preview">
|
|
50
|
+
<img :src="imagePreviews[index]" alt="file preview" />
|
|
51
|
+
<p class="image-preview-label">
|
|
52
|
+
{{ file.name }}
|
|
53
|
+
<button class="delete is-small" type="button" @click="deleteFileToUpload(index)"></button>
|
|
54
|
+
</p>
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
<section v-else class="modal-card-body" style="overflow: visible;">
|
|
59
|
+
<b-progress size="is-medium" type="is-info"></b-progress>
|
|
60
|
+
</section>
|
|
61
|
+
<footer class="modal-card-foot is-flex">
|
|
62
|
+
<div class="is-flex-grow-1"></div>
|
|
63
|
+
<b-button @click="close">Close</b-button>
|
|
64
|
+
<b-button type="is-success" :disabled="isUploading" @click="doUpload">Upload</b-button>
|
|
65
|
+
</footer>
|
|
66
|
+
</div>
|
|
67
|
+
</b-dropdown-item>
|
|
68
|
+
</b-dropdown>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
import {morphToNotification} from "../../api";
|
|
73
|
+
import MediaApi from "../../MediaApi";
|
|
74
|
+
import {getDirectoryPathString} from "../../MediaDirectoryApi";
|
|
75
|
+
import MinimalDropdownButton from "../MinimalDropdownButton.vue";
|
|
76
|
+
|
|
77
|
+
export default {
|
|
78
|
+
name: "MediaUploadDropdown",
|
|
79
|
+
components: { MinimalDropdownButton },
|
|
80
|
+
props: {
|
|
81
|
+
currentDirectory: { type: Object, default: null },
|
|
82
|
+
minimal: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
data() {
|
|
88
|
+
return {
|
|
89
|
+
filesToUpload: [],
|
|
90
|
+
isUploading: false,
|
|
91
|
+
mediaApi: new MediaApi()
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
computed: {
|
|
95
|
+
currentPath() {
|
|
96
|
+
return getDirectoryPathString(this.currentDirectory);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
asyncComputed: {
|
|
100
|
+
async imagePreviews() {
|
|
101
|
+
let promises = this.filesToUpload.map(file => {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
let reader = new FileReader();
|
|
104
|
+
reader.onload = function (e) {
|
|
105
|
+
resolve(e.target.result);
|
|
106
|
+
};
|
|
107
|
+
reader.readAsDataURL(file);
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
return Promise.all(promises);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
methods: {
|
|
114
|
+
deleteFileToUpload(index) {
|
|
115
|
+
this.filesToUpload.splice(index, 1);
|
|
116
|
+
},
|
|
117
|
+
async doUpload() {
|
|
118
|
+
if(this.filesToUpload.length === 0) {
|
|
119
|
+
return this.$buefy.toast.open({
|
|
120
|
+
message: 'No files selected for upload',
|
|
121
|
+
type: 'is-warning',
|
|
122
|
+
queue: false
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
this.isUploading = true;
|
|
126
|
+
try {
|
|
127
|
+
let data = await this.mediaApi.create({
|
|
128
|
+
files: this.filesToUpload,
|
|
129
|
+
currentDirectory: this.currentDirectory
|
|
130
|
+
});
|
|
131
|
+
this.$buefy.toast.open(morphToNotification(data));
|
|
132
|
+
this.$emit('uploaded');
|
|
133
|
+
this.close();
|
|
134
|
+
} catch(e) {
|
|
135
|
+
// upload failed
|
|
136
|
+
console.warn(e);
|
|
137
|
+
this.close();
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
close() {
|
|
141
|
+
this.$refs.dropdown.toggle();
|
|
142
|
+
this.isUploading = false;
|
|
143
|
+
this.filesToUpload = [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
.media-upload-dropdown .dropdown-content {
|
|
151
|
+
padding-top: 0;
|
|
152
|
+
padding-bottom: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.media-upload-dropdown .dropdown-menu {
|
|
156
|
+
overflow: visible !important;
|
|
157
|
+
min-width: 30rem;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.media-upload-dropdown .modal-card-title {
|
|
161
|
+
flex-shrink: unset;
|
|
162
|
+
flex-grow: unset;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
165
|
+
|
|
166
|
+
<style scoped>
|
|
167
|
+
.image-preview {
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-direction: column;
|
|
170
|
+
height: auto;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.image-preview img {
|
|
174
|
+
margin: 0.5rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.image-preview-label {
|
|
178
|
+
margin: 0.5rem;
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<b-dropdown
|
|
3
|
+
ref="dropdown"
|
|
4
|
+
:position="minimal ? 'is-bottom-right' : 'is-bottom-left'"
|
|
5
|
+
aria-role="menu"
|
|
6
|
+
trap-focus
|
|
7
|
+
append-to-body
|
|
8
|
+
class="create-page-dropdown">
|
|
9
|
+
<template #trigger="{ active }">
|
|
10
|
+
<MinimalDropdownButton v-if="minimal" icon="plus"/>
|
|
11
|
+
<b-button v-else
|
|
12
|
+
:type="active ? '' : 'is-success'"
|
|
13
|
+
icon-left="pencil-alt"
|
|
14
|
+
:disabled="active">
|
|
15
|
+
Create Page
|
|
16
|
+
</b-button>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<b-dropdown-item aria-role="menu-item" custom paddingless>
|
|
20
|
+
<div class="modal-card" style="width: auto; overflow: visible">
|
|
21
|
+
<header class="modal-card-head has-background-success-light">
|
|
22
|
+
<p class="modal-card-title">
|
|
23
|
+
<b-icon icon="file-alt" size="is-normal" class="push-right"></b-icon>
|
|
24
|
+
Create Page
|
|
25
|
+
</p>
|
|
26
|
+
</header>
|
|
27
|
+
<section class="modal-card-body" style="overflow: visible;">
|
|
28
|
+
<b-field label="Title">
|
|
29
|
+
<b-input
|
|
30
|
+
v-model="title"
|
|
31
|
+
placeholder="e.g.: About Us"
|
|
32
|
+
autofocus
|
|
33
|
+
@keyup.enter.native="submit">
|
|
34
|
+
</b-input>
|
|
35
|
+
</b-field>
|
|
36
|
+
|
|
37
|
+
<b-field label="URL Part" message="Leave blank to auto-generate from title">
|
|
38
|
+
<b-input
|
|
39
|
+
v-model="slugPart"
|
|
40
|
+
:placeholder="slugifyTitle(title) || 'e.g.: about-us'"
|
|
41
|
+
@keyup.enter.native="submit">
|
|
42
|
+
</b-input>
|
|
43
|
+
</b-field>
|
|
44
|
+
|
|
45
|
+
<b-field label="Parent Page">
|
|
46
|
+
<b-autocomplete
|
|
47
|
+
v-if="!selectedParent"
|
|
48
|
+
v-model="parentSearchQuery"
|
|
49
|
+
:disabled="isLoading"
|
|
50
|
+
open-on-focus
|
|
51
|
+
:data="sortedPagesList"
|
|
52
|
+
placeholder="Search for parent page..."
|
|
53
|
+
clearable
|
|
54
|
+
@select="onSelectParent"
|
|
55
|
+
@input="onParentSearchInput">
|
|
56
|
+
<template #default="props">
|
|
57
|
+
<span :style="props.option.slug === '/' ? 'font-weight: bold;' : ''">
|
|
58
|
+
{{ props.option.title }} - {{ props.option.slug }}
|
|
59
|
+
</span>
|
|
60
|
+
</template>
|
|
61
|
+
<template #empty>No results found</template>
|
|
62
|
+
</b-autocomplete>
|
|
63
|
+
<div v-else style="display: flex; align-items: center; padding: 0.5rem 0.75rem; border: 1px solid #dbdbdb; border-radius: 4px; background-color: #f5f5f5;">
|
|
64
|
+
<span style="flex: 1;">
|
|
65
|
+
<strong>{{ selectedParent.title }}</strong> - {{ selectedParent.slug }}
|
|
66
|
+
</span>
|
|
67
|
+
<a @click.stop="clearSelectedParent" style="color: #f14668; cursor: pointer; margin-left: 0.5rem;">
|
|
68
|
+
<b-icon icon="times" size="is-small"></b-icon>
|
|
69
|
+
</a>
|
|
70
|
+
</div>
|
|
71
|
+
</b-field>
|
|
72
|
+
</section>
|
|
73
|
+
<footer class="modal-card-foot is-flex">
|
|
74
|
+
<div class="is-flex-grow-1"></div>
|
|
75
|
+
<b-button @click="close">Cancel</b-button>
|
|
76
|
+
<b-button type="is-success" :loading="submitting" @click="submit">
|
|
77
|
+
Create Page
|
|
78
|
+
</b-button>
|
|
79
|
+
</footer>
|
|
80
|
+
</div>
|
|
81
|
+
</b-dropdown-item>
|
|
82
|
+
</b-dropdown>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script>
|
|
86
|
+
import PagesApi from "../../PagesApi.js";
|
|
87
|
+
import { morphToNotification } from "../../api.js";
|
|
88
|
+
import { slugify } from "../../util.js";
|
|
89
|
+
import MinimalDropdownButton from "../MinimalDropdownButton.vue";
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
name: "CreatePageDropdown",
|
|
93
|
+
components: { MinimalDropdownButton },
|
|
94
|
+
props: {
|
|
95
|
+
minimal: {
|
|
96
|
+
type: Boolean,
|
|
97
|
+
default: false
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
created() {
|
|
101
|
+
this.fetchPages();
|
|
102
|
+
},
|
|
103
|
+
data() {
|
|
104
|
+
return {
|
|
105
|
+
title: '',
|
|
106
|
+
slugPart: '',
|
|
107
|
+
parentSearchQuery: '',
|
|
108
|
+
selectedParent: null,
|
|
109
|
+
submitting: false,
|
|
110
|
+
isLoading: false,
|
|
111
|
+
pagesList: [],
|
|
112
|
+
pagesApi: new PagesApi()
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
computed: {
|
|
116
|
+
sortedPagesList() {
|
|
117
|
+
// Sort with Home page (slug '/') at the top, then alphabetically by title
|
|
118
|
+
return [...this.pagesList].sort((a, b) => {
|
|
119
|
+
if (a.slug === '/') return -1;
|
|
120
|
+
if (b.slug === '/') return 1;
|
|
121
|
+
return a.title.localeCompare(b.title);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
watch: {
|
|
126
|
+
'parentSearchQuery': 'fetchPages'
|
|
127
|
+
},
|
|
128
|
+
methods: {
|
|
129
|
+
slugifyTitle(str) {
|
|
130
|
+
return slugify(str);
|
|
131
|
+
},
|
|
132
|
+
async fetchPages() {
|
|
133
|
+
this.isLoading = true;
|
|
134
|
+
let data = await this.pagesApi.list({ inTrash: false, page: 1, q: this.parentSearchQuery });
|
|
135
|
+
this.pagesList = data.items;
|
|
136
|
+
this.isLoading = false;
|
|
137
|
+
},
|
|
138
|
+
onSelectParent(option) {
|
|
139
|
+
this.selectedParent = option;
|
|
140
|
+
// Clear the search query after selection to reset the autocomplete
|
|
141
|
+
this.$nextTick(() => {
|
|
142
|
+
this.parentSearchQuery = '';
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
onParentSearchInput() {
|
|
146
|
+
// Clear selected parent if user starts typing
|
|
147
|
+
if (this.parentSearchQuery && this.selectedParent) {
|
|
148
|
+
this.selectedParent = null;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
clearSelectedParent() {
|
|
152
|
+
this.selectedParent = null;
|
|
153
|
+
this.parentSearchQuery = '';
|
|
154
|
+
},
|
|
155
|
+
async submit() {
|
|
156
|
+
if (!this.title.trim()) {
|
|
157
|
+
this.$buefy.toast.open({
|
|
158
|
+
type: 'is-danger',
|
|
159
|
+
message: 'Please enter a title'
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.submitting = true;
|
|
165
|
+
try {
|
|
166
|
+
let data = {
|
|
167
|
+
title: this.title,
|
|
168
|
+
slugPart: this.slugPart || this.slugifyTitle(this.title),
|
|
169
|
+
parent: this.selectedParent ? this.selectedParent.id : null
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let response = await this.pagesApi.create(data);
|
|
173
|
+
this.$buefy.toast.open(morphToNotification(response));
|
|
174
|
+
this.close();
|
|
175
|
+
this.$emit('created', response.item);
|
|
176
|
+
this.$router.push('/pages/' + response.item.id + '/edit');
|
|
177
|
+
} catch(e) {
|
|
178
|
+
// Error handled by API layer
|
|
179
|
+
}
|
|
180
|
+
this.submitting = false;
|
|
181
|
+
},
|
|
182
|
+
close() {
|
|
183
|
+
this.$refs.dropdown.toggle();
|
|
184
|
+
// Reset form
|
|
185
|
+
this.title = '';
|
|
186
|
+
this.slugPart = '';
|
|
187
|
+
this.parentSearchQuery = '';
|
|
188
|
+
this.selectedParent = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<style>
|
|
195
|
+
.create-page-dropdown .dropdown-content {
|
|
196
|
+
padding-top: 0;
|
|
197
|
+
padding-bottom: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.create-page-dropdown .dropdown-menu {
|
|
201
|
+
overflow: visible !important;
|
|
202
|
+
min-width: 25rem;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.create-page-dropdown .modal-card-title {
|
|
206
|
+
flex-shrink: unset;
|
|
207
|
+
flex-grow: unset;
|
|
208
|
+
}
|
|
209
|
+
</style>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<b-dropdown
|
|
3
|
+
ref="dropdown"
|
|
4
|
+
:position="minimal ? 'is-bottom-right' : 'is-bottom-left'"
|
|
5
|
+
aria-role="menu"
|
|
6
|
+
trap-focus
|
|
7
|
+
append-to-body
|
|
8
|
+
class="create-partial-dropdown">
|
|
9
|
+
<template #trigger="{ active }">
|
|
10
|
+
<MinimalDropdownButton v-if="minimal" icon="plus"/>
|
|
11
|
+
<b-button v-else
|
|
12
|
+
:type="active ? '' : 'is-success'"
|
|
13
|
+
icon-left="pencil-alt"
|
|
14
|
+
:disabled="active">
|
|
15
|
+
Create Partial
|
|
16
|
+
</b-button>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<b-dropdown-item aria-role="menu-item" custom paddingless>
|
|
20
|
+
<div class="modal-card" style="width: auto; overflow: visible">
|
|
21
|
+
<header class="modal-card-head has-background-success-light">
|
|
22
|
+
<p class="modal-card-title">
|
|
23
|
+
<b-icon icon="puzzle-piece" size="is-normal" class="push-right"></b-icon>
|
|
24
|
+
Create Partial
|
|
25
|
+
</p>
|
|
26
|
+
</header>
|
|
27
|
+
<section class="modal-card-body" style="overflow: visible;">
|
|
28
|
+
<b-field label="Title">
|
|
29
|
+
<b-input
|
|
30
|
+
v-model="title"
|
|
31
|
+
placeholder="e.g.: Footer Copyright Text"
|
|
32
|
+
autofocus
|
|
33
|
+
@keyup.enter.native="submit">
|
|
34
|
+
</b-input>
|
|
35
|
+
</b-field>
|
|
36
|
+
|
|
37
|
+
<b-field label="Key" message="Leave blank to auto-generate from title">
|
|
38
|
+
<b-input
|
|
39
|
+
v-model="key"
|
|
40
|
+
:placeholder="slugifyKey(title) || 'e.g.: footer.copyright'"
|
|
41
|
+
@keyup.enter.native="submit">
|
|
42
|
+
</b-input>
|
|
43
|
+
</b-field>
|
|
44
|
+
</section>
|
|
45
|
+
<footer class="modal-card-foot is-flex">
|
|
46
|
+
<div class="is-flex-grow-1"></div>
|
|
47
|
+
<b-button @click="close">Cancel</b-button>
|
|
48
|
+
<b-button type="is-success" :loading="submitting" @click="submit">
|
|
49
|
+
Create Partial
|
|
50
|
+
</b-button>
|
|
51
|
+
</footer>
|
|
52
|
+
</div>
|
|
53
|
+
</b-dropdown-item>
|
|
54
|
+
</b-dropdown>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
import PartialsApi from "../../PartialsApi.js";
|
|
59
|
+
import { morphToNotification } from "../../api.js";
|
|
60
|
+
import { slugifyKey } from "../../util.js";
|
|
61
|
+
import MinimalDropdownButton from "../MinimalDropdownButton.vue";
|
|
62
|
+
|
|
63
|
+
export default {
|
|
64
|
+
name: "CreatePartialDropdown",
|
|
65
|
+
components: { MinimalDropdownButton },
|
|
66
|
+
props: {
|
|
67
|
+
minimal: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: false
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
title: '',
|
|
75
|
+
key: '',
|
|
76
|
+
submitting: false,
|
|
77
|
+
partialsApi: new PartialsApi()
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
methods: {
|
|
81
|
+
slugifyKey(str) {
|
|
82
|
+
return slugifyKey(str);
|
|
83
|
+
},
|
|
84
|
+
async submit() {
|
|
85
|
+
if (!this.title.trim()) {
|
|
86
|
+
this.$buefy.toast.open({
|
|
87
|
+
type: 'is-danger',
|
|
88
|
+
message: 'Please enter a title'
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.submitting = true;
|
|
94
|
+
try {
|
|
95
|
+
let data = {
|
|
96
|
+
title: this.title,
|
|
97
|
+
key: this.key || this.slugifyKey(this.title)
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let response = await this.partialsApi.create(data);
|
|
101
|
+
this.$buefy.toast.open(morphToNotification(response));
|
|
102
|
+
this.close();
|
|
103
|
+
this.$emit('created', response.item);
|
|
104
|
+
this.$router.push('/partials/' + response.item.id + '/edit');
|
|
105
|
+
} catch(e) {
|
|
106
|
+
// Error handled by API layer
|
|
107
|
+
}
|
|
108
|
+
this.submitting = false;
|
|
109
|
+
},
|
|
110
|
+
close() {
|
|
111
|
+
this.$refs.dropdown.toggle();
|
|
112
|
+
// Reset form
|
|
113
|
+
this.title = '';
|
|
114
|
+
this.key = '';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<style>
|
|
121
|
+
.create-partial-dropdown .dropdown-content {
|
|
122
|
+
padding-top: 0;
|
|
123
|
+
padding-bottom: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.create-partial-dropdown .dropdown-menu {
|
|
127
|
+
overflow: visible !important;
|
|
128
|
+
min-width: 25rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.create-partial-dropdown .modal-card-title {
|
|
132
|
+
flex-shrink: unset;
|
|
133
|
+
flex-grow: unset;
|
|
134
|
+
}
|
|
135
|
+
</style>
|
package/src/modules/Media.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import MediaPage from "../components/media/MediaPage.vue";
|
|
2
2
|
import MediaResponsiveImages from "../components/media/MediaResponsiveImages.vue";
|
|
3
|
+
import MediaUploadDropdown from "../components/media/MediaUploadDropdown.vue";
|
|
3
4
|
import {WEB_CONTENT} from "../main";
|
|
4
5
|
|
|
5
6
|
export default function(ui) {
|
|
@@ -10,6 +11,7 @@ export default function(ui) {
|
|
|
10
11
|
addAction: '/media/list/?upload=true',
|
|
11
12
|
addIcon: 'upload',
|
|
12
13
|
addPermission: 'media.postCreate',
|
|
14
|
+
addDropdownComponent: MediaUploadDropdown,
|
|
13
15
|
listAction: '/media/list',
|
|
14
16
|
listPermission: 'media.getList',
|
|
15
17
|
items: {
|
|
@@ -8,6 +8,8 @@ import PartialsApi from "../PartialsApi.js";
|
|
|
8
8
|
import PartialTable from "../components/PartialTable.vue";
|
|
9
9
|
import PageActions from "../components/PageActions.vue";
|
|
10
10
|
import PartialActions from "../components/PartialActions.vue";
|
|
11
|
+
import CreatePageDropdown from "../components/pages/CreatePageDropdown.vue";
|
|
12
|
+
import CreatePartialDropdown from "../components/partials/CreatePartialDropdown.vue";
|
|
11
13
|
|
|
12
14
|
export default function(ui) {
|
|
13
15
|
ui.addMainMenuGroup(WEB_CONTENT, {
|
|
@@ -18,6 +20,7 @@ export default function(ui) {
|
|
|
18
20
|
addIcon: 'plus',
|
|
19
21
|
addPermission: 'pages.postCreate',
|
|
20
22
|
addAction: '/pages/create',
|
|
23
|
+
addDropdownComponent: CreatePageDropdown,
|
|
21
24
|
items: {
|
|
22
25
|
}
|
|
23
26
|
});
|
|
@@ -29,25 +32,15 @@ export default function(ui) {
|
|
|
29
32
|
addIcon: 'plus',
|
|
30
33
|
addPermission: 'partials.postCreate',
|
|
31
34
|
addAction: '/partials/create',
|
|
35
|
+
addDropdownComponent: CreatePartialDropdown,
|
|
32
36
|
items: {
|
|
33
37
|
}
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
const partialsProps = { displayName: 'Partials', routePrefix: 'partials', inTrash: false, tableComponent: PartialTable, actionsComponent: PartialActions, singularDisplayName: 'Partial', defaultSortField: 'title', defaultSortOrder: 'asc', resourceApi: new PartialsApi() }
|
|
37
|
-
const pagesProps = { displayName: 'Pages', routePrefix: 'pages', inTrash: false, tableComponent: PageTable, actionsComponent: PageActions, singularDisplayName: 'Page', defaultSortField: 'title', defaultSortOrder: 'asc', resourceApi: new PagesApi() }
|
|
40
|
+
const partialsProps = { displayName: 'Partials', routePrefix: 'partials', inTrash: false, tableComponent: PartialTable, actionsComponent: PartialActions, singularDisplayName: 'Partial', defaultSortField: 'title', defaultSortOrder: 'asc', resourceApi: new PartialsApi(), createDropdownComponent: CreatePartialDropdown }
|
|
41
|
+
const pagesProps = { displayName: 'Pages', routePrefix: 'pages', inTrash: false, tableComponent: PageTable, actionsComponent: PageActions, singularDisplayName: 'Page', defaultSortField: 'title', defaultSortOrder: 'asc', resourceApi: new PagesApi(), createDropdownComponent: CreatePageDropdown }
|
|
38
42
|
|
|
39
43
|
ui.addAuthenticatedRoutes([
|
|
40
|
-
{
|
|
41
|
-
path: '(pages|partials)/create',
|
|
42
|
-
component: LegacyPage,
|
|
43
|
-
props: (route) => {
|
|
44
|
-
return {
|
|
45
|
-
fullPath: route.fullPath,
|
|
46
|
-
legacyPrefix: '/oxygen/view',
|
|
47
|
-
adminPrefix: '/oxygen'
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
44
|
{
|
|
52
45
|
path: '(pages|partials)/:subpath/edit',
|
|
53
46
|
component: LegacyPage,
|
package/src/util.js
CHANGED
|
@@ -62,4 +62,24 @@ const tryParseTelephone = (telephone) => {
|
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
const slugify = (str) => {
|
|
66
|
+
if(!str) return '';
|
|
67
|
+
return str
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.trim()
|
|
70
|
+
.replace(/[^\w\s-]/g, '') // Remove non-word chars except spaces and hyphens
|
|
71
|
+
.replace(/[\s_-]+/g, '-') // Replace spaces, underscores, multiple hyphens with single hyphen
|
|
72
|
+
.replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const slugifyKey = (str) => {
|
|
76
|
+
if(!str) return '';
|
|
77
|
+
return str
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.trim()
|
|
80
|
+
.replace(/[^\w\s.-]/g, '') // Remove non-word chars except spaces, dots, and hyphens
|
|
81
|
+
.replace(/[\s_-]+/g, '.') // Replace spaces, underscores, hyphens with dots
|
|
82
|
+
.replace(/^\.+|\.+$/g, ''); // Remove leading/trailing dots
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export { strEquals, convertStr, nestedGet, nestedSet, tryParseTelephone, slugify, slugifyKey };
|
package/.idea/workspace.xml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ChangeListManager">
|
|
4
|
-
<list default="true" id="4ad0dcde-54f6-459b-8ef0-38a17e946358" name="Changes" comment="" />
|
|
5
|
-
<option name="SHOW_DIALOG" value="false" />
|
|
6
|
-
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
7
|
-
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
8
|
-
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
9
|
-
</component>
|
|
10
|
-
<component name="ComposerSettings">
|
|
11
|
-
<execution />
|
|
12
|
-
</component>
|
|
13
|
-
<component name="ProjectId" id="27rS1jBVazc5EdthHlwaXfJm1hC" />
|
|
14
|
-
<component name="ProjectViewState">
|
|
15
|
-
<option name="hideEmptyMiddlePackages" value="true" />
|
|
16
|
-
<option name="showLibraryContents" value="true" />
|
|
17
|
-
</component>
|
|
18
|
-
<component name="PropertiesComponent"><![CDATA[{
|
|
19
|
-
"keyToString": {
|
|
20
|
-
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
|
21
|
-
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
22
|
-
"WebServerToolWindowFactoryState": "false",
|
|
23
|
-
"last_opened_file_path": "/home/chris/code/oxygen/Components/ui"
|
|
24
|
-
}
|
|
25
|
-
}]]></component>
|
|
26
|
-
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
|
27
|
-
<component name="TaskManager">
|
|
28
|
-
<task active="true" id="Default" summary="Default task">
|
|
29
|
-
<changelist id="4ad0dcde-54f6-459b-8ef0-38a17e946358" name="Changes" comment="" />
|
|
30
|
-
<created>1650076498224</created>
|
|
31
|
-
<option name="number" value="Default" />
|
|
32
|
-
<option name="presentableId" value="Default" />
|
|
33
|
-
<updated>1650076498224</updated>
|
|
34
|
-
</task>
|
|
35
|
-
<servers />
|
|
36
|
-
</component>
|
|
37
|
-
</project>
|