Add prior permalink management

This commit is contained in:
Daniel J. Summers 2022-05-20 12:15:55 -04:00
parent dc24da8ed7
commit ea8d4b1c63
12 changed files with 276 additions and 18 deletions

View File

@ -394,6 +394,21 @@ module Page =
]
write; withRetryDefault; ignoreResult
}
/// Update prior permalinks for a page
let updatePriorPermalinks pageId webLogId (permalinks : Permalink list) conn = task {
match! findById pageId webLogId conn with
| Some _ ->
do! rethink {
withTable Table.Page
get pageId
update [ "priorPermalinks", permalinks :> obj ]
write; withRetryDefault; ignoreResult conn
}
return true
| None -> return false
}
/// Functions to manipulate posts
module Post =
@ -542,6 +557,27 @@ module Post =
write; withRetryDefault; ignoreResult
}
/// Update prior permalinks for a post
let updatePriorPermalinks (postId : PostId) webLogId (permalinks : Permalink list) conn = task {
match! (
rethink<Post> {
withTable Table.Post
get postId
without [ "revisions"; "priorPermalinks" ]
resultOption; withRetryOptionDefault
}
|> verifyWebLog webLogId (fun p -> p.webLogId)) conn with
| Some _ ->
do! rethink {
withTable Table.Post
get postId
update [ "priorPermalinks", permalinks :> obj ]
write; withRetryDefault; ignoreResult conn
}
return true
| None -> return false
}
/// Functions to manipulate web logs
module WebLog =

View File

@ -299,6 +299,44 @@ type LogOnModel =
{ emailAddress = ""; password = ""; returnTo = None }
/// View model to manage permalinks
[<CLIMutable; NoComparison; NoEquality>]
type ManagePermalinksModel =
{ /// The ID for the entity being edited
id : string
/// The type of entity being edited ("page" or "post")
entity : string
/// The current title of the page or post
currentTitle : string
/// The current permalink of the page or post
currentPermalink : string
/// The prior permalinks for the page or post
prior : string[]
}
/// Create a permalink model from a page
static member fromPage (pg : Page) =
{ id = PageId.toString pg.id
entity = "page"
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
entity = "post"
currentTitle = post.title
currentPermalink = Permalink.toString post.permalink
prior = post.priorPermalinks |> List.map Permalink.toString |> Array.ofList
}
/// View model for posts in a list
[<NoComparison; NoEquality>]
type PostListItem =

View File

@ -45,6 +45,31 @@ let edit pgId : HttpHandler = requireUser >=> fun next ctx -> task {
| None -> return! Error.notFound next ctx
}
// GET /page/{id}/permalinks
let editPermalinks pgId : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.Page.findByFullId (PageId pgId) (webLogId ctx) (conn ctx) with
| Some pg ->
return!
Hash.FromAnonymousObject {|
csrf = csrfToken ctx
model = ManagePermalinksModel.fromPage pg
page_title = $"Manage Prior Permalinks"
|}
|> viewForTheme "admin" "permalinks" next ctx
| None -> return! Error.notFound next ctx
}
// POST /page/permalinks
let savePermalinks : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<ManagePermalinksModel> ()
let links = model.prior |> Array.map Permalink |> List.ofArray
match! Data.Page.updatePriorPermalinks (PageId model.id) (webLogId ctx) links (conn ctx) with
| true ->
do! addMessage ctx { UserMessage.success with message = "Page permalinks saved successfully" }
return! redirectToGet $"/page/{model.id}/permalinks" next ctx
| false -> return! Error.notFound next ctx
}
open System
#nowarn "3511"

View File

@ -328,6 +328,31 @@ let edit postId : HttpHandler = requireUser >=> fun next ctx -> task {
| None -> return! Error.notFound next ctx
}
// GET /post/{id}/permalinks
let editPermalinks postId : HttpHandler = requireUser >=> fun next ctx -> task {
match! Data.Post.findByFullId (PostId postId) (webLogId ctx) (conn ctx) with
| Some post ->
return!
Hash.FromAnonymousObject {|
csrf = csrfToken ctx
model = ManagePermalinksModel.fromPost post
page_title = $"Manage Prior Permalinks"
|}
|> viewForTheme "admin" "permalinks" next ctx
| None -> return! Error.notFound next ctx
}
// POST /post/permalinks
let savePermalinks : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
let! model = ctx.BindFormAsync<ManagePermalinksModel> ()
let links = model.prior |> Array.map Permalink |> List.ofArray
match! Data.Post.updatePriorPermalinks (PostId model.id) (webLogId ctx) links (conn ctx) with
| true ->
do! addMessage ctx { UserMessage.success with message = "Post permalinks saved successfully" }
return! redirectToGet $"/post/{model.id}/permalinks" next ctx
| false -> return! Error.notFound next ctx
}
#nowarn "3511"
// POST /post/save

View File

@ -30,24 +30,27 @@ let endpoints = [
]
subRoute "/page" [
GET [
routef "/%d" Post.pageOfPosts
//routef "/%d/" (fun pg -> redirectTo true $"/page/{pg}")
routef "/%s/edit" Page.edit
route "s" (Page.all 1)
routef "s/page/%d" Page.all
routef "/%d" Post.pageOfPosts
routef "/%s/edit" Page.edit
routef "/%s/permalinks" Page.editPermalinks
route "s" (Page.all 1)
routef "s/page/%d" Page.all
]
POST [
route "/save" Page.save
route "/permalinks" Page.savePermalinks
route "/save" Page.save
]
]
subRoute "/post" [
GET [
routef "/%s/edit" Post.edit
route "s" (Post.all 1)
routef "s/page/%d" Post.all
routef "/%s/edit" Post.edit
routef "/%s/permalinks" Post.editPermalinks
route "s" (Post.all 1)
routef "s/page/%d" Post.all
]
POST [
route "/save" Post.save
route "/permalinks" Post.savePermalinks
route "/save" Post.save
]
]
subRoute "/tag" [

View File

@ -253,9 +253,10 @@ let main args =
[ // Domain types
typeof<MetaItem>; typeof<Page>; typeof<WebLog>
// View models
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
typeof<EditPageModel>; typeof<EditPostModel>; typeof<EditUserModel>; typeof<LogOnModel>
typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage>
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
typeof<EditPageModel>; typeof<EditPostModel>; typeof<EditUserModel>; typeof<LogOnModel>
typeof<ManagePermalinksModel>; typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>
typeof<UserMessage>
// Framework types
typeof<AntiforgeryTokenSet>; typeof<KeyValuePair>; typeof<MetaItem list>; typeof<string list>
typeof<string option>

View File

@ -3,5 +3,5 @@
"hostname": "data02.bitbadger.solutions",
"database": "myWebLog_dev"
},
"Generator": "myWebLog 2.0-alpha05"
"Generator": "myWebLog 2.0-alpha06"
}

View File

@ -15,6 +15,9 @@
<input type="text" name="permalink" id="permalink" class="form-control" required
value="{{ model.permalink }}">
<label for="permalink">Permalink</label>
{%- if model.page_id != "new" %}
<span class="form-text"><a href="/page/{{ model.page_id }}/permalinks">Manage Permalinks</a></span>
{% endif -%}
</div>
</div>
<div class="col-3">

View File

@ -0,0 +1,57 @@
<h2 class="my-3">{{ page_title }}</h2>
<article>
<form action="/{{ model.entity }}/permalinks" method="post">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
<input type="hidden" name="id" value="{{ model.id }}">
<div class="container">
<div class="row">
<div class="col">
<p style="line-height:1.2rem;">
<strong>{{ model.current_title }}</strong><br>
<small class="text-muted">
<span class="fst-italic">{{ model.current_permalink }}</span><br>
<a href="/{{ model.entity }}/{{ model.id }}/edit">&laquo; Back to Edit {{ model.entity | capitalize }}</a>
</small>
</p>
</div>
</div>
<div class="row mb-3">
<div class="col">
<button type="button" class="btn btn-sm btn-secondary" onclick="Admin.addPermalink()">Add a Permalink</button>
</div>
</div>
<div class="row mb-3">
<div class="col">
<div id="permalinks" class="container">
{%- assign link_count = 0 -%}
{%- for link in model.prior %}
<div id="link_{{ link_count }}" class="row mb-3">
<div class="col-1 text-center align-self-center">
<button type="button" class="btn btn-sm btn-danger" onclick="Admin.removePermalink({{ link_count }})">
&minus;
</button>
</div>
<div class="col-11">
<div class="form-floating">
<input type="text" name="prior" id="prior_{{ link_count }}" class="form-control"
placeholder="Link" value="{{ link }}">
<label for="prior_{{ link_count }}">Link</label>
</div>
</div>
</div>
{%- assign link_count = link_count | plus: 1 -%}
{% endfor -%}
<script>
document.addEventListener("DOMContentLoaded", () => Admin.setPermalinkIndex({{ link_count }}))
</script>
</div>
</div>
</div>
<div class="row pb-3">
<div class="col">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</div>
</form>
</article>

View File

@ -15,6 +15,9 @@
<input type="text" name="permalink" id="permalink" class="form-control" placeholder="Permalink" required
value="{{ model.permalink }}">
<label for="permalink">Permalink</label>
{%- if model.page_id != "new" %}
<span class="form-text"><a href="/post/{{ model.post_id }}/permalinks">Manage Permalinks</a></span>
{% endif -%}
</div>
<div class="mb-2">
<label for="text">Text</label> &nbsp; &nbsp;

View File

@ -9,6 +9,7 @@ body {
scrollbar-color: var(--dark-gray) gray;
padding-top: 3rem;
padding-bottom: 2rem;
min-height: 100vh;
}
body::-webkit-scrollbar {
width: 12px;

View File

@ -2,15 +2,25 @@
/** The next index for a metadata item */
nextMetaIndex : 0,
/** The next index for a permalink */
nextPermalink : 0,
/**
* Set the next meta item index
* @param idx The index to set
*/
// Calling a function with a Liquid variable does not look like an error in the IDE...
setNextMetaIndex(idx) {
this.nextMetaIndex = idx
},
/**
* Set the next permalink index
* @param idx The index to set
*/
setPermalinkIndex(idx) {
this.nextPermalink = idx
},
/**
* Add a new row for metadata entry
*/
@ -80,7 +90,55 @@
document.getElementById(nameField.id).focus()
this.nextMetaIndex++
},
/**
* Add a new row for a permalink
*/
addPermalink() {
// Remove button
const removeBtn = document.createElement("button")
removeBtn.type = "button"
removeBtn.className = "btn btn-sm btn-danger"
removeBtn.innerHTML = "&minus;"
removeBtn.setAttribute("onclick", `Admin.removePermalink(${this.nextPermalink})`)
const removeCol = document.createElement("div")
removeCol.className = "col-1 text-center align-self-center"
removeCol.appendChild(removeBtn)
// Link
const linkField = document.createElement("input")
linkField.type = "text"
linkField.name = "prior"
linkField.id = `prior_${this.nextPermalink}`
linkField.className = "form-control"
linkField.placeholder = "Link"
const linkLabel = document.createElement("label")
linkLabel.htmlFor = linkField.id
linkLabel.innerText = linkField.placeholder
const linkFloat = document.createElement("div")
linkFloat.className = "form-floating"
linkFloat.appendChild(linkField)
linkFloat.appendChild(linkLabel)
const linkCol = document.createElement("div")
linkCol.className = "col-11"
linkCol.appendChild(linkFloat)
// Put it all together
const newRow = document.createElement("div")
newRow.className = "row mb-3"
newRow.id = `meta_${this.nextPermalink}`
newRow.appendChild(removeCol)
newRow.appendChild(linkCol)
document.getElementById("permalinks").appendChild(newRow)
document.getElementById(linkField.id).focus()
this.nextPermalink++
},
/**
* Remove a metadata item
* @param idx The index of the metadata item to remove
@ -88,7 +146,15 @@
removeMetaItem(idx) {
document.getElementById(`meta_${idx}`).remove()
},
/**
* Remove a permalink
* @param idx The index of the permalink to remove
*/
removePermalink(idx) {
document.getElementById(`link_${idx}`).remove()
},
/**
* Confirm and delete a category
* @param id The ID of the category to be deleted