Add Page Find[Full]ById tests

- Add prior permalink to a page
This commit is contained in:
danieljsummers committed 2024-01-27 23:22:54 -05:00
1 parent 0e8044b948
commit 3f269ed3ba
8 files changed
+503 -20

No files matched your search

@@ -53,15 +53,18 @@ type PostgresPageData(log: ILogger) =
log.LogTrace "Page.countListed" log.LogTrace "Page.countListed"
Count.byContains Table.Page {| webLogDoc webLogId with IsInPageList = true |} Count.byContains Table.Page {| webLogDoc webLogId with IsInPageList = true |}
/// Find a page by its ID (without revisions) /// Find a page by its ID (without revisions or prior permalinks)
let findById pageId webLogId = let findById pageId webLogId = backgroundTask {
log.LogTrace "Page.findById" log.LogTrace "Page.findById"
Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId with
| Some page -> return Some { page with PriorPermalinks = [] }
| None -> return None
}
/// Find a complete page by its ID /// Find a complete page by its ID
let findFullById pageId webLogId = backgroundTask { let findFullById pageId webLogId = backgroundTask {
log.LogTrace "Page.findFullById" log.LogTrace "Page.findFullById"
match! findById pageId webLogId with match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId with
| Some page -> | Some page ->
let! withMore = appendPageRevisions page let! withMore = appendPageRevisions page
return Some withMore return Some withMore
+18 -11
View File
@@ -444,20 +444,27 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
return result.Deleted > 0UL return result.Deleted > 0UL
} }
member _.FindById pageId webLogId = member _.FindById pageId webLogId = backgroundTask {
rethink<Page> { let! page =
withTable Table.Page rethink<Page list> {
get pageId withTable Table.Page
without [ nameof Page.Empty.PriorPermalinks; nameof Page.Empty.Revisions ] getAll [ pageId ]
resultOption; withRetryOptionDefault without [ nameof Page.Empty.PriorPermalinks; nameof Page.Empty.Revisions ]
} result; withRetryDefault
|> verifyWebLog webLogId (fun it -> it.WebLogId) <| conn }
|> tryFirst <| conn
return
page
|> Option.filter (fun pg -> pg.WebLogId = webLogId)
|> Option.map (fun pg -> { pg with Revisions = []; PriorPermalinks = [] })
}
member _.FindByPermalink permalink webLogId = member _.FindByPermalink permalink webLogId =
rethink<Page list> { rethink<Page list> {
withTable Table.Page withTable Table.Page
getAll [ [| webLogId :> obj; permalink |] ] (nameof Page.Empty.Permalink) getAll [ [| webLogId :> obj; permalink |] ] (nameof Page.Empty.Permalink)
without [ nameof Page.Empty.PriorPermalinks; nameof Page.Empty.Revisions ] merge (r.HashMap(nameof Page.Empty.PriorPermalinks, [||])
.With(nameof Page.Empty.Revisions, [||]))
limit 1 limit 1
result; withRetryDefault result; withRetryDefault
} }
@@ -474,7 +481,7 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
result; withRetryDefault result; withRetryDefault
} }
|> tryFirst) conn |> tryFirst) conn
return result |> Option.map (fun pg -> pg.Permalink) return result |> Option.map _.Permalink
} }
member _.FindFullById pageId webLogId = member _.FindFullById pageId webLogId =
@@ -483,7 +490,7 @@ type RethinkDbData(conn: Net.IConnection, config: DataConfig, log: ILogger<Rethi
get pageId get pageId
resultOption; withRetryOptionDefault resultOption; withRetryOptionDefault
} }
|> verifyWebLog webLogId (fun it -> it.WebLogId) <| conn |> verifyWebLog webLogId _.WebLogId <| conn
member _.FindFullByWebLog webLogId = rethink<Page> { member _.FindFullByWebLog webLogId = rethink<Page> {
withTable Table.Page withTable Table.Page
+7 -4
View File
@@ -57,15 +57,18 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
[ webLogParam webLogId ] [ webLogParam webLogId ]
(toCount >> int) (toCount >> int)
/// Find a page by its ID (without revisions) /// Find a page by its ID (without revisions and prior permalinks)
let findById pageId webLogId = let findById pageId webLogId = backgroundTask {
log.LogTrace "Page.findById" log.LogTrace "Page.findById"
Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with
| Some page -> return Some { page with PriorPermalinks = [] }
| None -> return None
}
/// Find a complete page by its ID /// Find a complete page by its ID
let findFullById pageId webLogId = backgroundTask { let findFullById pageId webLogId = backgroundTask {
log.LogTrace "Page.findFullById" log.LogTrace "Page.findFullById"
match! findById pageId webLogId with match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with
| Some page -> | Some page ->
let! page = appendPageRevisions page let! page = appendPageRevisions page
return Some page return Some page
+60
View File
@@ -1,5 +1,6 @@
module PageDataTests module PageDataTests
open System
open Expecto open Expecto
open MyWebLog open MyWebLog
open MyWebLog.Data open MyWebLog.Data
@@ -8,6 +9,12 @@ open NodaTime
/// The ID of the root web log /// The ID of the root web log
let rootId = WebLogId "uSitJEuD3UyzWC9jgOHc8g" let rootId = WebLogId "uSitJEuD3UyzWC9jgOHc8g"
/// The ID of the "A cool page" page
let coolPageId = PageId "hgc_BLEZ50SoAWLuPNISvA"
/// The published and updated time of the "A cool page" page
let coolPagePublished = Instant.FromDateTimeOffset(DateTimeOffset.Parse "2024-01-20T22:14:28Z")
let ``Add succeeds`` (data: IData) = task { let ``Add succeeds`` (data: IData) = task {
let page = let page =
{ Id = PageId "added-page" { Id = PageId "added-page"
@@ -55,6 +62,8 @@ let ``All succeeds`` (data: IData) = task {
Expect.isEmpty pg.Metadata $"Page {idx} should have had no metadata" Expect.isEmpty pg.Metadata $"Page {idx} should have had no metadata"
Expect.isEmpty pg.Revisions $"Page {idx} should have had no revisions" Expect.isEmpty pg.Revisions $"Page {idx} should have had no revisions"
Expect.isEmpty pg.PriorPermalinks $"Page {idx} should have had no prior permalinks") Expect.isEmpty pg.PriorPermalinks $"Page {idx} should have had no prior permalinks")
let! others = data.Page.All (WebLogId "not-there")
Expect.isEmpty others "There should not be pages retrieved"
} }
let ``CountAll succeeds`` (data: IData) = task { let ``CountAll succeeds`` (data: IData) = task {
@@ -66,3 +75,54 @@ let ``CountListed succeeds`` (data: IData) = task {
let! pages = data.Page.CountListed rootId let! pages = data.Page.CountListed rootId
Expect.equal pages 1 "There should have been 1 page in the page list" Expect.equal pages 1 "There should have been 1 page in the page list"
} }
let ``FindById succeeds when a page is found`` (data: IData) = task {
let! page = data.Page.FindById coolPageId rootId
Expect.isSome page "A page should have been returned"
let pg = page.Value
Expect.equal pg.Id coolPageId "The wrong page was retrieved"
Expect.equal pg.WebLogId rootId "The page's web log did not match the called parameter"
Expect.equal pg.AuthorId (WebLogUserId "5EM2rimH9kONpmd2zQkiVA") "Author ID is incorrect"
Expect.equal pg.Title "Page Title" "Title is incorrect"
Expect.equal pg.Permalink (Permalink "a-cool-page.html") "Permalink is incorrect"
Expect.equal
pg.PublishedOn (Instant.FromDateTimeOffset(DateTimeOffset.Parse "2024-01-20T22:14:28Z")) "Published On is incorrect"
Expect.equal pg.UpdatedOn (Instant.FromDateTimeOffset(DateTimeOffset.Parse "2024-01-20T22:14:28Z")) "Updated On is incorrect"
Expect.isFalse pg.IsInPageList "Is in page list flag should not have been set"
Expect.equal pg.Text "<h1 id=\"a-cool-page\">A Cool Page</h1>\n<p>It really is cool!</p>\n" "Text is incorrect"
Expect.hasLength pg.Metadata 2 "There should be 2 metadata items on this page"
Expect.equal pg.Metadata[0].Name "Cool" "Meta item 0 name is incorrect"
Expect.equal pg.Metadata[0].Value "true" "Meta item 0 value is incorrect"
Expect.equal pg.Metadata[1].Name "Warm" "Meta item 1 name is incorrect"
Expect.equal pg.Metadata[1].Value "false" "Meta item 1 value is incorrect"
Expect.isEmpty pg.Revisions "Revisions should not have been retrieved"
Expect.isEmpty pg.PriorPermalinks "Prior permalinks should not have been retrieved"
}
let ``FindById succeeds when a page is not found (incorrect weblog)`` (data: IData) = task {
let! page = data.Page.FindById coolPageId (WebLogId "wrong")
Expect.isNone page "The page should not have been retrieved"
}
let ``FindById succeeds when a page is not found (bad page ID)`` (data: IData) = task {
let! page = data.Page.FindById (PageId "missing") rootId
Expect.isNone page "The page should not have been retrieved"
}
let ``FindFullById succeeds when a page is found`` (data: IData) = task {
let! page = data.Page.FindFullById coolPageId rootId
Expect.isSome page "A page should have been returned"
let pg = page.Value
Expect.equal pg.Id coolPageId "The wrong page was retrieved"
Expect.equal pg.WebLogId rootId "The page's web log did not match the called parameter"
Expect.hasLength pg.Revisions 1 "There should be 1 revision"
Expect.equal pg.Revisions[0].AsOf coolPagePublished "Revision 0 as-of is incorrect"
Expect.equal pg.Revisions[0].Text (Markdown "# A Cool Page\n\nIt really is cool!") "Revision 0 text is incorrect"
Expect.hasLength pg.PriorPermalinks 1 "There should be 1 prior permalink"
Expect.equal pg.PriorPermalinks[0] (Permalink "a-cool-pg.html") "Prior permalink 0 is incorrect"
}
let ``FindFullById succeeds when a page is not found`` (data: IData) = task {
let! page = data.Page.FindFullById (PageId "not-there") rootId
Expect.isNone page "A page should not have been retrieved"
}
@@ -131,6 +131,25 @@ let pageTests = testList "Page" [
testTask "CountListed succeeds" { testTask "CountListed succeeds" {
do! PageDataTests.``CountListed succeeds`` (mkData ()) do! PageDataTests.``CountListed succeeds`` (mkData ())
} }
testList "FindById" [
testTask "succeeds when a page is found" {
do! PageDataTests.``FindById succeeds when a page is found`` (mkData ())
}
testTask "succeeds when a page is not found (incorrect weblog)" {
do! PageDataTests.``FindById succeeds when a page is not found (incorrect weblog)`` (mkData ())
}
testTask "succeeds when a page is not found (bad page ID)" {
do! PageDataTests.``FindById succeeds when a page is not found (bad page ID)`` (mkData ())
}
]
testList "FindFullById" [
testTask "succeeds when a page is found" {
do! PageDataTests.``FindFullById succeeds when a page is found`` (mkData ())
}
testTask "succeeds when a page is not found" {
do! PageDataTests.``FindFullById succeeds when a page is not found`` (mkData ())
}
]
] ]
/// Drop the throwaway PostgreSQL database /// Drop the throwaway PostgreSQL database
+19
View File
@@ -130,6 +130,25 @@ let pageTests = testList "Page" [
testTask "CountListed succeeds" { testTask "CountListed succeeds" {
do! PageDataTests.``CountListed succeeds`` data.Value do! PageDataTests.``CountListed succeeds`` data.Value
} }
testList "FindById" [
testTask "succeeds when a page is found" {
do! PageDataTests.``FindById succeeds when a page is found`` data.Value
}
testTask "succeeds when a page is not found (incorrect weblog)" {
do! PageDataTests.``FindById succeeds when a page is not found (incorrect weblog)`` data.Value
}
testTask "succeeds when a page is not found (bad page ID)" {
do! PageDataTests.``FindById succeeds when a page is not found (bad page ID)`` data.Value
}
]
testList "FindFullById" [
testTask "succeeds when a page is found" {
do! PageDataTests.``FindFullById succeeds when a page is found`` data.Value
}
testTask "succeeds when a page is not found" {
do! PageDataTests.``FindFullById succeeds when a page is not found`` data.Value
}
]
] ]
/// Drop the throwaway RethinkDB database /// Drop the throwaway RethinkDB database
@@ -164,6 +164,35 @@ let pageTests = testList "Page" [
try do! PageDataTests.``CountListed succeeds`` data try do! PageDataTests.``CountListed succeeds`` data
finally dispose data finally dispose data
} }
testList "FindById" [
testTask "succeeds when a page is found" {
let data = mkData ()
try do! PageDataTests.``FindById succeeds when a page is found`` data
finally dispose data
}
testTask "succeeds when a page is not found (incorrect weblog)" {
let data = mkData ()
try do! PageDataTests.``FindById succeeds when a page is not found (incorrect weblog)`` data
finally dispose data
}
testTask "succeeds when a page is not found (bad page ID)" {
let data = mkData ()
try do! PageDataTests.``FindById succeeds when a page is not found (bad page ID)`` data
finally dispose data
}
]
testList "FindFullById" [
testTask "succeeds when a page is found" {
let data = mkData ()
try do! PageDataTests.``FindFullById succeeds when a page is found`` data
finally dispose data
}
testTask "succeeds when a page is not found" {
let data = mkData ()
try do! PageDataTests.``FindFullById succeeds when a page is not found`` data
finally dispose data
}
]
] ]
/// Delete the SQLite database /// Delete the SQLite database
+344 -1
View File
@@ -1 +1,344 @@
{"WebLog":{"Id":"uSitJEuD3UyzWC9jgOHc8g","Name":"Root WebLog","Slug":"root-weblog","Subtitle":"This is the main one","DefaultPage":"posts","PostsPerPage":9,"ThemeId":"default","UrlBase":"http://localhost:8081","TimeZone":"America/Denver","Rss":{"IsFeedEnabled":true,"FeedName":"feed","ItemsInFeed":7,"IsCategoryEnabled":true,"IsTagEnabled":true,"Copyright":"CC40-NC-BY","CustomFeeds":[{"Id":"isPQ6drbDEydxohQzaiYtQ","Source":"tag:podcast","Path":"podcast-feed","Podcast":{"Title":"Root Podcast","ItemsInFeed":23,"Summary":"All things that happen in the domain root","DisplayedAuthor":"Podcaster Extraordinaire","Email":"podcaster@example.com","ImageUrl":"images/cover-art.png","AppleCategory":"Fiction","AppleSubcategory":"Drama","Explicit":"no","DefaultMediaType":"audio/mpeg","MediaBaseUrl":"https://media.example.com/root/","PodcastGuid":"10fd7f79-c719-4e1d-9da7-10405dd4fd96","FundingUrl":"https://example.com/support-us","FundingText":"Support Our Work","Medium":"newsletter"}}]},"AutoHtmx":true,"Uploads":"Database","RedirectRules":[]},"Users":[{"Id":"5EM2rimH9kONpmd2zQkiVA","WebLogId":"uSitJEuD3UyzWC9jgOHc8g","Email":"root@example.com","FirstName":"Root","LastName":"Owner","PreferredName":"Admin","PasswordHash":"AQAAAAIAAYagAAAAEEnq9J9lKZoMQZaTOJHKIQo44skDdzDigzqS+o6myMop38YuHfm/vNs9b/WpYjsOxg==","AccessLevel":"Administrator","CreatedOn":"2024-01-20T21:49:03Z","LastSeenOn":"2024-01-20T22:25:03Z"},{"Id":"GPbJaSOwTkKt14ZKYyveKA","WebLogId":"uSitJEuD3UyzWC9jgOHc8g","Email":"editor@example.com","FirstName":"Ed","LastName":"It-Or","PreferredName":"Edits","PasswordHash":"AQAAAAIAAYagAAAAEA8E3NwJkZO+q35FTmUT0wMNB8IpBOSVACKQcccXpaWfZJMRmZzjPEzd4j/f9h+rEA==","AccessLevel":"Editor","CreatedOn":"2024-01-20T21:58:42Z"},{"Id":"iIRNLSeY0EanxRPyqGuwVg","WebLogId":"uSitJEuD3UyzWC9jgOHc8g","Email":"author@example.com","FirstName":"Author","LastName":"Dude","PreferredName":"Mister","PasswordHash":"AQAAAAIAAYagAAAAEBYNf1sR/pjaX2dZgqlvpH/Tqpz2h/CG3rsk/wH2ReTysjpK/gxSqht7IULWXM7KHQ==","Url":"https://example.com/author","AccessLevel":"Author","CreatedOn":"2024-01-20T21:53:27Z"}],"Theme":{"Id":"default","Name":"myWebLog Default Theme","Version":"2.1.0","Templates":[{"Name":"single-post","Text":"{%- assign post = model.posts | first -%}\n<h1>{{ post.title }}</h1>\n<h4 class=\"item-meta text-muted\">\n {% if post.published_on -%}\n Published {{ post.published_on | date: \"dddd, MMMM d, yyyy\" }}\n at {{ post.published_on | date: \"h:mm tt\" | downcase }}\n {%- else -%}\n **DRAFT**\n {% endif %}\n by {{ model.authors | value: post.author_id }}\n {%- if is_editor or is_author and user_id == post.author_id %}\n &bull; <a hx-boost=false href=\"{{ post | edit_post_link }}\">Edit Post</a>\n {%- endif %}\n</h4>\n<div>\n <article class=\"container mt-3\">\n <div>{{ post.text }}</div>\n {%- assign cat_count = post.category_ids | size -%}\n {% if cat_count > 0 -%}\n <h4 class=\"item-meta text-muted\">\n Categorized under\n {% for cat_id in post.category_ids -%}\n {% assign cat = categories | where: \"Id\", cat_id | first %}\n <span class=text-nowrap>\n <a href=\"{{ cat | category_link }}\" title=\"Categorized under &ldquo;{{ cat.name | escape }}&rdquo;\">\n {{ cat.name }}\n </a>\n </span>\n {% unless forloop.last %} &bull; {% endunless %}\n {%- endfor %}\n </h4>\n {%- endif %}\n {%- assign tag_count = post.tags | size -%}\n {% if tag_count > 0 -%}\n <h4 class=\"item-meta text-muted\">\n Tagged\n {% for tag in post.tags %}\n <span class=text-nowrap>\n <a href=\"{{ tag | tag_link }}\" title=\"Posts tagged &ldquo;{{ tag | escape }}&rdquo;\" rel=\"tag\">{{ tag }}</a>\n </span>\n {% unless forloop.last %} &bull; {% endunless %}\n {%- endfor %}\n </h4>\n {%- endif %}\n </article>\n <div>\n <nav aria-label=pagination>\n <ul class=\"pagination justify-content-evenly mt-5\">\n {% if model.newer_link -%}\n <li class=page-item>\n <a class=page-link href=\"{{ model.newer_link.value | relative_link }}\">{{ model.newer_name.value }}</a>\n <span class=text-muted>&laquo; Previous Post</span>\n {% endif %}\n {% if model.older_link -%}\n <li class=\"page-item text-end\">\n <a class=page-link href=\"{{ model.older_link.value | relative_link }}\">{{ model.older_name.value }}</a>\n <span class=text-muted>Next Post &raquo;</span>\n {%- endif -%}\n </ul>\n </nav>\n </div>\n</div>\n"},{"Name":"single-page","Text":"<h2 class=py-3>{{ page.title }}</h2>\n<article class=\"container\">{{ page.text }}</article>\n"},{"Name":"layout","Text":"<!DOCTYPE html>\n<html lang=en>\n <meta charset=utf-8>\n <meta name=viewport content=\"width=device-width\">\n <link rel=stylesheet href=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css\n integrity=sha384-1BmE4kWBq78iYhFldvKLine truncated {
"WebLog": {
"Id": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Root WebLog",
"Slug": "root-weblog",
"Subtitle": "This is the main one",
"DefaultPage": "posts",
"PostsPerPage": 9,
"ThemeId": "default",
"UrlBase": "http://localhost:8081",
"TimeZone": "America/Denver",
"Rss": {
"IsFeedEnabled": true,
"FeedName": "feed",
"ItemsInFeed": 7,
"IsCategoryEnabled": true,
"IsTagEnabled": true,
"Copyright": "CC40-NC-BY",
"CustomFeeds": [
{
"Id": "isPQ6drbDEydxohQzaiYtQ",
"Source": "tag:podcast",
"Path": "podcast-feed",
"Podcast": {
"Title": "Root Podcast",
"ItemsInFeed": 23,
"Summary": "All things that happen in the domain root",
"DisplayedAuthor": "Podcaster Extraordinaire",
"Email": "podcaster@example.com",
"ImageUrl": "images/cover-art.png",
"AppleCategory": "Fiction",
"AppleSubcategory": "Drama",
"Explicit": "no",
"DefaultMediaType": "audio/mpeg",
"MediaBaseUrl": "https://media.example.com/root/",
"PodcastGuid": "10fd7f79-c719-4e1d-9da7-10405dd4fd96",
"FundingUrl": "https://example.com/support-us",
"FundingText": "Support Our Work",
"Medium": "newsletter"
}
}
]
},
"AutoHtmx": true,
"Uploads": "Database",
"RedirectRules": []
},
"Users": [
{
"Id": "5EM2rimH9kONpmd2zQkiVA",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Email": "root@example.com",
"FirstName": "Root",
"LastName": "Owner",
"PreferredName": "Admin",
"PasswordHash": "AQAAAAIAAYagAAAAEEnq9J9lKZoMQZaTOJHKIQo44skDdzDigzqS+o6myMop38YuHfm/vNs9b/WpYjsOxg==",
"AccessLevel": "Administrator",
"CreatedOn": "2024-01-20T21:49:03Z",
"LastSeenOn": "2024-01-20T22:25:03Z"
},
{
"Id": "GPbJaSOwTkKt14ZKYyveKA",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Email": "editor@example.com",
"FirstName": "Ed",
"LastName": "It-Or",
"PreferredName": "Edits",
"PasswordHash": "AQAAAAIAAYagAAAAEA8E3NwJkZO+q35FTmUT0wMNB8IpBOSVACKQcccXpaWfZJMRmZzjPEzd4j/f9h+rEA==",
"AccessLevel": "Editor",
"CreatedOn": "2024-01-20T21:58:42Z"
},
{
"Id": "iIRNLSeY0EanxRPyqGuwVg",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Email": "author@example.com",
"FirstName": "Author",
"LastName": "Dude",
"PreferredName": "Mister",
"PasswordHash": "AQAAAAIAAYagAAAAEBYNf1sR/pjaX2dZgqlvpH/Tqpz2h/CG3rsk/wH2ReTysjpK/gxSqht7IULWXM7KHQ==",
"Url": "https://example.com/author",
"AccessLevel": "Author",
"CreatedOn": "2024-01-20T21:53:27Z"
}
],
"Theme": {
"Id": "default",
"Name": "myWebLog Default Theme",
"Version": "2.1.0",
"Templates": [
{
"Name": "single-post",
"Text": "{%- assign post = model.posts | first -%}\n<h1>{{ post.title }}</h1>\n<h4 class=\"item-meta text-muted\">\n {% if post.published_on -%}\n Published {{ post.published_on | date: \"dddd, MMMM d, yyyy\" }}\n at {{ post.published_on | date: \"h:mm tt\" | downcase }}\n {%- else -%}\n **DRAFT**\n {% endif %}\n by {{ model.authors | value: post.author_id }}\n {%- if is_editor or is_author and user_id == post.author_id %}\n &bull; <a hx-boost=false href=\"{{ post | edit_post_link }}\">Edit Post</a>\n {%- endif %}\n</h4>\n<div>\n <article class=\"container mt-3\">\n <div>{{ post.text }}</div>\n {%- assign cat_count = post.category_ids | size -%}\n {% if cat_count > 0 -%}\n <h4 class=\"item-meta text-muted\">\n Categorized under\n {% for cat_id in post.category_ids -%}\n {% assign cat = categories | where: \"Id\", cat_id | first %}\n <span class=text-nowrap>\n <a href=\"{{ cat | category_link }}\" title=\"Categorized under &ldquo;{{ cat.name | escape }}&rdquo;\">\n {{ cat.name }}\n </a>\n </span>\n {% unless forloop.last %} &bull; {% endunless %}\n {%- endfor %}\n </h4>\n {%- endif %}\n {%- assign tag_count = post.tags | size -%}\n {% if tag_count > 0 -%}\n <h4 class=\"item-meta text-muted\">\n Tagged\n {% for tag in post.tags %}\n <span class=text-nowrap>\n <a href=\"{{ tag | tag_link }}\" title=\"Posts tagged &ldquo;{{ tag | escape }}&rdquo;\" rel=\"tag\">{{ tag }}</a>\n </span>\n {% unless forloop.last %} &bull; {% endunless %}\n {%- endfor %}\n </h4>\n {%- endif %}\n </article>\n <div>\n <nav aria-label=pagination>\n <ul class=\"pagination justify-content-evenly mt-5\">\n {% if model.newer_link -%}\n <li class=page-item>\n <a class=page-link href=\"{{ model.newer_link.value | relative_link }}\">{{ model.newer_name.value }}</a>\n <span class=text-muted>&laquo; Previous Post</span>\n {% endif %}\n {% if model.older_link -%}\n <li class=\"page-item text-end\">\n <a class=page-link href=\"{{ model.older_link.value | relative_link }}\">{{ model.older_name.value }}</a>\n <span class=text-muted>Next Post &raquo;</span>\n {%- endif -%}\n </ul>\n </nav>\n </div>\n</div>\n"
},
{
"Name": "single-page",
"Text": "<h2 class=py-3>{{ page.title }}</h2>\n<article class=\"container\">{{ page.text }}</article>\n"
},
{
"Name": "layout",
"Text": "<!DOCTYPE html>\n<html lang=en>\n <meta charset=utf-8>\n <meta name=viewport content=\"width=device-width\">\n <link rel=stylesheet href=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css\n integrity=sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3 crossorigin=anonymous>\n <title>{{ page_title | strip_html }}{% if page_title %} &laquo; {% endif %}{{ web_log.name | strip_html }}</title>\n {% page_head -%}\n <header>\n <nav class=\"navbar navbar-light bg-light navbar-expand-md justify-content-start px-2\">\n <div class=container-fluid>\n <a class=navbar-brand href=\"{{ \"\" | relative_link }}\">{{ web_log.name }}</a>\n <button class=navbar-toggler type=button data-bs-toggle=collapse data-bs-target=#navbarText\n aria-controls=navbarText aria-expanded=false aria-label=\"Toggle navigation\">\n <span class=navbar-toggler-icon></span>\n </button>\n <div class=\"collapse navbar-collapse\" id=navbarText>\n {% if web_log.subtitle -%}\n <span class=navbar-text>{{ web_log.subtitle.value }}</span>\n {%- endif %}\n <ul class=navbar-nav>\n {% unless web_log.default_page == \"posts\" %}{{ \"page/1\" | nav_link: \"Posts\" }}{% endunless %}\n {% if page_list -%}\n {% for pg in page_list %}{{ pg.permalink | nav_link: pg.title }}{% endfor %}\n {%- endif %}\n </ul>\n {% user_links %}\n </div>\n </div>\n </nav>\n </header>\n <main class=mx-3>\n {% if messages %}\n <div class=messages>\n {% for msg in messages %}\n <div role=alert class=\"alert alert-{{ msg.level }} alert-dismissible fade show\">\n {{ msg.message }}\n <button type=button class=btn-close data-bs-dismiss=alert aria-label=Close></button>\n {% if msg.detail %}<hr><p>{{ msg.detail.value }}{% endif %}\n </div>\n {% endfor %}\n </div>\n {% endif %}\n {{ content }} \n </main>\n <footer>\n <hr>\n <div class=\"container-fluid text-end\">\n <img src=\"{{ \"themes/admin/logo-dark.png\" | relative_link }}\" alt=myWebLog width=120 height=34>\n </div>\n </footer>\n <script src=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js\n integrity=sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p\n crossorigin=anonymous></script>\n</html>\n"
},
{
"Name": "index",
"Text": "{%- if is_category or is_tag %}\n <h1 class=index-title>{{ page_title }}</h1>\n {%- if subtitle %}<h4 class=text-muted>{{ subtitle }}</h4>{% endif -%}\n{% endif %}\n{%- assign post_count = model.posts | size -%}\n{%- if post_count > 0 %}\n <section class=\"container mt-3\" aria-label=\"The posts for the page\">\n {%- for post in model.posts %}\n <article>\n <h1>\n <a href=\"{{ post | relative_link }}\" title=\"Permanent link to &quot;{{ post.title | escape }}&quot;\">\n {{ post.title }}\n </a>\n </h1>\n <p>\n Published on {{ post.published_on | date: \"MMMM d, yyyy\" }}\n at {{ post.published_on | date: \"h:mmtt\" | downcase }}\n by {{ model.authors | value: post.author_id }}\n {{ post.text }}\n {%- assign category_count = post.category_ids | size -%}\n {%- assign tag_count = post.tags | size -%}\n {% if category_count > 0 or tag_count > 0 %}\n <footer>\n <p>\n {%- if category_count > 0 -%}\n Categorized under:\n {% for cat in post.category_ids -%}\n {%- assign this_cat = categories | where: \"Id\", cat | first -%}\n {{ this_cat.name }}{% unless forloop.last %}, {% endunless %}\n {%- assign cat_names = this_cat.name | concat: cat_names -%}\n {%- endfor -%}\n {%- assign cat_names = \"\" -%}\n <br>\n {% endif -%}\n {%- if tag_count > 0 %}\n Tagged: {{ post.tags | join: \", \" }}\n {% endif -%}\n </footer>\n {% endif %}\n <hr>\n </article>\n {% endfor %}\n </section>\n <nav aria-label=pagination>\n <ul class=\"pagination justify-content-evenly mt-2\">\n {% if model.newer_link -%}\n <li class=page-item><a class=page-link href=\"{{ model.newer_link.value }}\">&laquo; Newer Posts</a>\n {% endif %}\n {% if model.older_link -%}\n <li class=page-item><a class=page-link href=\"{{ model.older_link.value }}\">Older Posts &raquo;</a>\n {%- endif -%}\n </ul>\n </nav>\n{%- else %}\n <article><p class=\"text-center mt-3\">No posts found</article>\n{%- endif %}\n"
}
]
},
"Assets": [
{
"Id": "default/style.css",
"UpdatedOn": "2023-07-02T20:36:28Z",
"Data": "Lm1lc3NhZ2VzIHsKICBtYXgtd2lkdGg6IDYwcmVtOwogIG1hcmdpbjogYXV0bzsKfQpibG9ja3F1b3RlIHsKICBib3JkZXItbGVmdDogc29saWQgNHB4IGxpZ2h0Z3JheTsKICBwYWRkaW5nLWxlZnQ6IDFyZW07Cn0KLml0ZW0tbWV0YSB7CiAgZm9udC1zaXplOiAxLjFyZW07CiAgZm9udC13ZWlnaHQ6IG5vcm1hbDs7Cn0KLml0ZW0tbWV0YTo6YmVmb3JlIHsKICBjb250ZW50OiAiwrsiOwogIHZlcnRpY2FsLWFsaWduOiB0ZXh0LXRvcDsKfQphOmxpbmssIGE6dmlzaXRlZCB7CiAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwp9CmE6aG92ZXIgewogIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOwp9Cg=="
}
],
"Categories": [
{
"Id": "S5JflPsJ9EG7gA2LD4m92A",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Favorites",
"Slug": "favorites",
"Description": "Favorite posts"
},
{
"Id": "jw6N69YtTEWVHAO33jHU-w",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Spitball",
"Slug": "spitball",
"Description": "Posts that may or may not work"
},
{
"Id": "ScVpyu1e7UiP7bDdge3ZEw",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Name": "Moonshot",
"Slug": "moonshot",
"ParentId": "jw6N69YtTEWVHAO33jHU-w"
}
],
"TagMappings": [
{
"Id": "Icm027noqE-rPHKZA98vAw",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Tag": "f#",
"UrlValue": "f-sharp"
},
{
"Id": "GdryXh-S0kGsNBs2RIacGA",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"Tag": "ghoti",
"UrlValue": "fish"
}
],
"Pages": [
{
"Id": "hgc_BLEZ50SoAWLuPNISvA",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Title": "Page Title",
"Permalink": "a-cool-page.html",
"PublishedOn": "2024-01-20T22:14:28Z",
"UpdatedOn": "2024-01-20T22:14:28Z",
"IsInPageList": false,
"Text": "<h1 id=\"a-cool-page\">A Cool Page</h1>\n<p>It really is cool!</p>\n",
"Metadata": [
{
"Name": "Cool",
"Value": "true"
},
{
"Name": "Warm",
"Value": "false"
}
],
"PriorPermalinks": [
"a-cool-pg.html"
],
"Revisions": [
{
"AsOf": "2024-01-20T22:14:28Z",
"Text": "Markdown: # A Cool Page\n\nIt really is cool!"
}
]
},
{
"Id": "KouRjvSmm0Wz6TMD8xf67A",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Title": "Yet Another Page",
"Permalink": "this-again.html",
"PublishedOn": "2024-01-20T22:15:08Z",
"UpdatedOn": "2024-01-20T22:15:08Z",
"IsInPageList": true,
"Text": "<h1>Page 2</h1>\n\n<p>It's a trip.",
"Metadata": [],
"PriorPermalinks": [],
"Revisions": [
{
"AsOf": "2024-01-20T22:15:08Z",
"Text": "HTML: <h1>Page 2</h1>\n\n<p>It's a trip."
}
]
}
],
"Posts": [
{
"Id": "RCsCU2puYEmkpzotoi8p4g",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Status": "Published",
"Title": "Test Post 1",
"Permalink": "2024/test-post-1.html",
"PublishedOn": "2024-01-20T22:17:29Z",
"UpdatedOn": "2024-01-20T22:17:29Z",
"Text": "<h2 id=\"introduction\">Introduction</h2>\n<p>Visit <a href=\"https://example.com\">my web site</a> or <a href=\"/a-fine-page.html\">my local page</a> for more information.</p>\n",
"CategoryIds": [
"ScVpyu1e7UiP7bDdge3ZEw"
],
"Tags": [
"f#",
"howdy",
"intro"
],
"Metadata": [],
"PriorPermalinks": [],
"Revisions": [
{
"AsOf": "2024-01-20T22:17:29Z",
"Text": "Markdown: ## Introduction\n\nVisit [my web site](https://example.com) or [my local page](/a-fine-page.html) for more information."
}
]
},
{
"Id": "osxMfWGlAkyugUbJ1-xD1g",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Status": "Published",
"Title": "Episode 1",
"Permalink": "2024/episode-1.html",
"PublishedOn": "2024-01-20T22:24:01Z",
"UpdatedOn": "2024-01-20T22:24:01Z",
"Text": "<p>It's the launch of my new podcast - y'all come listen!",
"CategoryIds": [
"S5JflPsJ9EG7gA2LD4m92A"
],
"Tags": [
"general",
"podcast"
],
"Episode": {
"Media": "episode-1.mp3",
"Length": 124302,
"Duration": "0:12:22",
"ImageUrl": "images/ep1-cover.png",
"Subtitle": "An introduction to this podcast",
"Explicit": "clean",
"ChapterFile": "uploads/chapters.json",
"TranscriptUrl": "uploads/transcript.srt",
"TranscriptType": "application/srt",
"TranscriptLang": "en",
"TranscriptCaptions": true,
"SeasonNumber": 1,
"SeasonDescription": "The First Season",
"EpisodeNumber": 1.0,
"EpisodeDescription": "The first episode ever!"
},
"Metadata": [
{
"Name": "Density",
"Value": "Non-existent"
},
{
"Name": "Intensity",
"Value": "Low"
}
],
"PriorPermalinks": [],
"Revisions": [
{
"AsOf": "2024-01-20T22:24:01Z",
"Text": "HTML: <p>It's the launch of my new podcast - y'all come listen!"
}
]
},
{
"Id": "l4_Eh4aFO06SqqJjOymNzA",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Status": "Published",
"Title": "Episode 2",
"Permalink": "2024/episode-2.html",
"PublishedOn": "2024-01-20T22:31:32Z",
"UpdatedOn": "2024-01-20T22:31:32Z",
"Text": "<p>m i n i m a l",
"CategoryIds": [],
"Tags": [
"podcast"
],
"Episode": {
"Media": "episode-2.mp3",
"Length": 12873952,
"Duration": "1:03:24",
"SeasonNumber": 1,
"SeasonDescription": "The First Season",
"EpisodeNumber": 2.0,
"EpisodeDescription": "A long update"
},
"Metadata": [],
"PriorPermalinks": [],
"Revisions": [
{
"AsOf": "2024-01-20T22:31:32Z",
"Text": "HTML: <p>m i n i m a l"
}
]
},
{
"Id": "QweKbWQiOkqqrjEdgP9wwg",
"WebLogId": "uSitJEuD3UyzWC9jgOHc8g",
"AuthorId": "5EM2rimH9kONpmd2zQkiVA",
"Status": "Published",
"Title": "Something May Happen",
"Permalink": "2024/something.html",
"PublishedOn": "2024-01-20T22:32:59Z",
"UpdatedOn": "2024-01-20T22:32:59Z",
"Text": "<h2>Hmm</h2>",
"CategoryIds": [
"jw6N69YtTEWVHAO33jHU-w"
],
"Tags": [
"f#",
"ghoti",
"speculation"
],
"Metadata": [],
"PriorPermalinks": [],
"Revisions": [
{
"AsOf": "2024-01-20T22:32:59Z",
"Text": "HTML: <h2>Hmm</h2>"
}
]
}
],
"Uploads": []
}