V2 #1
|
@ -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 =
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" [
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
"hostname": "data02.bitbadger.solutions",
|
||||
"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
|
||||
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">
|
||||
|
|
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
|
||||
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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = "−"
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user