Add OG types to page/post, add rendering in page head (#52)
This commit is contained in:
parent
fa4e1d327a
commit
210dd41cee
@ -120,6 +120,9 @@ type Page = {
|
||||
|
||||
/// <summary>Revisions of this page</summary>
|
||||
Revisions: Revision list
|
||||
|
||||
/// <summary>Common OpenGraph information for this post</summary>
|
||||
OpenGraph: OpenGraphProperties option
|
||||
} with
|
||||
|
||||
/// <summary>An empty page</summary>
|
||||
@ -136,7 +139,8 @@ type Page = {
|
||||
Text = ""
|
||||
Metadata = []
|
||||
PriorPermalinks = []
|
||||
Revisions = [] }
|
||||
Revisions = []
|
||||
OpenGraph = None }
|
||||
|
||||
|
||||
/// <summary>A web log post</summary>
|
||||
@ -189,6 +193,9 @@ type Post = {
|
||||
|
||||
/// <summary>The revisions for this post</summary>
|
||||
Revisions: Revision list
|
||||
|
||||
/// <summary>OpenGraph information for this post</summary>
|
||||
OpenGraph: OpenGraphProperties option
|
||||
} with
|
||||
|
||||
/// <summary>An empty post</summary>
|
||||
@ -208,7 +215,8 @@ type Post = {
|
||||
Episode = None
|
||||
Metadata = []
|
||||
PriorPermalinks = []
|
||||
Revisions = [] }
|
||||
Revisions = []
|
||||
OpenGraph = None }
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -387,43 +387,57 @@ type Revision = {
|
||||
type OpenGraphAudio = {
|
||||
/// <summary>The URL for this audio file</summary>
|
||||
Url: string
|
||||
|
||||
|
||||
/// <summary>The URL for this audio file for sites that require https</summary>
|
||||
SecureUrl: string option
|
||||
|
||||
|
||||
/// <summary>The MIME type of the audio file</summary>
|
||||
Type: string option
|
||||
} with
|
||||
|
||||
|
||||
/// <summary>An empty audio file</summary>
|
||||
static member Empty =
|
||||
{ Url = ""
|
||||
SecureUrl = 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>
|
||||
[<CLIMutable>]
|
||||
type OpenGraphImage = {
|
||||
/// <summary>The URL for this image</summary>
|
||||
Url: string
|
||||
|
||||
|
||||
/// <summary>The URL for this image for sites that require https</summary>
|
||||
SecureUrl: string option
|
||||
|
||||
|
||||
/// <summary>The MIME type of the image</summary>
|
||||
Type: string option
|
||||
|
||||
|
||||
/// <summary>The image width (in pixels)</summary>
|
||||
Width: int option
|
||||
|
||||
|
||||
/// <summary>The image height (in pixels)</summary>
|
||||
Height: int option
|
||||
|
||||
|
||||
/// <summary>Alternative text for the image</summary>
|
||||
Alt: string option
|
||||
} with
|
||||
|
||||
|
||||
/// <summary>An empty image file</summary>
|
||||
static member Empty =
|
||||
{ Url = ""
|
||||
@ -433,26 +447,43 @@ type OpenGraphImage = {
|
||||
Height = 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>
|
||||
[<CLIMutable>]
|
||||
type OpenGraphVideo = {
|
||||
/// <summary>The URL for this video</summary>
|
||||
Url: string
|
||||
|
||||
|
||||
/// <summary>The URL for this video for sites that require https</summary>
|
||||
SecureUrl: string option
|
||||
|
||||
|
||||
/// <summary>The MIME type of the video</summary>
|
||||
Type: string option
|
||||
|
||||
|
||||
/// <summary>The video width (in pixels)</summary>
|
||||
Width: int option
|
||||
|
||||
|
||||
/// <summary>The video height (in pixels)</summary>
|
||||
Height: int option
|
||||
} with
|
||||
|
||||
|
||||
/// <summary>An empty video file</summary>
|
||||
static member Empty =
|
||||
{ Url = ""
|
||||
@ -461,6 +492,22 @@ type OpenGraphVideo = {
|
||||
Width = 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>
|
||||
[<Struct>]
|
||||
@ -478,7 +525,7 @@ type OpenGraphType =
|
||||
| VideoOther
|
||||
| VideoTvShow
|
||||
| Website
|
||||
|
||||
|
||||
/// <summary>Parse a string into an OpenGraph type</summary>
|
||||
/// <param name="typ">The string to be parsed</param>
|
||||
/// <returns>The <c>OpenGraphType</c> parsed from the string</returns>
|
||||
@ -499,7 +546,7 @@ type OpenGraphType =
|
||||
| "video.tv_show" -> VideoTvShow
|
||||
| "website" -> Website
|
||||
| _ -> invalidArg (nameof typ) $"{typ} is not a valid OpenGraph type"
|
||||
|
||||
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Article -> "article"
|
||||
@ -517,36 +564,39 @@ type OpenGraphType =
|
||||
| Website -> "website"
|
||||
|
||||
|
||||
/// <summary>Top-level properties for OpenGraph</summary>
|
||||
/// <summary>Properties for OpenGraph</summary>
|
||||
[<CLIMutable>]
|
||||
type OpenGraphTopLevel = {
|
||||
|
||||
type OpenGraphProperties = {
|
||||
|
||||
/// <summary>The type of object represented</summary>
|
||||
Type: OpenGraphType
|
||||
|
||||
|
||||
/// <summary>An image representing the object</summary>
|
||||
Image: OpenGraphImage
|
||||
|
||||
|
||||
/// <summary>An audio file associated with the object</summary>
|
||||
Audio: OpenGraphAudio option
|
||||
|
||||
|
||||
/// <summary>A short description of the object</summary>
|
||||
Description: string option
|
||||
|
||||
|
||||
/// <summary>The article (a, an, the, etc.) which should be used to refer to this object</summary>
|
||||
Determiner: string option
|
||||
|
||||
|
||||
/// <summary>The primary locale of the content of the object</summary>
|
||||
Locale: string option
|
||||
|
||||
|
||||
/// <summary>Other locales in which the content of the object is available</summary>
|
||||
LocaleAlternate: string list option
|
||||
|
||||
|
||||
/// <summary>A video file assigned with the object</summary>
|
||||
Video: OpenGraphVideo option
|
||||
|
||||
/// <summary>Free-form items</summary>
|
||||
Other: MetaItem list option
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>A permanent link</summary>
|
||||
[<Struct>]
|
||||
type Permalink =
|
||||
|
@ -112,6 +112,9 @@ type DisplayPage = {
|
||||
|
||||
/// <summary>The metadata for the page</summary>
|
||||
Metadata: MetaItem list
|
||||
|
||||
/// <summary>The OpenGraph properties for the page</summary>
|
||||
OpenGraph: OpenGraphProperties option
|
||||
} with
|
||||
|
||||
/// <summary>Create a minimal display page (no text or metadata) from a database page</summary>
|
||||
@ -128,7 +131,8 @@ type DisplayPage = {
|
||||
IsInPageList = page.IsInPageList
|
||||
IsDefault = string page.Id = webLog.DefaultPage
|
||||
Text = ""
|
||||
Metadata = [] }
|
||||
Metadata = []
|
||||
OpenGraph = None }
|
||||
|
||||
/// <summary>Create a display page from a database page</summary>
|
||||
/// <param name="webLog">The web log to which the page belongs</param>
|
||||
@ -136,8 +140,9 @@ type DisplayPage = {
|
||||
/// <returns>A <c>DisplayPage</c> with text and metadata</returns>
|
||||
static member FromPage webLog page =
|
||||
{ DisplayPage.FromPageMinimal webLog page with
|
||||
Text = addBaseToRelativeUrls webLog.ExtraPath page.Text
|
||||
Metadata = page.Metadata
|
||||
Text = addBaseToRelativeUrls webLog.ExtraPath page.Text
|
||||
Metadata = page.Metadata
|
||||
OpenGraph = page.OpenGraph
|
||||
}
|
||||
|
||||
|
||||
@ -1165,6 +1170,9 @@ type PostListItem = {
|
||||
|
||||
/// <summary>Metadata for the post</summary>
|
||||
Metadata: MetaItem list
|
||||
|
||||
/// <summary>OpenGraph properties for the post</summary>
|
||||
OpenGraph: OpenGraphProperties option
|
||||
} with
|
||||
|
||||
/// <summary>Create a post list item from a post</summary>
|
||||
@ -1183,7 +1191,8 @@ type PostListItem = {
|
||||
CategoryIds = post.CategoryIds |> List.map string
|
||||
Tags = post.Tags
|
||||
Episode = post.Episode
|
||||
Metadata = post.Metadata }
|
||||
Metadata = post.Metadata
|
||||
OpenGraph = post.OpenGraph }
|
||||
|
||||
|
||||
/// <summary>View model for displaying posts</summary>
|
||||
|
@ -188,9 +188,35 @@ let parser =
|
||||
// Create various items in the page header based on the state of the page being generated
|
||||
it.RegisterEmptyTag("page_head",
|
||||
fun writer encoder context ->
|
||||
let app = context.App
|
||||
// let getBool name =
|
||||
// defaultArg (context.Environments[0].[name] |> Option.ofObj |> Option.map Convert.ToBoolean) false
|
||||
let app = context.App
|
||||
let attrEnc = System.Web.HttpUtility.HtmlAttributeEncode
|
||||
|
||||
// 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}">"""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user