Add OG types to page/post, add rendering in page head (#52)

This commit is contained in:
Daniel J. Summers 2025-07-09 22:04:37 -04:00
parent fa4e1d327a
commit 210dd41cee
4 changed files with 129 additions and 36 deletions

View File

@ -120,6 +120,9 @@ type Page = {
/// <summary>Revisions of this page</summary> /// <summary>Revisions of this page</summary>
Revisions: Revision list Revisions: Revision list
/// <summary>Common OpenGraph information for this post</summary>
OpenGraph: OpenGraphProperties option
} with } with
/// <summary>An empty page</summary> /// <summary>An empty page</summary>
@ -136,7 +139,8 @@ type Page = {
Text = "" Text = ""
Metadata = [] Metadata = []
PriorPermalinks = [] PriorPermalinks = []
Revisions = [] } Revisions = []
OpenGraph = None }
/// <summary>A web log post</summary> /// <summary>A web log post</summary>
@ -189,6 +193,9 @@ type Post = {
/// <summary>The revisions for this post</summary> /// <summary>The revisions for this post</summary>
Revisions: Revision list Revisions: Revision list
/// <summary>OpenGraph information for this post</summary>
OpenGraph: OpenGraphProperties option
} with } with
/// <summary>An empty post</summary> /// <summary>An empty post</summary>
@ -208,7 +215,8 @@ type Post = {
Episode = None Episode = None
Metadata = [] Metadata = []
PriorPermalinks = [] PriorPermalinks = []
Revisions = [] } Revisions = []
OpenGraph = None }
/// <summary> /// <summary>

View File

@ -401,6 +401,20 @@ type OpenGraphAudio = {
SecureUrl = None SecureUrl = None
Type = None } Type = None }
/// <summary>The <c>meta</c> properties for this image</summary>
member this.Properties = seq {
yield ("og:audio", this.Url)
match this.SecureUrl with
| Some url -> yield ("og:audio:secure_url", url)
| None when this.Url.StartsWith "https:" -> yield ("og:audio:secure_url", this.Url)
| None -> ()
match this.Type with
| Some typ -> yield ("og:audio:type", typ)
| None ->
// TODO: derive mime type from extension
()
}
/// <summary>Properties for an OpenGraph image</summary> /// <summary>Properties for an OpenGraph image</summary>
[<CLIMutable>] [<CLIMutable>]
@ -433,6 +447,23 @@ type OpenGraphImage = {
Height = None Height = None
Alt = None } Alt = None }
/// <summary>The <c>meta</c> properties for this image</summary>
member this.Properties = seq {
yield ("og:image", this.Url)
match this.SecureUrl with
| Some url -> yield ("og:image:secure_url", url)
| None when this.Url.StartsWith "https:" -> yield ("og:image:secure_url", this.Url)
| None -> ()
match this.Type with
| Some typ -> yield ("og:image:type", typ)
| None ->
// TODO: derive mime type based on common image extensions
()
match this.Width with Some width -> yield ("og:image:width", string width) | None -> ()
match this.Height with Some height -> yield ("og:image:height", string height) | None -> ()
match this.Alt with Some alt -> yield ("og:image:alt", alt) | None -> ()
}
/// <summary>Properties for an OpenGraph video</summary> /// <summary>Properties for an OpenGraph video</summary>
[<CLIMutable>] [<CLIMutable>]
@ -461,6 +492,22 @@ type OpenGraphVideo = {
Width = None Width = None
Height = None } Height = None }
/// <summary>The <c>meta</c> properties for this video</summary>
member this.Properties = seq {
yield ("og:video", this.Url)
match this.SecureUrl with
| Some url -> yield ("og:video:secure_url", url)
| None when this.Url.StartsWith "https:" -> yield ("og:video:secure_url", this.Url)
| None -> ()
match this.Type with
| Some typ -> yield ("og:video:type", typ)
| None ->
// TODO: derive mime type based on common video extensions
()
match this.Width with Some width -> yield ("og:video:width", string width) | None -> ()
match this.Height with Some height -> yield ("og:video:height", string height) | None -> ()
}
/// <summary>Valid <c>og:type</c> values</summary> /// <summary>Valid <c>og:type</c> values</summary>
[<Struct>] [<Struct>]
@ -517,9 +564,9 @@ type OpenGraphType =
| Website -> "website" | Website -> "website"
/// <summary>Top-level properties for OpenGraph</summary> /// <summary>Properties for OpenGraph</summary>
[<CLIMutable>] [<CLIMutable>]
type OpenGraphTopLevel = { type OpenGraphProperties = {
/// <summary>The type of object represented</summary> /// <summary>The type of object represented</summary>
Type: OpenGraphType Type: OpenGraphType
@ -544,6 +591,9 @@ type OpenGraphTopLevel = {
/// <summary>A video file assigned with the object</summary> /// <summary>A video file assigned with the object</summary>
Video: OpenGraphVideo option Video: OpenGraphVideo option
/// <summary>Free-form items</summary>
Other: MetaItem list option
} }

View File

@ -112,6 +112,9 @@ type DisplayPage = {
/// <summary>The metadata for the page</summary> /// <summary>The metadata for the page</summary>
Metadata: MetaItem list Metadata: MetaItem list
/// <summary>The OpenGraph properties for the page</summary>
OpenGraph: OpenGraphProperties option
} with } with
/// <summary>Create a minimal display page (no text or metadata) from a database page</summary> /// <summary>Create a minimal display page (no text or metadata) from a database page</summary>
@ -128,7 +131,8 @@ type DisplayPage = {
IsInPageList = page.IsInPageList IsInPageList = page.IsInPageList
IsDefault = string page.Id = webLog.DefaultPage IsDefault = string page.Id = webLog.DefaultPage
Text = "" Text = ""
Metadata = [] } Metadata = []
OpenGraph = None }
/// <summary>Create a display page from a database page</summary> /// <summary>Create a display page from a database page</summary>
/// <param name="webLog">The web log to which the page belongs</param> /// <param name="webLog">The web log to which the page belongs</param>
@ -138,6 +142,7 @@ type DisplayPage = {
{ DisplayPage.FromPageMinimal webLog page with { DisplayPage.FromPageMinimal webLog page with
Text = addBaseToRelativeUrls webLog.ExtraPath page.Text Text = addBaseToRelativeUrls webLog.ExtraPath page.Text
Metadata = page.Metadata Metadata = page.Metadata
OpenGraph = page.OpenGraph
} }
@ -1165,6 +1170,9 @@ type PostListItem = {
/// <summary>Metadata for the post</summary> /// <summary>Metadata for the post</summary>
Metadata: MetaItem list Metadata: MetaItem list
/// <summary>OpenGraph properties for the post</summary>
OpenGraph: OpenGraphProperties option
} with } with
/// <summary>Create a post list item from a post</summary> /// <summary>Create a post list item from a post</summary>
@ -1183,7 +1191,8 @@ type PostListItem = {
CategoryIds = post.CategoryIds |> List.map string CategoryIds = post.CategoryIds |> List.map string
Tags = post.Tags Tags = post.Tags
Episode = post.Episode Episode = post.Episode
Metadata = post.Metadata } Metadata = post.Metadata
OpenGraph = post.OpenGraph }
/// <summary>View model for displaying posts</summary> /// <summary>View model for displaying posts</summary>

View File

@ -189,8 +189,34 @@ let parser =
it.RegisterEmptyTag("page_head", it.RegisterEmptyTag("page_head",
fun writer encoder context -> fun writer encoder context ->
let app = context.App let app = context.App
// let getBool name = let attrEnc = System.Web.HttpUtility.HtmlAttributeEncode
// defaultArg (context.Environments[0].[name] |> Option.ofObj |> Option.map Convert.ToBoolean) false
// OpenGraph tags
if app.IsPage || app.IsPost then
let writeOgProp (name, value) =
writer.WriteLine $"""{s}<meta property=%s{name} content="{attrEnc value}">"""
writeOgProp ("og:title", if app.IsPage then app.Page.Title else app.Posts.Posts[0].Title)
writeOgProp ("og:site_name", app.WebLog.Name)
if app.IsPage then app.Page.Permalink else app.Posts.Posts[0].Permalink
|> Permalink
|> app.WebLog.AbsoluteUrl
|> function url -> writeOgProp ("og:url", url)
match if app.IsPage then app.Page.OpenGraph else app.Posts.Posts[0].OpenGraph with
| Some props ->
writeOgProp ("og:type", string props.Type)
props.Image.Properties |> Seq.iter writeOgProp
match props.Description with Some desc -> writeOgProp ("og:description", desc) | None -> ()
match props.Determiner with Some det -> writeOgProp ("og:determiner", det) | None -> ()
match props.Locale with Some loc -> writeOgProp ("og:locale", loc) | None -> ()
match props.LocaleAlternate with
| Some alt -> alt |> List.iter (fun it -> writeOgProp ("og:locale:alternate", it))
| None -> ()
match props.Audio with Some audio -> audio.Properties |> Seq.iter writeOgProp | None -> ()
match props.Video with Some video -> video.Properties |> Seq.iter writeOgProp | None -> ()
match props.Other with
| Some oth -> oth |> List.iter (fun it -> writeOgProp (it.Name, it.Value))
| None -> ()
| None -> ()
writer.WriteLine $"""{s}<meta name=generator content="{app.Generator}">""" writer.WriteLine $"""{s}<meta name=generator content="{app.Generator}">"""