Skip to content

Commit

Permalink
Feature/metadata preview (#388)
Browse files Browse the repository at this point in the history
* Implementation for metadata preview

* Refresh on edit + add more tags

* MetadataField layout

* Generic titles for metadata preview

---------

Co-authored-by: Stijn Van Doorslaer <stijn@codeurs.be>
  • Loading branch information
dmerckx and StijnVanDoorslaer committed Jun 26, 2024
1 parent fc63ede commit 959b27c
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 32 deletions.
21 changes: 21 additions & 0 deletions apps/web/content/media/20-06-2024-04-51-47.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"_id": "2i9FuEJofr5yFe9DuleODHjjWkm",
"_type": "MediaFile",
"_index": "a0",
"_i18nId": "2i9FuEJofr5yFe9DuleODHjjWkm",
"_root": "media",
"title": "20-06-2024-04-51-47",
"location": "/20-06-2024-04-51-47.2i9FuEJofr5yFe9DuleODHjjWkm.png",
"extension": ".png",
"size": 105986,
"hash": "3869032d",
"width": 640,
"height": 300,
"averageColor": "#c8c4eb",
"focus": {
"x": 0.4828125,
"y": 0.5
},
"thumbHash": "9AYGC4DWeWh3iPc4gHr5rNU=",
"preview": "data:image/webp;base64,UklGRp4EAABXRUJQVlA4WAoAAAAgAAAAnwAASgAASUNDUMgBAAAAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADZWUDggsAIAAPAQAJ0BKqAASwA+bTaYSKQjIqEimAk4gA2JaQDPBaY24jix+AHZT/YOUuPXPxGcJxllQI/tJ6M/+p5FPnL2Df5H/Veqr6Jv7SCpLwLkx9NA9AAV+SPw33PyO5t9DR7EG4lRCpRKtK5aU6l6+2ca+1wiIEFEMNeBfTeR3ItE/aIIPIZrlXiX5Fj/OIL6fhDcAAD+9vnv7ec2e3mKaKbuXv5xgI6RuLccrqOeTYW+5tvPW6awKuhPTJjw6qtKCuv58MAnfvhJpAdN96Z8B1vVuwpbud5eC7WYMgTPR56xXgPwHYhbQqr7xxCsJ4FAx6+NvWpj3lpj8h7fJ69HZqAyj9/IiW5mDr9gwuED/5N/HJX4r/hZ5tM9QZ9i0ZM00giAwhJ8ywtO4+1uLOjXd/aLNOJgVGZPW8kJofj43dH4f76eaZq861FLJ6lx6n06H2Yf79jt5HOEencQT7wn7UFOWJ8TEQpOFb95fuawwXbFWO+WGCbEFIshhkV0DLL06jory78ywAgnluoxskiTrca7lqPKMbQ8A/ZZehgsQfevg56GsaGe7RKlgvJsxdCXkkj050fF9qN6OUTL/9TjJ5+VdymuAZ3kpDyoPfx3Dg2+n1qOBAd1JoaqLP3l+uvu0j5v0qbqBTq6fwCd1/kPfECFZ52J8kitYHnioB+sBL9KuTeC2cpiBjnvQVAFk1dBn4oZzO7E86iuFzRykXdzq7rHr+M4g/CPHrXyYeBEeqy3y3QF8eXw2WfXdErf2QkViYMXW+dpzaMZW4WwMT6aW7rzaZg5Knds4fNicBcmPR4JoOcHyNIsZ1AQOcmKLQaPoZQLIdmkaCOXlaOazwvjdCjneIrzkuoELrNuSNz42wtW9B1y2HwgcfwFhySixQTkF6pkwsgSC5SZqOOKgNtpmCMAAAA="
}
13 changes: 8 additions & 5 deletions apps/web/content/pages/blog/alinea-0-4-0.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
"_type": "BlogPost",
"_index": "Zy",
"_i18nId": "2YlP39QVFZOzf94YMwDVrzd4gNz",
"_root": "pages",
"title": "Alinea 0.4.0 ⚡",
"publishDate": "2023-12-01",
"author": {
"name": "Ben Merckx",
"url": {
"_type": "url",
"_id": "2YlPCEdeGLXkqwQQq4r482BrNmj",
"_type": "url",
"_url": "https://github.com/benmerckx",
"_target": "_blank"
},
"avatar": {
"_type": "url",
"_id": "2YlPFxJGo8JMh6ZDWt7NDu7MPSN",
"_type": "url",
"_url": "https://avatars.githubusercontent.com/u/10584189?v=4&s=48",
"_target": "_self"
}
Expand Down Expand Up @@ -220,8 +219,8 @@
"_type": "ImageBlock",
"_id": "2YlUKcEMi7vbNDku6d1QlCoO5oA",
"image": {
"_type": "image",
"_id": "2YlUafLKDxzdvHmJP6Kpyd6y9xJ",
"_type": "image",
"_entry": "2YlUadqGteQCGAVaJ6QuSiwsdHr"
}
},
Expand Down Expand Up @@ -251,7 +250,11 @@
"description": "",
"openGraph": {
"title": "",
"image": {},
"image": {
"_id": "2i9FuSoeYtbbK9Oq7YNBJuygCit",
"_type": "image",
"_entry": "2i9FuEJofr5yFe9DuleODHjjWkm"
},
"description": ""
}
}
Expand Down
21 changes: 13 additions & 8 deletions apps/web/content/pages/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"headline": "Shape content\nfor the modern web.",
"byline": "Open source headless CMS with minimal setup.\nFully typed content right in your repository.",
"action": {
"_type": "entry",
"_id": "2YcxykzomCJyGwRFMXARgTxib4b",
"_type": "entry",
"_entry": "2YWqKUTrANlgEFMyBUEYdih2HpP",
"label": "Get started"
},
Expand Down Expand Up @@ -81,41 +81,41 @@
},
"links": [
{
"_type": "entry",
"_id": "2a7ftYTVOui6duXcA4Js0xG8QC6",
"_index": "a0",
"_type": "entry",
"_entry": "docs",
"label": "Docs",
"active": ""
},
{
"_type": "entry",
"_id": "2a7ftRO1unpBDnfvXtgJUBIBwNr",
"_index": "a1",
"_type": "entry",
"_entry": "CC8aJ6-2U3s31micAzTLU",
"label": "Roadmap",
"active": ""
},
{
"_type": "entry",
"_id": "2a7ftUxPD7w8EMFYWjpYoJVF6GP",
"_index": "a2",
"_type": "entry",
"_entry": "gx3H52whBKO_DdfcMNI3I",
"label": "Blog",
"active": ""
}
],
"footer": [
{
"_type": "Section",
"_id": "2ANVIUDmjNddF0QO8aB0gwXcRq8",
"_index": "a0",
"_type": "Section",
"label": "Developer",
"links": [
{
"_type": "entry",
"_id": "2a7fvVi9M7zcX2wx3IJfYBrCqeC",
"_index": "a0",
"_type": "entry",
"_entry": "docs",
"label": ""
}
Expand All @@ -124,10 +124,15 @@
],
"metadata": {
"title": "Alinea - Open source headless CMS",
"description": "",
"description": "Alinea is a open source headless CMS with minimal setup. Fully typed content right in your repository.",
"openGraph": {
"siteName": "Alinea",
"image": {
"_id": "2i9HvjoJjgs3bDP5bMyQyekUfGu",
"_type": "image",
"_entry": "2i9FuEJofr5yFe9DuleODHjjWkm"
},
"title": "",
"image": {},
"description": ""
}
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 20 additions & 3 deletions apps/web/src/page/BlogPostPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {TextFieldView} from '@/page/blocks/TextFieldView'
import {BlogPost} from '@/schema/BlogPost'
import {Query} from 'alinea'
import {fromModule} from 'alinea/ui'
import {MetadataRoute} from 'next'
import {Metadata, MetadataRoute} from 'next'
import {Breadcrumbs} from '../layout/Breadcrumbs'
import css from './BlogPostPage.module.scss'
import {BlogPostMeta} from './blog/BlogPostMeta'
Expand All @@ -22,9 +22,26 @@ export async function generateStaticParams() {
return slugs.map(slug => ({slug}))
}

export async function generateMetadata({params}: BlogPostPageProps) {
export async function generateMetadata({
params
}: BlogPostPageProps): Promise<Metadata> {
const page = await cms.get(Query(BlogPost).whereUrl(`/blog/${params.slug}`))
return {title: page.metadata?.title || page.title}
const openGraphImage = page.metadata?.openGraph.image
return {
title: page.metadata?.title || page.title,
description: page.metadata?.description || page.introduction,
openGraph: {
images: openGraphImage
? [
{
url: openGraphImage.src,
width: openGraphImage.width,
height: openGraphImage.height
}
]
: []
}
}
}

export default async function BlogPostPage({params}: BlogPostPageProps) {
Expand Down
37 changes: 33 additions & 4 deletions apps/web/src/page/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,52 @@ import {PageContainer} from '@/layout/Page'
import WebLayout from '@/layout/WebLayout'
import {WebTypo} from '@/layout/WebTypo'
import {Home} from '@/schema/Home'
import {Query} from 'alinea'
import {HStack, VStack} from 'alinea/ui/Stack'
import {IcRoundInsertDriveFile} from 'alinea/ui/icons/IcRoundInsertDriveFile'
import {IcRoundPublish} from 'alinea/ui/icons/IcRoundPublish'
import {PhGlobe} from 'alinea/ui/icons/PhGlobe'
import {RiFlashlightFill} from 'alinea/ui/icons/RiFlashlightFill'
import {fromModule} from 'alinea/ui/util/Styler'
import {px} from 'alinea/ui/util/Units'
import type {MetadataRoute} from 'next'
import type {Metadata, MetadataRoute} from 'next'
import {ComponentType, PropsWithChildren} from 'react'
import {Link} from '../layout/nav/Link'
import css from './HomePage.module.scss'

const styles = fromModule(css)

export async function generateMetadata() {
const home = await cms.get(Home())
return {title: home.metadata?.title || home.title}
export async function generateMetadata(): Promise<Metadata> {
const page = await cms.get(
Query(Home).select({
url: (Query as any).url,
title: (Query as any).title,
metadata: (Home as any).metadata
})
)
const appUrl = 'https://alinea.sh'
const title = page.metadata?.title || page.title
const ogTitle = page.metadata?.openGraph?.title || title
const ogDescription =
page.metadata?.openGraph?.description || page.metadata?.description
const openGraphImage = page.metadata?.openGraph.image

return {
metadataBase: new URL(appUrl),
title,
description: page.metadata?.description,
openGraph: {
url: appUrl + page.url,
siteName: page.metadata?.openGraph?.siteName,
title: ogTitle,
description: ogDescription,
images: openGraphImage?.src && {
url: openGraphImage.src,
width: openGraphImage.width,
height: openGraphImage.height
}
}
}
}

interface HighlightProps {
Expand Down
20 changes: 20 additions & 0 deletions src/core/Resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ export interface PreviewUpdate {
update: string
}

export interface PreviewMetadata {
title: string
description?: string
language?: string
robots?: string
canonical?: string
'og:url'?: string
'og:site_name'?: string
'og:title'?: string
'og:description'?: string
'og:image'?: string
'og:image:width'?: string
'og:image:height'?: string
'twitter:card'?: string
'twitter:title'?: string
'twitter:image'?: string
'twitter:image:width'?: string
'twitter:image:height'?: string
}

export interface ResolveRequest {
selection: Selection
location?: Array<string>
Expand Down
5 changes: 3 additions & 2 deletions src/dashboard/view/EntryEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {EntryNotice} from './entry/EntryNotice.js'
import {EntryPreview} from './entry/EntryPreview.js'
import {EntryTitle} from './entry/EntryTitle.js'
import {FieldToolbar} from './entry/FieldToolbar.js'
import {BrowserPreviewMetaProvider} from './preview/BrowserPreview.js'

const styles = fromModule(css)

Expand Down Expand Up @@ -115,7 +116,7 @@ export function EntryEdit({editor}: EntryEditProps) {
if (isBlocking && !isNavigationChange) confirm?.()
}, [isBlocking, isNavigationChange, confirm])
return (
<>
<BrowserPreviewMetaProvider entryId={editor.entryId}>
{alineaDev && (
<>
<Statusbar.Slot>
Expand Down Expand Up @@ -266,6 +267,6 @@ export function EntryEdit({editor}: EntryEditProps) {
</Suspense>
</Preview>
)}
</>
</BrowserPreviewMetaProvider>
)
}
45 changes: 44 additions & 1 deletion src/dashboard/view/preview/BrowserPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {PreviewMetadata} from 'alinea/core/Resolver'
import {PreviewAction, PreviewMessage} from 'alinea/preview/PreviewMessage'
import {HStack, Loader, Typo, fromModule, px} from 'alinea/ui'
import {AppBar} from 'alinea/ui/AppBar'
Expand All @@ -6,7 +7,14 @@ import {IcRoundArrowForward} from 'alinea/ui/icons/IcRoundArrowForward'
import {IcRoundLock} from 'alinea/ui/icons/IcRoundLock'
import {IcRoundOpenInNew} from 'alinea/ui/icons/IcRoundOpenInNew'
import {IcRoundRefresh} from 'alinea/ui/icons/IcRoundRefresh'
import {useEffect, useRef, useState} from 'react'
import {
PropsWithChildren,
createContext,
useContext,
useEffect,
useRef,
useState
} from 'react'
import {LivePreview} from '../entry/EntryPreview.js'
import css from './BrowserPreview.module.scss'

Expand All @@ -17,10 +25,42 @@ export interface BrowserPreviewProps {
registerLivePreview(api: LivePreview): void
}

const BrowserPreviewMetaContext = createContext<{
setMetadata: (msg: PreviewMetadata) => void
metadata?: PreviewMetadata
}>({
setMetadata: () => {}
})

export const BrowserPreviewMetaProvider: React.FC<
PropsWithChildren<{
entryId: string
}>
> = ({entryId, children}) => {
const [metdata, setMetadata] = useState<PreviewMetadata>()

useEffect(() => {
setMetadata(undefined)
}, [entryId])

return (
<BrowserPreviewMetaContext.Provider
value={{setMetadata, metadata: metdata}}
>
{children}
</BrowserPreviewMetaContext.Provider>
)
}

export function usePreviewMetadata() {
return useContext(BrowserPreviewMetaContext)?.metadata
}

export function BrowserPreview({
url,
registerLivePreview
}: BrowserPreviewProps) {
const metaContext = useContext(BrowserPreviewMetaContext)
const iframe = useRef<HTMLIFrameElement>(null)
const [loading, setLoading] = useState(true)
const hasPreviewListener = useRef(false)
Expand All @@ -41,6 +81,9 @@ export function BrowserPreview({
}
})
}
if (event.data.action === PreviewAction.Meta) {
metaContext.setMetadata(event.data)
}
}

addEventListener('message', handleMessage)
Expand Down
Loading

0 comments on commit 959b27c

Please sign in to comment.