@live-change/content-frontend 0.2.8 → 0.2.9

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.
@@ -0,0 +1,84 @@
1
+ {
2
+ "content": {
3
+ "metadata": {
4
+ "title": "Title",
5
+ "description": "Description",
6
+ "og:title": "Open Graph",
7
+ "og": {
8
+ "title": "Title",
9
+ "description": "Description",
10
+ "image": "Image",
11
+ "determiner": "Determiner",
12
+ "locale": "Locale",
13
+ "localeAlternate:title": "Locale Alternate",
14
+ "type": "Type",
15
+ "type:options": {
16
+ "website": "Website",
17
+ "article": "Article",
18
+ "profile": "Profile",
19
+ "book": "Book",
20
+ "music": {
21
+ "song": "Music Song",
22
+ "album": "Music Album",
23
+ "playlist": "Music Playlist",
24
+ "radio_station": "Music Radio Station"
25
+ },
26
+ "video": {
27
+ "movie": "Video Movie",
28
+ "episode": "Video Episode",
29
+ "tv_show": "Video TV Show",
30
+ "other": "Video Other"
31
+ }
32
+ },
33
+ "music:title": "Music",
34
+ "music": {
35
+ "duration": "Duration",
36
+ "song:title": "Song",
37
+ "creator:title": "Creator",
38
+ "album:title": "Album",
39
+ "album": {
40
+ "url": "URL",
41
+ "disc": "Disc",
42
+ "track": "Track"
43
+ },
44
+ "song": {
45
+ "url": "URL",
46
+ "disc": "Disc",
47
+ "track": "Track"
48
+ },
49
+ "musician:title": "Musician"
50
+ },
51
+ "video:title": "Video",
52
+ "video": {
53
+ "duration": "Duration",
54
+ "actor:title": "Actor",
55
+ "actor": {
56
+ "url": "URL",
57
+ "role": "Role"
58
+ },
59
+ "director:title": "Director",
60
+ "writer:title": "Writer",
61
+ "releaseDate": "Release Date",
62
+ "tag:title": "Tag",
63
+ "series": "Series"
64
+ },
65
+ "article:title": "Article",
66
+ "article": {
67
+ "publishedTime": "Published Time",
68
+ "modifiedTime": "Modified Time",
69
+ "expirationTime": "Expiration Time",
70
+ "author:title": "Authors",
71
+ "section": "Section",
72
+ "tag:title": "Tags"
73
+ },
74
+ "book:title": "Book",
75
+ "book": {
76
+ "author:title": "Authors",
77
+ "isbn": "ISBN",
78
+ "releaseDate": "Release Date",
79
+ "tag:title": "Tags"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
package/front/src/App.vue CHANGED
@@ -13,11 +13,14 @@
13
13
  import ViewRoot from "@live-change/frontend-base/ViewRoot.vue"
14
14
  import NavBar from "./NavBar.vue"
15
15
 
16
+ import { useI18n } from 'vue-i18n'
17
+ const i18n = useI18n()
18
+
16
19
  import { useMeta } from 'vue-meta'
17
20
  const { meta } = useMeta({
18
21
  title: 'Title',
19
22
  htmlAttrs: {
20
- lang: 'en',
23
+ lang: i18n.locale.value,
21
24
  amp: true
22
25
  }
23
26
  })
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <!-- <pre>{{ JSON.stringify(metadata, null, " ") }}</pre>-->
3
+ </template>
4
+
5
+ <script setup>
6
+ const props = defineProps({
7
+ objectType: {
8
+ type: String,
9
+ required: true
10
+ },
11
+ object: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ url: {
16
+ type: Object,
17
+ required: true
18
+ }
19
+ })
20
+
21
+ import { computed, ref, onMounted } from 'vue'
22
+ import { path, live } from '@live-change/vue3-ssr'
23
+ import { useHost } from "@live-change/frontend-base"
24
+ const host = useHost()
25
+
26
+ const p = path()
27
+
28
+ const metadataLivePath = computed(
29
+ () => p.content.objectOwnedMetadata({ objectType: props.objectType, object: props.object })
30
+ .with(metadata => p.image.image({ image: metadata.og.image }).bind('ogImage'))
31
+ )
32
+ const canonicalUrlLivePath = computed(
33
+ () => p.url.targetOwnedCanonical({ targetType: props.objectType, target: props.object })
34
+ )
35
+ const [metadata, canonical] = await Promise.all([
36
+ live(metadataLivePath),
37
+ live(canonicalUrlLivePath)
38
+ ])
39
+
40
+ function metaProperty(name, value) {
41
+ return value ? { property: name, content: value } : undefined
42
+ }
43
+ function metaProperties(name, value) {
44
+ if(!value) return []
45
+ return value.map(v => ({ property: name, content: v }))
46
+ }
47
+ function metaPropertiesObjects(name, value) {
48
+ if(!value) return []
49
+ return value.map(v => {
50
+ for(let key in v) {
51
+ return { property: name+':'+key, content: v[key] }
52
+ }
53
+ })
54
+ }
55
+ import { useMeta } from 'vue-meta'
56
+ const m = metadata.value
57
+ const canonicalUrlDomain = canonical.value?.domain || host
58
+ const canonicalUrl = `https://${canonicalUrlDomain}/${canonical.value?.path ?? ''}`
59
+
60
+ let ogImage = []
61
+ if(m.ogImage) {
62
+ const image = m.ogImage
63
+ ogImage = [
64
+ { property: 'og:image', content: `https://${canonicalUrlDomain}/api/image/image/${image.id}` },
65
+ { property: 'og:image:width', content: image.width },
66
+ { property: 'og:image:height', content: image.height },
67
+ { property: 'og:image:type', content: 'image/'+image.extension }
68
+ ]
69
+ }
70
+
71
+ useMeta({
72
+ title: m.title,
73
+ description: m.description,
74
+ link: [
75
+ { rel: 'canonical', href: canonicalUrl }
76
+ ],
77
+ meta: [
78
+ metaProperty('og:title', m.og.title),
79
+ metaProperty('og:description', m.og.description),
80
+ ...ogImage,
81
+ metaProperty('og:determiner', m.og.determiner),
82
+ metaProperty('og:locale', m.og.locale),
83
+ ...metaProperties('og:locale:alternate', m.og.localeAlternate),
84
+ metaProperty('og:type', m.og.type),
85
+
86
+ metaProperty('og:music:duration', m.og.music.duration),
87
+ ...metaPropertiesObjects('og:music:album', m.og.music.album),
88
+ ...metaPropertiesObjects('og:music:song', m.og.music.song),
89
+ ...metaProperties('og:music:musician', m.og.music.duration),
90
+ metaProperty('og:music:release_date', m.og.music.releaseDate),
91
+ ...metaProperties('og:music:creator', m.og.music.creator),
92
+
93
+ metaProperty('og:video:duration', m.og.video.duration),
94
+ metaProperty('og:viceo:release_date', m.og.video.releaseDate),
95
+ ...metaPropertiesObjects('og:video:duration', m.og.video.actors),
96
+ ...metaProperties('og:video:director', m.og.video.director),
97
+ ...metaProperties('og:video:writer', m.og.video.writer),
98
+ ...metaProperties('og:video:series', m.og.video.series),
99
+ ...metaProperties('og:video:tag', m.og.video.tag),
100
+
101
+ ...metaProperties('og:profile:first_name', m.og.profile.firstName),
102
+ ...metaProperties('og:profile:last_name', m.og.profile.lastName),
103
+ ...metaProperties('og:profile:username', m.og.profile.username),
104
+ ...metaProperties('og:profile:gender', m.og.profile.gender),
105
+
106
+ metaProperty('og:article:published_time', m.og.article.publishedTime),
107
+ metaProperty('og:article:modified_time', m.og.article.modifiedTime),
108
+ metaProperty('og:article:expiration_time', m.og.article.expirationTime),
109
+ ...metaProperties('og:article:author', m.og.article.author),
110
+ metaProperty('og:article:section', m.og.article.section),
111
+ ...metaProperties('og:article:tag', m.og.article.tag),
112
+
113
+ ...metaProperties('og:book:author', m.og.book.author),
114
+ metaProperty('og:book:isbn', m.og.book.isbn),
115
+ metaProperty('og:book:release_date', m.og.book.releaseDate),
116
+ ...metaProperties('og:book:tag', m.og.book.tag),
117
+
118
+ { property: 'og:url', content: canonicalUrl },
119
+
120
+
121
+ /* { property: 'og:url', content: m.url },
122
+ { property: 'og:type', content: 'website' },
123
+ { property: 'twitter:card', content: 'summary_large_image' },
124
+ { property: 'twitter:title', content: metadata.value.title },
125
+ { property: 'twitter:description', content: metadata.value.description },
126
+ { property: 'twitter:image', content: metadata.value.image },
127
+ { property: 'twitter:url', content: metadata.value.url }*/
128
+ ].filter(x => !!x)
129
+ })
130
+ </script>
131
+
132
+ <style scoped>
133
+
134
+ </style>
@@ -1,8 +1,10 @@
1
1
  <template>
2
- <pre style="white-space: pre-wrap; word-wrap: break-word;">{{ JSON.stringify(editable, null, ' ') }}</pre>
3
- <auto-editor :definition="editableDefinition" v-model="editable" :rootValue="editable" />
2
+ <!-- <pre style="white-space: pre-wrap; word-wrap: break-word;">{{ JSON.stringify(editable, null, ' ') }}</pre>-->
3
+ <auto-editor :definition="editableDefinition" v-model="editable" :rootValue="editable" i18n="content.metadata." />
4
4
  <Button label="Save metadata" icon="pi pi-save" :disabled="!changed || error" @click="save" />
5
- <small v-if="error" class="p-error">Fix errors above to save</small>
5
+ <div>
6
+ <small v-if="error" class="p-error">Fix errors above to save</small>
7
+ </div>
6
8
  </template>
7
9
 
8
10
  <script setup>
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <ResolveUrl targetType="content_Page" :path="urlPath" :fetchMore="urlMore">
3
3
  <template #default="{ target, style, class: clazz }">
4
+ <Metadata objectType="content_Page" :object="target" />
4
5
  <LimitedAccess :requiredRoles="['writer']" objectType="content_Page" :object="target" hidden>
5
6
  <PageAdminButtons :page="target" :style="style" :class="clazz" :name="urlPath.value" />
6
7
  </LimitedAccess>
@@ -21,6 +22,7 @@
21
22
  import { ResolveUrl, NotFound } from "@live-change/url-frontend"
22
23
  import { LimitedAccess } from "@live-change/access-control-frontend";
23
24
  import Content from "./Content.vue"
25
+ import Metadata from "./Metadata.vue"
24
26
 
25
27
  import { computed, watch, ref, onMounted } from 'vue'
26
28
  import { toRefs } from "@vueuse/core"
@@ -34,7 +36,9 @@
34
36
 
35
37
  const urlMore = [
36
38
  url => p.content.page({ page: url.target }),
37
- url => p.content.content({ objectType: 'content_Page', object: 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 })
38
42
  ]
39
43
 
40
44
  const canCreatePage = computed(() => api.client.value.roles.includes('writer'))
@@ -14,7 +14,8 @@
14
14
  </AccordionTab>
15
15
  <AccordionTab>
16
16
  <template #header>
17
- <span v-if="metadata" class="font-bold mr-1">Title: {{ metadata.title }}</span>
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>
18
19
  <span v-else class="font-bold text-red-600">Metadata not set</span>
19
20
  </template>
20
21
  <MetadataEditor objectType="content_Page" :object="pageId" :key="pageId"></MetadataEditor>
@@ -0,0 +1,13 @@
1
+ import deepmerge from 'deepmerge';
2
+
3
+ import contentEn from "../locales/en.json"
4
+ import { locales as autoFormLocales } from "@live-change/frontend-auto-form"
5
+
6
+ export default {
7
+ i18nMessages: {
8
+ en: deepmerge.all([
9
+ contentEn,
10
+ autoFormLocales.en
11
+ ])
12
+ }
13
+ }
@@ -1,6 +1,6 @@
1
1
  import { clientEntry } from '@live-change/frontend-base/client-entry.js'
2
2
  import App from './App.vue'
3
3
  import { createRouter } from './router'
4
+ import config from './config.js'
4
5
 
5
-
6
- clientEntry(App, createRouter)
6
+ await clientEntry(App, createRouter, config)
@@ -1,6 +1,7 @@
1
1
  import { serverEntry } from '@live-change/frontend-base/server-entry.js'
2
2
  import App from './App.vue'
3
3
  import { createRouter } from './router'
4
+ import config from './config.js'
4
5
 
5
- const render = serverEntry(App, createRouter)
6
+ const render = serverEntry(App, createRouter, config)
6
7
  export { render }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/content-frontend",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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",
@@ -20,7 +20,7 @@
20
20
  "debug": "node --inspect-brk server"
21
21
  },
22
22
  "dependencies": {
23
- "@fortawesome/fontawesome-free": "^6.1.1",
23
+ "@fortawesome/fontawesome-free": "^6.2.0",
24
24
  "@live-change/cli": "0.7.4",
25
25
  "@live-change/dao": "0.5.8",
26
26
  "@live-change/dao-vue3": "0.5.8",
@@ -38,6 +38,7 @@
38
38
  "codeceptjs-assert": "^0.0.5",
39
39
  "compression": "^1.7.4",
40
40
  "cross-env": "^7.0.3",
41
+ "deepmerge": "^4.2.2",
41
42
  "get-port-sync": "1.0.1",
42
43
  "pica": "^9.0.1",
43
44
  "pretty-bytes": "^6.0.0",
@@ -66,5 +67,5 @@
66
67
  "author": "",
67
68
  "license": "ISC",
68
69
  "description": "",
69
- "gitHead": "72bd46680370e7804b168fec5dde28de205487e0"
70
+ "gitHead": "703c9723562ade7b2420e1b25b28ced51a96db09"
70
71
  }
package/server/init.js CHANGED
@@ -96,6 +96,40 @@ module.exports = async function(services) {
96
96
  target: 'one'
97
97
  })
98
98
 
99
+ await services.content.models.Metadata.create({
100
+ id: App.encodeIdentifier(['content_Page', 'one']),
101
+ objectType: 'content_Page',
102
+ object: 'one',
103
+ title: 'Test Page',
104
+ description: 'Test Description',
105
+ "og": {
106
+ "locale": "en_US",
107
+ "localeAlternate": [],
108
+ "type": "website",
109
+ "music": {
110
+ "song": [],
111
+ "album": [],
112
+ "musician": [],
113
+ "creator": []
114
+ },
115
+ "video": {
116
+ "actor": [],
117
+ "director": [],
118
+ "writer": [],
119
+ "tag": []
120
+ },
121
+ "article": {
122
+ "author": [],
123
+ "tag": []
124
+ },
125
+ "profile": {},
126
+ "book": {
127
+ "author": [],
128
+ "tag": []
129
+ }
130
+ }
131
+ })
132
+
99
133
  await createPage('two')
100
134
 
101
135
  await services.url.models.Canonical.create({