Add prior permalink management
This commit is contained in:
parent
dc24da8ed7
commit
ea8d4b1c63
|
@ -394,6 +394,21 @@ module Page =
|
||||||
]
|
]
|
||||||
write; withRetryDefault; ignoreResult
|
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
|
/// Functions to manipulate posts
|
||||||
module Post =
|
module Post =
|
||||||
|
@ -542,6 +557,27 @@ module Post =
|
||||||
write; withRetryDefault; ignoreResult
|
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
|
/// Functions to manipulate web logs
|
||||||
module WebLog =
|
module WebLog =
|
||||||
|
|
|
@ -299,6 +299,44 @@ type LogOnModel =
|
||||||
{ emailAddress = ""; password = ""; returnTo = None }
|
{ 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
|
/// View model for posts in a list
|
||||||
[<NoComparison; NoEquality>]
|
[<NoComparison; NoEquality>]
|
||||||
type PostListItem =
|
type PostListItem =
|
||||||
|
|
|
@ -45,6 +45,31 @@ let edit pgId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
| None -> return! Error.notFound next ctx
|
| 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
|
open System
|
||||||
|
|
||||||
#nowarn "3511"
|
#nowarn "3511"
|
||||||
|
|
|
@ -328,6 +328,31 @@ let edit postId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
| None -> return! Error.notFound next ctx
|
| 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"
|
#nowarn "3511"
|
||||||
|
|
||||||
// POST /post/save
|
// POST /post/save
|
||||||
|
|
|
@ -30,24 +30,27 @@ let endpoints = [
|
||||||
]
|
]
|
||||||
subRoute "/page" [
|
subRoute "/page" [
|
||||||
GET [
|
GET [
|
||||||
routef "/%d" Post.pageOfPosts
|
routef "/%d" Post.pageOfPosts
|
||||||
//routef "/%d/" (fun pg -> redirectTo true $"/page/{pg}")
|
routef "/%s/edit" Page.edit
|
||||||
routef "/%s/edit" Page.edit
|
routef "/%s/permalinks" Page.editPermalinks
|
||||||
route "s" (Page.all 1)
|
route "s" (Page.all 1)
|
||||||
routef "s/page/%d" Page.all
|
routef "s/page/%d" Page.all
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
route "/save" Page.save
|
route "/permalinks" Page.savePermalinks
|
||||||
|
route "/save" Page.save
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
subRoute "/post" [
|
subRoute "/post" [
|
||||||
GET [
|
GET [
|
||||||
routef "/%s/edit" Post.edit
|
routef "/%s/edit" Post.edit
|
||||||
route "s" (Post.all 1)
|
routef "/%s/permalinks" Post.editPermalinks
|
||||||
routef "s/page/%d" Post.all
|
route "s" (Post.all 1)
|
||||||
|
routef "s/page/%d" Post.all
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
route "/save" Post.save
|
route "/permalinks" Post.savePermalinks
|
||||||
|
route "/save" Post.save
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
subRoute "/tag" [
|
subRoute "/tag" [
|
||||||
|
|
|
@ -253,9 +253,10 @@ let main args =
|
||||||
[ // Domain types
|
[ // Domain types
|
||||||
typeof<MetaItem>; typeof<Page>; typeof<WebLog>
|
typeof<MetaItem>; typeof<Page>; typeof<WebLog>
|
||||||
// View models
|
// View models
|
||||||
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
|
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
|
||||||
typeof<EditPageModel>; typeof<EditPostModel>; typeof<EditUserModel>; typeof<LogOnModel>
|
typeof<EditPageModel>; typeof<EditPostModel>; typeof<EditUserModel>; typeof<LogOnModel>
|
||||||
typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage>
|
typeof<ManagePermalinksModel>; typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>
|
||||||
|
typeof<UserMessage>
|
||||||
// Framework types
|
// Framework types
|
||||||
typeof<AntiforgeryTokenSet>; typeof<KeyValuePair>; typeof<MetaItem list>; typeof<string list>
|
typeof<AntiforgeryTokenSet>; typeof<KeyValuePair>; typeof<MetaItem list>; typeof<string list>
|
||||||
typeof<string option>
|
typeof<string option>
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
"hostname": "data02.bitbadger.solutions",
|
"hostname": "data02.bitbadger.solutions",
|
||||||
"database": "myWebLog_dev"
|
"database": "myWebLog_dev"
|
||||||
},
|
},
|
||||||
"Generator": "myWebLog 2.0-alpha05"
|
"Generator": "myWebLog 2.0-alpha06"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
<input type="text" name="permalink" id="permalink" class="form-control" required
|
<input type="text" name="permalink" id="permalink" class="form-control" required
|
||||||
value="{{ model.permalink }}">
|
value="{{ model.permalink }}">
|
||||||
<label for="permalink">Permalink</label>
|
<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>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
|
|
57
src/MyWebLog/themes/admin/permalinks.liquid
Normal file
57
src/MyWebLog/themes/admin/permalinks.liquid
Normal 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">« 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 }})">
|
||||||
|
−
|
||||||
|
</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>
|
|
@ -15,6 +15,9 @@
|
||||||
<input type="text" name="permalink" id="permalink" class="form-control" placeholder="Permalink" required
|
<input type="text" name="permalink" id="permalink" class="form-control" placeholder="Permalink" required
|
||||||
value="{{ model.permalink }}">
|
value="{{ model.permalink }}">
|
||||||
<label for="permalink">Permalink</label>
|
<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>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="text">Text</label>
|
<label for="text">Text</label>
|
||||||
|
|
|
@ -9,6 +9,7 @@ body {
|
||||||
scrollbar-color: var(--dark-gray) gray;
|
scrollbar-color: var(--dark-gray) gray;
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
|
|
@ -2,15 +2,25 @@
|
||||||
/** The next index for a metadata item */
|
/** The next index for a metadata item */
|
||||||
nextMetaIndex : 0,
|
nextMetaIndex : 0,
|
||||||
|
|
||||||
|
/** The next index for a permalink */
|
||||||
|
nextPermalink : 0,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the next meta item index
|
* Set the next meta item index
|
||||||
* @param idx The index to set
|
* @param idx The index to set
|
||||||
*/
|
*/
|
||||||
// Calling a function with a Liquid variable does not look like an error in the IDE...
|
|
||||||
setNextMetaIndex(idx) {
|
setNextMetaIndex(idx) {
|
||||||
this.nextMetaIndex = 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
|
* Add a new row for metadata entry
|
||||||
*/
|
*/
|
||||||
|
@ -80,7 +90,55 @@
|
||||||
document.getElementById(nameField.id).focus()
|
document.getElementById(nameField.id).focus()
|
||||||
this.nextMetaIndex++
|
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 = "−"
|
||||||
|
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
|
* Remove a metadata item
|
||||||
* @param idx The index of the metadata item to remove
|
* @param idx The index of the metadata item to remove
|
||||||
|
@ -88,7 +146,15 @@
|
||||||
removeMetaItem(idx) {
|
removeMetaItem(idx) {
|
||||||
document.getElementById(`meta_${idx}`).remove()
|
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
|
* Confirm and delete a category
|
||||||
* @param id The ID of the category to be deleted
|
* @param id The ID of the category to be deleted
|
||||||
|
|
Loading…
Reference in New Issue
Block a user