@live-change/content-frontend 0.2.3 → 0.2.5

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/App.vue CHANGED
@@ -29,4 +29,11 @@
29
29
  console.log("WATCH CLIENT", oldClient, '=>', newClient)
30
30
  })
31
31
 
32
+ import { useApi } from '@live-change/vue3-ssr'
33
+ const api = useApi()
34
+ import emailValidator from "@live-change/email-service/clientEmailValidator.js"
35
+ import passwordValidator from "@live-change/password-authentication-service/clientPasswordValidator.js"
36
+ api.validators.email = emailValidator
37
+ api.validators.password = passwordValidator
38
+
32
39
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="surface-overlay py-3 px-6 shadow-2 flex align-items-center justify-content-between
3
- relative md:sticky top-0 z-5"
3
+ relative sticky top-0 z-5"
4
4
  style="min-height: 80px" key="navbar">
5
5
  <img src="/images/logo.svg" alt="Image" height="40" class="mr-0 lg:mr-6">
6
6
  <div class="hidden lg:flex">
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <!-- <pre>{{ JSON.stringify(content, null, " ") }}</pre>-->
3
- <ContentView :content="JSON.stringify(content.content)" :config="contentConfig" />
3
+ <ContentView :content="JSON.stringify(content?.content)" :config="contentConfig" />
4
4
  </template>
5
5
 
6
6
  <script setup>
@@ -15,6 +15,10 @@
15
15
  object: {
16
16
  type: String,
17
17
  required: true
18
+ },
19
+ preview: {
20
+ type: Boolean,
21
+ default: false
18
22
  }
19
23
  })
20
24
 
@@ -26,7 +30,9 @@
26
30
 
27
31
  const p = path()
28
32
  const liveContentPath = computed(() =>
29
- p.content.content({ objectType: props.objectType, object: props.object })
33
+ props.preview
34
+ ? p.content.contentPreview({ objectType: props.objectType, object: props.object })
35
+ : p.content.content({ objectType: props.objectType, object: props.object })
30
36
  )
31
37
 
32
38
  const [content] = await Promise.all([
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
3
+ <Button icon="pi pi-plus"
4
+ class="p-button p-button-icon-only p-button-rounded p-button-danger"
5
+ @click="createPage" />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup>
10
+ import Button from 'primevue/button'
11
+
12
+ const props = defineProps({
13
+ path: {
14
+ type: String,
15
+ required: true
16
+ }
17
+ })
18
+
19
+ import { toRefs } from "@vueuse/core"
20
+
21
+ import { useHost } from "@live-change/frontend-base"
22
+ const host = useHost()
23
+
24
+ const { path } = toRefs(props)
25
+
26
+ import { useToast } from 'primevue/usetoast'
27
+ const toast = useToast()
28
+ import { useConfirm } from 'primevue/useconfirm'
29
+ const confirm = useConfirm()
30
+
31
+ import { useRouter } from 'vue-router'
32
+ const router = useRouter()
33
+
34
+ function createPage() {
35
+ confirm.require({
36
+ target: event.currentTarget,
37
+ message: `Do you want to create page at ${host}/${path.value} ?`,
38
+ icon: 'pi pi-info-circle',
39
+ acceptClass: 'p-button-danger',
40
+ accept: async () => {
41
+ const pageId = await api.actions.content.createPage()
42
+ await api.actions.url.takeUrl({
43
+ target: pageId, targetType: 'content_Page', path: path.value, domain: host, redirect: false
44
+ })
45
+ toast.add({ severity:'success', summary: 'Page created', life: 1500 })
46
+ setTimeout(() => {
47
+ router.push({ name: 'content:pageEditor', params: { pageId } })
48
+ }, 200)
49
+ },
50
+ reject: () => {
51
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
52
+ }
53
+ })
54
+ }
55
+
56
+ </script>
57
+
58
+ <style scoped>
59
+
60
+ </style>
@@ -1,43 +1,29 @@
1
1
  <template>
2
2
  <ResolveUrl targetType="content_Page" :path="urlPath" :fetchMore="urlMore">
3
- <template #default="{ target }">
3
+ <template #default="{ target, style, class: clazz }">
4
4
  <LimitedAccess :requiredRoles="['writer']" objectType="content_Page" :object="target" hidden>
5
- <div class="absolute top-0 right-0 pr-4 max-h-0 flex align-items-center z-5">
6
- <Button icon="pi pi-pencil"
7
- class="p-button p-button-icon-only p-button-rounded p-button-warning mr-2"
8
- @click="editPage(target)" />
9
- <Button icon="pi pi-trash"
10
- class="p-button p-button-icon-only p-button-rounded p-button-danger"
11
- @click="deletePage(target)" />
12
- </div>
5
+ <PageAdminButtons :page="target" :style="style" :class="clazz" :name="urlPath.value" />
13
6
  </LimitedAccess>
14
7
  <Content objectType="content_Page" :object="target" />
15
8
  </template>
16
- <template #notFound="{ path }">
17
- <div class="surface-section px-4 py-8 md:px-6 lg:px-8">
18
- <div style="background: radial-gradient(50% 109137.91% at 50% 50%, rgba(233, 30, 99, 0.1) 0%, rgba(254, 244, 247, 0) 100%);" class="text-center">
19
- <span class="bg-white text-pink-500 font-bold text-2xl inline-block px-3">404</span>
20
- </div>
21
- <div class="mt-6 mb-5 font-bold text-6xl text-900 text-center">Page Not Found</div>
22
- <p class="text-700 text-3xl mt-0 mb-6 text-center">Sorry, we couldn't find the page.</p>
23
- <div class="text-center">
24
- <Button class="p-button-text mr-2" label="Go Back" icon="pi pi-arrow-left"></Button>
25
- <Button label="Go to Dashboard" icon="pi pi-home"></Button>
26
- </div>
27
- </div>
9
+ <template #notFound="{ path, style, class: clazz }">
10
+ <NotFoundAdminButtons v-if="canCreatePage" :path="urlPath" :style="style" :class="clazz" />
11
+ <NotFound :style="style" :class="clazz" />
28
12
  </template>
29
13
  </ResolveUrl>
30
14
  </template>
31
15
 
32
16
  <script setup>
33
17
  import Button from "primevue/button"
34
- import { ResolveUrl } from "@live-change/url-frontend"
35
- import {LimitedAccess} from "@live-change/access-control-frontend";
18
+ import PageAdminButtons from "./PageAdminButtons.vue"
19
+ import NotFoundAdminButtons from "./NotFoundAdminButtons.vue"
20
+
21
+ import { ResolveUrl, NotFound } from "@live-change/url-frontend"
22
+ import { LimitedAccess } from "@live-change/access-control-frontend";
36
23
  import Content from "./Content.vue"
37
24
 
38
25
  import { computed, watch, ref, onMounted } from 'vue'
39
26
  import { toRefs } from "@vueuse/core"
40
- import { useHost } from "@live-change/frontend-base"
41
27
 
42
28
  const isMounted = ref(false)
43
29
  onMounted(() => isMounted.value = true)
@@ -47,40 +33,27 @@
47
33
  const p = path()
48
34
 
49
35
  const urlMore = [
50
- url => p.content.page({ page: url.target })
36
+ url => p.content.page({ page: url.target }),
37
+ url => p.content.content({ objectType: 'content_Page', object: url.target })
51
38
  ]
52
39
 
40
+ const canCreatePage = computed(() => api.client.value.roles.includes('writer'))
41
+
53
42
  const props = defineProps({
54
43
  path: {
55
44
  type: String,
56
45
  required: true
46
+ },
47
+ class: {
48
+ type: String,
49
+ default: ''
50
+ },
51
+ style: {
52
+ type: String,
53
+ default: ''
57
54
  }
58
55
  })
59
- const { path: urlPath } = toRefs(props)
60
-
61
- import { useToast } from 'primevue/usetoast'
62
- const toast = useToast()
63
- import { useConfirm } from 'primevue/useconfirm'
64
- const confirm = useConfirm()
65
-
66
- function editPage(page) {
67
-
68
- }
69
- function deletePage(page) {
70
- confirm.require({
71
- target: event.currentTarget,
72
- message: `Do you want to remove page ${urlPath.value} ?`,
73
- icon: 'pi pi-info-circle',
74
- acceptClass: 'p-button-danger',
75
- accept: async () => {
76
- await api.actions.content.deletePage({ page })
77
- toast.add({ severity:'info', summary: 'Page deleted', life: 1500 })
78
- },
79
- reject: () => {
80
- toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
81
- }
82
- })
83
- }
56
+ const { path: urlPath, class: clazz, style } = toRefs(props)
84
57
 
85
58
  </script>
86
59
 
@@ -0,0 +1,56 @@
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="{ name: 'content:pageEditor', params: { pageId: page } }" 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
+ <Button icon="pi pi-trash"
8
+ class="p-button p-button-icon-only p-button-rounded p-button-danger"
9
+ @click="deletePage" />
10
+ </div>
11
+ </template>
12
+
13
+ <script setup>
14
+ import Button from 'primevue/button'
15
+
16
+ const props = defineProps({
17
+ page: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ name: {
22
+ type: String,
23
+ default: ""
24
+ }
25
+ })
26
+
27
+ import { toRefs } from "@vueuse/core"
28
+
29
+ const { page, name } = toRefs(props)
30
+
31
+ import { useToast } from 'primevue/usetoast'
32
+ const toast = useToast()
33
+ import { useConfirm } from 'primevue/useconfirm'
34
+ const confirm = useConfirm()
35
+
36
+ function deletePage() {
37
+ confirm.require({
38
+ target: event.currentTarget,
39
+ message: `Do you want to remove page ${name} ?`,
40
+ icon: 'pi pi-info-circle',
41
+ acceptClass: 'p-button-danger',
42
+ accept: async () => {
43
+ await api.actions.content.deletePage({ page })
44
+ toast.add({ severity:'info', summary: 'Page deleted', life: 1500 })
45
+ },
46
+ reject: () => {
47
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
48
+ }
49
+ })
50
+ }
51
+
52
+ </script>
53
+
54
+ <style scoped>
55
+
56
+ </style>
@@ -1,8 +1,123 @@
1
1
  <template>
2
+ <Accordion v-if="pageData" :multiple="true" class="w-full">
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 header="Header II">
10
+ Content
11
+ </AccordionTab>
12
+ <AccordionTab header="Header III">
13
+ Content
14
+ </AccordionTab>-->
15
+ </Accordion>
2
16
 
17
+ <DocumentEditor v-if="pageData" targetType="content_Page" :target="pageId"
18
+ :config="contentConfig" type="content" v-model:saveState="saveState" v-model:version="version">
19
+ <template #menuEnd="{}">
20
+
21
+ <Button icon="pi pi-eye" label="Preview" class="p-button-secondary p-button-sm mr-1 mb-1" />
22
+
23
+ <div class="p-buttonset mr-1 mb-1 border-round">
24
+ <button type="button"
25
+ class="p-button p-component p-button-sm p-button-outlined p-button-secondary cursor-auto inline-block"
26
+ :class="{ 'p-disabled': saveState == 'saving' }">
27
+ <span class="pi p-button-icon p-button-icon-left"
28
+ :class="[stateSave == 'saving' ? 'pi-sync' : 'pi-hashtag' ]" />
29
+ <span class="p-button-label">{{ ( version ?? 0 ).toFixed().padStart(10, '0') }}</span>
30
+ </button>
31
+ <Button icon="pi pi-save" label="Publish" class="p-button-success p-button-sm" type="button"
32
+ :disabled="saveState == 'saving'" @click="publish" />
33
+ </div>
34
+
35
+ </template>
36
+ </DocumentEditor>
37
+
38
+ <NotFound v-if="pageData === null" />
3
39
  </template>
4
40
 
5
- <script>
41
+ <script setup>
42
+
43
+ import Accordion from 'primevue/accordion'
44
+ import AccordionTab from 'primevue/accordiontab'
45
+ import Button from 'primevue/button'
46
+
47
+ import { UrlsInfo, Urls, NotFound } from '@live-change/url-frontend'
48
+ import { DocumentEditor, EditorMenu } from "@live-change/wysiwyg-frontend"
49
+ import contentConfig from "./contentConfig.js";
50
+
51
+ import { computed, watch, ref, onMounted, inject } from 'vue'
52
+ import { toRefs } from "@vueuse/core"
53
+
54
+ const isMounted = ref(false)
55
+ onMounted(() => isMounted.value = true)
56
+
57
+ const props = defineProps({
58
+ pageId: {
59
+ type: String,
60
+ required: true
61
+ },
62
+ class: {},
63
+ style: {}
64
+ })
65
+ const { pageId, class: clazz, style } = toRefs(props)
66
+
67
+ const document = computed(() => `${JSON.stringify('content_Page')}:${JSON.stringify(pageId.value)}`)
68
+
69
+ const saveState = ref()
70
+ const version = ref()
71
+
72
+ import { useToast } from 'primevue/usetoast'
73
+ const toast = useToast()
74
+ import { useConfirm } from 'primevue/useconfirm'
75
+ const confirm = useConfirm()
76
+
77
+ const workingZone = inject('workingZone')
78
+
79
+ import { useApi, path, live } from '@live-change/vue3-ssr'
80
+
81
+ const api = useApi()
82
+
83
+ const p = path()
84
+
85
+ const livePagePath = computed(
86
+ () => p.content.page({ page: pageId.value })
87
+ )
88
+ const liveCanonicalUrlPath = computed(
89
+ () => p.url.targetOwnedCanonical({ targetType: 'content_Page', target: pageId.value })
90
+ )
91
+ const [pageData, canonicalUrlData] = await Promise.all([
92
+ live(livePagePath),
93
+ live(liveCanonicalUrlPath)
94
+ ])
95
+
96
+ function publish() {
97
+ const snapshotVersion = version.value
98
+
99
+ confirm.require({
100
+ target: event.currentTarget,
101
+ message: `Do you want to publish this content version ${snapshotVersion} to page `+
102
+ `${canonicalUrlData.value.domain ?? '*'}/${canonicalUrlData.value.path}?`,
103
+ icon: 'pi pi-info-circle',
104
+ acceptClass: 'p-button-danger',
105
+ accept: async () => {
106
+ api.actions.content.publish({
107
+ objectType: 'content_Page', object: pageId.value, version: snapshotVersion, type: 'content'
108
+ }).then(() => {
109
+ toast.add({ severity: 'success', summary: 'Published', detail: 'Page published', life: 3000 })
110
+ }).catch(e => {
111
+ toast.add({ severity: 'error', summary: 'Error', detail: 'Error publishing page', life: 3000 })
112
+ })
113
+ },
114
+ reject: () => {
115
+ toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
116
+ }
117
+ })
118
+
119
+
120
+ }
6
121
 
7
122
  </script>
8
123
 
@@ -0,0 +1,59 @@
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
+
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
+ pageId: {
41
+ type: String,
42
+ required: true
43
+ },
44
+ class: {
45
+ type: String,
46
+ default: ''
47
+ },
48
+ style: {
49
+ type: String,
50
+ default: ''
51
+ }
52
+ })
53
+ const { pageId, class: clazz, style } = toRefs(props)
54
+
55
+ </script>
56
+
57
+ <style scoped>
58
+
59
+ </style>
@@ -5,10 +5,12 @@ export function contentEditRoutes(config = {}) {
5
5
 
6
6
  return [
7
7
 
8
- route({ name: 'content:editor', path: prefix + 'content-editor/:documentId', meta: { },
8
+ route({ name: 'content:editor', path: prefix + 'content-editor/:documentId', meta: { }, props: true,
9
9
  component: () => import("./ContentEditor.vue") }),
10
- route({ name: 'content:pageEditor', path: prefix + 'page-editor/:pageId', meta: { },
10
+ route({ name: 'content:pageEditor', path: prefix + 'page-editor/:pageId', meta: { }, props: true,
11
11
  component: () => import("./PageEditor.vue") }),
12
+ route({ name: 'content:pagePreview', path: prefix + 'page-preview/:pageId', meta: { }, props: true,
13
+ component: () => import("./PagePreview.vue") }),
12
14
 
13
15
  ]
14
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/content-frontend",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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,13 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@fortawesome/fontawesome-free": "^6.1.1",
24
- "@live-change/cli": "0.6.14",
24
+ "@live-change/cli": "0.7.2",
25
25
  "@live-change/dao": "0.5.6",
26
26
  "@live-change/dao-vue3": "0.5.6",
27
27
  "@live-change/dao-websocket": "0.5.6",
28
- "@live-change/framework": "0.6.14",
29
- "@live-change/image-service": "0.2.51",
30
- "@live-change/session-service": "0.2.51",
28
+ "@live-change/framework": "0.7.2",
29
+ "@live-change/image-service": "0.3.0",
30
+ "@live-change/session-service": "0.3.0",
31
31
  "@live-change/vue3-components": "0.2.15",
32
32
  "@live-change/vue3-ssr": "0.2.15",
33
33
  "@tiptap/extension-highlight": "^2.0.0-beta.33",
@@ -42,8 +42,8 @@
42
42
  "pica": "^9.0.1",
43
43
  "pretty-bytes": "^6.0.0",
44
44
  "primeflex": "^3.2.1",
45
- "primeicons": "^5.0.0",
46
- "primevue": "^3.15.0",
45
+ "primeicons": "^6.0.1",
46
+ "primevue": "^3.18.1",
47
47
  "rollup-plugin-node-builtins": "^2.1.2",
48
48
  "rollup-plugin-visualizer": "5.6.0",
49
49
  "serialize-javascript": "^6.0.0",
@@ -54,7 +54,7 @@
54
54
  "vue3-scroll-border": "0.1.2"
55
55
  },
56
56
  "devDependencies": {
57
- "@live-change/codeceptjs-helper": "0.6.14",
57
+ "@live-change/codeceptjs-helper": "0.7.2",
58
58
  "@wdio/selenium-standalone-service": "^7.20.8",
59
59
  "codeceptjs": "^3.3.4",
60
60
  "generate-password": "1.7.0",
@@ -66,5 +66,5 @@
66
66
  "author": "",
67
67
  "license": "ISC",
68
68
  "description": "",
69
- "gitHead": "a91ef8d26ea58cabf9fef85347826ccd32096f8d"
69
+ "gitHead": "76ac0dd54acf671cb564a1aa943d81a9e2bc99e9"
70
70
  }
package/server/init.js CHANGED
@@ -43,9 +43,9 @@ module.exports = async function(services) {
43
43
  id: documentId,
44
44
  ownerType: 'content_Page',
45
45
  owner: 'one',
46
- type: 'rich',
46
+ type: 'content',
47
47
  purpose: 'page',
48
- version: 0,
48
+ version: 1,
49
49
  content: documentContent,
50
50
  created: documentTime,
51
51
  lastModified: documentTime
@@ -53,7 +53,7 @@ module.exports = async function(services) {
53
53
  await services.prosemirror.models.Snapshot.create({
54
54
  id: snapshotId,
55
55
  document: documentId,
56
- version: 0,
56
+ version: 1,
57
57
  content: documentContent,
58
58
  timestamp: documentTime,
59
59
  })
@@ -43,7 +43,7 @@ module.exports = {
43
43
  name: 'prosemirror',
44
44
  path: '@live-change/prosemirror-service',
45
45
  documentTypes: {
46
- rich: require('./rich.documentType.js'),
46
+ content: require('./content.documentType.js'),
47
47
  },
48
48
  testLatency: 2000
49
49
  },