diff --git a/src/MyWebLog.Domain/DataTypes.fs b/src/MyWebLog.Domain/DataTypes.fs
index e1d6463..37abd69 100644
--- a/src/MyWebLog.Domain/DataTypes.fs
+++ b/src/MyWebLog.Domain/DataTypes.fs
@@ -120,6 +120,9 @@ type Page = {
/// Revisions of this page
Revisions: Revision list
+
+ /// Common OpenGraph information for this post
+ OpenGraph: OpenGraphProperties option
} with
/// An empty page
@@ -136,7 +139,8 @@ type Page = {
Text = ""
Metadata = []
PriorPermalinks = []
- Revisions = [] }
+ Revisions = []
+ OpenGraph = None }
/// A web log post
@@ -189,6 +193,9 @@ type Post = {
/// The revisions for this post
Revisions: Revision list
+
+ /// OpenGraph information for this post
+ OpenGraph: OpenGraphProperties option
} with
/// An empty post
@@ -208,7 +215,8 @@ type Post = {
Episode = None
Metadata = []
PriorPermalinks = []
- Revisions = [] }
+ Revisions = []
+ OpenGraph = None }
///
diff --git a/src/MyWebLog.Domain/SupportTypes.fs b/src/MyWebLog.Domain/SupportTypes.fs
index 775c916..b39d4ff 100644
--- a/src/MyWebLog.Domain/SupportTypes.fs
+++ b/src/MyWebLog.Domain/SupportTypes.fs
@@ -387,43 +387,57 @@ type Revision = {
type OpenGraphAudio = {
/// The URL for this audio file
Url: string
-
+
/// The URL for this audio file for sites that require https
SecureUrl: string option
-
+
/// The MIME type of the audio file
Type: string option
} with
-
+
/// An empty audio file
static member Empty =
{ Url = ""
SecureUrl = None
Type = None }
+ /// The meta properties for this image
+ 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
+ ()
+ }
+
/// Properties for an OpenGraph image
[]
type OpenGraphImage = {
/// The URL for this image
Url: string
-
+
/// The URL for this image for sites that require https
SecureUrl: string option
-
+
/// The MIME type of the image
Type: string option
-
+
/// The image width (in pixels)
Width: int option
-
+
/// The image height (in pixels)
Height: int option
-
+
/// Alternative text for the image
Alt: string option
} with
-
+
/// An empty image file
static member Empty =
{ Url = ""
@@ -433,26 +447,43 @@ type OpenGraphImage = {
Height = None
Alt = None }
+ /// The meta properties for this image
+ 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 -> ()
+ }
+
/// Properties for an OpenGraph video
[]
type OpenGraphVideo = {
/// The URL for this video
Url: string
-
+
/// The URL for this video for sites that require https
SecureUrl: string option
-
+
/// The MIME type of the video
Type: string option
-
+
/// The video width (in pixels)
Width: int option
-
+
/// The video height (in pixels)
Height: int option
} with
-
+
/// An empty video file
static member Empty =
{ Url = ""
@@ -461,6 +492,22 @@ type OpenGraphVideo = {
Width = None
Height = None }
+ /// The meta properties for this video
+ 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 -> ()
+ }
+
/// Valid og:type values
[]
@@ -478,7 +525,7 @@ type OpenGraphType =
| VideoOther
| VideoTvShow
| Website
-
+
/// Parse a string into an OpenGraph type
/// The string to be parsed
/// The OpenGraphType parsed from the string
@@ -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"
-/// Top-level properties for OpenGraph
+/// Properties for OpenGraph
[]
-type OpenGraphTopLevel = {
-
+type OpenGraphProperties = {
+
/// The type of object represented
Type: OpenGraphType
-
+
/// An image representing the object
Image: OpenGraphImage
-
+
/// An audio file associated with the object
Audio: OpenGraphAudio option
-
+
/// A short description of the object
Description: string option
-
+
/// The article (a, an, the, etc.) which should be used to refer to this object
Determiner: string option
-
+
/// The primary locale of the content of the object
Locale: string option
-
+
/// Other locales in which the content of the object is available
LocaleAlternate: string list option
-
+
/// A video file assigned with the object
Video: OpenGraphVideo option
+
+ /// Free-form items
+ Other: MetaItem list option
}
-
+
/// A permanent link
[]
type Permalink =
diff --git a/src/MyWebLog.Domain/ViewModels.fs b/src/MyWebLog.Domain/ViewModels.fs
index 3972f4e..9d81220 100644
--- a/src/MyWebLog.Domain/ViewModels.fs
+++ b/src/MyWebLog.Domain/ViewModels.fs
@@ -112,6 +112,9 @@ type DisplayPage = {
/// The metadata for the page
Metadata: MetaItem list
+
+ /// The OpenGraph properties for the page
+ OpenGraph: OpenGraphProperties option
} with
/// Create a minimal display page (no text or metadata) from a database page
@@ -128,7 +131,8 @@ type DisplayPage = {
IsInPageList = page.IsInPageList
IsDefault = string page.Id = webLog.DefaultPage
Text = ""
- Metadata = [] }
+ Metadata = []
+ OpenGraph = None }
/// Create a display page from a database page
/// The web log to which the page belongs
@@ -136,8 +140,9 @@ type DisplayPage = {
/// A DisplayPage with text and metadata
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 = {
/// Metadata for the post
Metadata: MetaItem list
+
+ /// OpenGraph properties for the post
+ OpenGraph: OpenGraphProperties option
} with
/// Create a post list item from a post
@@ -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 }
/// View model for displaying posts
diff --git a/src/MyWebLog/Template.fs b/src/MyWebLog/Template.fs
index feab8f1..5529b39 100644
--- a/src/MyWebLog/Template.fs
+++ b/src/MyWebLog/Template.fs
@@ -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}"""
+ 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}"""