Clean up database names (#21)

- Moved user edit to "my info" (#19)
This commit is contained in:
2022-07-18 20:05:10 -04:00
parent 5fb3a73dcf
commit 7eaad4a076
36 changed files with 1993 additions and 1745 deletions

View File

@@ -7,22 +7,22 @@ open MyWebLog
[<CLIMutable; NoComparison; NoEquality>]
type Category =
{ /// The ID of the category
id : CategoryId
Id : CategoryId
/// The ID of the web log to which the category belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The displayed name
name : string
Name : string
/// The slug (used in category URLs)
slug : string
Slug : string
/// A longer description of the category
description : string option
Description : string option
/// The parent ID of this category (if a subcategory)
parentId : CategoryId option
ParentId : CategoryId option
}
/// Functions to support categories
@@ -30,12 +30,12 @@ module Category =
/// An empty category
let empty =
{ id = CategoryId.empty
webLogId = WebLogId.empty
name = ""
slug = ""
description = None
parentId = None
{ Id = CategoryId.empty
WebLogId = WebLogId.empty
Name = ""
Slug = ""
Description = None
ParentId = None
}
@@ -43,31 +43,31 @@ module Category =
[<CLIMutable; NoComparison; NoEquality>]
type Comment =
{ /// The ID of the comment
id : CommentId
Id : CommentId
/// The ID of the post to which this comment applies
postId : PostId
PostId : PostId
/// The ID of the comment to which this comment is a reply
inReplyToId : CommentId option
InReplyToId : CommentId option
/// The name of the commentor
name : string
Name : string
/// The e-mail address of the commentor
email : string
Email : string
/// The URL of the commentor's personal website
url : string option
Url : string option
/// The status of the comment
status : CommentStatus
Status : CommentStatus
/// When the comment was posted
postedOn : DateTime
PostedOn : DateTime
/// The text of the comment
text : string
Text : string
}
/// Functions to support comments
@@ -75,15 +75,15 @@ module Comment =
/// An empty comment
let empty =
{ id = CommentId.empty
postId = PostId.empty
inReplyToId = None
name = ""
email = ""
url = None
status = Pending
postedOn = DateTime.UtcNow
text = ""
{ Id = CommentId.empty
PostId = PostId.empty
InReplyToId = None
Name = ""
Email = ""
Url = None
Status = Pending
PostedOn = DateTime.UtcNow
Text = ""
}
@@ -91,43 +91,43 @@ module Comment =
[<CLIMutable; NoComparison; NoEquality>]
type Page =
{ /// The ID of this page
id : PageId
Id : PageId
/// The ID of the web log to which this page belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The ID of the author of this page
authorId : WebLogUserId
AuthorId : WebLogUserId
/// The title of the page
title : string
Title : string
/// The link at which this page is displayed
permalink : Permalink
Permalink : Permalink
/// When this page was published
publishedOn : DateTime
PublishedOn : DateTime
/// When this page was last updated
updatedOn : DateTime
UpdatedOn : DateTime
/// Whether this page shows as part of the web log's navigation
showInPageList : bool
IsInPageList : bool
/// The template to use when rendering this page
template : string option
Template : string option
/// The current text of the page
text : string
Text : string
/// Metadata for this page
metadata : MetaItem list
Metadata : MetaItem list
/// Permalinks at which this page may have been previously served (useful for migrated content)
priorPermalinks : Permalink list
PriorPermalinks : Permalink list
/// Revisions of this page
revisions : Revision list
Revisions : Revision list
}
/// Functions to support pages
@@ -135,19 +135,19 @@ module Page =
/// An empty page
let empty =
{ id = PageId.empty
webLogId = WebLogId.empty
authorId = WebLogUserId.empty
title = ""
permalink = Permalink.empty
publishedOn = DateTime.MinValue
updatedOn = DateTime.MinValue
showInPageList = false
template = None
text = ""
metadata = []
priorPermalinks = []
revisions = []
{ Id = PageId.empty
WebLogId = WebLogId.empty
AuthorId = WebLogUserId.empty
Title = ""
Permalink = Permalink.empty
PublishedOn = DateTime.MinValue
UpdatedOn = DateTime.MinValue
IsInPageList = false
Template = None
Text = ""
Metadata = []
PriorPermalinks = []
Revisions = []
}
@@ -155,52 +155,52 @@ module Page =
[<CLIMutable; NoComparison; NoEquality>]
type Post =
{ /// The ID of this post
id : PostId
Id : PostId
/// The ID of the web log to which this post belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The ID of the author of this post
authorId : WebLogUserId
AuthorId : WebLogUserId
/// The status
status : PostStatus
Status : PostStatus
/// The title
title : string
Title : string
/// The link at which the post resides
permalink : Permalink
Permalink : Permalink
/// The instant on which the post was originally published
publishedOn : DateTime option
PublishedOn : DateTime option
/// The instant on which the post was last updated
updatedOn : DateTime
UpdatedOn : DateTime
/// The template to use in displaying the post
template : string option
Template : string option
/// The text of the post in HTML (ready to display) format
text : string
Text : string
/// The Ids of the categories to which this is assigned
categoryIds : CategoryId list
CategoryIds : CategoryId list
/// The tags for the post
tags : string list
Tags : string list
/// Podcast episode information for this post
episode : Episode option
Episode : Episode option
/// Metadata for the post
metadata : MetaItem list
Metadata : MetaItem list
/// Permalinks at which this post may have been previously served (useful for migrated content)
priorPermalinks : Permalink list
PriorPermalinks : Permalink list
/// The revisions for this post
revisions : Revision list
Revisions : Revision list
}
/// Functions to support posts
@@ -208,38 +208,38 @@ module Post =
/// An empty post
let empty =
{ id = PostId.empty
webLogId = WebLogId.empty
authorId = WebLogUserId.empty
status = Draft
title = ""
permalink = Permalink.empty
publishedOn = None
updatedOn = DateTime.MinValue
text = ""
template = None
categoryIds = []
tags = []
episode = None
metadata = []
priorPermalinks = []
revisions = []
{ Id = PostId.empty
WebLogId = WebLogId.empty
AuthorId = WebLogUserId.empty
Status = Draft
Title = ""
Permalink = Permalink.empty
PublishedOn = None
UpdatedOn = DateTime.MinValue
Text = ""
Template = None
CategoryIds = []
Tags = []
Episode = None
Metadata = []
PriorPermalinks = []
Revisions = []
}
/// A mapping between a tag and its URL value, used to translate restricted characters (ex. "#1" -> "number-1")
type TagMap =
{ /// The ID of this tag mapping
id : TagMapId
Id : TagMapId
/// The ID of the web log to which this tag mapping belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The tag which should be mapped to a different value in links
tag : string
Tag : string
/// The value by which the tag should be linked
urlValue : string
UrlValue : string
}
/// Functions to support tag mappings
@@ -247,26 +247,26 @@ module TagMap =
/// An empty tag mapping
let empty =
{ id = TagMapId.empty
webLogId = WebLogId.empty
tag = ""
urlValue = ""
{ Id = TagMapId.empty
WebLogId = WebLogId.empty
Tag = ""
UrlValue = ""
}
/// A theme
type Theme =
{ /// The ID / path of the theme
id : ThemeId
Id : ThemeId
/// A long name of the theme
name : string
Name : string
/// The version of the theme
version : string
Version : string
/// The templates for this theme
templates: ThemeTemplate list
Templates: ThemeTemplate list
}
/// Functions to support themes
@@ -274,10 +274,10 @@ module Theme =
/// An empty theme
let empty =
{ id = ThemeId ""
name = ""
version = ""
templates = []
{ Id = ThemeId ""
Name = ""
Version = ""
Templates = []
}
@@ -285,32 +285,42 @@ module Theme =
type ThemeAsset =
{
/// The ID of the asset (consists of theme and path)
id : ThemeAssetId
Id : ThemeAssetId
/// The updated date (set from the file date from the ZIP archive)
updatedOn : DateTime
UpdatedOn : DateTime
/// The data for the asset
data : byte[]
Data : byte[]
}
/// Functions to support theme assets
module ThemeAsset =
/// An empty theme asset
let empty =
{ Id = ThemeAssetId (ThemeId "", "")
UpdatedOn = DateTime.MinValue
Data = [||]
}
/// An uploaded file
type Upload =
{ /// The ID of the upload
id : UploadId
Id : UploadId
/// The ID of the web log to which this upload belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The link at which this upload is served
path : Permalink
Path : Permalink
/// The updated date/time for this upload
updatedOn : DateTime
UpdatedOn : DateTime
/// The data for the upload
data : byte[]
Data : byte[]
}
/// Functions to support uploaded files
@@ -318,11 +328,11 @@ module Upload =
/// An empty upload
let empty = {
id = UploadId.empty
webLogId = WebLogId.empty
path = Permalink.empty
updatedOn = DateTime.MinValue
data = [||]
Id = UploadId.empty
WebLogId = WebLogId.empty
Path = Permalink.empty
UpdatedOn = DateTime.MinValue
Data = [||]
}
@@ -330,40 +340,40 @@ module Upload =
[<CLIMutable; NoComparison; NoEquality>]
type WebLog =
{ /// The ID of the web log
id : WebLogId
Id : WebLogId
/// The name of the web log
name : string
Name : string
/// The slug of the web log
slug : string
Slug : string
/// A subtitle for the web log
subtitle : string option
Subtitle : string option
/// The default page ("posts" or a page Id)
defaultPage : string
DefaultPage : string
/// The number of posts to display on pages of posts
postsPerPage : int
PostsPerPage : int
/// The path of the theme (within /themes)
themePath : string
/// The ID of the theme (also the path within /themes)
ThemeId : ThemeId
/// The URL base
urlBase : string
UrlBase : string
/// The time zone in which dates/times should be displayed
timeZone : string
TimeZone : string
/// The RSS options for this web log
rss : RssOptions
Rss : RssOptions
/// Whether to automatically load htmx
autoHtmx : bool
AutoHtmx : bool
/// Where uploads are placed
uploads : UploadDestination
Uploads : UploadDestination
}
/// Functions to support web logs
@@ -371,29 +381,29 @@ module WebLog =
/// An empty web log
let empty =
{ id = WebLogId.empty
name = ""
slug = ""
subtitle = None
defaultPage = ""
postsPerPage = 10
themePath = "default"
urlBase = ""
timeZone = ""
rss = RssOptions.empty
autoHtmx = false
uploads = Database
{ Id = WebLogId.empty
Name = ""
Slug = ""
Subtitle = None
DefaultPage = ""
PostsPerPage = 10
ThemeId = ThemeId "default"
UrlBase = ""
TimeZone = ""
Rss = RssOptions.empty
AutoHtmx = false
Uploads = Database
}
/// Get the host (including scheme) and extra path from the URL base
let hostAndPath webLog =
let scheme = webLog.urlBase.Split "://"
let scheme = webLog.UrlBase.Split "://"
let host = scheme[1].Split "/"
$"{scheme[0]}://{host[0]}", if host.Length > 1 then $"""/{String.Join ("/", host |> Array.skip 1)}""" else ""
/// Generate an absolute URL for the given link
let absoluteUrl webLog permalink =
$"{webLog.urlBase}/{Permalink.toString permalink}"
$"{webLog.UrlBase}/{Permalink.toString permalink}"
/// Generate a relative URL for the given link
let relativeUrl webLog permalink =
@@ -403,47 +413,47 @@ module WebLog =
/// Convert a UTC date/time to the web log's local date/time
let localTime webLog (date : DateTime) =
TimeZoneInfo.ConvertTimeFromUtc
(DateTime (date.Ticks, DateTimeKind.Utc), TimeZoneInfo.FindSystemTimeZoneById webLog.timeZone)
(DateTime (date.Ticks, DateTimeKind.Utc), TimeZoneInfo.FindSystemTimeZoneById webLog.TimeZone)
/// A user of the web log
[<CLIMutable; NoComparison; NoEquality>]
type WebLogUser =
{ /// The ID of the user
id : WebLogUserId
Id : WebLogUserId
/// The ID of the web log to which this user belongs
webLogId : WebLogId
WebLogId : WebLogId
/// The user name (e-mail address)
userName : string
Email : string
/// The user's first name
firstName : string
FirstName : string
/// The user's last name
lastName : string
LastName : string
/// The user's preferred name
preferredName : string
PreferredName : string
/// The hash of the user's password
passwordHash : string
PasswordHash : string
/// Salt used to calculate the user's password hash
salt : Guid
Salt : Guid
/// The URL of the user's personal site
url : string option
Url : string option
/// The user's access level
accessLevel : AccessLevel
AccessLevel : AccessLevel
/// When the user was created
createdOn : DateTime
CreatedOn : DateTime
/// When the user last logged on
lastSeenOn : DateTime option
LastSeenOn : DateTime option
}
/// Functions to support web log users
@@ -451,27 +461,27 @@ module WebLogUser =
/// An empty web log user
let empty =
{ id = WebLogUserId.empty
webLogId = WebLogId.empty
userName = ""
firstName = ""
lastName = ""
preferredName = ""
passwordHash = ""
salt = Guid.Empty
url = None
accessLevel = Author
createdOn = DateTime.UnixEpoch
lastSeenOn = None
{ Id = WebLogUserId.empty
WebLogId = WebLogId.empty
Email = ""
FirstName = ""
LastName = ""
PreferredName = ""
PasswordHash = ""
Salt = Guid.Empty
Url = None
AccessLevel = Author
CreatedOn = DateTime.UnixEpoch
LastSeenOn = None
}
/// Get the user's displayed name
let displayName user =
let name =
seq { match user.preferredName with "" -> user.firstName | n -> n; " "; user.lastName }
seq { match user.PreferredName with "" -> user.FirstName | n -> n; " "; user.LastName }
|> Seq.reduce (+)
name.Trim ()
/// Does a user have the required access level?
let hasAccess level user =
AccessLevel.hasAccess level user.accessLevel
AccessLevel.hasAccess level user.AccessLevel

View File

@@ -8,8 +8,8 @@ module private Helpers =
/// Create a new ID (short GUID)
// https://www.madskristensen.net/blog/A-shorter-and-URL-friendly-GUID
let newId() =
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('/', '_').Replace('+', '-').Substring (0, 22)
let newId () =
Convert.ToBase64String(Guid.NewGuid().ToByteArray ()).Replace('/', '_').Replace('+', '-').Substring (0, 22)
/// A user's access level
@@ -140,55 +140,55 @@ module ExplicitRating =
/// A podcast episode
type Episode =
{ /// The URL to the media file for the episode (may be permalink)
media : string
Media : string
/// The length of the media file, in bytes
length : int64
Length : int64
/// The duration of the episode
duration : TimeSpan option
Duration : TimeSpan option
/// The media type of the file (overrides podcast default if present)
mediaType : string option
MediaType : string option
/// The URL to the image file for this episode (overrides podcast image if present, may be permalink)
imageUrl : string option
ImageUrl : string option
/// A subtitle for this episode
subtitle : string option
Subtitle : string option
/// This episode's explicit rating (overrides podcast rating if present)
explicit : ExplicitRating option
Explicit : ExplicitRating option
/// A link to a chapter file
chapterFile : string option
ChapterFile : string option
/// The MIME type for the chapter file
chapterType : string option
ChapterType : string option
/// The URL for the transcript of the episode (may be permalink)
transcriptUrl : string option
TranscriptUrl : string option
/// The MIME type of the transcript
transcriptType : string option
TranscriptType : string option
/// The language in which the transcript is written
transcriptLang : string option
TranscriptLang : string option
/// If true, the transcript will be declared (in the feed) to be a captions file
transcriptCaptions : bool option
TranscriptCaptions : bool option
/// The season number (for serialized podcasts)
seasonNumber : int option
SeasonNumber : int option
/// A description of the season
seasonDescription : string option
SeasonDescription : string option
/// The episode number
episodeNumber : double option
EpisodeNumber : double option
/// A description of the episode
episodeDescription : string option
EpisodeDescription : string option
}
/// Functions to support episodes
@@ -196,23 +196,23 @@ module Episode =
/// An empty episode
let empty = {
media = ""
length = 0L
duration = None
mediaType = None
imageUrl = None
subtitle = None
explicit = None
chapterFile = None
chapterType = None
transcriptUrl = None
transcriptType = None
transcriptLang = None
transcriptCaptions = None
seasonNumber = None
seasonDescription = None
episodeNumber = None
episodeDescription = None
Media = ""
Length = 0L
Duration = None
MediaType = None
ImageUrl = None
Subtitle = None
Explicit = None
ChapterFile = None
ChapterType = None
TranscriptUrl = None
TranscriptType = None
TranscriptLang = None
TranscriptCaptions = None
SeasonNumber = None
SeasonDescription = None
EpisodeNumber = None
EpisodeDescription = None
}
@@ -256,10 +256,10 @@ module MarkupText =
[<CLIMutable; NoComparison; NoEquality>]
type MetaItem =
{ /// The name of the metadata value
name : string
Name : string
/// The metadata value
value : string
Value : string
}
/// Functions to support metadata items
@@ -267,17 +267,17 @@ module MetaItem =
/// An empty metadata item
let empty =
{ name = ""; value = "" }
{ Name = ""; Value = "" }
/// A revision of a page or post
[<CLIMutable; NoComparison; NoEquality>]
type Revision =
{ /// When this revision was saved
asOf : DateTime
AsOf : DateTime
/// The text of the revision
text : MarkupText
Text : MarkupText
}
/// Functions to support revisions
@@ -285,8 +285,8 @@ module Revision =
/// An empty revision
let empty =
{ asOf = DateTime.UtcNow
text = Html ""
{ AsOf = DateTime.UtcNow
Text = Html ""
}
@@ -436,68 +436,68 @@ module CustomFeedSource =
/// Options for a feed that describes a podcast
type PodcastOptions =
{ /// The title of the podcast
title : string
Title : string
/// A subtitle for the podcast
subtitle : string option
Subtitle : string option
/// The number of items in the podcast feed
itemsInFeed : int
ItemsInFeed : int
/// A summary of the podcast (iTunes field)
summary : string
Summary : string
/// The display name of the podcast author (iTunes field)
displayedAuthor : string
DisplayedAuthor : string
/// The e-mail address of the user who registered the podcast at iTunes
email : string
Email : string
/// The link to the image for the podcast
imageUrl : Permalink
ImageUrl : Permalink
/// The category from iTunes under which this podcast is categorized
iTunesCategory : string
/// The category from Apple Podcasts (iTunes) under which this podcast is categorized
AppleCategory : string
/// A further refinement of the categorization of this podcast (iTunes field / values)
iTunesSubcategory : string option
/// A further refinement of the categorization of this podcast (Apple Podcasts/iTunes field / values)
AppleSubcategory : string option
/// The explictness rating (iTunes field)
explicit : ExplicitRating
Explicit : ExplicitRating
/// The default media type for files in this podcast
defaultMediaType : string option
DefaultMediaType : string option
/// The base URL for relative URL media files for this podcast (optional; defaults to web log base)
mediaBaseUrl : string option
MediaBaseUrl : string option
/// A GUID for this podcast
guid : Guid option
PodcastGuid : Guid option
/// A URL at which information on supporting the podcast may be found (supports permalinks)
fundingUrl : string option
FundingUrl : string option
/// The text to be displayed in the funding item within the feed
fundingText : string option
FundingText : string option
/// The medium (what the podcast IS, not what it is ABOUT)
medium : PodcastMedium option
Medium : PodcastMedium option
}
/// A custom feed
type CustomFeed =
{ /// The ID of the custom feed
id : CustomFeedId
Id : CustomFeedId
/// The source for the custom feed
source : CustomFeedSource
Source : CustomFeedSource
/// The path for the custom feed
path : Permalink
Path : Permalink
/// Podcast options, if the feed defines a podcast
podcast : PodcastOptions option
Podcast : PodcastOptions option
}
/// Functions to support custom feeds
@@ -505,10 +505,10 @@ module CustomFeed =
/// An empty custom feed
let empty =
{ id = CustomFeedId ""
source = Category (CategoryId "")
path = Permalink ""
podcast = None
{ Id = CustomFeedId ""
Source = Category (CategoryId "")
Path = Permalink ""
Podcast = None
}
@@ -516,25 +516,25 @@ module CustomFeed =
[<CLIMutable; NoComparison; NoEquality>]
type RssOptions =
{ /// Whether the site feed of posts is enabled
feedEnabled : bool
IsFeedEnabled : bool
/// The name of the file generated for the site feed
feedName : string
FeedName : string
/// Override the "posts per page" setting for the site feed
itemsInFeed : int option
ItemsInFeed : int option
/// Whether feeds are enabled for all categories
categoryEnabled : bool
IsCategoryEnabled : bool
/// Whether feeds are enabled for all tags
tagEnabled : bool
IsTagEnabled : bool
/// A copyright string to be placed in all feeds
copyright : string option
Copyright : string option
/// Custom feeds for this web log
customFeeds: CustomFeed list
CustomFeeds: CustomFeed list
}
/// Functions to support RSS options
@@ -542,13 +542,13 @@ module RssOptions =
/// An empty set of RSS options
let empty =
{ feedEnabled = true
feedName = "feed.xml"
itemsInFeed = None
categoryEnabled = true
tagEnabled = true
copyright = None
customFeeds = []
{ IsFeedEnabled = true
FeedName = "feed.xml"
ItemsInFeed = None
IsCategoryEnabled = true
IsTagEnabled = true
Copyright = None
CustomFeeds = []
}
@@ -594,10 +594,10 @@ module ThemeAssetId =
/// A template for a theme
type ThemeTemplate =
{ /// The name of the template
name : string
Name : string
/// The text of the template
text : string
Text : string
}
@@ -610,13 +610,13 @@ type UploadDestination =
module UploadDestination =
/// Convert an upload destination to its string representation
let toString = function Database -> "database" | Disk -> "disk"
let toString = function Database -> "Database" | Disk -> "Disk"
/// Parse an upload destination from its string representation
let parse value =
match value with
| "database" -> Database
| "disk" -> Disk
| "Database" -> Database
| "Disk" -> Disk
| it -> invalidOp $"{it} is not a valid upload destination"

View File

@@ -76,13 +76,13 @@ type DisplayCustomFeed =
/// Create a display version from a custom feed
static member fromFeed (cats : DisplayCategory[]) (feed : CustomFeed) : DisplayCustomFeed =
let source =
match feed.source with
match feed.Source with
| Category (CategoryId catId) -> $"Category: {(cats |> Array.find (fun cat -> cat.Id = catId)).Name}"
| Tag tag -> $"Tag: {tag}"
{ Id = CustomFeedId.toString feed.id
{ Id = CustomFeedId.toString feed.Id
Source = source
Path = Permalink.toString feed.path
IsPodcast = Option.isSome feed.podcast
Path = Permalink.toString feed.Path
IsPodcast = Option.isSome feed.Podcast
}
@@ -108,7 +108,7 @@ type DisplayPage =
UpdatedOn : DateTime
/// Whether this page shows as part of the web log's navigation
ShowInPageList : bool
IsInPageList : bool
/// Is this the default page?
IsDefault : bool
@@ -122,33 +122,33 @@ type DisplayPage =
/// Create a minimal display page (no text or metadata) from a database page
static member fromPageMinimal webLog (page : Page) =
let pageId = PageId.toString page.id
{ Id = pageId
AuthorId = WebLogUserId.toString page.authorId
Title = page.title
Permalink = Permalink.toString page.permalink
PublishedOn = page.publishedOn
UpdatedOn = page.updatedOn
ShowInPageList = page.showInPageList
IsDefault = pageId = webLog.defaultPage
Text = ""
Metadata = []
let pageId = PageId.toString page.Id
{ Id = pageId
AuthorId = WebLogUserId.toString page.AuthorId
Title = page.Title
Permalink = Permalink.toString page.Permalink
PublishedOn = page.PublishedOn
UpdatedOn = page.UpdatedOn
IsInPageList = page.IsInPageList
IsDefault = pageId = webLog.DefaultPage
Text = ""
Metadata = []
}
/// Create a display page from a database page
static member fromPage webLog (page : Page) =
let _, extra = WebLog.hostAndPath webLog
let pageId = PageId.toString page.id
{ Id = pageId
AuthorId = WebLogUserId.toString page.authorId
Title = page.title
Permalink = Permalink.toString page.permalink
PublishedOn = page.publishedOn
UpdatedOn = page.updatedOn
ShowInPageList = page.showInPageList
IsDefault = pageId = webLog.defaultPage
Text = if extra = "" then page.text else page.text.Replace ("href=\"/", $"href=\"{extra}/")
Metadata = page.metadata
let pageId = PageId.toString page.Id
{ Id = pageId
AuthorId = WebLogUserId.toString page.AuthorId
Title = page.Title
Permalink = Permalink.toString page.Permalink
PublishedOn = page.PublishedOn
UpdatedOn = page.UpdatedOn
IsInPageList = page.IsInPageList
IsDefault = pageId = webLog.DefaultPage
Text = if extra = "" then page.Text else page.Text.Replace ("href=\"/", $"href=\"{extra}/")
Metadata = page.Metadata
}
@@ -168,9 +168,9 @@ with
/// Create a display revision from an actual revision
static member fromRevision webLog (rev : Revision) =
{ AsOf = rev.asOf
AsOfLocal = WebLog.localTime webLog rev.asOf
Format = MarkupText.sourceType rev.text
{ AsOf = rev.AsOf
AsOfLocal = WebLog.localTime webLog rev.AsOf
Format = MarkupText.sourceType rev.Text
}
@@ -197,12 +197,12 @@ type DisplayUpload =
/// Create a display uploaded file
static member fromUpload webLog source (upload : Upload) =
let path = Permalink.toString upload.path
let path = Permalink.toString upload.Path
let name = Path.GetFileName path
{ Id = UploadId.toString upload.id
{ Id = UploadId.toString upload.Id
Name = name
Path = path.Replace (name, "")
UpdatedOn = Some (WebLog.localTime webLog upload.updatedOn)
UpdatedOn = Some (WebLog.localTime webLog upload.UpdatedOn)
Source = UploadDestination.toString source
}
@@ -228,11 +228,11 @@ type EditCategoryModel =
/// Create an edit model from an existing category
static member fromCategory (cat : Category) =
{ CategoryId = CategoryId.toString cat.id
Name = cat.name
Slug = cat.slug
Description = defaultArg cat.description ""
ParentId = cat.parentId |> Option.map CategoryId.toString |> Option.defaultValue ""
{ CategoryId = CategoryId.toString cat.Id
Name = cat.Name
Slug = cat.Slug
Description = defaultArg cat.Description ""
ParentId = cat.ParentId |> Option.map CategoryId.toString |> Option.defaultValue ""
}
@@ -275,11 +275,11 @@ type EditCustomFeedModel =
/// The link to the image for the podcast
ImageUrl : string
/// The category from iTunes under which this podcast is categorized
iTunesCategory : string
/// The category from Apple Podcasts (iTunes) under which this podcast is categorized
AppleCategory : string
/// A further refinement of the categorization of this podcast (iTunes field / values)
iTunesSubcategory : string
/// A further refinement of the categorization of this podcast (Apple Podcasts/iTunes field / values)
AppleSubcategory : string
/// The explictness rating (iTunes field)
Explicit : string
@@ -305,92 +305,122 @@ type EditCustomFeedModel =
/// An empty custom feed model
static member empty =
{ Id = ""
SourceType = "category"
SourceValue = ""
Path = ""
IsPodcast = false
Title = ""
Subtitle = ""
ItemsInFeed = 25
Summary = ""
DisplayedAuthor = ""
Email = ""
ImageUrl = ""
iTunesCategory = ""
iTunesSubcategory = ""
Explicit = "no"
DefaultMediaType = "audio/mpeg"
MediaBaseUrl = ""
FundingUrl = ""
FundingText = ""
PodcastGuid = ""
Medium = ""
{ Id = ""
SourceType = "category"
SourceValue = ""
Path = ""
IsPodcast = false
Title = ""
Subtitle = ""
ItemsInFeed = 25
Summary = ""
DisplayedAuthor = ""
Email = ""
ImageUrl = ""
AppleCategory = ""
AppleSubcategory = ""
Explicit = "no"
DefaultMediaType = "audio/mpeg"
MediaBaseUrl = ""
FundingUrl = ""
FundingText = ""
PodcastGuid = ""
Medium = ""
}
/// Create a model from a custom feed
static member fromFeed (feed : CustomFeed) =
let rss =
{ EditCustomFeedModel.empty with
Id = CustomFeedId.toString feed.id
SourceType = match feed.source with Category _ -> "category" | Tag _ -> "tag"
SourceValue = match feed.source with Category (CategoryId catId) -> catId | Tag tag -> tag
Path = Permalink.toString feed.path
Id = CustomFeedId.toString feed.Id
SourceType = match feed.Source with Category _ -> "category" | Tag _ -> "tag"
SourceValue = match feed.Source with Category (CategoryId catId) -> catId | Tag tag -> tag
Path = Permalink.toString feed.Path
}
match feed.podcast with
match feed.Podcast with
| Some p ->
{ rss with
IsPodcast = true
Title = p.title
Subtitle = defaultArg p.subtitle ""
ItemsInFeed = p.itemsInFeed
Summary = p.summary
DisplayedAuthor = p.displayedAuthor
Email = p.email
ImageUrl = Permalink.toString p.imageUrl
iTunesCategory = p.iTunesCategory
iTunesSubcategory = defaultArg p.iTunesSubcategory ""
Explicit = ExplicitRating.toString p.explicit
DefaultMediaType = defaultArg p.defaultMediaType ""
MediaBaseUrl = defaultArg p.mediaBaseUrl ""
FundingUrl = defaultArg p.fundingUrl ""
FundingText = defaultArg p.fundingText ""
PodcastGuid = p.guid
|> Option.map (fun it -> it.ToString().ToLowerInvariant ())
|> Option.defaultValue ""
Medium = p.medium |> Option.map PodcastMedium.toString |> Option.defaultValue ""
IsPodcast = true
Title = p.Title
Subtitle = defaultArg p.Subtitle ""
ItemsInFeed = p.ItemsInFeed
Summary = p.Summary
DisplayedAuthor = p.DisplayedAuthor
Email = p.Email
ImageUrl = Permalink.toString p.ImageUrl
AppleCategory = p.AppleCategory
AppleSubcategory = defaultArg p.AppleSubcategory ""
Explicit = ExplicitRating.toString p.Explicit
DefaultMediaType = defaultArg p.DefaultMediaType ""
MediaBaseUrl = defaultArg p.MediaBaseUrl ""
FundingUrl = defaultArg p.FundingUrl ""
FundingText = defaultArg p.FundingText ""
PodcastGuid = p.PodcastGuid
|> Option.map (fun it -> it.ToString().ToLowerInvariant ())
|> Option.defaultValue ""
Medium = p.Medium |> Option.map PodcastMedium.toString |> Option.defaultValue ""
}
| None -> rss
/// Update a feed with values from this model
member this.updateFeed (feed : CustomFeed) =
member this.UpdateFeed (feed : CustomFeed) =
{ feed with
source = if this.SourceType = "tag" then Tag this.SourceValue else Category (CategoryId this.SourceValue)
path = Permalink this.Path
podcast =
Source = if this.SourceType = "tag" then Tag this.SourceValue else Category (CategoryId this.SourceValue)
Path = Permalink this.Path
Podcast =
if this.IsPodcast then
Some {
title = this.Title
subtitle = noneIfBlank this.Subtitle
itemsInFeed = this.ItemsInFeed
summary = this.Summary
displayedAuthor = this.DisplayedAuthor
email = this.Email
imageUrl = Permalink this.ImageUrl
iTunesCategory = this.iTunesCategory
iTunesSubcategory = noneIfBlank this.iTunesSubcategory
explicit = ExplicitRating.parse this.Explicit
defaultMediaType = noneIfBlank this.DefaultMediaType
mediaBaseUrl = noneIfBlank this.MediaBaseUrl
guid = noneIfBlank this.PodcastGuid |> Option.map Guid.Parse
fundingUrl = noneIfBlank this.FundingUrl
fundingText = noneIfBlank this.FundingText
medium = noneIfBlank this.Medium |> Option.map PodcastMedium.parse
Title = this.Title
Subtitle = noneIfBlank this.Subtitle
ItemsInFeed = this.ItemsInFeed
Summary = this.Summary
DisplayedAuthor = this.DisplayedAuthor
Email = this.Email
ImageUrl = Permalink this.ImageUrl
AppleCategory = this.AppleCategory
AppleSubcategory = noneIfBlank this.AppleSubcategory
Explicit = ExplicitRating.parse this.Explicit
DefaultMediaType = noneIfBlank this.DefaultMediaType
MediaBaseUrl = noneIfBlank this.MediaBaseUrl
PodcastGuid = noneIfBlank this.PodcastGuid |> Option.map Guid.Parse
FundingUrl = noneIfBlank this.FundingUrl
FundingText = noneIfBlank this.FundingText
Medium = noneIfBlank this.Medium |> Option.map PodcastMedium.parse
}
else
None
}
/// View model for a user to edit their own information
[<CLIMutable; NoComparison; NoEquality>]
type EditMyInfoModel =
{ /// The user's first name
FirstName : string
/// The user's last name
LastName : string
/// The user's preferred name
PreferredName : string
/// A new password for the user
NewPassword : string
/// A new password for the user, confirmed
NewPasswordConfirm : string
}
/// Create an edit model from a user
static member fromUser (user : WebLogUser) =
{ FirstName = user.FirstName
LastName = user.LastName
PreferredName = user.PreferredName
NewPassword = ""
NewPasswordConfirm = ""
}
/// View model to edit a page
[<CLIMutable; NoComparison; NoEquality>]
type EditPageModel =
@@ -425,19 +455,19 @@ type EditPageModel =
/// Create an edit model from an existing page
static member fromPage (page : Page) =
let latest =
match page.revisions |> List.sortByDescending (fun r -> r.asOf) |> List.tryHead with
match page.Revisions |> List.sortByDescending (fun r -> r.AsOf) |> List.tryHead with
| Some rev -> rev
| None -> Revision.empty
let page = if page.metadata |> List.isEmpty then { page with metadata = [ MetaItem.empty ] } else page
{ PageId = PageId.toString page.id
Title = page.title
Permalink = Permalink.toString page.permalink
Template = defaultArg page.template ""
IsShownInPageList = page.showInPageList
Source = MarkupText.sourceType latest.text
Text = MarkupText.text latest.text
MetaNames = page.metadata |> List.map (fun m -> m.name) |> Array.ofList
MetaValues = page.metadata |> List.map (fun m -> m.value) |> Array.ofList
let page = if page.Metadata |> List.isEmpty then { page with Metadata = [ MetaItem.empty ] } else page
{ PageId = PageId.toString page.Id
Title = page.Title
Permalink = Permalink.toString page.Permalink
Template = defaultArg page.Template ""
IsShownInPageList = page.IsInPageList
Source = MarkupText.sourceType latest.Text
Text = MarkupText.text latest.Text
MetaNames = page.Metadata |> List.map (fun m -> m.Name) |> Array.ofList
MetaValues = page.Metadata |> List.map (fun m -> m.Value) |> Array.ofList
}
@@ -547,94 +577,94 @@ type EditPostModel =
/// Create an edit model from an existing past
static member fromPost webLog (post : Post) =
let latest =
match post.revisions |> List.sortByDescending (fun r -> r.asOf) |> List.tryHead with
match post.Revisions |> List.sortByDescending (fun r -> r.AsOf) |> List.tryHead with
| Some rev -> rev
| None -> Revision.empty
let post = if post.metadata |> List.isEmpty then { post with metadata = [ MetaItem.empty ] } else post
let episode = defaultArg post.episode Episode.empty
{ PostId = PostId.toString post.id
Title = post.title
Permalink = Permalink.toString post.permalink
Source = MarkupText.sourceType latest.text
Text = MarkupText.text latest.text
Tags = String.Join (", ", post.tags)
Template = defaultArg post.template ""
CategoryIds = post.categoryIds |> List.map CategoryId.toString |> Array.ofList
Status = PostStatus.toString post.status
let post = if post.Metadata |> List.isEmpty then { post with Metadata = [ MetaItem.empty ] } else post
let episode = defaultArg post.Episode Episode.empty
{ PostId = PostId.toString post.Id
Title = post.Title
Permalink = Permalink.toString post.Permalink
Source = MarkupText.sourceType latest.Text
Text = MarkupText.text latest.Text
Tags = String.Join (", ", post.Tags)
Template = defaultArg post.Template ""
CategoryIds = post.CategoryIds |> List.map CategoryId.toString |> Array.ofList
Status = PostStatus.toString post.Status
DoPublish = false
MetaNames = post.metadata |> List.map (fun m -> m.name) |> Array.ofList
MetaValues = post.metadata |> List.map (fun m -> m.value) |> Array.ofList
MetaNames = post.Metadata |> List.map (fun m -> m.Name) |> Array.ofList
MetaValues = post.Metadata |> List.map (fun m -> m.Value) |> Array.ofList
SetPublished = false
PubOverride = post.publishedOn |> Option.map (WebLog.localTime webLog) |> Option.toNullable
PubOverride = post.PublishedOn |> Option.map (WebLog.localTime webLog) |> Option.toNullable
SetUpdated = false
IsEpisode = Option.isSome post.episode
Media = episode.media
Length = episode.length
Duration = defaultArg (episode.duration |> Option.map (fun it -> it.ToString """hh\:mm\:ss""")) ""
MediaType = defaultArg episode.mediaType ""
ImageUrl = defaultArg episode.imageUrl ""
Subtitle = defaultArg episode.subtitle ""
Explicit = defaultArg (episode.explicit |> Option.map ExplicitRating.toString) ""
ChapterFile = defaultArg episode.chapterFile ""
ChapterType = defaultArg episode.chapterType ""
TranscriptUrl = defaultArg episode.transcriptUrl ""
TranscriptType = defaultArg episode.transcriptType ""
TranscriptLang = defaultArg episode.transcriptLang ""
TranscriptCaptions = defaultArg episode.transcriptCaptions false
SeasonNumber = defaultArg episode.seasonNumber 0
SeasonDescription = defaultArg episode.seasonDescription ""
EpisodeNumber = defaultArg (episode.episodeNumber |> Option.map string) ""
EpisodeDescription = defaultArg episode.episodeDescription ""
IsEpisode = Option.isSome post.Episode
Media = episode.Media
Length = episode.Length
Duration = defaultArg (episode.Duration |> Option.map (fun it -> it.ToString """hh\:mm\:ss""")) ""
MediaType = defaultArg episode.MediaType ""
ImageUrl = defaultArg episode.ImageUrl ""
Subtitle = defaultArg episode.Subtitle ""
Explicit = defaultArg (episode.Explicit |> Option.map ExplicitRating.toString) ""
ChapterFile = defaultArg episode.ChapterFile ""
ChapterType = defaultArg episode.ChapterType ""
TranscriptUrl = defaultArg episode.TranscriptUrl ""
TranscriptType = defaultArg episode.TranscriptType ""
TranscriptLang = defaultArg episode.TranscriptLang ""
TranscriptCaptions = defaultArg episode.TranscriptCaptions false
SeasonNumber = defaultArg episode.SeasonNumber 0
SeasonDescription = defaultArg episode.SeasonDescription ""
EpisodeNumber = defaultArg (episode.EpisodeNumber |> Option.map string) ""
EpisodeDescription = defaultArg episode.EpisodeDescription ""
}
/// Update a post with values from the submitted form
member this.updatePost (post : Post) (revision : Revision) now =
member this.UpdatePost (post : Post) (revision : Revision) now =
{ post with
title = this.Title
permalink = Permalink this.Permalink
publishedOn = if this.DoPublish then Some now else post.publishedOn
updatedOn = now
text = MarkupText.toHtml revision.text
tags = this.Tags.Split ","
Title = this.Title
Permalink = Permalink this.Permalink
PublishedOn = if this.DoPublish then Some now else post.PublishedOn
UpdatedOn = now
Text = MarkupText.toHtml revision.Text
Tags = this.Tags.Split ","
|> Seq.ofArray
|> Seq.map (fun it -> it.Trim().ToLower ())
|> Seq.filter (fun it -> it <> "")
|> Seq.sort
|> List.ofSeq
template = match this.Template.Trim () with "" -> None | tmpl -> Some tmpl
categoryIds = this.CategoryIds |> Array.map CategoryId |> List.ofArray
status = if this.DoPublish then Published else post.status
metadata = Seq.zip this.MetaNames this.MetaValues
Template = match this.Template.Trim () with "" -> None | tmpl -> Some tmpl
CategoryIds = this.CategoryIds |> Array.map CategoryId |> List.ofArray
Status = if this.DoPublish then Published else post.Status
Metadata = Seq.zip this.MetaNames this.MetaValues
|> Seq.filter (fun it -> fst it > "")
|> Seq.map (fun it -> { name = fst it; value = snd it })
|> Seq.sortBy (fun it -> $"{it.name.ToLower ()} {it.value.ToLower ()}")
|> Seq.map (fun it -> { Name = fst it; Value = snd it })
|> Seq.sortBy (fun it -> $"{it.Name.ToLower ()} {it.Value.ToLower ()}")
|> List.ofSeq
revisions = match post.revisions |> List.tryHead with
| Some r when r.text = revision.text -> post.revisions
| _ -> revision :: post.revisions
episode =
Revisions = match post.Revisions |> List.tryHead with
| Some r when r.Text = revision.Text -> post.Revisions
| _ -> revision :: post.Revisions
Episode =
if this.IsEpisode then
Some {
media = this.Media
length = this.Length
duration = noneIfBlank this.Duration |> Option.map TimeSpan.Parse
mediaType = noneIfBlank this.MediaType
imageUrl = noneIfBlank this.ImageUrl
subtitle = noneIfBlank this.Subtitle
explicit = noneIfBlank this.Explicit |> Option.map ExplicitRating.parse
chapterFile = noneIfBlank this.ChapterFile
chapterType = noneIfBlank this.ChapterType
transcriptUrl = noneIfBlank this.TranscriptUrl
transcriptType = noneIfBlank this.TranscriptType
transcriptLang = noneIfBlank this.TranscriptLang
transcriptCaptions = if this.TranscriptCaptions then Some true else None
seasonNumber = if this.SeasonNumber = 0 then None else Some this.SeasonNumber
seasonDescription = noneIfBlank this.SeasonDescription
episodeNumber = match noneIfBlank this.EpisodeNumber |> Option.map Double.Parse with
Media = this.Media
Length = this.Length
Duration = noneIfBlank this.Duration |> Option.map TimeSpan.Parse
MediaType = noneIfBlank this.MediaType
ImageUrl = noneIfBlank this.ImageUrl
Subtitle = noneIfBlank this.Subtitle
Explicit = noneIfBlank this.Explicit |> Option.map ExplicitRating.parse
ChapterFile = noneIfBlank this.ChapterFile
ChapterType = noneIfBlank this.ChapterType
TranscriptUrl = noneIfBlank this.TranscriptUrl
TranscriptType = noneIfBlank this.TranscriptType
TranscriptLang = noneIfBlank this.TranscriptLang
TranscriptCaptions = if this.TranscriptCaptions then Some true else None
SeasonNumber = if this.SeasonNumber = 0 then None else Some this.SeasonNumber
SeasonDescription = noneIfBlank this.SeasonDescription
EpisodeNumber = match noneIfBlank this.EpisodeNumber |> Option.map Double.Parse with
| Some it when it = 0.0 -> None
| Some it -> Some (double it)
| None -> None
episodeDescription = noneIfBlank this.EpisodeDescription
EpisodeDescription = noneIfBlank this.EpisodeDescription
}
else
None
@@ -665,23 +695,23 @@ type EditRssModel =
/// Create an edit model from a set of RSS options
static member fromRssOptions (rss : RssOptions) =
{ IsFeedEnabled = rss.feedEnabled
FeedName = rss.feedName
ItemsInFeed = defaultArg rss.itemsInFeed 0
IsCategoryEnabled = rss.categoryEnabled
IsTagEnabled = rss.tagEnabled
Copyright = defaultArg rss.copyright ""
{ IsFeedEnabled = rss.IsFeedEnabled
FeedName = rss.FeedName
ItemsInFeed = defaultArg rss.ItemsInFeed 0
IsCategoryEnabled = rss.IsCategoryEnabled
IsTagEnabled = rss.IsTagEnabled
Copyright = defaultArg rss.Copyright ""
}
/// Update RSS options from values in this mode
member this.updateOptions (rss : RssOptions) =
member this.UpdateOptions (rss : RssOptions) =
{ rss with
feedEnabled = this.IsFeedEnabled
feedName = this.FeedName
itemsInFeed = if this.ItemsInFeed = 0 then None else Some this.ItemsInFeed
categoryEnabled = this.IsCategoryEnabled
tagEnabled = this.IsTagEnabled
copyright = noneIfBlank this.Copyright
IsFeedEnabled = this.IsFeedEnabled
FeedName = this.FeedName
ItemsInFeed = if this.ItemsInFeed = 0 then None else Some this.ItemsInFeed
IsCategoryEnabled = this.IsCategoryEnabled
IsTagEnabled = this.IsTagEnabled
Copyright = noneIfBlank this.Copyright
}
@@ -703,37 +733,9 @@ type EditTagMapModel =
/// Create an edit model from the tag mapping
static member fromMapping (tagMap : TagMap) : EditTagMapModel =
{ Id = TagMapId.toString tagMap.id
Tag = tagMap.tag
UrlValue = tagMap.urlValue
}
/// View model to edit a user
[<CLIMutable; NoComparison; NoEquality>]
type EditUserModel =
{ /// The user's first name
FirstName : string
/// The user's last name
LastName : string
/// The user's preferred name
PreferredName : string
/// A new password for the user
NewPassword : string
/// A new password for the user, confirmed
NewPasswordConfirm : string
}
/// Create an edit model from a user
static member fromUser (user : WebLogUser) =
{ FirstName = user.firstName
LastName = user.lastName
PreferredName = user.preferredName
NewPassword = ""
NewPasswordConfirm = ""
{ Id = TagMapId.toString tagMap.Id
Tag = tagMap.Tag
UrlValue = tagMap.UrlValue
}
@@ -776,20 +778,20 @@ type ManagePermalinksModel =
/// Create a permalink model from a page
static member fromPage (pg : Page) =
{ Id = PageId.toString pg.id
{ Id = PageId.toString pg.Id
Entity = "page"
CurrentTitle = pg.title
CurrentPermalink = Permalink.toString pg.permalink
Prior = pg.priorPermalinks |> List.map Permalink.toString |> Array.ofList
CurrentTitle = pg.Title
CurrentPermalink = Permalink.toString pg.Permalink
Prior = pg.PriorPermalinks |> List.map Permalink.toString |> Array.ofList
}
/// Create a permalink model from a post
static member fromPost (post : Post) =
{ Id = PostId.toString post.id
{ Id = PostId.toString post.Id
Entity = "post"
CurrentTitle = post.title
CurrentPermalink = Permalink.toString post.permalink
Prior = post.priorPermalinks |> List.map Permalink.toString |> Array.ofList
CurrentTitle = post.Title
CurrentPermalink = Permalink.toString post.Permalink
Prior = post.PriorPermalinks |> List.map Permalink.toString |> Array.ofList
}
@@ -811,18 +813,18 @@ type ManageRevisionsModel =
/// Create a revision model from a page
static member fromPage webLog (pg : Page) =
{ Id = PageId.toString pg.id
{ Id = PageId.toString pg.Id
Entity = "page"
CurrentTitle = pg.title
Revisions = pg.revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList
CurrentTitle = pg.Title
Revisions = pg.Revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList
}
/// Create a revision model from a post
static member fromPost webLog (post : Post) =
{ Id = PostId.toString post.id
{ Id = PostId.toString post.Id
Entity = "post"
CurrentTitle = post.title
Revisions = post.revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList
CurrentTitle = post.Title
Revisions = post.Revisions |> List.map (DisplayRevision.fromRevision webLog) |> Array.ofList
}
@@ -870,18 +872,18 @@ type PostListItem =
static member fromPost (webLog : WebLog) (post : Post) =
let _, extra = WebLog.hostAndPath webLog
let inTZ = WebLog.localTime webLog
{ Id = PostId.toString post.id
AuthorId = WebLogUserId.toString post.authorId
Status = PostStatus.toString post.status
Title = post.title
Permalink = Permalink.toString post.permalink
PublishedOn = post.publishedOn |> Option.map inTZ |> Option.toNullable
UpdatedOn = inTZ post.updatedOn
Text = if extra = "" then post.text else post.text.Replace ("href=\"/", $"href=\"{extra}/")
CategoryIds = post.categoryIds |> List.map CategoryId.toString
Tags = post.tags
Episode = post.episode
Metadata = post.metadata
{ Id = PostId.toString post.Id
AuthorId = WebLogUserId.toString post.AuthorId
Status = PostStatus.toString post.Status
Title = post.Title
Permalink = Permalink.toString post.Permalink
PublishedOn = post.PublishedOn |> Option.map inTZ |> Option.toNullable
UpdatedOn = inTZ post.UpdatedOn
Text = if extra = "" then post.Text else post.Text.Replace ("href=\"/", $"href=\"{extra}/")
CategoryIds = post.CategoryIds |> List.map CategoryId.toString
Tags = post.Tags
Episode = post.Episode
Metadata = post.Metadata
}
@@ -932,7 +934,7 @@ type SettingsModel =
TimeZone : string
/// The theme to use to display the web log
ThemePath : string
ThemeId : string
/// Whether to automatically load htmx
AutoHtmx : bool
@@ -943,29 +945,29 @@ type SettingsModel =
/// Create a settings model from a web log
static member fromWebLog (webLog : WebLog) =
{ Name = webLog.name
Slug = webLog.slug
Subtitle = defaultArg webLog.subtitle ""
DefaultPage = webLog.defaultPage
PostsPerPage = webLog.postsPerPage
TimeZone = webLog.timeZone
ThemePath = webLog.themePath
AutoHtmx = webLog.autoHtmx
Uploads = UploadDestination.toString webLog.uploads
{ Name = webLog.Name
Slug = webLog.Slug
Subtitle = defaultArg webLog.Subtitle ""
DefaultPage = webLog.DefaultPage
PostsPerPage = webLog.PostsPerPage
TimeZone = webLog.TimeZone
ThemeId = ThemeId.toString webLog.ThemeId
AutoHtmx = webLog.AutoHtmx
Uploads = UploadDestination.toString webLog.Uploads
}
/// Update a web log with settings from the form
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
themePath = this.ThemePath
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
}