Move OpenGraph property generation to models (#52)
- Add auto-OpenGraph field to web log - Only generate properties for posts/pages without them if this flag is set - Set flag to yes on v3 database migration - Add JSON converter for OpenGraph type - Add tests for models
This commit is contained in:
@@ -348,6 +348,9 @@ type WebLog = {
|
||||
|
||||
/// <summary>Redirect rules for this weblog</summary>
|
||||
RedirectRules: RedirectRule list
|
||||
|
||||
/// <summary>Whether to automatically apply OpenGraph properties to all pages / posts</summary>
|
||||
AutoOpenGraph: bool
|
||||
} with
|
||||
|
||||
/// <summary>An empty web log</summary>
|
||||
@@ -364,7 +367,8 @@ type WebLog = {
|
||||
Rss = RssOptions.Empty
|
||||
AutoHtmx = false
|
||||
Uploads = Database
|
||||
RedirectRules = [] }
|
||||
RedirectRules = []
|
||||
AutoOpenGraph = true }
|
||||
|
||||
/// <summary>
|
||||
/// Any extra path where this web log is hosted (blank if web log is hosted at the root of the domain)
|
||||
|
||||
@@ -19,6 +19,17 @@ module private Helpers =
|
||||
/// <summary>Pipeline with most extensions enabled</summary>
|
||||
let markdownPipeline = MarkdownPipelineBuilder().UseSmartyPants().UseAdvancedExtensions().UseColorCode().Build()
|
||||
|
||||
/// <summary>Derive a MIME type from the given URL and candidates</summary>
|
||||
/// <param name="url">The URL from which the MIME type should be derived</param>
|
||||
/// <param name="candidates">The candidates for the MIME type derivation</param>
|
||||
/// <returns><c>Some</c> with the type if it was derived, <c>None</c> otherwise</returns>
|
||||
let deriveMimeType (url: string) (candidates: System.Collections.Generic.IDictionary<string, string>) =
|
||||
match url.LastIndexOf '.' with
|
||||
| extIdx when extIdx >= 0 ->
|
||||
let ext = url[extIdx + 1..]
|
||||
if candidates.ContainsKey ext then Some candidates[ext] else None
|
||||
| _ -> None
|
||||
|
||||
|
||||
/// <summary>Functions to support NodaTime manipulation</summary>
|
||||
module Noda =
|
||||
@@ -401,6 +412,15 @@ type OpenGraphAudio = {
|
||||
SecureUrl = None
|
||||
Type = None }
|
||||
|
||||
/// <summary>MIME types we can derive from the file extension</summary>
|
||||
static member private DeriveTypes =
|
||||
[ "aac", "audio/aac"
|
||||
"mp3", "audio/mpeg"
|
||||
"oga", "audio/ogg"
|
||||
"wav", "audio/wav"
|
||||
"weba", "audio/webm" ]
|
||||
|> dict
|
||||
|
||||
/// <summary>The <c>meta</c> properties for this image</summary>
|
||||
member this.Properties = seq {
|
||||
yield ("og:audio", this.Url)
|
||||
@@ -411,8 +431,9 @@ type OpenGraphAudio = {
|
||||
match this.Type with
|
||||
| Some typ -> yield ("og:audio:type", typ)
|
||||
| None ->
|
||||
// TODO: derive mime type from extension
|
||||
()
|
||||
match deriveMimeType this.Url OpenGraphAudio.DeriveTypes with
|
||||
| Some it -> yield "og:audio:type", it
|
||||
| None -> ()
|
||||
}
|
||||
|
||||
|
||||
@@ -447,21 +468,36 @@ type OpenGraphImage = {
|
||||
Height = None
|
||||
Alt = None }
|
||||
|
||||
/// <summary>MIME types we can derive from the file extension</summary>
|
||||
static member private DeriveTypes =
|
||||
[ "bmp", "image/bmp"
|
||||
"gif", "image/gif"
|
||||
"ico", "image/vnd.microsoft.icon"
|
||||
"jpeg", "image/jpeg"
|
||||
"jpg", "image/jpeg"
|
||||
"png", "image/png"
|
||||
"svg", "image/svg+xml"
|
||||
"tif", "image/tiff"
|
||||
"tiff", "image/tiff"
|
||||
"webp", "image/webp" ]
|
||||
|> dict
|
||||
|
||||
/// <summary>The <c>meta</c> properties for this image</summary>
|
||||
member this.Properties = seq {
|
||||
yield ("og:image", this.Url)
|
||||
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)
|
||||
| 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)
|
||||
| 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 -> ()
|
||||
match deriveMimeType this.Url OpenGraphImage.DeriveTypes with
|
||||
| Some it -> yield "og:image:type", it
|
||||
| None -> ()
|
||||
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 -> ()
|
||||
}
|
||||
|
||||
|
||||
@@ -492,20 +528,30 @@ type OpenGraphVideo = {
|
||||
Width = None
|
||||
Height = None }
|
||||
|
||||
/// <summary>MIME types we can derive from the file extension</summary>
|
||||
static member private DeriveTypes =
|
||||
[ "avi", "video/x-msvideo"
|
||||
"mp4", "video/mp4"
|
||||
"mpeg", "video/mpeg"
|
||||
"ogv", "video/ogg"
|
||||
"webm", "video/webm" ]
|
||||
|> dict
|
||||
|
||||
/// <summary>The <c>meta</c> properties for this video</summary>
|
||||
member this.Properties = seq {
|
||||
yield ("og:video", this.Url)
|
||||
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)
|
||||
| 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)
|
||||
| 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 -> ()
|
||||
match deriveMimeType this.Url OpenGraphVideo.DeriveTypes with
|
||||
| Some it -> yield "og:video:type", it
|
||||
| None -> ()
|
||||
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 -> ()
|
||||
}
|
||||
|
||||
|
||||
@@ -567,7 +613,6 @@ type OpenGraphType =
|
||||
/// <summary>Properties for OpenGraph</summary>
|
||||
[<CLIMutable>]
|
||||
type OpenGraphProperties = {
|
||||
|
||||
/// <summary>The type of object represented</summary>
|
||||
Type: OpenGraphType
|
||||
|
||||
@@ -594,7 +639,34 @@ type OpenGraphProperties = {
|
||||
|
||||
/// <summary>Free-form items</summary>
|
||||
Other: MetaItem list option
|
||||
}
|
||||
} with
|
||||
|
||||
/// <summary>An empty set of OpenGraph properties</summary>
|
||||
static member Empty =
|
||||
{ Type = Article
|
||||
Image = OpenGraphImage.Empty
|
||||
Audio = None
|
||||
Description = None
|
||||
Determiner = None
|
||||
Locale = None
|
||||
LocaleAlternate = None
|
||||
Video = None
|
||||
Other = None }
|
||||
|
||||
/// <summary>The <c>meta</c> properties for this page or post</summary>
|
||||
member this.Properties = seq {
|
||||
yield "og:type", string this.Type
|
||||
yield! this.Image.Properties
|
||||
match this.Description with Some desc -> yield "og:description", desc | None -> ()
|
||||
match this.Determiner with Some det -> yield "og:determiner", det | None -> ()
|
||||
match this.Locale with Some loc -> yield "og:locale", loc | None -> ()
|
||||
match this.LocaleAlternate with
|
||||
| Some alt -> yield! alt |> List.map (fun it -> "og:locale:alternate", it)
|
||||
| None -> ()
|
||||
match this.Audio with Some audio -> yield! audio.Properties | None -> ()
|
||||
match this.Video with Some video -> yield! video.Properties | None -> ()
|
||||
match this.Other with Some oth -> yield! oth |> List.map (fun it -> it.Name, it.Value) | None -> ()
|
||||
}
|
||||
|
||||
|
||||
/// <summary>A permanent link</summary>
|
||||
|
||||
@@ -1249,36 +1249,41 @@ type SettingsModel = {
|
||||
|
||||
/// <summary>The default location for uploads</summary>
|
||||
Uploads: string
|
||||
|
||||
/// <summary>Whether to automatically apply OpenGraph properties to all pages and posts</summary>
|
||||
AutoOpenGraph: bool
|
||||
} with
|
||||
|
||||
/// <summary>Create a settings model from a web log</summary>
|
||||
/// <param name="webLog">The web log from which this model should be created</param>
|
||||
/// <returns>A populated <c>SettingsModel</c> instance</returns>
|
||||
static member FromWebLog(webLog: WebLog) =
|
||||
{ Name = webLog.Name
|
||||
Slug = webLog.Slug
|
||||
Subtitle = defaultArg webLog.Subtitle ""
|
||||
DefaultPage = webLog.DefaultPage
|
||||
PostsPerPage = webLog.PostsPerPage
|
||||
TimeZone = webLog.TimeZone
|
||||
ThemeId = string webLog.ThemeId
|
||||
AutoHtmx = webLog.AutoHtmx
|
||||
Uploads = string webLog.Uploads }
|
||||
{ Name = webLog.Name
|
||||
Slug = webLog.Slug
|
||||
Subtitle = defaultArg webLog.Subtitle ""
|
||||
DefaultPage = webLog.DefaultPage
|
||||
PostsPerPage = webLog.PostsPerPage
|
||||
TimeZone = webLog.TimeZone
|
||||
ThemeId = string webLog.ThemeId
|
||||
AutoHtmx = webLog.AutoHtmx
|
||||
Uploads = string webLog.Uploads
|
||||
AutoOpenGraph = webLog.AutoOpenGraph }
|
||||
|
||||
/// <summary>Update a web log with settings from the form</summary>
|
||||
/// <param name="webLog">The web log to be updated</param>
|
||||
/// <returns>The web log, updated with the value from this model</returns>
|
||||
member this.Update(webLog: WebLog) =
|
||||
{ webLog with
|
||||
Name = this.Name
|
||||
Slug = this.Slug
|
||||
Subtitle = if this.Subtitle = "" then None else Some this.Subtitle
|
||||
DefaultPage = this.DefaultPage
|
||||
PostsPerPage = this.PostsPerPage
|
||||
TimeZone = this.TimeZone
|
||||
ThemeId = ThemeId this.ThemeId
|
||||
AutoHtmx = this.AutoHtmx
|
||||
Uploads = UploadDestination.Parse this.Uploads }
|
||||
Name = this.Name
|
||||
Slug = this.Slug
|
||||
Subtitle = if this.Subtitle = "" then None else Some this.Subtitle
|
||||
DefaultPage = this.DefaultPage
|
||||
PostsPerPage = this.PostsPerPage
|
||||
TimeZone = this.TimeZone
|
||||
ThemeId = ThemeId this.ThemeId
|
||||
AutoHtmx = this.AutoHtmx
|
||||
Uploads = UploadDestination.Parse this.Uploads
|
||||
AutoOpenGraph = this.AutoOpenGraph }
|
||||
|
||||
|
||||
/// <summary>View model for uploading a file</summary>
|
||||
|
||||
Reference in New Issue
Block a user