Version 2.1 #41

Merged
danieljsummers merged 123 commits from version-2.1 into main 2024-03-27 00:13:28 +00:00
5 changed files with 336 additions and 547 deletions
Showing only changes of commit 54e46fdeb6 - Show all commits

View File

@ -73,82 +73,6 @@ type DisplayCategory = {
} }
/// A display version of an episode chapter
type DisplayChapter = {
/// The start time of the chapter (H:mm:ss.FF format)
StartTime: string
/// The title of the chapter
Title: string
/// An image to display for this chapter
ImageUrl: string
/// A URL with information about this chapter
Url: string
/// Whether this chapter should be displayed in podcast players
IsHidden: bool
/// The end time of the chapter (H:mm:ss.FF format)
EndTime: string
/// The name of a location
LocationName: string
/// The geographic coordinates of the location
LocationGeo: string
/// An OpenStreetMap query for this location
LocationOsm: string
} with
/// Create a display chapter from a chapter
static member FromChapter (chapter: Chapter) =
let pattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF"
{ StartTime = pattern.Format chapter.StartTime
Title = defaultArg chapter.Title ""
ImageUrl = defaultArg chapter.ImageUrl ""
Url = defaultArg chapter.Url ""
IsHidden = defaultArg chapter.IsHidden false
EndTime = chapter.EndTime |> Option.map pattern.Format |> Option.defaultValue ""
LocationName = chapter.Location |> Option.map _.Name |> Option.defaultValue ""
LocationGeo = chapter.Location |> Option.map _.Geo |> Option.defaultValue ""
LocationOsm = chapter.Location |> Option.map _.Osm |> Option.flatten |> Option.defaultValue "" }
/// A display version of a custom feed definition
type DisplayCustomFeed = {
/// The ID of the custom feed
Id: string
/// The source of the custom feed
Source: string
/// The relative path at which the custom feed is served
Path: string
/// Whether this custom feed is for a podcast
IsPodcast: bool
} with
/// Create a display version from a custom feed
static member FromFeed (cats: DisplayCategory array) (feed: CustomFeed) =
let source =
match feed.Source with
| Category (CategoryId catId) ->
cats
|> Array.tryFind (fun cat -> cat.Id = catId)
|> Option.map _.Name
|> Option.defaultValue "--INVALID; DELETE THIS FEED--"
|> sprintf "Category: %s"
| Tag tag -> $"Tag: {tag}"
{ Id = string feed.Id
Source = source
Path = string feed.Path
IsPodcast = Option.isSome feed.Podcast }
/// Details about a page used to display page lists /// Details about a page used to display page lists
[<NoComparison; NoEquality>] [<NoComparison; NoEquality>]
type DisplayPage = { type DisplayPage = {
@ -269,50 +193,6 @@ type DisplayUpload = {
Source = string source } Source = string source }
/// View model to display a user's information
[<NoComparison; NoEquality>]
type DisplayUser = {
/// The ID of the user
Id: string
/// The user name (e-mail address)
Email: string
/// The user's first name
FirstName: string
/// The user's last name
LastName: string
/// The user's preferred name
PreferredName: string
/// The URL of the user's personal site
Url: string
/// The user's access level
AccessLevel: string
/// When the user was created
CreatedOn: DateTime
/// When the user last logged on
LastSeenOn: Nullable<DateTime>
} with
/// Construct a displayed user from a web log user
static member FromUser (webLog: WebLog) (user: WebLogUser) =
{ Id = string user.Id
Email = user.Email
FirstName = user.FirstName
LastName = user.LastName
PreferredName = user.PreferredName
Url = defaultArg user.Url ""
AccessLevel = string user.AccessLevel
CreatedOn = webLog.LocalTime user.CreatedOn
LastSeenOn = user.LastSeenOn |> Option.map webLog.LocalTime |> Option.toNullable }
/// View model for editing categories /// View model for editing categories
[<CLIMutable; NoComparison; NoEquality>] [<CLIMutable; NoComparison; NoEquality>]
type EditCategoryModel = { type EditCategoryModel = {
@ -386,19 +266,19 @@ type EditChapterModel = {
} with } with
/// Create a display chapter from a chapter /// Create a display chapter from a chapter
static member FromChapter (postId: PostId) idx chapter = static member FromChapter (postId: PostId) idx (chapter: Chapter) =
let it = DisplayChapter.FromChapter chapter let pattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF"
{ PostId = string postId { PostId = string postId
Index = idx Index = idx
StartTime = it.StartTime StartTime = pattern.Format chapter.StartTime
Title = it.Title Title = defaultArg chapter.Title ""
ImageUrl = it.ImageUrl ImageUrl = defaultArg chapter.ImageUrl ""
Url = it.Url Url = defaultArg chapter.Url ""
IsHidden = it.IsHidden IsHidden = defaultArg chapter.IsHidden false
EndTime = it.EndTime EndTime = chapter.EndTime |> Option.map pattern.Format |> Option.defaultValue ""
LocationName = it.LocationName LocationName = chapter.Location |> Option.map _.Name |> Option.defaultValue ""
LocationGeo = it.LocationGeo LocationGeo = chapter.Location |> Option.map _.Geo |> Option.defaultValue ""
LocationOsm = it.LocationOsm LocationOsm = chapter.Location |> Option.map _.Osm |> Option.flatten |> Option.defaultValue ""
AddAnother = false } AddAnother = false }
/// Create a chapter from the values in this model /// Create a chapter from the values in this model
@ -427,6 +307,76 @@ type EditChapterModel = {
Location = location } Location = location }
/// View model common to page and post edits
type EditCommonModel() =
/// Find the latest revision within a list of revisions
let findLatestRevision (revs: Revision list) =
match revs |> List.sortByDescending _.AsOf |> List.tryHead with Some rev -> rev | None -> Revision.Empty
/// The ID of the page or post
member val Id = "" with get, set
/// The title of the page or post
member val Title = "" with get, set
/// The permalink for the page or post
member val Permalink = "" with get, set
/// The entity to which this model applies ("page" or "post")
member val Entity = "" with get, set
/// Whether to provide a link to manage chapters
member val IncludeChapterLink = false with get, set
/// The template to use to display the page
member val Template = "" with get, set
/// The source type ("HTML" or "Markdown")
member val Source = "" with get, set
/// The text of the page or post
member val Text = "" with get, set
/// Names of metadata items
member val MetaNames: string array = [||] with get, set
/// Values of metadata items
member val MetaValues: string array = [||] with get, set
/// Whether this is a new page or post
member this.IsNew with get () = this.Id = "new"
/// Fill the properties of this object from a page
member this.PopulateFromPage (page: Page) =
let latest = findLatestRevision page.Revisions
let metaItems = if page.Metadata.Length = 0 then [ MetaItem.Empty ] else page.Metadata
this.Id <- string page.Id
this.Title <- page.Title
this.Permalink <- string page.Permalink
this.Entity <- "page"
this.Template <- defaultArg page.Template ""
this.Source <- latest.Text.SourceType
this.Text <- latest.Text.Text
this.MetaNames <- metaItems |> List.map _.Name |> Array.ofList
this.MetaValues <- metaItems |> List.map _.Value |> Array.ofList
/// Fill the properties of this object from a post
member this.PopulateFromPost (post: Post) =
let latest = findLatestRevision post.Revisions
let metaItems = if post.Metadata.Length = 0 then [ MetaItem.Empty ] else post.Metadata
this.Id <- string post.Id
this.Title <- post.Title
this.Permalink <- string post.Permalink
this.Entity <- "post"
this.IncludeChapterLink <- Option.isSome post.Episode && Option.isSome post.Episode.Value.Chapters
this.Template <- defaultArg post.Template ""
this.Source <- latest.Text.SourceType
this.Text <- latest.Text.Text
this.MetaNames <- metaItems |> List.map _.Name |> Array.ofList
this.MetaValues <- metaItems |> List.map _.Value |> Array.ofList
/// View model to edit a custom RSS feed /// View model to edit a custom RSS feed
[<CLIMutable; NoComparison; NoEquality>] [<CLIMutable; NoComparison; NoEquality>]
type EditCustomFeedModel = { type EditCustomFeedModel = {
@ -604,74 +554,6 @@ type EditMyInfoModel = {
NewPasswordConfirm = "" } NewPasswordConfirm = "" }
/// View model common to page and post edits
type EditCommonModel() =
/// Find the latest revision within a list of revisions
let findLatestRevision (revs: Revision list) =
match revs |> List.sortByDescending _.AsOf |> List.tryHead with Some rev -> rev | None -> Revision.Empty
/// The ID of the page or post
member val Id = "" with get, set
/// The title of the page or post
member val Title = "" with get, set
/// The permalink for the page or post
member val Permalink = "" with get, set
/// The entity to which this model applies ("page" or "post")
member val Entity = "" with get, set
/// Whether to provide a link to manage chapters
member val IncludeChapterLink = false with get, set
/// The template to use to display the page
member val Template = "" with get, set
/// The source type ("HTML" or "Markdown")
member val Source = "" with get, set
/// The text of the page or post
member val Text = "" with get, set
/// Names of metadata items
member val MetaNames: string array = [||] with get, set
/// Values of metadata items
member val MetaValues: string array = [||] with get, set
/// Whether this is a new page or post
member this.IsNew with get () = this.Id = "new"
/// Fill the properties of this object from a page
member this.PopulateFromPage (page: Page) =
let latest = findLatestRevision page.Revisions
this.Id <- string page.Id
this.Title <- page.Title
this.Permalink <- string page.Permalink
this.Entity <- "page"
this.Template <- defaultArg page.Template ""
this.Source <- latest.Text.SourceType
this.Text <- latest.Text.Text
this.MetaNames <- page.Metadata |> List.map _.Name |> Array.ofList
this.MetaValues <- page.Metadata |> List.map _.Value |> Array.ofList
/// Fill the properties of this object from a post
member this.PopulateFromPost (post: Post) =
let latest = findLatestRevision post.Revisions
this.Id <- string post.Id
this.Title <- post.Title
this.Permalink <- string post.Permalink
this.Entity <- "post"
this.IncludeChapterLink <- Option.isSome post.Episode && Option.isSome post.Episode.Value.Chapters
this.Template <- defaultArg post.Template ""
this.Source <- latest.Text.SourceType
this.Text <- latest.Text.Text
this.MetaNames <- post.Metadata |> List.map _.Name |> Array.ofList
this.MetaValues <- post.Metadata |> List.map _.Value |> Array.ofList
/// View model to edit a page /// View model to edit a page
type EditPageModel() = type EditPageModel() =
inherit EditCommonModel() inherit EditCommonModel()
@ -801,7 +683,6 @@ type EditPostModel() =
/// Create an edit model from an existing past /// Create an edit model from an existing past
static member FromPost (webLog: WebLog) (post: Post) = static member FromPost (webLog: WebLog) (post: Post) =
let model = EditPostModel() let model = EditPostModel()
let post = if post.Metadata |> List.isEmpty then { post with Metadata = [ MetaItem.Empty ] } else post
model.PopulateFromPost post model.PopulateFromPost post
let episode = defaultArg post.Episode Episode.Empty let episode = defaultArg post.Episode Episode.Empty
model.Tags <- post.Tags |> String.concat ", " model.Tags <- post.Tags |> String.concat ", "

View File

@ -33,88 +33,6 @@ let addBaseToRelativeUrlsTests = testList "PublicHelpers.addBaseToRelativeUrls"
} }
] ]
/// Unit tests for the DisplayChapter type
let displayChapterTests = testList "DisplayChapter.FromChapter" [
test "succeeds for a minimally-filled chapter" {
let chapter = DisplayChapter.FromChapter { Chapter.Empty with StartTime = Duration.FromSeconds 322L }
Expect.equal chapter.StartTime "0:05:22" "Start time not filled/formatted properly"
Expect.equal chapter.Title "" "Title not filled properly"
Expect.equal chapter.ImageUrl "" "Image URL not filled properly"
Expect.isFalse chapter.IsHidden "Is hidden flag not filled properly"
Expect.equal chapter.EndTime "" "End time not filled properly"
Expect.equal chapter.LocationName "" "Location name not filled properly"
Expect.equal chapter.LocationGeo "" "Location geo URL not filled properly"
Expect.equal chapter.LocationOsm "" "Location OSM query not filled properly"
}
test "succeeds for a fully-filled chapter" {
let chapter =
DisplayChapter.FromChapter
{ StartTime = Duration.FromSeconds 7201.43242
Title = Some "My Test Chapter"
ImageUrl = Some "two-hours-in.jpg"
Url = Some "https://example.com/about"
IsHidden = Some true
EndTime = Some (Duration.FromSeconds 7313.788)
Location = Some { Name = "Over Here"; Geo = "geo:23432"; Osm = Some "SF98fFSu-8" } }
Expect.equal chapter.StartTime "2:00:01.43" "Start time not filled/formatted properly"
Expect.equal chapter.Title "My Test Chapter" "Title not filled properly"
Expect.equal chapter.ImageUrl "two-hours-in.jpg" "Image URL not filled properly"
Expect.equal chapter.Url "https://example.com/about" "URL not filled properly"
Expect.isTrue chapter.IsHidden "Is hidden flag not filled properly"
Expect.equal chapter.EndTime "2:01:53.78" "End time not filled/formatted properly"
Expect.equal chapter.LocationName "Over Here" "Location name not filled properly"
Expect.equal chapter.LocationGeo "geo:23432" "Location geo URL not filled properly"
Expect.equal chapter.LocationOsm "SF98fFSu-8" "Location OSM query not filled properly"
}
]
/// Unit tests for the DisplayCustomFeed type
let displayCustomFeedTests = testList "DisplayCustomFeed.FromFeed" [
test "succeeds for a feed for an existing category" {
let cats =
[| { DisplayCategory.Id = "abc"
Slug = "a-b-c"
Name = "My Lovely Category"
Description = None
ParentNames = [||]
PostCount = 3 } |]
let feed =
{ CustomFeed.Empty with
Id = CustomFeedId "test-feed"
Source = Category (CategoryId "abc")
Path = Permalink "test-feed.xml" }
let model = DisplayCustomFeed.FromFeed cats feed
Expect.equal model.Id "test-feed" "Id not filled properly"
Expect.equal model.Source "Category: My Lovely Category" "Source not filled properly"
Expect.equal model.Path "test-feed.xml" "Path not filled properly"
Expect.isFalse model.IsPodcast "IsPodcast not filled properly"
}
test "succeeds for a feed for a non-existing category" {
let feed =
{ CustomFeed.Empty with
Id = CustomFeedId "bad-feed"
Source = Category (CategoryId "xyz")
Path = Permalink "trouble.xml" }
let model = DisplayCustomFeed.FromFeed [||] feed
Expect.equal model.Id "bad-feed" "Id not filled properly"
Expect.equal model.Source "Category: --INVALID; DELETE THIS FEED--" "Source not filled properly"
Expect.equal model.Path "trouble.xml" "Path not filled properly"
Expect.isFalse model.IsPodcast "IsPodcast not filled properly"
}
test "succeeds for a feed for a tag" {
let feed =
{ Id = CustomFeedId "tag-feed"
Source = Tag "testing"
Path = Permalink "testing-posts.xml"
Podcast = Some PodcastOptions.Empty }
let model = DisplayCustomFeed.FromFeed [||] feed
Expect.equal model.Id "tag-feed" "Id not filled properly"
Expect.equal model.Source "Tag: testing" "Source not filled properly"
Expect.equal model.Path "testing-posts.xml" "Path not filled properly"
Expect.isTrue model.IsPodcast "IsPodcast not filled properly"
}
]
/// Unit tests for the DisplayPage type /// Unit tests for the DisplayPage type
let displayPageTests = testList "DisplayPage" [ let displayPageTests = testList "DisplayPage" [
let page = let page =
@ -242,47 +160,6 @@ let displayUploadTests = test "DisplayUpload.FromUpload succeeds" {
model.UpdatedOn.Value ((Noda.epoch + Duration.FromHours 1).ToDateTimeUtc()) "UpdatedOn not filled properly" model.UpdatedOn.Value ((Noda.epoch + Duration.FromHours 1).ToDateTimeUtc()) "UpdatedOn not filled properly"
} }
/// Unit tests for the DisplayUser type
let displayUserTests = testList "DisplayUser.FromUser" [
let minimalUser =
{ WebLogUser.Empty with
Id = WebLogUserId "test-user"
Email = "jim.james@example.com"
FirstName = "Jim"
LastName = "James"
PreferredName = "John"
AccessLevel = Editor
CreatedOn = Noda.epoch }
test "succeeds when the user has minimal information" {
let model = DisplayUser.FromUser WebLog.Empty minimalUser
Expect.equal model.Id "test-user" "Id not filled properly"
Expect.equal model.Email "jim.james@example.com" "Email not filled properly"
Expect.equal model.FirstName "Jim" "FirstName not filled properly"
Expect.equal model.LastName "James" "LastName not filled properly"
Expect.equal model.PreferredName "John" "PreferredName not filled properly"
Expect.equal model.Url "" "Url not filled properly"
Expect.equal model.AccessLevel "Editor" "AccessLevel not filled properly"
Expect.equal model.CreatedOn (Noda.epoch.ToDateTimeUtc()) "CreatedOn not filled properly"
Expect.isFalse model.LastSeenOn.HasValue "LastSeenOn should have been null"
}
test "succeeds when the user has all information" {
let model =
DisplayUser.FromUser
{ WebLog.Empty with TimeZone = "Etc/GMT-1" }
{ minimalUser with
Url = Some "https://my.site"
LastSeenOn = Some (Noda.epoch + Duration.FromDays 4) }
Expect.equal model.Url "https://my.site" "Url not filled properly"
Expect.equal
model.CreatedOn ((Noda.epoch + Duration.FromHours 1).ToDateTimeUtc()) "CreatedOn not filled properly"
Expect.isTrue model.LastSeenOn.HasValue "LastSeenOn should not have been null"
Expect.equal
model.LastSeenOn.Value
((Noda.epoch + Duration.FromDays 4 + Duration.FromHours 1).ToDateTimeUtc())
"LastSeenOn not filled properly"
}
]
/// Unit tests for the EditCategoryModel type /// Unit tests for the EditCategoryModel type
let editCategoryModelTests = testList "EditCategoryModel" [ let editCategoryModelTests = testList "EditCategoryModel" [
testList "FromCategory" [ testList "FromCategory" [
@ -315,6 +192,131 @@ let editCategoryModelTests = testList "EditCategoryModel" [
] ]
] ]
/// A full page used to test various models
let private testFullPage =
{ Page.Empty with
Id = PageId "the-page"
Title = "Test Page"
Permalink = Permalink "blog/page.html"
Template = Some "bork"
IsInPageList = true
Revisions =
[ { AsOf = Noda.epoch + Duration.FromHours 1; Text = Markdown "# Howdy!" }
{ AsOf = Noda.epoch; Text = Html "<h1>howdy</h1>" } ]
Metadata = [ { Name = "Test"; Value = "me" }; { Name = "Two"; Value = "2" } ] }
/// A full post used to test various models
let testFullPost =
{ Post.Empty with
Id = PostId "a-post"
Status = Published
Title = "A Post"
Permalink = Permalink "1970/01/a-post.html"
PublishedOn = Some (Noda.epoch + Duration.FromDays 7)
UpdatedOn = Noda.epoch + Duration.FromDays 365
Template = Some "demo"
Text = "<p>A post!</p>"
CategoryIds = [ CategoryId "cat-a"; CategoryId "cat-b"; CategoryId "cat-n" ]
Tags = [ "demo"; "post" ]
Metadata = [ { Name = "A Meta"; Value = "A Value" } ]
Revisions =
[ { AsOf = Noda.epoch + Duration.FromDays 365; Text = Html "<p>A post!</p>" }
{ AsOf = Noda.epoch + Duration.FromDays 7; Text = Markdown "A post!" } ]
Episode =
Some { Media = "a-post-ep.mp3"
Length = 15555L
Duration = Some (Duration.FromMinutes 15L + Duration.FromSeconds 22L)
MediaType = Some "audio/mpeg3"
ImageUrl = Some "uploads/podcast-cover.jpg"
Subtitle = Some "Narration"
Explicit = Some Clean
Chapters = None
ChapterFile = Some "uploads/1970/01/chapters.txt"
ChapterType = Some "chapters"
ChapterWaypoints = Some true
TranscriptUrl = Some "uploads/1970/01/transcript.txt"
TranscriptType = Some "transcript"
TranscriptLang = Some "EN-us"
TranscriptCaptions = Some true
SeasonNumber = Some 3
SeasonDescription = Some "Season Three"
EpisodeNumber = Some 322.
EpisodeDescription = Some "Episode 322" } }
/// Unit tests for the EditCommonModel type
let editCommonModelTests = testList "EditCommonModel" [
testList "IsNew" [
test "succeeds for a new page or post" {
Expect.isTrue (EditCommonModel(Id = "new")).IsNew "IsNew should have been set"
}
test "succeeds for an existing page or post" {
Expect.isFalse (EditCommonModel(Id = string (PageId.Create ()))).IsNew "IsNew should not have been set"
}
]
testList "PopulateFromPage" [
test "succeeds for empty page" {
let model = EditCommonModel()
model.PopulateFromPage { Page.Empty with Id = PageId "abc" }
Expect.equal model.Id "abc" "PageId not filled properly"
Expect.equal model.Title "" "Title not filled properly"
Expect.equal model.Permalink "" "Permalink not filled properly"
Expect.equal model.Template "" "Template not filled properly"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "" "Text not set properly"
Expect.equal model.MetaNames.Length 1 "MetaNames should have one entry"
Expect.equal model.MetaNames[0] "" "Meta name not set properly"
Expect.equal model.MetaValues.Length 1 "MetaValues should have one entry"
Expect.equal model.MetaValues[0] "" "Meta value not set properly"
}
test "succeeds for filled page" {
let model = EditCommonModel()
model.PopulateFromPage testFullPage
Expect.equal model.Id "the-page" "PageId not filled properly"
Expect.equal model.Title "Test Page" "Title not filled properly"
Expect.equal model.Permalink "blog/page.html" "Permalink not filled properly"
Expect.equal model.Template "bork" "Template not filled properly"
Expect.equal model.Source "Markdown" "Source not filled properly"
Expect.equal model.Text "# Howdy!" "Text not filled properly"
Expect.equal model.MetaNames.Length 2 "MetaNames should have two entries"
Expect.equal model.MetaNames[0] "Test" "Meta name 0 not set properly"
Expect.equal model.MetaNames[1] "Two" "Meta name 1 not set properly"
Expect.equal model.MetaValues.Length 2 "MetaValues should have two entries"
Expect.equal model.MetaValues[0] "me" "Meta value 0 not set properly"
Expect.equal model.MetaValues[1] "2" "Meta value 1 not set properly"
}
]
testList "PopulateFromPost" [
test "succeeds for empty post" {
let model = EditCommonModel()
model.PopulateFromPost { Post.Empty with Id = PostId "la-la-la" }
Expect.equal model.Id "la-la-la" "PostId not filled properly"
Expect.equal model.Title "" "Title not filled properly"
Expect.equal model.Permalink "" "Permalink not filled properly"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "" "Text not filled properly"
Expect.equal model.Template "" "Template not filled properly"
Expect.equal model.MetaNames.Length 1 "MetaNames not filled properly"
Expect.equal model.MetaNames[0] "" "Meta name 0 not filled properly"
Expect.equal model.MetaValues.Length 1 "MetaValues not filled properly"
Expect.equal model.MetaValues[0] "" "Meta value 0 not filled properly"
}
test "succeeds for full post with external chapters" {
let model = EditCommonModel()
model.PopulateFromPost testFullPost
Expect.equal model.Id "a-post" "PostId not filled properly"
Expect.equal model.Title "A Post" "Title not filled properly"
Expect.equal model.Permalink "1970/01/a-post.html" "Permalink not filled properly"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "<p>A post!</p>" "Text not filled properly"
Expect.equal model.Template "demo" "Template not filled properly"
Expect.equal model.MetaNames.Length 1 "MetaNames not filled properly"
Expect.equal model.MetaNames[0] "A Meta" "Meta name 0 not filled properly"
Expect.equal model.MetaValues.Length 1 "MetaValues not filled properly"
Expect.equal model.MetaValues[0] "A Value" "Meta value 0 not filled properly"
}
]
]
/// Unit tests for the EditCustomFeedModel type /// Unit tests for the EditCustomFeedModel type
let editCustomFeedModelTests = testList "EditCustomFeedModel" [ let editCustomFeedModelTests = testList "EditCustomFeedModel" [
let minimalPodcast = let minimalPodcast =
@ -502,63 +504,26 @@ let editMyInfoModelTests = test "EditMyInfoModel.FromUser succeeds" {
Expect.equal model.NewPasswordConfirm "" "NewPasswordConfirm not filled properly" Expect.equal model.NewPasswordConfirm "" "NewPasswordConfirm not filled properly"
} }
/// Unit tests for the EditPageModel type
let editPageModelTests = testList "EditPageModel" [ let editPageModelTests = testList "EditPageModel" [
let fullPage =
{ Page.Empty with
Id = PageId "the-page"
Title = "Test Page"
Permalink = Permalink "blog/page.html"
Template = Some "bork"
IsInPageList = true
Revisions =
[ { AsOf = Noda.epoch + Duration.FromHours 1; Text = Markdown "# Howdy!" }
{ AsOf = Noda.epoch; Text = Html "<h1>howdy</h1>" } ]
Metadata = [ { Name = "Test"; Value = "me" }; { Name = "Two"; Value = "2" } ] }
testList "FromPage" [ testList "FromPage" [
test "succeeds for empty page" { test "succeeds for empty page" {
let model = EditPageModel.FromPage { Page.Empty with Id = PageId "abc" } let model = EditPageModel.FromPage { Page.Empty with Id = PageId "abc" }
Expect.equal model.PageId "abc" "PageId not filled properly" Expect.equal model.Id "abc" "Parent fields not filled properly"
Expect.equal model.Title "" "Title not filled properly"
Expect.equal model.Permalink "" "Permalink not filled properly"
Expect.equal model.Template "" "Template not filled properly"
Expect.isFalse model.IsShownInPageList "IsShownInPageList should not have been set" Expect.isFalse model.IsShownInPageList "IsShownInPageList should not have been set"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "" "Text not set properly"
Expect.equal model.MetaNames.Length 1 "MetaNames should have one entry"
Expect.equal model.MetaNames[0] "" "Meta name not set properly"
Expect.equal model.MetaValues.Length 1 "MetaValues should have one entry"
Expect.equal model.MetaValues[0] "" "Meta value not set properly"
} }
test "succeeds for filled page" { test "succeeds for filled page" {
let model = EditPageModel.FromPage fullPage let model = EditPageModel.FromPage testFullPage
Expect.equal model.PageId "the-page" "PageId not filled properly" Expect.equal model.Id "the-page" "Parent fields not filled properly"
Expect.equal model.Title "Test Page" "Title not filled properly"
Expect.equal model.Permalink "blog/page.html" "Permalink not filled properly"
Expect.equal model.Template "bork" "Template not filled properly"
Expect.isTrue model.IsShownInPageList "IsShownInPageList should have been set" Expect.isTrue model.IsShownInPageList "IsShownInPageList should have been set"
Expect.equal model.Source "Markdown" "Source not filled properly"
Expect.equal model.Text "# Howdy!" "Text not filled properly"
Expect.equal model.MetaNames.Length 2 "MetaNames should have two entries"
Expect.equal model.MetaNames[0] "Test" "Meta name 0 not set properly"
Expect.equal model.MetaNames[1] "Two" "Meta name 1 not set properly"
Expect.equal model.MetaValues.Length 2 "MetaValues should have two entries"
Expect.equal model.MetaValues[0] "me" "Meta value 0 not set properly"
Expect.equal model.MetaValues[1] "2" "Meta value 1 not set properly"
}
]
testList "IsNew" [
test "succeeds for a new page" {
Expect.isTrue
(EditPageModel.FromPage { Page.Empty with Id = PageId "new" }).IsNew "IsNew should have been set"
}
test "succeeds for an existing page" {
Expect.isFalse (EditPageModel.FromPage Page.Empty).IsNew "IsNew should not have been set"
} }
] ]
testList "UpdatePage" [ testList "UpdatePage" [
test "succeeds with minimal changes" { test "succeeds with minimal changes" {
let model = { EditPageModel.FromPage fullPage with Title = "Updated Page"; IsShownInPageList = false } let model = EditPageModel.FromPage testFullPage
let page = model.UpdatePage fullPage (Noda.epoch + Duration.FromHours 4) model.Title <- "Updated Page"
model.IsShownInPageList <- false
let page = model.UpdatePage testFullPage (Noda.epoch + Duration.FromHours 4)
Expect.equal page.Title "Updated Page" "Title not filled properly" Expect.equal page.Title "Updated Page" "Title not filled properly"
Expect.equal page.Permalink (Permalink "blog/page.html") "Permalink not filled properly" Expect.equal page.Permalink (Permalink "blog/page.html") "Permalink not filled properly"
Expect.isEmpty page.PriorPermalinks "PriorPermalinks should be empty" Expect.isEmpty page.PriorPermalinks "PriorPermalinks should be empty"
@ -582,18 +547,18 @@ let editPageModelTests = testList "EditPageModel" [
Expect.equal rev2.Text (Html "<h1>howdy</h1>") "Revision 1 text not filled properly" Expect.equal rev2.Text (Html "<h1>howdy</h1>") "Revision 1 text not filled properly"
} }
test "succeeds with all changes" { test "succeeds with all changes" {
let model = let model = EditPageModel()
{ PageId = "this-page" model.Id <- "this-page"
Title = "My Updated Page" model.Title <- "My Updated Page"
Permalink = "blog/updated.html" model.Permalink <- "blog/updated.html"
Template = "" model.Template <- ""
IsShownInPageList = false model.IsShownInPageList <- false
Source = "HTML" model.Source <- "HTML"
Text = "<h1>Howdy, partners!</h1>" model.Text <- "<h1>Howdy, partners!</h1>"
MetaNames = [| "banana"; "apple"; "grape" |] model.MetaNames <- [| "banana"; "apple"; "grape" |]
MetaValues = [| "monkey"; "zebra"; "ape" |] } model.MetaValues <- [| "monkey"; "zebra"; "ape" |]
let now = Noda.epoch + Duration.FromDays 7 let now = Noda.epoch + Duration.FromDays 7
let page = model.UpdatePage fullPage now let page = model.UpdatePage testFullPage now
Expect.equal page.Title "My Updated Page" "Title not filled properly" Expect.equal page.Title "My Updated Page" "Title not filled properly"
Expect.equal page.Permalink (Permalink "blog/updated.html") "Permalink not filled properly" Expect.equal page.Permalink (Permalink "blog/updated.html") "Permalink not filled properly"
Expect.equal page.PriorPermalinks [ Permalink "blog/page.html" ] "PriorPermalinks not filled properly" Expect.equal page.PriorPermalinks [ Permalink "blog/page.html" ] "PriorPermalinks not filled properly"
@ -621,59 +586,14 @@ let editPageModelTests = testList "EditPageModel" [
/// Unit tests for the EditPostModel type /// Unit tests for the EditPostModel type
let editPostModelTests = testList "EditPostModel" [ let editPostModelTests = testList "EditPostModel" [
let fullPost =
{ Post.Empty with
Id = PostId "a-post"
Status = Published
Title = "A Post"
Permalink = Permalink "1970/01/a-post.html"
PublishedOn = Some (Noda.epoch + Duration.FromDays 7)
UpdatedOn = Noda.epoch + Duration.FromDays 365
Template = Some "demo"
Text = "<p>A post!</p>"
CategoryIds = [ CategoryId "cat-a"; CategoryId "cat-b"; CategoryId "cat-n" ]
Tags = [ "demo"; "post" ]
Metadata = [ { Name = "A Meta"; Value = "A Value" } ]
Revisions =
[ { AsOf = Noda.epoch + Duration.FromDays 365; Text = Html "<p>A post!</p>" }
{ AsOf = Noda.epoch + Duration.FromDays 7; Text = Markdown "A post!" } ]
Episode =
Some { Media = "a-post-ep.mp3"
Length = 15555L
Duration = Some (Duration.FromMinutes 15L + Duration.FromSeconds 22L)
MediaType = Some "audio/mpeg3"
ImageUrl = Some "uploads/podcast-cover.jpg"
Subtitle = Some "Narration"
Explicit = Some Clean
Chapters = None
ChapterFile = Some "uploads/1970/01/chapters.txt"
ChapterType = Some "chapters"
ChapterWaypoints = Some true
TranscriptUrl = Some "uploads/1970/01/transcript.txt"
TranscriptType = Some "transcript"
TranscriptLang = Some "EN-us"
TranscriptCaptions = Some true
SeasonNumber = Some 3
SeasonDescription = Some "Season Three"
EpisodeNumber = Some 322.
EpisodeDescription = Some "Episode 322" } }
testList "FromPost" [ testList "FromPost" [
test "succeeds for empty post" { test "succeeds for empty post" {
let model = EditPostModel.FromPost WebLog.Empty { Post.Empty with Id = PostId "la-la-la" } let model = EditPostModel.FromPost WebLog.Empty { Post.Empty with Id = PostId "la-la-la" }
Expect.equal model.PostId "la-la-la" "PostId not filled properly" Expect.equal model.Id "la-la-la" "Parent fields not filled properly"
Expect.equal model.Title "" "Title not filled properly"
Expect.equal model.Permalink "" "Permalink not filled properly"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "" "Text not filled properly"
Expect.equal model.Tags "" "Tags not filled properly" Expect.equal model.Tags "" "Tags not filled properly"
Expect.equal model.Template "" "Template not filled properly"
Expect.isEmpty model.CategoryIds "CategoryIds not filled properly" Expect.isEmpty model.CategoryIds "CategoryIds not filled properly"
Expect.equal model.Status (string Draft) "Status not filled properly" Expect.equal model.Status (string Draft) "Status not filled properly"
Expect.isFalse model.DoPublish "DoPublish should not have been set" Expect.isFalse model.DoPublish "DoPublish should not have been set"
Expect.equal model.MetaNames.Length 1 "MetaNames not filled properly"
Expect.equal model.MetaNames[0] "" "Meta name 0 not filled properly"
Expect.equal model.MetaValues.Length 1 "MetaValues not filled properly"
Expect.equal model.MetaValues[0] "" "Meta value 0 not filled properly"
Expect.isFalse model.SetPublished "SetPublished should not have been set" Expect.isFalse model.SetPublished "SetPublished should not have been set"
Expect.isFalse model.PubOverride.HasValue "PubOverride not filled properly" Expect.isFalse model.PubOverride.HasValue "PubOverride not filled properly"
Expect.isFalse model.SetUpdated "SetUpdated should not have been set" Expect.isFalse model.SetUpdated "SetUpdated should not have been set"
@ -699,21 +619,12 @@ let editPostModelTests = testList "EditPostModel" [
Expect.equal model.EpisodeDescription "" "EpisodeDescription not filled properly" Expect.equal model.EpisodeDescription "" "EpisodeDescription not filled properly"
} }
test "succeeds for full post with external chapters" { test "succeeds for full post with external chapters" {
let model = EditPostModel.FromPost { WebLog.Empty with TimeZone = "Etc/GMT+1" } fullPost let model = EditPostModel.FromPost { WebLog.Empty with TimeZone = "Etc/GMT+1" } testFullPost
Expect.equal model.PostId "a-post" "PostId not filled properly" Expect.equal model.Id "a-post" "Parent fields not filled properly"
Expect.equal model.Title "A Post" "Title not filled properly"
Expect.equal model.Permalink "1970/01/a-post.html" "Permalink not filled properly"
Expect.equal model.Source "HTML" "Source not filled properly"
Expect.equal model.Text "<p>A post!</p>" "Text not filled properly"
Expect.equal model.Tags "demo, post" "Tags not filled properly" Expect.equal model.Tags "demo, post" "Tags not filled properly"
Expect.equal model.Template "demo" "Template not filled properly"
Expect.equal model.CategoryIds [| "cat-a"; "cat-b"; "cat-n" |] "CategoryIds not filled properly" Expect.equal model.CategoryIds [| "cat-a"; "cat-b"; "cat-n" |] "CategoryIds not filled properly"
Expect.equal model.Status (string Published) "Status not filled properly" Expect.equal model.Status (string Published) "Status not filled properly"
Expect.isFalse model.DoPublish "DoPublish should not have been set" Expect.isFalse model.DoPublish "DoPublish should not have been set"
Expect.equal model.MetaNames.Length 1 "MetaNames not filled properly"
Expect.equal model.MetaNames[0] "A Meta" "Meta name 0 not filled properly"
Expect.equal model.MetaValues.Length 1 "MetaValues not filled properly"
Expect.equal model.MetaValues[0] "A Value" "Meta value 0 not filled properly"
Expect.isFalse model.SetPublished "SetPublished should not have been set" Expect.isFalse model.SetPublished "SetPublished should not have been set"
Expect.isTrue model.PubOverride.HasValue "PubOverride should not have been null" Expect.isTrue model.PubOverride.HasValue "PubOverride should not have been null"
Expect.equal Expect.equal
@ -746,63 +657,52 @@ let editPostModelTests = testList "EditPostModel" [
let model = let model =
EditPostModel.FromPost EditPostModel.FromPost
{ WebLog.Empty with TimeZone = "Etc/GMT+1" } { WebLog.Empty with TimeZone = "Etc/GMT+1" }
{ fullPost with { testFullPost with
Episode = Episode =
Some Some
{ fullPost.Episode.Value with { testFullPost.Episode.Value with
Chapters = Some [] Chapters = Some []
ChapterFile = None ChapterFile = None
ChapterType = None } } ChapterType = None } }
Expect.equal model.ChapterSource "internal" "ChapterSource not filled properly" Expect.equal model.ChapterSource "internal" "ChapterSource not filled properly"
} }
] ]
testList "IsNew" [ let updatedModel () =
test "succeeds for a new post" { let model = EditPostModel.FromPost WebLog.Empty testFullPost
Expect.isTrue model.Title <- "An Updated Post"
(EditPostModel.FromPost WebLog.Empty { Post.Empty with Id = PostId "new" }).IsNew model.Permalink <- "1970/01/updated-post.html"
"IsNew should be set for new post" model.Source <- "HTML"
} model.Text <- "<p>An updated post!</p>"
test "succeeds for a not-new post" { model.Tags <- "Zebras, Aardvarks, , Turkeys"
Expect.isFalse model.Template <- "updated"
(EditPostModel.FromPost WebLog.Empty { Post.Empty with Id = PostId "nu" }).IsNew model.CategoryIds <- [| "cat-x"; "cat-y" |]
"IsNew should not be set for not-new post" model.MetaNames <- [| "Zed Meta"; "A Meta" |]
} model.MetaValues <- [| "A Value"; "Zed Value" |]
] model.Media <- "an-updated-ep.mp3"
let updatedModel = model.Length <- 14444L
{ EditPostModel.FromPost WebLog.Empty fullPost with model.Duration <- "0:14:42"
Title = "An Updated Post" model.MediaType <- "audio/mp3"
Permalink = "1970/01/updated-post.html" model.ImageUrl <- "updated-cover.png"
Source = "HTML" model.Subtitle <- "Talking"
Text = "<p>An updated post!</p>" model.Explicit <- "no"
Tags = "Zebras, Aardvarks, , Turkeys" model.ChapterSource <- "external"
Template = "updated" model.ChapterFile <- "updated-chapters.txt"
CategoryIds = [| "cat-x"; "cat-y" |] model.ChapterType <- "indexes"
MetaNames = [| "Zed Meta"; "A Meta" |] model.TranscriptUrl <- "updated-transcript.txt"
MetaValues = [| "A Value"; "Zed Value" |] model.TranscriptType <- "subtitles"
Media = "an-updated-ep.mp3" model.TranscriptLang <- "ES-mx"
Length = 14444L model.SeasonNumber <- 4
Duration = "0:14:42" model.SeasonDescription <- "Season Fo"
MediaType = "audio/mp3" model.EpisodeNumber <- "432.1"
ImageUrl = "updated-cover.png" model.EpisodeDescription <- "Four Three Two pt One"
Subtitle = "Talking" model
Explicit = "no"
ChapterSource = "external"
ChapterFile = "updated-chapters.txt"
ChapterType = "indexes"
TranscriptUrl = "updated-transcript.txt"
TranscriptType = "subtitles"
TranscriptLang = "ES-mx"
SeasonNumber = 4
SeasonDescription = "Season Fo"
EpisodeNumber = "432.1"
EpisodeDescription = "Four Three Two pt One" }
testList "UpdatePost" [ testList "UpdatePost" [
test "succeeds for a full podcast episode" { test "succeeds for a full podcast episode" {
let post = updatedModel.UpdatePost fullPost (Noda.epoch + Duration.FromDays 400) let post = (updatedModel ()).UpdatePost testFullPost (Noda.epoch + Duration.FromDays 400)
Expect.equal post.Title "An Updated Post" "Title not filled properly" Expect.equal post.Title "An Updated Post" "Title not filled properly"
Expect.equal post.Permalink (Permalink "1970/01/updated-post.html") "Permalink not filled properly" Expect.equal post.Permalink (Permalink "1970/01/updated-post.html") "Permalink not filled properly"
Expect.equal post.PriorPermalinks [ Permalink "1970/01/a-post.html" ] "PriorPermalinks not filled properly" Expect.equal post.PriorPermalinks [ Permalink "1970/01/a-post.html" ] "PriorPermalinks not filled properly"
Expect.equal post.PublishedOn fullPost.PublishedOn "PublishedOn should not have changed" Expect.equal post.PublishedOn testFullPost.PublishedOn "PublishedOn should not have changed"
Expect.equal post.UpdatedOn (Noda.epoch + Duration.FromDays 400) "UpdatedOn not filled properly" Expect.equal post.UpdatedOn (Noda.epoch + Duration.FromDays 400) "UpdatedOn not filled properly"
Expect.equal post.Text "<p>An updated post!</p>" "Text not filled properly" Expect.equal post.Text "<p>An updated post!</p>" "Text not filled properly"
Expect.equal post.Tags [ "aardvarks"; "turkeys"; "zebras" ] "Tags not filled properly" Expect.equal post.Tags [ "aardvarks"; "turkeys"; "zebras" ] "Tags not filled properly"
@ -841,25 +741,24 @@ let editPostModelTests = testList "EditPostModel" [
Expect.equal ep.EpisodeDescription (Some "Four Three Two pt One") "EpisodeDescription not filled properly" Expect.equal ep.EpisodeDescription (Some "Four Three Two pt One") "EpisodeDescription not filled properly"
} }
test "succeeds for a minimal podcast episode" { test "succeeds for a minimal podcast episode" {
let minModel = let minModel = updatedModel ()
{ updatedModel with minModel.Duration <- ""
Duration = "" minModel.MediaType <- ""
MediaType = "" minModel.ImageUrl <- ""
ImageUrl = "" minModel.Subtitle <- ""
Subtitle = "" minModel.Explicit <- ""
Explicit = "" minModel.ChapterFile <- ""
ChapterFile = "" minModel.ChapterType <- ""
ChapterType = "" minModel.ContainsWaypoints <- false
ContainsWaypoints = false minModel.TranscriptUrl <- ""
TranscriptUrl = "" minModel.TranscriptType <- ""
TranscriptType = "" minModel.TranscriptLang <- ""
TranscriptLang = "" minModel.TranscriptCaptions <- false
TranscriptCaptions = false minModel.SeasonNumber <- 0
SeasonNumber = 0 minModel.SeasonDescription <- ""
SeasonDescription = "" minModel.EpisodeNumber <- ""
EpisodeNumber = "" minModel.EpisodeDescription <- ""
EpisodeDescription = "" } let post = minModel.UpdatePost testFullPost (Noda.epoch + Duration.FromDays 500)
let post = minModel.UpdatePost fullPost (Noda.epoch + Duration.FromDays 500)
Expect.isSome post.Episode "There should have been a podcast episode" Expect.isSome post.Episode "There should have been a podcast episode"
let ep = post.Episode.Value let ep = post.Episode.Value
Expect.equal ep.Media "an-updated-ep.mp3" "Media not filled properly" Expect.equal ep.Media "an-updated-ep.mp3" "Media not filled properly"
@ -882,12 +781,11 @@ let editPostModelTests = testList "EditPostModel" [
Expect.isNone ep.EpisodeDescription "EpisodeDescription not filled properly" Expect.isNone ep.EpisodeDescription "EpisodeDescription not filled properly"
} }
test "succeeds for a podcast episode with internal chapters" { test "succeeds for a podcast episode with internal chapters" {
let minModel = let minModel = updatedModel ()
{ updatedModel with minModel.ChapterSource <- "internal"
ChapterSource = "internal" minModel.ChapterFile <- ""
ChapterFile = "" minModel.ChapterType <- ""
ChapterType = "" } let post = minModel.UpdatePost testFullPost (Noda.epoch + Duration.FromDays 500)
let post = minModel.UpdatePost fullPost (Noda.epoch + Duration.FromDays 500)
Expect.isSome post.Episode "There should have been a podcast episode" Expect.isSome post.Episode "There should have been a podcast episode"
let ep = post.Episode.Value let ep = post.Episode.Value
Expect.equal ep.Chapters (Some []) "Chapters not filled properly" Expect.equal ep.Chapters (Some []) "Chapters not filled properly"
@ -895,10 +793,11 @@ let editPostModelTests = testList "EditPostModel" [
Expect.isNone ep.ChapterType "ChapterType not filled properly" Expect.isNone ep.ChapterType "ChapterType not filled properly"
} }
test "succeeds for a podcast episode with no chapters" { test "succeeds for a podcast episode with no chapters" {
let minModel = { updatedModel with ChapterSource = "none" } let minModel = updatedModel ()
minModel.ChapterSource <- "none"
let post = let post =
minModel.UpdatePost minModel.UpdatePost
{ fullPost with Episode = Some { fullPost.Episode.Value with Chapters = Some [] } } { testFullPost with Episode = Some { testFullPost.Episode.Value with Chapters = Some [] } }
(Noda.epoch + Duration.FromDays 500) (Noda.epoch + Duration.FromDays 500)
Expect.isSome post.Episode "There should have been a podcast episode" Expect.isSome post.Episode "There should have been a podcast episode"
let ep = post.Episode.Value let ep = post.Episode.Value
@ -908,14 +807,17 @@ let editPostModelTests = testList "EditPostModel" [
Expect.isNone ep.ChapterWaypoints "ChapterWaypoints not filled properly" Expect.isNone ep.ChapterWaypoints "ChapterWaypoints not filled properly"
} }
test "succeeds for no podcast episode and no template" { test "succeeds for no podcast episode and no template" {
let post = { updatedModel with IsEpisode = false; Template = "" }.UpdatePost fullPost Noda.epoch let model = updatedModel ()
model.IsEpisode <- false
model.Template <- ""
let post = model.UpdatePost testFullPost Noda.epoch
Expect.isNone post.Template "Template not filled properly" Expect.isNone post.Template "Template not filled properly"
Expect.isNone post.Episode "Episode not filled properly" Expect.isNone post.Episode "Episode not filled properly"
} }
test "succeeds when publishing a draft" { test "succeeds when publishing a draft" {
let post = let model = updatedModel ()
{ updatedModel with DoPublish = true }.UpdatePost model.DoPublish <- true
{ fullPost with Status = Draft } (Noda.epoch + Duration.FromDays 375) let post = model.UpdatePost { testFullPost with Status = Draft } (Noda.epoch + Duration.FromDays 375)
Expect.equal post.Status Published "Status not set properly" Expect.equal post.Status Published "Status not set properly"
Expect.equal post.PublishedOn (Some (Noda.epoch + Duration.FromDays 375)) "PublishedOn not set properly" Expect.equal post.PublishedOn (Some (Noda.epoch + Duration.FromDays 375)) "PublishedOn not set properly"
} }
@ -1322,13 +1224,11 @@ let userMessageTests = testList "UserMessage" [
/// All tests in the Domain.ViewModels file /// All tests in the Domain.ViewModels file
let all = testList "ViewModels" [ let all = testList "ViewModels" [
addBaseToRelativeUrlsTests addBaseToRelativeUrlsTests
displayChapterTests
displayCustomFeedTests
displayPageTests displayPageTests
displayThemeTests displayThemeTests
displayUploadTests displayUploadTests
displayUserTests
editCategoryModelTests editCategoryModelTests
editCommonModelTests
editCustomFeedModelTests editCustomFeedModelTests
editMyInfoModelTests editMyInfoModelTests
editPageModelTests editPageModelTests

View File

@ -225,12 +225,11 @@ let register () =
Template.RegisterTag<UserLinksTag> "user_links" Template.RegisterTag<UserLinksTag> "user_links"
[ // Domain types [ // Domain types
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page> typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>; typeof<RssOptions>
typeof<RedirectRule>; typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog> typeof<TagMap>; typeof<WebLog>
// View models // View models
typeof<AppViewContext>; typeof<DisplayCategory>; typeof<DisplayCustomFeed>; typeof<DisplayPage> typeof<AppViewContext>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditPageModel>; typeof<PostDisplay>
typeof<DisplayTheme>; typeof<DisplayUpload>; typeof<DisplayUser>; typeof<EditPageModel> typeof<PostListItem>; typeof<UserMessage>
typeof<EditPostModel>; typeof<PostDisplay>; typeof<PostListItem>; typeof<UserMessage>
// Framework types // Framework types
typeof<AntiforgeryTokenSet>; typeof<DateTime option>; typeof<int option>; typeof<KeyValuePair> typeof<AntiforgeryTokenSet>; typeof<DateTime option>; typeof<int option>; typeof<KeyValuePair>
typeof<MetaItem list>; typeof<string list>; typeof<string option>; typeof<TagMap list> typeof<MetaItem list>; typeof<string list>; typeof<string option>; typeof<TagMap list>

View File

@ -455,11 +455,9 @@ module WebLog =
|> List.append [ { Page.Empty with Id = PageId "posts"; Title = "- First Page of Posts -" } ] |> List.append [ { Page.Empty with Id = PageId "posts"; Title = "- First Page of Posts -" } ]
let! themes = data.Theme.All() let! themes = data.Theme.All()
let uploads = [ Database; Disk ] let uploads = [ Database; Disk ]
let feeds = ctx.WebLog.Rss.CustomFeeds |> List.map (DisplayCustomFeed.FromFeed (CategoryCache.get ctx))
return! return!
Views.WebLog.webLogSettings Views.WebLog.webLogSettings
(SettingsModel.FromWebLog ctx.WebLog) themes pages uploads (EditRssModel.FromRssOptions ctx.WebLog.Rss) (SettingsModel.FromWebLog ctx.WebLog) themes pages uploads (EditRssModel.FromRssOptions ctx.WebLog.Rss)
feeds
|> adminPage "Web Log Settings" true next ctx |> adminPage "Web Log Settings" true next ctx
} }

View File

@ -699,7 +699,40 @@ let uploadNew app = [
/// Web log settings page /// Web log settings page
let webLogSettings let webLogSettings
(model: SettingsModel) (themes: Theme list) (pages: Page list) (uploads: UploadDestination list) (model: SettingsModel) (themes: Theme list) (pages: Page list) (uploads: UploadDestination list)
(rss: EditRssModel) (feeds: DisplayCustomFeed list) app = [ (rss: EditRssModel) (app: AppViewContext) = [
let feedDetail (feed: CustomFeed) =
let source =
match feed.Source with
| Category (CategoryId catId) ->
app.Categories
|> Array.tryFind (fun cat -> cat.Id = catId)
|> Option.map _.Name
|> Option.defaultValue "--INVALID; DELETE THIS FEED--"
|> sprintf "Category: %s"
| Tag tag -> $"Tag: {tag}"
div [ _class "row mwl-table-detail" ] [
div [ _class "col-12 col-md-6" ] [
txt source
if Option.isSome feed.Podcast then
raw " &nbsp; "; span [ _class "badge bg-primary" ] [ raw "PODCAST" ]
br []
small [] [
let feedUrl = relUrl app $"admin/settings/rss/{feed.Id}"
a [ _href (relUrl app (string feed.Path)); _target "_blank" ] [ raw "View Feed" ]
actionSpacer
a [ _href $"{feedUrl}/edit" ] [ raw "Edit" ]; actionSpacer
a [ _href feedUrl; _hxDelete feedUrl; _class "text-danger"
_hxConfirm $"Are you sure you want to delete the custom RSS feed based on {feed.Source}? This action cannot be undone." ] [
raw "Delete"
]
]
]
div [ _class "col-12 col-md-6" ] [
small [ _class "d-md-none" ] [ raw "Served at "; txt (string feed.Path) ]
span [ _class "d-none d-md-inline" ] [ txt (string feed.Path) ]
]
]
h2 [ _class "my-3" ] [ txt app.WebLog.Name; raw " Settings" ] h2 [ _class "my-3" ] [ txt app.WebLog.Name; raw " Settings" ]
article [] [ article [] [
p [ _class "text-muted" ] [ p [ _class "text-muted" ] [
@ -824,7 +857,7 @@ let webLogSettings
a [ _class "btn btn-sm btn-secondary"; _href (relUrl app "admin/settings/rss/new/edit") ] [ a [ _class "btn btn-sm btn-secondary"; _href (relUrl app "admin/settings/rss/new/edit") ] [
raw "Add a New Custom Feed" raw "Add a New Custom Feed"
] ]
if feeds.Length = 0 then if app.WebLog.Rss.CustomFeeds.Length = 0 then
p [ _class "text-muted fst-italic text-center" ] [ raw "No custom feeds defined" ] p [ _class "text-muted fst-italic text-center" ] [ raw "No custom feeds defined" ]
else else
form [ _method "post"; _class "container g-0"; _hxTarget "body" ] [ form [ _method "post"; _class "container g-0"; _hxTarget "body" ] [
@ -834,31 +867,9 @@ let webLogSettings
span [ _class "d-md-none" ] [ raw "Feed" ] span [ _class "d-md-none" ] [ raw "Feed" ]
span [ _class "d-none d-md-inline" ] [ raw "Source" ] span [ _class "d-none d-md-inline" ] [ raw "Source" ]
] ]
div [ _class $"col-12 col-md-6 d-none d-md-inline-block" ] [ raw "Relative Path" ] div [ _class "col-12 col-md-6 d-none d-md-inline-block" ] [ raw "Relative Path" ]
] ]
for feed in feeds do yield! app.WebLog.Rss.CustomFeeds |> List.map feedDetail
div [ _class "row mwl-table-detail" ] [
div [ _class "col-12 col-md-6" ] [
txt feed.Source
if feed.IsPodcast then
raw " &nbsp; "; span [ _class "badge bg-primary" ] [ raw "PODCAST" ]
br []
small [] [
let feedUrl = relUrl app $"admin/settings/rss/{feed.Id}"
a [ _href (relUrl app feed.Path); _target "_blank" ] [ raw "View Feed" ]
actionSpacer
a [ _href $"{feedUrl}/edit" ] [ raw "Edit" ]; actionSpacer
a [ _href feedUrl; _hxDelete feedUrl; _class "text-danger"
_hxConfirm $"Are you sure you want to delete the custom RSS feed based on {feed.Source}? This action cannot be undone." ] [
raw "Delete"
]
]
]
div [ _class "col-12 col-md-6" ] [
small [ _class "d-md-none" ] [ raw "Served at "; txt feed.Path ]
span [ _class "d-none d-md-inline" ] [ txt feed.Path ]
]
]
] ]
] ]
] ]