@live-change/content-frontend 0.2.15 → 0.2.17
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/front/src/components/{PageAdminButtons.vue → ContentAdminButtons.vue} +14 -2
- package/front/src/components/ContentEditor.vue +134 -0
- package/front/src/components/ContentPreview.vue +71 -0
- package/front/src/components/ContentSettings.vue +80 -0
- package/front/src/components/Metadata.vue +70 -72
- package/front/src/components/NotAuthorizedAdminButtons.vue +44 -0
- package/front/src/components/NotFoundAdminButtons.vue +41 -8
- package/front/src/components/Page.vue +4 -46
- package/front/src/components/PageEditor.vue +13 -95
- package/front/src/components/PagePreview.vue +2 -32
- package/front/src/components/UrlContent.vue +81 -0
- package/front/src/router.js +2 -2
- package/index.js +14 -7
- package/package.json +9 -8
- package/server/init.js +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
|
|
3
|
-
<router-link :to="
|
|
3
|
+
<router-link :to="editorRoute(objectType, object)" class="no-underline">
|
|
4
4
|
<Button icon="pi pi-pencil"
|
|
5
5
|
class="p-button p-button-icon-only p-button-rounded p-button-warning mr-2" />
|
|
6
6
|
</router-link>
|
|
@@ -14,13 +14,25 @@
|
|
|
14
14
|
import Button from 'primevue/button'
|
|
15
15
|
|
|
16
16
|
const props = defineProps({
|
|
17
|
-
|
|
17
|
+
objectType: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
object: {
|
|
18
22
|
type: String,
|
|
19
23
|
required: true
|
|
20
24
|
},
|
|
21
25
|
name: {
|
|
22
26
|
type: String,
|
|
23
27
|
default: ""
|
|
28
|
+
},
|
|
29
|
+
editorRoute: {
|
|
30
|
+
type: Function,
|
|
31
|
+
default: (objectType, object) => {
|
|
32
|
+
const [service, type] = objectType.split('_')
|
|
33
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
34
|
+
return { name: `${service}:${prop}Editor`, params: { [prop+'Id']: object }}
|
|
35
|
+
}
|
|
24
36
|
}
|
|
25
37
|
})
|
|
26
38
|
|
|
@@ -1,9 +1,143 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
|
+
<DocumentEditor :targetType="objectType" :target="object"
|
|
4
|
+
:purpose="purpose" :type="contentType"
|
|
5
|
+
:config="contentConfig || defaultContentConfig"
|
|
6
|
+
:class="className" :style="style"
|
|
7
|
+
v-model:saveState="saveState" v-model:version="version">
|
|
8
|
+
|
|
9
|
+
<template #menuEnd="{}">
|
|
10
|
+
|
|
11
|
+
<router-link :to="previewRoute(objectType, object)" target="_blank"
|
|
12
|
+
class="no-underline">
|
|
13
|
+
<Button icon="pi pi-eye" label="Preview" class="p-button-secondary p-button-sm mr-1 mb-1" />
|
|
14
|
+
</router-link>
|
|
15
|
+
|
|
16
|
+
<div class="p-buttonset mr-1 mb-1 border-round">
|
|
17
|
+
<Button type="button"
|
|
18
|
+
class="p-button p-component p-button-sm p-button-outlined p-button-secondary cursor-auto inline-block"
|
|
19
|
+
:class="{ 'p-disabled': saveState == 'saving' }">
|
|
20
|
+
<span class="pi p-button-icon p-button-icon-left"
|
|
21
|
+
:class="[saveState == 'saving' ? 'pi-sync' : 'pi-hashtag' ]" />
|
|
22
|
+
<span class="p-button-label">{{ ( version ?? 0 ).toFixed().padStart(10, '0') }}</span>
|
|
23
|
+
</Button>
|
|
24
|
+
<Button icon="pi pi-save" label="Publish" class="p-button-success p-button-sm" type="button"
|
|
25
|
+
:disabled="saveState == 'saving'" @click="publish" />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
</DocumentEditor>
|
|
31
|
+
|
|
32
|
+
<div></div><!-- Because bug in vue -->
|
|
33
|
+
|
|
3
34
|
</template>
|
|
4
35
|
|
|
5
36
|
<script setup>
|
|
6
37
|
|
|
38
|
+
import Button from 'primevue/button'
|
|
39
|
+
|
|
40
|
+
import { DocumentEditor, EditorMenu } from "@live-change/wysiwyg-frontend"
|
|
41
|
+
|
|
42
|
+
import defaultContentConfig from "./contentConfig.js"
|
|
43
|
+
|
|
44
|
+
const props = defineProps({
|
|
45
|
+
objectType: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
object: {
|
|
50
|
+
type: String,
|
|
51
|
+
required: true
|
|
52
|
+
},
|
|
53
|
+
previewRoute: {
|
|
54
|
+
type: Function,
|
|
55
|
+
default: (objectType, object) => {
|
|
56
|
+
const [service, type] = objectType.split('_')
|
|
57
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
58
|
+
return { name: `${service}:${prop}Preview`, params: { [prop]: object }}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
publishTarget: {
|
|
62
|
+
type: String,
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
contentType: {
|
|
66
|
+
type: String,
|
|
67
|
+
required: true
|
|
68
|
+
},
|
|
69
|
+
purpose: {
|
|
70
|
+
type: String,
|
|
71
|
+
required: true
|
|
72
|
+
},
|
|
73
|
+
contentConfig: {
|
|
74
|
+
type: Object,
|
|
75
|
+
default: () => null
|
|
76
|
+
},
|
|
77
|
+
class: {},
|
|
78
|
+
style: {}
|
|
79
|
+
})
|
|
80
|
+
import { toRefs } from "@vueuse/core"
|
|
81
|
+
const {
|
|
82
|
+
objectType, object, previewRoute, publishTarget, contentType, purpose, contentConfig,
|
|
83
|
+
class: className, style
|
|
84
|
+
} = toRefs(props)
|
|
85
|
+
|
|
86
|
+
import { computed, watch, ref, onMounted, inject } from 'vue'
|
|
87
|
+
|
|
88
|
+
const document = computed(() => `${JSON.stringify(objectType.value)}:${JSON.stringify(object.value)}`)
|
|
89
|
+
|
|
90
|
+
const saveState = ref()
|
|
91
|
+
const version = ref()
|
|
92
|
+
|
|
93
|
+
import { useToast } from 'primevue/usetoast'
|
|
94
|
+
const toast = useToast()
|
|
95
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
96
|
+
const confirm = useConfirm()
|
|
97
|
+
|
|
98
|
+
const workingZone = inject('workingZone')
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
import { useApi, path, live } from '@live-change/vue3-ssr'
|
|
102
|
+
|
|
103
|
+
const api = useApi()
|
|
104
|
+
|
|
105
|
+
const p = path()
|
|
106
|
+
|
|
107
|
+
const liveCanonicalUrlPath = computed(
|
|
108
|
+
() => p.url.targetOwnedCanonical({ targetType: objectType.value, target: object.value })
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const [canonicalUrlData] = await Promise.all([
|
|
112
|
+
live(liveCanonicalUrlPath),
|
|
113
|
+
]).catch(e => [null, null, null, null])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
function publish() {
|
|
117
|
+
const snapshotVersion = version.value
|
|
118
|
+
const typeName = objectType.value.split('_')[1]
|
|
119
|
+
const typeNameLower = typeName[0].toLowerCase()+typeName.slice(1)
|
|
120
|
+
confirm.require({
|
|
121
|
+
target: event.currentTarget,
|
|
122
|
+
message: `Do you want to publish this content version ${snapshotVersion} to ${publishTarget.value}`,
|
|
123
|
+
icon: 'pi pi-info-circle',
|
|
124
|
+
acceptClass: 'p-button-danger',
|
|
125
|
+
accept: async () => {
|
|
126
|
+
api.actions.content.publish({
|
|
127
|
+
objectType: objectType.value, object: object.value, version: snapshotVersion, type: contentType.value
|
|
128
|
+
}).then(() => {
|
|
129
|
+
toast.add({ severity: 'success', summary: 'Published', detail: typeName+' published', life: 3000 })
|
|
130
|
+
}).catch(e => {
|
|
131
|
+
toast.add({ severity: 'error', summary: 'Error', detail: 'Error publishing ' + typeNameLower, life: 3000 })
|
|
132
|
+
})
|
|
133
|
+
},
|
|
134
|
+
reject: () => {
|
|
135
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
7
141
|
</script>
|
|
8
142
|
|
|
9
143
|
<style scoped>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<LimitedAccess :requiredRoles="['writer']" :objectType="objectType" :object="object" hidden>
|
|
4
|
+
<div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
|
|
5
|
+
<Badge severity="warning" value="This is page preview" class="mr-2" />
|
|
6
|
+
<router-link :to="editorRoute(objectType, object)" class="no-underline">
|
|
7
|
+
<Button icon="pi pi-pencil"
|
|
8
|
+
class="p-button p-button-icon-only p-button-rounded p-button-warning mr-2" />
|
|
9
|
+
</router-link>
|
|
10
|
+
</div>
|
|
11
|
+
<Content :objectType="objectType" :object="object" :style="style" :class="clazz" preview />
|
|
12
|
+
</LimitedAccess>
|
|
13
|
+
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup>
|
|
17
|
+
import Button from "primevue/button"
|
|
18
|
+
import Badge from "primevue/badge"
|
|
19
|
+
|
|
20
|
+
import { ResolveUrl, NotFound } from "@live-change/url-frontend"
|
|
21
|
+
import { LimitedAccess } from "@live-change/access-control-frontend";
|
|
22
|
+
import Content from "./Content.vue"
|
|
23
|
+
|
|
24
|
+
import { computed, watch, ref, onMounted } from 'vue'
|
|
25
|
+
import { toRefs } from "@vueuse/core"
|
|
26
|
+
import { useHost } from "@live-change/frontend-base"
|
|
27
|
+
|
|
28
|
+
const isMounted = ref(false)
|
|
29
|
+
onMounted(() => isMounted.value = true)
|
|
30
|
+
|
|
31
|
+
import { path, live, useApi } from '@live-change/vue3-ssr'
|
|
32
|
+
const api = useApi()
|
|
33
|
+
const p = path()
|
|
34
|
+
|
|
35
|
+
const urlMore = [
|
|
36
|
+
url => p.content.page({ page: url.target })
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
objectType: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: true
|
|
43
|
+
},
|
|
44
|
+
object: {
|
|
45
|
+
type: String,
|
|
46
|
+
required: true
|
|
47
|
+
},
|
|
48
|
+
class: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: ''
|
|
51
|
+
},
|
|
52
|
+
style: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: ''
|
|
55
|
+
},
|
|
56
|
+
editorRoute: {
|
|
57
|
+
type: Function,
|
|
58
|
+
default: (objectType, object) => {
|
|
59
|
+
const [service, type] = objectType.split('_')
|
|
60
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
61
|
+
return { name: `${service}:${prop}Editor`, params: { [prop+'Id']: object }}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
const { objectType, object, class: clazz, style } = toRefs(props)
|
|
66
|
+
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<style scoped>
|
|
70
|
+
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Accordion :multiple="true" class="w-full mb-2">
|
|
3
|
+
<AccordionTab>
|
|
4
|
+
<template #header>
|
|
5
|
+
<UrlsInfo :targetType="objectType" :target="object" class="w-full" />
|
|
6
|
+
</template>
|
|
7
|
+
<Urls :key="key" :targetType="objectType" :target="object" />
|
|
8
|
+
</AccordionTab>
|
|
9
|
+
<AccordionTab>
|
|
10
|
+
<template #header>
|
|
11
|
+
<span class="font-bold mr-1">Public access: {{ publicAccessLevel }}</span>
|
|
12
|
+
</template>
|
|
13
|
+
<AccessControl :objectType="objectType" :object="object" />
|
|
14
|
+
</AccordionTab>
|
|
15
|
+
<AccordionTab>
|
|
16
|
+
<template #header>
|
|
17
|
+
<span v-if="metadata" class="font-bold mr-1">Metadata:</span>
|
|
18
|
+
<span v-if="metadata" class="mr-1 font-normal">{{ metadata.title }}</span>
|
|
19
|
+
<span v-else class="font-bold text-red-600">Metadata not set</span>
|
|
20
|
+
</template>
|
|
21
|
+
<MetadataEditor :objectType="objectType" :object="object" :key="key"></MetadataEditor>
|
|
22
|
+
</AccordionTab>
|
|
23
|
+
</Accordion>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
|
|
28
|
+
import Accordion from 'primevue/accordion'
|
|
29
|
+
import AccordionTab from 'primevue/accordiontab'
|
|
30
|
+
import Button from 'primevue/button'
|
|
31
|
+
|
|
32
|
+
import { UrlsInfo, Urls, NotFound } from '@live-change/url-frontend'
|
|
33
|
+
import { AccessControl } from '@live-change/access-control-frontend'
|
|
34
|
+
import MetadataEditor from "./MetadataEditor.vue"
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
objectType: {
|
|
38
|
+
type: String,
|
|
39
|
+
required: true
|
|
40
|
+
},
|
|
41
|
+
object: {
|
|
42
|
+
type: String,
|
|
43
|
+
required: true
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
import { toRefs } from "@vueuse/core"
|
|
48
|
+
import { computed } from 'vue'
|
|
49
|
+
|
|
50
|
+
const { objectType, object } = toRefs(props)
|
|
51
|
+
|
|
52
|
+
const key = computed(() => `${objectType.value}:${object.value}`)
|
|
53
|
+
|
|
54
|
+
import { useApi, path, live } from '@live-change/vue3-ssr'
|
|
55
|
+
const api = useApi()
|
|
56
|
+
const p = path()
|
|
57
|
+
|
|
58
|
+
const livePublicAccessPath = computed(
|
|
59
|
+
() => p.accessControl.objectOwnedPublicAccess({ objectType: objectType.value, object: object.value })
|
|
60
|
+
)
|
|
61
|
+
const liveMetadataPath = computed(
|
|
62
|
+
() => p.content.objectOwnedMetadata({ objectType: objectType.value, object: object.value })
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const [publicAccessData, metadata] = await Promise.all([
|
|
66
|
+
live(livePublicAccessPath),
|
|
67
|
+
live(liveMetadataPath)
|
|
68
|
+
]).catch(e => [null, null, null, null])
|
|
69
|
+
|
|
70
|
+
const publicAccessLevel = computed(() => {
|
|
71
|
+
if(publicAccessData?.sessionRoles?.includes('reader')) return 'session'
|
|
72
|
+
if(publicAccessData?.userRoles?.includes('reader')) return 'user'
|
|
73
|
+
return 'none'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
|
|
80
|
+
</style>
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
object: {
|
|
12
12
|
type: String,
|
|
13
13
|
required: true
|
|
14
|
-
},
|
|
15
|
-
url: {
|
|
16
|
-
type: Object,
|
|
17
|
-
required: true
|
|
18
14
|
}
|
|
19
15
|
})
|
|
20
16
|
|
|
@@ -58,75 +54,77 @@
|
|
|
58
54
|
const canonicalUrl = `https://${canonicalUrlDomain}/${canonical.value?.path ?? ''}`
|
|
59
55
|
|
|
60
56
|
let ogImage = []
|
|
61
|
-
if(m
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
if(m) {
|
|
58
|
+
if (m.ogImage) {
|
|
59
|
+
const image = m.ogImage
|
|
60
|
+
ogImage = [
|
|
61
|
+
{property: 'og:image', content: `https://${canonicalUrlDomain}/api/image/image/${image.id}`},
|
|
62
|
+
{property: 'og:image:width', content: image.width},
|
|
63
|
+
{property: 'og:image:height', content: image.height},
|
|
64
|
+
{property: 'og:image:type', content: 'image/' + image.extension}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
68
|
+
useMeta({
|
|
69
|
+
title: m.title,
|
|
70
|
+
description: m.description,
|
|
71
|
+
link: [
|
|
72
|
+
{rel: 'canonical', href: canonicalUrl}
|
|
73
|
+
],
|
|
74
|
+
meta: [
|
|
75
|
+
metaProperty('og:title', m.og.title),
|
|
76
|
+
metaProperty('og:description', m.og.description),
|
|
77
|
+
...ogImage,
|
|
78
|
+
metaProperty('og:determiner', m.og.determiner),
|
|
79
|
+
metaProperty('og:locale', m.og.locale),
|
|
80
|
+
...metaProperties('og:locale:alternate', m.og.localeAlternate),
|
|
81
|
+
metaProperty('og:type', m.og.type),
|
|
82
|
+
|
|
83
|
+
metaProperty('og:music:duration', m.og.music.duration),
|
|
84
|
+
...metaPropertiesObjects('og:music:album', m.og.music.album),
|
|
85
|
+
...metaPropertiesObjects('og:music:song', m.og.music.song),
|
|
86
|
+
...metaProperties('og:music:musician', m.og.music.duration),
|
|
87
|
+
metaProperty('og:music:release_date', m.og.music.releaseDate),
|
|
88
|
+
...metaProperties('og:music:creator', m.og.music.creator),
|
|
89
|
+
|
|
90
|
+
metaProperty('og:video:duration', m.og.video.duration),
|
|
91
|
+
metaProperty('og:viceo:release_date', m.og.video.releaseDate),
|
|
92
|
+
...metaPropertiesObjects('og:video:duration', m.og.video.actors),
|
|
93
|
+
...metaProperties('og:video:director', m.og.video.director),
|
|
94
|
+
...metaProperties('og:video:writer', m.og.video.writer),
|
|
95
|
+
...metaProperties('og:video:series', m.og.video.series),
|
|
96
|
+
...metaProperties('og:video:tag', m.og.video.tag),
|
|
97
|
+
|
|
98
|
+
...metaProperties('og:profile:first_name', m.og.profile.firstName),
|
|
99
|
+
...metaProperties('og:profile:last_name', m.og.profile.lastName),
|
|
100
|
+
...metaProperties('og:profile:username', m.og.profile.username),
|
|
101
|
+
...metaProperties('og:profile:gender', m.og.profile.gender),
|
|
102
|
+
|
|
103
|
+
metaProperty('og:article:published_time', m.og.article.publishedTime),
|
|
104
|
+
metaProperty('og:article:modified_time', m.og.article.modifiedTime),
|
|
105
|
+
metaProperty('og:article:expiration_time', m.og.article.expirationTime),
|
|
106
|
+
...metaProperties('og:article:author', m.og.article.author),
|
|
107
|
+
metaProperty('og:article:section', m.og.article.section),
|
|
108
|
+
...metaProperties('og:article:tag', m.og.article.tag),
|
|
109
|
+
|
|
110
|
+
...metaProperties('og:book:author', m.og.book.author),
|
|
111
|
+
metaProperty('og:book:isbn', m.og.book.isbn),
|
|
112
|
+
metaProperty('og:book:release_date', m.og.book.releaseDate),
|
|
113
|
+
...metaProperties('og:book:tag', m.og.book.tag),
|
|
114
|
+
|
|
115
|
+
{property: 'og:url', content: canonicalUrl},
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
/* { property: 'og:url', content: m.url },
|
|
119
|
+
{ property: 'og:type', content: 'website' },
|
|
120
|
+
{ property: 'twitter:card', content: 'summary_large_image' },
|
|
121
|
+
{ property: 'twitter:title', content: metadata.value.title },
|
|
122
|
+
{ property: 'twitter:description', content: metadata.value.description },
|
|
123
|
+
{ property: 'twitter:image', content: metadata.value.image },
|
|
124
|
+
{ property: 'twitter:url', content: metadata.value.url }*/
|
|
125
|
+
].filter(x => !!x)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
130
128
|
</script>
|
|
131
129
|
|
|
132
130
|
<style scoped>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
|
|
3
|
+
<router-link :to="editorRoute(objectType, object)" class="no-underline">
|
|
4
|
+
<Button icon="pi pi-pencil"
|
|
5
|
+
class="p-button p-button-icon-only p-button-rounded p-button-warning mr-2" />
|
|
6
|
+
</router-link>
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup>
|
|
11
|
+
import Button from 'primevue/button'
|
|
12
|
+
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
path: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true
|
|
17
|
+
},
|
|
18
|
+
objectType: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: true
|
|
21
|
+
},
|
|
22
|
+
object: {
|
|
23
|
+
type: String,
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
editorRoute: {
|
|
27
|
+
type: Function,
|
|
28
|
+
default: (objectType, object) => {
|
|
29
|
+
const [service, type] = objectType.split('_')
|
|
30
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
31
|
+
return { name: `${service}:${prop}Editor`, params: { [prop+'Id']: object }}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
import { toRefs } from "@vueuse/core"
|
|
37
|
+
|
|
38
|
+
const { objectType, object, editorRoute } = toRefs(props)
|
|
39
|
+
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
|
|
44
|
+
</style>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
|
|
3
3
|
<Button icon="pi pi-plus"
|
|
4
4
|
class="p-button p-button-icon-only p-button-rounded p-button-danger"
|
|
5
|
-
@click="
|
|
5
|
+
@click="createContent" />
|
|
6
6
|
</div>
|
|
7
7
|
</template>
|
|
8
8
|
|
|
@@ -13,15 +13,36 @@
|
|
|
13
13
|
path: {
|
|
14
14
|
type: String,
|
|
15
15
|
required: true
|
|
16
|
+
},
|
|
17
|
+
objectType: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
editorRoute: {
|
|
22
|
+
type: Function,
|
|
23
|
+
default: (objectType, object) => {
|
|
24
|
+
const [service, type] = objectType.split('_')
|
|
25
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
26
|
+
return { name: `${service}:${prop}Editor`, params: { [prop+'Id']: object }}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
createActionName: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: null
|
|
32
|
+
},
|
|
33
|
+
createActionParams: {
|
|
34
|
+
type: Object,
|
|
35
|
+
default: () => {}
|
|
16
36
|
}
|
|
17
37
|
})
|
|
18
38
|
|
|
19
39
|
import { toRefs } from "@vueuse/core"
|
|
40
|
+
import { computed } from "vue"
|
|
20
41
|
|
|
21
42
|
import { useHost } from "@live-change/frontend-base"
|
|
22
43
|
const host = useHost()
|
|
23
44
|
|
|
24
|
-
const { path } = toRefs(props)
|
|
45
|
+
const { path, objectType } = toRefs(props)
|
|
25
46
|
|
|
26
47
|
import { useToast } from 'primevue/usetoast'
|
|
27
48
|
const toast = useToast()
|
|
@@ -31,20 +52,32 @@
|
|
|
31
52
|
import { useRouter } from 'vue-router'
|
|
32
53
|
const router = useRouter()
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
const serviceName = computed(() => {
|
|
56
|
+
const [service, type] = objectType.value.split('_')
|
|
57
|
+
return service
|
|
58
|
+
})
|
|
59
|
+
const typeName = computed(() => {
|
|
60
|
+
const [service, type] = objectType.value.split('_')
|
|
61
|
+
return type
|
|
62
|
+
})
|
|
63
|
+
const typeNameLower = computed(() => typeName.value[0].toLowerCase()+typeName.value.slice(1))
|
|
64
|
+
|
|
65
|
+
function createContent() {
|
|
35
66
|
confirm.require({
|
|
36
67
|
target: event.currentTarget,
|
|
37
|
-
message: `Do you want to create
|
|
68
|
+
message: `Do you want to create ${typeNameLower.value} at ${host}/${path.value} ?`,
|
|
38
69
|
icon: 'pi pi-info-circle',
|
|
39
70
|
acceptClass: 'p-button-danger',
|
|
40
71
|
accept: async () => {
|
|
41
|
-
const
|
|
72
|
+
const contentId = await api.actions[serviceName.value][props.createActionName || `create${typeName.value}`]({
|
|
73
|
+
...props.createActionParams
|
|
74
|
+
})
|
|
42
75
|
await api.actions.url.takeUrl({
|
|
43
|
-
target:
|
|
76
|
+
target: contentId, targetType: objectType.value, path: path.value, domain: host, redirect: false
|
|
44
77
|
})
|
|
45
|
-
toast.add({ severity:'success', summary:
|
|
78
|
+
toast.add({ severity:'success', summary: `${typeName} created`, life: 1500 })
|
|
46
79
|
setTimeout(() => {
|
|
47
|
-
router.push(
|
|
80
|
+
router.push(props.editorRoute(objectType.value, contentId))
|
|
48
81
|
}, 200)
|
|
49
82
|
},
|
|
50
83
|
reject: () => {
|
|
@@ -1,61 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<template #default="{ target, style, class: clazz }">
|
|
4
|
-
<Metadata objectType="content_Page" :object="target" />
|
|
5
|
-
<LimitedAccess :requiredRoles="['writer']" objectType="content_Page" :object="target" hidden>
|
|
6
|
-
<PageAdminButtons :page="target" :style="style" :class="clazz" :name="urlPath.value" />
|
|
7
|
-
</LimitedAccess>
|
|
8
|
-
<Content objectType="content_Page" :object="target" />
|
|
9
|
-
</template>
|
|
10
|
-
<template #notFound="{ path, style, class: clazz }">
|
|
11
|
-
<NotFoundAdminButtons v-if="canCreatePage" :path="urlPath" :style="style" :class="clazz" />
|
|
12
|
-
<NotFound :style="style" :class="clazz" />
|
|
13
|
-
</template>
|
|
14
|
-
</ResolveUrl>
|
|
2
|
+
<UrlContent objectType="content_Page" :path="urlPath" :class="clazz" :style="style" />
|
|
15
3
|
</template>
|
|
16
4
|
|
|
17
5
|
<script setup>
|
|
18
|
-
import Button from "primevue/button"
|
|
19
|
-
import PageAdminButtons from "./PageAdminButtons.vue"
|
|
20
|
-
import NotFoundAdminButtons from "./NotFoundAdminButtons.vue"
|
|
21
6
|
|
|
22
|
-
import { ResolveUrl, NotFound } from "@live-change/url-frontend"
|
|
23
|
-
import { LimitedAccess } from "@live-change/access-control-frontend";
|
|
24
|
-
import Content from "./Content.vue"
|
|
25
|
-
import Metadata from "./Metadata.vue"
|
|
26
|
-
|
|
27
|
-
import { computed, watch, ref, onMounted } from 'vue'
|
|
28
7
|
import { toRefs } from "@vueuse/core"
|
|
29
|
-
|
|
30
|
-
const isMounted = ref(false)
|
|
31
|
-
onMounted(() => isMounted.value = true)
|
|
32
|
-
|
|
33
|
-
import { path, live, useApi } from '@live-change/vue3-ssr'
|
|
34
|
-
const api = useApi()
|
|
35
|
-
const p = path()
|
|
36
|
-
|
|
37
|
-
const urlMore = [
|
|
38
|
-
url => p.content.page({ page: url.target }),
|
|
39
|
-
url => p.content.content({ objectType: 'content_Page', object: url.target }),
|
|
40
|
-
url => p.content.objectOwnedMetadata({ objectType: 'content_Page', object: url.target }),
|
|
41
|
-
url => p.url.targetOwnedCanonical({ targetType: 'content_Page', target: url.target })
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
const canCreatePage = computed(() => api.client.value.roles.includes('writer'))
|
|
8
|
+
import UrlContent from "./UrlContent.vue";
|
|
45
9
|
|
|
46
10
|
const props = defineProps({
|
|
47
11
|
path: {
|
|
48
12
|
type: String,
|
|
49
13
|
required: true
|
|
50
14
|
},
|
|
51
|
-
class: {
|
|
52
|
-
|
|
53
|
-
default: ''
|
|
54
|
-
},
|
|
55
|
-
style: {
|
|
56
|
-
type: String,
|
|
57
|
-
default: ''
|
|
58
|
-
}
|
|
15
|
+
class: { },
|
|
16
|
+
style: { }
|
|
59
17
|
})
|
|
60
18
|
const { path: urlPath, class: clazz, style } = toRefs(props)
|
|
61
19
|
|
|
@@ -1,62 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<AccordionTab>
|
|
4
|
-
<template #header>
|
|
5
|
-
<UrlsInfo targetType="content_Page" :target="pageId" class="w-full" />
|
|
6
|
-
</template>
|
|
7
|
-
<Urls :key="pageId" targetType="content_Page" :target="pageId" />
|
|
8
|
-
</AccordionTab>
|
|
9
|
-
<AccordionTab>
|
|
10
|
-
<template #header>
|
|
11
|
-
<span class="font-bold mr-1">Public access: {{ publicAccessLevel }}</span>
|
|
12
|
-
</template>
|
|
13
|
-
<AccessControl objectType="content_Page" :object="pageId" />
|
|
14
|
-
</AccordionTab>
|
|
15
|
-
<AccordionTab>
|
|
16
|
-
<template #header>
|
|
17
|
-
<span v-if="metadata" class="font-bold mr-1">Metadata:</span>
|
|
18
|
-
<span v-if="metadata" class="mr-1 font-normal">{{ metadata.title }}</span>
|
|
19
|
-
<span v-else class="font-bold text-red-600">Metadata not set</span>
|
|
20
|
-
</template>
|
|
21
|
-
<MetadataEditor objectType="content_Page" :object="pageId" :key="pageId"></MetadataEditor>
|
|
22
|
-
</AccordionTab>
|
|
23
|
-
</Accordion>
|
|
24
|
-
|
|
25
|
-
<DocumentEditor v-if="pageData" targetType="content_Page" :target="pageId" purpose="page"
|
|
26
|
-
:config="contentConfig" type="page" v-model:saveState="saveState" v-model:version="version">
|
|
27
|
-
<template #menuEnd="{}">
|
|
28
|
-
|
|
29
|
-
<Button icon="pi pi-eye" label="Preview" class="p-button-secondary p-button-sm mr-1 mb-1" />
|
|
30
|
-
|
|
31
|
-
<div class="p-buttonset mr-1 mb-1 border-round">
|
|
32
|
-
<button type="button"
|
|
33
|
-
class="p-button p-component p-button-sm p-button-outlined p-button-secondary cursor-auto inline-block"
|
|
34
|
-
:class="{ 'p-disabled': saveState == 'saving' }">
|
|
35
|
-
<span class="pi p-button-icon p-button-icon-left"
|
|
36
|
-
:class="[saveState == 'saving' ? 'pi-sync' : 'pi-hashtag' ]" />
|
|
37
|
-
<span class="p-button-label">{{ ( version ?? 0 ).toFixed().padStart(10, '0') }}</span>
|
|
38
|
-
</button>
|
|
39
|
-
<Button icon="pi pi-save" label="Publish" class="p-button-success p-button-sm" type="button"
|
|
40
|
-
:disabled="saveState == 'saving'" @click="publish" />
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
</template>
|
|
44
|
-
</DocumentEditor>
|
|
2
|
+
<!-- <ContentSettings v-if="pageData" objectType="content_Page" :object="pageId" />-->
|
|
45
3
|
|
|
4
|
+
<ContentEditor v-if="pageData"
|
|
5
|
+
objectType="content_Page" :object="pageId"
|
|
6
|
+
:publishTarget="`page `+`${canonicalUrlData.domain ?? '*'}/${canonicalUrlData.path}`"
|
|
7
|
+
contentType="page" purpose="page" />
|
|
46
8
|
<NotFound v-if="pageData === null" />
|
|
9
|
+
|
|
47
10
|
</template>
|
|
48
11
|
|
|
49
12
|
<script setup>
|
|
13
|
+
import { NotFound } from "@live-change/url-frontend";
|
|
50
14
|
|
|
51
|
-
import
|
|
52
|
-
import
|
|
53
|
-
import Button from 'primevue/button'
|
|
54
|
-
|
|
55
|
-
import { UrlsInfo, Urls, NotFound } from '@live-change/url-frontend'
|
|
56
|
-
import { DocumentEditor, EditorMenu } from "@live-change/wysiwyg-frontend"
|
|
57
|
-
import { AccessControl } from '@live-change/access-control-frontend'
|
|
58
|
-
import MetadataEditor from "./MetadataEditor.vue"
|
|
59
|
-
import contentConfig from "./contentConfig.js";
|
|
15
|
+
import ContentSettings from "./ContentSettings.vue"
|
|
16
|
+
import ContentEditor from "./ContentEditor.vue"
|
|
60
17
|
|
|
61
18
|
import { computed, watch, ref, onMounted, inject } from 'vue'
|
|
62
19
|
import { toRefs } from "@vueuse/core"
|
|
@@ -74,22 +31,8 @@
|
|
|
74
31
|
})
|
|
75
32
|
const { pageId, class: clazz, style } = toRefs(props)
|
|
76
33
|
|
|
77
|
-
const document = computed(() => `${JSON.stringify('content_Page')}:${JSON.stringify(pageId.value)}`)
|
|
78
|
-
|
|
79
|
-
const saveState = ref()
|
|
80
|
-
const version = ref()
|
|
81
|
-
|
|
82
|
-
import { useToast } from 'primevue/usetoast'
|
|
83
|
-
const toast = useToast()
|
|
84
|
-
import { useConfirm } from 'primevue/useconfirm'
|
|
85
|
-
const confirm = useConfirm()
|
|
86
|
-
|
|
87
|
-
const workingZone = inject('workingZone')
|
|
88
|
-
|
|
89
34
|
import { useApi, path, live } from '@live-change/vue3-ssr'
|
|
90
|
-
|
|
91
35
|
const api = useApi()
|
|
92
|
-
|
|
93
36
|
const p = path()
|
|
94
37
|
|
|
95
38
|
const livePagePath = computed(
|
|
@@ -112,36 +55,11 @@
|
|
|
112
55
|
live(liveMetadataPath)
|
|
113
56
|
]).catch(e => [null, null, null, null])
|
|
114
57
|
|
|
115
|
-
const publicAccessLevel = computed(() => {
|
|
116
|
-
if(publicAccessData?.sessionRoles?.includes('reader')) return 'session'
|
|
117
|
-
if(publicAccessData?.userRoles?.includes('reader')) return 'user'
|
|
118
|
-
return 'none'
|
|
119
|
-
})
|
|
120
58
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
target: event.currentTarget,
|
|
126
|
-
message: `Do you want to publish this content version ${snapshotVersion} to page `+
|
|
127
|
-
`${canonicalUrlData.value.domain ?? '*'}/${canonicalUrlData.value.path}?`,
|
|
128
|
-
icon: 'pi pi-info-circle',
|
|
129
|
-
acceptClass: 'p-button-danger',
|
|
130
|
-
accept: async () => {
|
|
131
|
-
api.actions.content.publish({
|
|
132
|
-
objectType: 'content_Page', object: pageId.value, version: snapshotVersion, type: 'content'
|
|
133
|
-
}).then(() => {
|
|
134
|
-
toast.add({ severity: 'success', summary: 'Published', detail: 'Page published', life: 3000 })
|
|
135
|
-
}).catch(e => {
|
|
136
|
-
toast.add({ severity: 'error', summary: 'Error', detail: 'Error publishing page', life: 3000 })
|
|
137
|
-
})
|
|
138
|
-
},
|
|
139
|
-
reject: () => {
|
|
140
|
-
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
}
|
|
59
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
|
60
|
+
|
|
61
|
+
console.log("WAITING!")
|
|
62
|
+
await sleep(5000)
|
|
145
63
|
|
|
146
64
|
</script>
|
|
147
65
|
|
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
<LimitedAccess :requiredRoles="['writer']" objectType="content_Page" :object="pageId" hidden>
|
|
4
|
-
<div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
|
|
5
|
-
<Badge severity="warning" value="This is page preview" class="mr-2" />
|
|
6
|
-
<router-link :to="{ name: 'content:pageEditor', params: { pageId } }" class="no-underline">
|
|
7
|
-
<Button icon="pi pi-pencil"
|
|
8
|
-
class="p-button p-button-icon-only p-button-rounded p-button-warning mr-2" />
|
|
9
|
-
</router-link>
|
|
10
|
-
</div>
|
|
11
|
-
<Content objectType="content_Page" :object="pageId" :style="style" :class="clazz" preview />
|
|
12
|
-
</LimitedAccess>
|
|
13
|
-
|
|
2
|
+
<ContentPreview objectType="content_Page" :object="pageId" :style="style" :class="clazz" />
|
|
14
3
|
</template>
|
|
15
4
|
|
|
16
5
|
<script setup>
|
|
17
|
-
import Button from "primevue/button"
|
|
18
|
-
import Badge from "primevue/badge"
|
|
19
6
|
|
|
20
|
-
import
|
|
21
|
-
import { LimitedAccess } from "@live-change/access-control-frontend";
|
|
22
|
-
import Content from "./Content.vue"
|
|
23
|
-
|
|
24
|
-
import { computed, watch, ref, onMounted } from 'vue'
|
|
7
|
+
import ContentPreview from "./ContentPreview.vue";
|
|
25
8
|
import { toRefs } from "@vueuse/core"
|
|
26
|
-
import { useHost } from "@live-change/frontend-base"
|
|
27
|
-
|
|
28
|
-
const isMounted = ref(false)
|
|
29
|
-
onMounted(() => isMounted.value = true)
|
|
30
|
-
|
|
31
|
-
import { path, live, useApi } from '@live-change/vue3-ssr'
|
|
32
|
-
const api = useApi()
|
|
33
|
-
const p = path()
|
|
34
|
-
|
|
35
|
-
const urlMore = [
|
|
36
|
-
url => p.content.page({ page: url.target })
|
|
37
|
-
]
|
|
38
9
|
|
|
39
10
|
const props = defineProps({
|
|
40
11
|
pageId: {
|
|
@@ -51,7 +22,6 @@
|
|
|
51
22
|
}
|
|
52
23
|
})
|
|
53
24
|
const { pageId, class: clazz, style } = toRefs(props)
|
|
54
|
-
|
|
55
25
|
</script>
|
|
56
26
|
|
|
57
27
|
<style scoped>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ResolveUrl :targetType="objectType" :path="urlPath" :fetchMore="urlMore">
|
|
3
|
+
<template #default="{ target, style, class: clazz }">
|
|
4
|
+
<Metadata :objectType="objectType" :object="target" />
|
|
5
|
+
<LimitedAccess :requiredRoles="['writer']" :objectType="objectType" :object="target" hidden>
|
|
6
|
+
<ContentAdminButtons :objectType="objectType" :object="target" :style="style" :class="clazz"
|
|
7
|
+
:name="urlPath.value" :editorRoute="editorRoute" />
|
|
8
|
+
</LimitedAccess>
|
|
9
|
+
<Content :objectType="objectType" :object="target" class="w-full" :style="style" :class="clazz" />
|
|
10
|
+
</template>
|
|
11
|
+
<template #notFound="{ path, style, class: clazz }">
|
|
12
|
+
<NotFoundAdminButtons v-if="canCreateContent" :path="urlPath"
|
|
13
|
+
:objectType="objectType" :editorRoute="editorRoute"
|
|
14
|
+
:style="style" :class="clazz" />
|
|
15
|
+
<NotFound :style="style" :class="clazz" />
|
|
16
|
+
</template>
|
|
17
|
+
<template #notAuthorized="{ path, style, class: clazz, target, access }">
|
|
18
|
+
<NotAuthorizedAdminButtons v-if="(access?.roles ?? []).includes('writer')"
|
|
19
|
+
:path="urlPath" :style="style" :class="clazz" :object="target"
|
|
20
|
+
:objectType="objectType" :editorRoute="editorRoute"/>
|
|
21
|
+
<NotAuthorized :style="style" :class="clazz" />
|
|
22
|
+
</template>
|
|
23
|
+
</ResolveUrl>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup>
|
|
27
|
+
import Button from "primevue/button"
|
|
28
|
+
import ContentAdminButtons from "./ContentAdminButtons.vue"
|
|
29
|
+
import NotFoundAdminButtons from "./NotFoundAdminButtons.vue"
|
|
30
|
+
import NotAuthorizedAdminButtons from "./NotAuthorizedAdminButtons.vue"
|
|
31
|
+
|
|
32
|
+
import { ResolveUrl, NotFound, NotAuthorized } from "@live-change/url-frontend"
|
|
33
|
+
import { LimitedAccess } from "@live-change/access-control-frontend";
|
|
34
|
+
import Content from "./Content.vue"
|
|
35
|
+
import Metadata from "./Metadata.vue"
|
|
36
|
+
|
|
37
|
+
import { computed, watch, ref, onMounted } from 'vue'
|
|
38
|
+
import { toRefs } from "@vueuse/core"
|
|
39
|
+
|
|
40
|
+
const isMounted = ref(false)
|
|
41
|
+
onMounted(() => isMounted.value = true)
|
|
42
|
+
|
|
43
|
+
import { path, live, useApi } from '@live-change/vue3-ssr'
|
|
44
|
+
const api = useApi()
|
|
45
|
+
const p = path()
|
|
46
|
+
|
|
47
|
+
const props = defineProps({
|
|
48
|
+
objectType: {
|
|
49
|
+
type: String,
|
|
50
|
+
required: true
|
|
51
|
+
},
|
|
52
|
+
path: {
|
|
53
|
+
type: String,
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
editorRoute: {
|
|
57
|
+
type: Function,
|
|
58
|
+
default: (objectType, object) => {
|
|
59
|
+
const [service, type] = objectType.split('_')
|
|
60
|
+
const prop = type[0].toLowerCase()+type.slice(1)
|
|
61
|
+
return { name: `${service}:${prop}Editor`, params: { [prop+'Id']: object }}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
class: {},
|
|
65
|
+
style: {}
|
|
66
|
+
})
|
|
67
|
+
const { objectType, path: urlPath, class: clazz, style } = toRefs(props)
|
|
68
|
+
|
|
69
|
+
const urlMore = computed(() => [
|
|
70
|
+
url => p.content.content({ objectType: objectType.value, object: url.target }),
|
|
71
|
+
url => p.content.objectOwnedMetadata({ objectType: objectType.value, object: url.target }),
|
|
72
|
+
url => p.url.targetOwnedCanonical({ targetType: objectType.value, target: url.target })
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
const canCreateContent = computed(() => api.client.value.roles.includes('writer'))
|
|
76
|
+
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
|
|
81
|
+
</style>
|
package/front/src/router.js
CHANGED
|
@@ -8,7 +8,7 @@ import { dbAdminRoutes } from "@live-change/db-admin"
|
|
|
8
8
|
import { userRoutes } from "@live-change/user-frontend";
|
|
9
9
|
import { catchAllPagesRoute, contentEditRoutes, pagesSitemap } from "./components/routes";
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function contentRoutes(config = {}) {
|
|
12
12
|
const { prefix = '/', route = (r) => r } = config
|
|
13
13
|
return [
|
|
14
14
|
...userRoutes({ ...config, prefix: prefix + 'user/' }),
|
|
@@ -35,7 +35,7 @@ export async function sitemap(route, api) {
|
|
|
35
35
|
export function createRouter(app, config) {
|
|
36
36
|
const router = _createRouter({
|
|
37
37
|
history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
|
|
38
|
-
routes:
|
|
38
|
+
routes: contentRoutes(config)
|
|
39
39
|
})
|
|
40
40
|
return router
|
|
41
41
|
}
|
package/index.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import Content from './front/src/components/Content.vue'
|
|
2
|
-
import ContentEditor from './front/src/components/ContentEditor.vue'
|
|
3
1
|
import Metadata from './front/src/components/Metadata.vue'
|
|
4
2
|
import MetadataEditor from './front/src/components/MetadataEditor.vue'
|
|
3
|
+
import Content from './front/src/components/Content.vue'
|
|
4
|
+
import ContentEditor from './front/src/components/ContentEditor.vue'
|
|
5
|
+
import ContentPreview from './front/src/components/ContentPreview.vue'
|
|
6
|
+
import ContentSettings from './front/src/components/ContentSettings.vue'
|
|
7
|
+
|
|
8
|
+
export { Metadata, MetadataEditor, Content, ContentEditor, ContentPreview, ContentSettings }
|
|
9
|
+
|
|
5
10
|
import NotFoundAdminButtons from './front/src/components/NotFoundAdminButtons.vue'
|
|
11
|
+
import NotAuthorizedAdminButtons from "./front/src/components/NotAuthorizedAdminButtons.vue"
|
|
12
|
+
import ContentAdminButtons from './front/src/components/ContentAdminButtons.vue'
|
|
13
|
+
import UrlContent from './front/src/components/UrlContent.vue'
|
|
14
|
+
|
|
15
|
+
export { NotFoundAdminButtons, NotAuthorizedAdminButtons, ContentAdminButtons, UrlContent }
|
|
16
|
+
|
|
6
17
|
import Page from './front/src/components/Page.vue'
|
|
7
18
|
import PageEditor from './front/src/components/PageEditor.vue'
|
|
8
19
|
import PagePreview from './front/src/components/PagePreview.vue'
|
|
9
|
-
import PageAdminButtons from './front/src/components/PageAdminButtons.vue'
|
|
10
20
|
|
|
11
|
-
export {
|
|
12
|
-
Content, ContentEditor, Metadata, MetadataEditor, NotFoundAdminButtons, Page, PageEditor, PagePreview,
|
|
13
|
-
PageAdminButtons
|
|
14
|
-
}
|
|
21
|
+
export { Page, PageEditor, PagePreview }
|
|
15
22
|
|
|
16
23
|
import { catchAllPagesRoute, contentEditRoutes, pagesSitemap } from "./front/src/components/routes"
|
|
17
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/content-frontend",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
@@ -21,13 +21,14 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@fortawesome/fontawesome-free": "^6.2.0",
|
|
24
|
-
"@live-change/cli": "0.7.
|
|
24
|
+
"@live-change/cli": "0.7.6",
|
|
25
25
|
"@live-change/dao": "0.5.8",
|
|
26
26
|
"@live-change/dao-vue3": "0.5.8",
|
|
27
27
|
"@live-change/dao-websocket": "0.5.8",
|
|
28
|
-
"@live-change/framework": "0.7.
|
|
29
|
-
"@live-change/image-service": "0.3.
|
|
30
|
-
"@live-change/session-service": "0.3.
|
|
28
|
+
"@live-change/framework": "0.7.6",
|
|
29
|
+
"@live-change/image-service": "0.3.8",
|
|
30
|
+
"@live-change/session-service": "0.3.8",
|
|
31
|
+
"@live-change/url-frontend": "^0.2.17",
|
|
31
32
|
"@live-change/vue3-components": "0.2.16",
|
|
32
33
|
"@live-change/vue3-ssr": "0.2.16",
|
|
33
34
|
"@vueuse/core": "^9.1.0",
|
|
@@ -48,10 +49,10 @@
|
|
|
48
49
|
"v-shared-element": "3.1.0",
|
|
49
50
|
"vue-meta": "^3.0.0-alpha.9",
|
|
50
51
|
"vue-router": "^4.1.3",
|
|
51
|
-
"vue3-scroll-border": "0.1.
|
|
52
|
+
"vue3-scroll-border": "0.1.4"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@live-change/codeceptjs-helper": "0.7.
|
|
55
|
+
"@live-change/codeceptjs-helper": "0.7.6",
|
|
55
56
|
"@wdio/selenium-standalone-service": "^7.20.8",
|
|
56
57
|
"codeceptjs": "^3.3.4",
|
|
57
58
|
"generate-password": "1.7.0",
|
|
@@ -63,5 +64,5 @@
|
|
|
63
64
|
"author": "",
|
|
64
65
|
"license": "ISC",
|
|
65
66
|
"description": "",
|
|
66
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "61ea1e9554f3bf8cb8f9debffdabd8a88855c3b2"
|
|
67
68
|
}
|