Migrate more templates to GVE
This commit is contained in:
parent
5f114c7955
commit
b99cd5b94b
@ -204,26 +204,6 @@ type DisplayPage = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Information about a revision used for display
|
|
||||||
[<NoComparison; NoEquality>]
|
|
||||||
type DisplayRevision = {
|
|
||||||
/// The as-of date/time for the revision
|
|
||||||
AsOf: DateTime
|
|
||||||
|
|
||||||
/// The as-of date/time for the revision in the web log's local time zone
|
|
||||||
AsOfLocal: DateTime
|
|
||||||
|
|
||||||
/// The format of the text of the revision
|
|
||||||
Format: string
|
|
||||||
} with
|
|
||||||
|
|
||||||
/// Create a display revision from an actual revision
|
|
||||||
static member FromRevision (webLog: WebLog) (rev : Revision) =
|
|
||||||
{ AsOf = rev.AsOf.ToDateTimeUtc()
|
|
||||||
AsOfLocal = webLog.LocalTime rev.AsOf
|
|
||||||
Format = rev.Text.SourceType }
|
|
||||||
|
|
||||||
|
|
||||||
open System.IO
|
open System.IO
|
||||||
|
|
||||||
/// Information about a theme used for display
|
/// Information about a theme used for display
|
||||||
@ -1180,22 +1160,22 @@ type ManageRevisionsModel = {
|
|||||||
CurrentTitle: string
|
CurrentTitle: string
|
||||||
|
|
||||||
/// The revisions for the page or post
|
/// The revisions for the page or post
|
||||||
Revisions: DisplayRevision array
|
Revisions: Revision list
|
||||||
} with
|
} with
|
||||||
|
|
||||||
/// Create a revision model from a page
|
/// Create a revision model from a page
|
||||||
static member FromPage webLog (page: Page) =
|
static member FromPage (page: Page) =
|
||||||
{ Id = string page.Id
|
{ Id = string page.Id
|
||||||
Entity = "page"
|
Entity = "page"
|
||||||
CurrentTitle = page.Title
|
CurrentTitle = page.Title
|
||||||
Revisions = page.Revisions |> List.map (DisplayRevision.FromRevision webLog) |> Array.ofList }
|
Revisions = page.Revisions }
|
||||||
|
|
||||||
/// Create a revision model from a post
|
/// Create a revision model from a post
|
||||||
static member FromPost webLog (post: Post) =
|
static member FromPost (post: Post) =
|
||||||
{ Id = string post.Id
|
{ Id = string post.Id
|
||||||
Entity = "post"
|
Entity = "post"
|
||||||
CurrentTitle = post.Title
|
CurrentTitle = post.Title
|
||||||
Revisions = post.Revisions |> List.map (DisplayRevision.FromRevision webLog) |> Array.ofList }
|
Revisions = post.Revisions }
|
||||||
|
|
||||||
|
|
||||||
/// View model for posts in a list
|
/// View model for posts in a list
|
||||||
|
@ -179,17 +179,6 @@ let displayPageTests = testList "DisplayPage" [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Unit tests for the DisplayRevision type
|
|
||||||
let displayRevisionTests = test "DisplayRevision.FromRevision succeeds" {
|
|
||||||
let model =
|
|
||||||
DisplayRevision.FromRevision
|
|
||||||
{ WebLog.Empty with TimeZone = "Etc/GMT+1" }
|
|
||||||
{ Text = Html "howdy"; AsOf = Noda.epoch }
|
|
||||||
Expect.equal model.AsOf (Noda.epoch.ToDateTimeUtc()) "AsOf not filled properly"
|
|
||||||
Expect.equal model.AsOfLocal ((Noda.epoch - Duration.FromHours 1).ToDateTimeUtc()) "AsOfLocal not filled properly"
|
|
||||||
Expect.equal model.Format "HTML" "Format not filled properly"
|
|
||||||
}
|
|
||||||
|
|
||||||
open System.IO
|
open System.IO
|
||||||
|
|
||||||
/// Unit tests for the DisplayTheme type
|
/// Unit tests for the DisplayTheme type
|
||||||
@ -1346,7 +1335,6 @@ let all = testList "ViewModels" [
|
|||||||
displayChapterTests
|
displayChapterTests
|
||||||
displayCustomFeedTests
|
displayCustomFeedTests
|
||||||
displayPageTests
|
displayPageTests
|
||||||
displayRevisionTests
|
|
||||||
displayThemeTests
|
displayThemeTests
|
||||||
displayUploadTests
|
displayUploadTests
|
||||||
displayUserTests
|
displayUserTests
|
||||||
|
@ -228,16 +228,10 @@ let register () =
|
|||||||
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>
|
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>
|
||||||
typeof<RedirectRule>; typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog>
|
typeof<RedirectRule>; typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog>
|
||||||
// View models
|
// View models
|
||||||
typeof<AppViewContext>; typeof<DashboardModel>; typeof<DisplayCategory>
|
typeof<AppViewContext>; typeof<DisplayCategory>; typeof<DisplayCustomFeed>; typeof<DisplayPage>
|
||||||
typeof<DisplayChapter>; typeof<DisplayCustomFeed>; typeof<DisplayPage>
|
typeof<DisplayTheme>; typeof<DisplayUpload>; typeof<DisplayUser>; typeof<EditCategoryModel>
|
||||||
typeof<DisplayRevision>; typeof<DisplayTheme>; typeof<DisplayUpload>
|
typeof<EditCustomFeedModel>; typeof<EditPageModel>; typeof<EditPostModel>; typeof<EditRssModel>
|
||||||
typeof<DisplayUser>; typeof<EditCategoryModel>; typeof<EditChapterModel>
|
typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>; typeof<UserMessage>
|
||||||
typeof<EditCustomFeedModel>; typeof<EditMyInfoModel>; typeof<EditPageModel>
|
|
||||||
typeof<EditPostModel>; typeof<EditRedirectRuleModel>; typeof<EditRssModel>
|
|
||||||
typeof<EditTagMapModel>; typeof<EditUserModel>; typeof<LogOnModel>
|
|
||||||
typeof<ManageChaptersModel>; typeof<ManagePermalinksModel>; typeof<ManageRevisionsModel>
|
|
||||||
typeof<PostDisplay>; typeof<PostListItem>; typeof<SettingsModel>
|
|
||||||
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>
|
||||||
|
@ -27,45 +27,35 @@ module Dashboard =
|
|||||||
ListedPages = listed
|
ListedPages = listed
|
||||||
Categories = cats
|
Categories = cats
|
||||||
TopLevelCategories = topCats }
|
TopLevelCategories = topCats }
|
||||||
return! adminPage "Dashboard" false (Views.Admin.dashboard model) next ctx
|
return! adminPage "Dashboard" false next ctx (Views.Admin.dashboard model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/administration
|
// GET /admin/administration
|
||||||
let admin : HttpHandler = requireAccess Administrator >=> fun next ctx -> task {
|
let admin : HttpHandler = requireAccess Administrator >=> fun next ctx -> task {
|
||||||
match! TemplateCache.get adminTheme "theme-list-body" ctx.Data with
|
let! themes = ctx.Data.Theme.All()
|
||||||
| Ok bodyTemplate ->
|
let cachedTemplates = TemplateCache.allNames ()
|
||||||
let! themes = ctx.Data.Theme.All()
|
return!
|
||||||
let cachedTemplates = TemplateCache.allNames ()
|
hashForPage "myWebLog Administration"
|
||||||
let! hash =
|
|> withAntiCsrf ctx
|
||||||
hashForPage "myWebLog Administration"
|
|> addToHash "cached_themes" (
|
||||||
|> withAntiCsrf ctx
|
themes
|
||||||
|> addToHash "themes" (
|
|> Seq.ofList
|
||||||
themes
|
|> Seq.map (fun it -> [|
|
||||||
|> List.map (DisplayTheme.FromTheme WebLogCache.isThemeInUse)
|
string it.Id
|
||||||
|> Array.ofList)
|
it.Name
|
||||||
|> addToHash "cached_themes" (
|
cachedTemplates
|
||||||
themes
|
|> List.filter _.StartsWith(string it.Id)
|
||||||
|> Seq.ofList
|
|> List.length
|
||||||
|> Seq.map (fun it -> [|
|
|> string
|
||||||
string it.Id
|
|])
|
||||||
it.Name
|
|> Array.ofSeq)
|
||||||
cachedTemplates
|
|> addToHash "web_logs" (
|
||||||
|> List.filter _.StartsWith(string it.Id)
|
WebLogCache.all ()
|
||||||
|> List.length
|
|> Seq.ofList
|
||||||
|> string
|
|> Seq.sortBy _.Name
|
||||||
|])
|
|> Seq.map (fun it -> [| string it.Id; it.Name; it.UrlBase |])
|
||||||
|> Array.ofSeq)
|
|> Array.ofSeq)
|
||||||
|> addToHash "web_logs" (
|
|> adminView "admin-dashboard" next ctx
|
||||||
WebLogCache.all ()
|
|
||||||
|> Seq.ofList
|
|
||||||
|> Seq.sortBy _.Name
|
|
||||||
|> Seq.map (fun it -> [| string it.Id; it.Name; it.UrlBase |])
|
|
||||||
|> Array.ofSeq)
|
|
||||||
|> addViewContext ctx
|
|
||||||
return!
|
|
||||||
addToHash "theme_list" (bodyTemplate.Render hash) hash
|
|
||||||
|> adminView "admin-dashboard" next ctx
|
|
||||||
| Error message -> return! Error.server message next ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redirect the user to the admin dashboard
|
/// Redirect the user to the admin dashboard
|
||||||
@ -215,11 +205,11 @@ module RedirectRules =
|
|||||||
|
|
||||||
// GET /admin/settings/redirect-rules
|
// GET /admin/settings/redirect-rules
|
||||||
let all : HttpHandler = fun next ctx ->
|
let all : HttpHandler = fun next ctx ->
|
||||||
adminPage "Redirect Rules" true (Views.Admin.redirectList ctx.WebLog.RedirectRules) next ctx
|
adminPage "Redirect Rules" true next ctx (Views.Admin.redirectList ctx.WebLog.RedirectRules)
|
||||||
|
|
||||||
// GET /admin/settings/redirect-rules/[index]
|
// GET /admin/settings/redirect-rules/[index]
|
||||||
let edit idx : HttpHandler = fun next ctx ->
|
let edit idx : HttpHandler = fun next ctx ->
|
||||||
let titleAndModel =
|
let titleAndView =
|
||||||
if idx = -1 then
|
if idx = -1 then
|
||||||
Some ("Add", Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule -1 RedirectRule.Empty))
|
Some ("Add", Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule -1 RedirectRule.Empty))
|
||||||
else
|
else
|
||||||
@ -228,8 +218,8 @@ module RedirectRules =
|
|||||||
None
|
None
|
||||||
else
|
else
|
||||||
Some ("Edit", (Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule idx (List.item idx rules))))
|
Some ("Edit", (Views.Admin.redirectEdit (EditRedirectRuleModel.FromRule idx (List.item idx rules))))
|
||||||
match titleAndModel with
|
match titleAndView with
|
||||||
| Some (title, model) -> adminBarePage $"{title} Redirect Rule" true model next ctx
|
| Some (title, view) -> adminBarePage $"{title} Redirect Rule" true next ctx view
|
||||||
| None -> Error.notFound next ctx
|
| None -> Error.notFound next ctx
|
||||||
|
|
||||||
/// Update the web log's redirect rules in the database, the request web log, and the web log cache
|
/// Update the web log's redirect rules in the database, the request web log, and the web log cache
|
||||||
@ -294,7 +284,7 @@ module TagMapping =
|
|||||||
// GET /admin/settings/tag-mappings
|
// GET /admin/settings/tag-mappings
|
||||||
let all : HttpHandler = fun next ctx -> task {
|
let all : HttpHandler = fun next ctx -> task {
|
||||||
let! mappings = ctx.Data.TagMap.FindByWebLog ctx.WebLog.Id
|
let! mappings = ctx.Data.TagMap.FindByWebLog ctx.WebLog.Id
|
||||||
return! adminBarePage "Tag Mapping List" true (Views.Admin.tagMapList mappings) next ctx
|
return! adminBarePage "Tag Mapping List" true next ctx (Views.Admin.tagMapList mappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/settings/tag-mapping/{id}/edit
|
// GET /admin/settings/tag-mapping/{id}/edit
|
||||||
@ -306,9 +296,8 @@ module TagMapping =
|
|||||||
match! tagMap with
|
match! tagMap with
|
||||||
| Some tm ->
|
| Some tm ->
|
||||||
return!
|
return!
|
||||||
adminBarePage
|
Views.Admin.tagMapEdit (EditTagMapModel.FromMapping tm)
|
||||||
(if isNew then "Add Tag Mapping" else $"Mapping for {tm.Tag} Tag") true
|
|> adminBarePage (if isNew then "Add Tag Mapping" else $"Mapping for {tm.Tag} Tag") true next ctx
|
||||||
(Views.Admin.tagMapEdit (EditTagMapModel.FromMapping tm)) next ctx
|
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,17 +338,13 @@ module Theme =
|
|||||||
let all : HttpHandler = requireAccess Administrator >=> fun next ctx -> task {
|
let all : HttpHandler = requireAccess Administrator >=> fun next ctx -> task {
|
||||||
let! themes = ctx.Data.Theme.All ()
|
let! themes = ctx.Data.Theme.All ()
|
||||||
return!
|
return!
|
||||||
hashForPage "Themes"
|
Views.Admin.themeList (List.map (DisplayTheme.FromTheme WebLogCache.isThemeInUse) themes)
|
||||||
|> withAntiCsrf ctx
|
|> adminBarePage "Themes" true next ctx
|
||||||
|> addToHash "themes" (themes |> List.map (DisplayTheme.FromTheme WebLogCache.isThemeInUse) |> Array.ofList)
|
|
||||||
|> adminBareView "theme-list-body" next ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/theme/new
|
// GET /admin/theme/new
|
||||||
let add : HttpHandler = requireAccess Administrator >=> fun next ctx ->
|
let add : HttpHandler = requireAccess Administrator >=> fun next ctx ->
|
||||||
hashForPage "Upload a Theme File"
|
adminBarePage "Upload a Theme File" true next ctx Views.Admin.themeUpload
|
||||||
|> withAntiCsrf ctx
|
|
||||||
|> adminBareView "theme-upload" next ctx
|
|
||||||
|
|
||||||
/// Update the name and version for a theme based on the version.txt file, if present
|
/// Update the name and version for a theme based on the version.txt file, if present
|
||||||
let private updateNameAndVersion (theme: Theme) (zip: ZipArchive) = backgroundTask {
|
let private updateNameAndVersion (theme: Theme) (zip: ZipArchive) = backgroundTask {
|
||||||
|
@ -282,8 +282,9 @@ module Error =
|
|||||||
let notAuthorized : HttpHandler = fun next ctx ->
|
let notAuthorized : HttpHandler = fun next ctx ->
|
||||||
if ctx.Request.Method = "GET" then
|
if ctx.Request.Method = "GET" then
|
||||||
let redirectUrl = $"user/log-on?returnUrl={WebUtility.UrlEncode ctx.Request.Path}"
|
let redirectUrl = $"user/log-on?returnUrl={WebUtility.UrlEncode ctx.Request.Path}"
|
||||||
if isHtmx ctx then (withHxRedirect redirectUrl >=> redirectToGet redirectUrl) next ctx
|
(next, ctx)
|
||||||
else redirectToGet redirectUrl next ctx
|
||> if isHtmx ctx then withHxRedirect redirectUrl >=> withHxRetarget "body" >=> redirectToGet redirectUrl
|
||||||
|
else redirectToGet redirectUrl
|
||||||
else
|
else
|
||||||
if isHtmx ctx then
|
if isHtmx ctx then
|
||||||
let messages = [|
|
let messages = [|
|
||||||
@ -370,7 +371,7 @@ let adminBareView template =
|
|||||||
bareForTheme adminTheme template
|
bareForTheme adminTheme template
|
||||||
|
|
||||||
/// Display a page for an admin endpoint
|
/// Display a page for an admin endpoint
|
||||||
let adminPage pageTitle includeCsrf (content: AppViewContext -> XmlNode list) : HttpHandler = fun next ctx -> task {
|
let adminPage pageTitle includeCsrf next ctx (content: AppViewContext -> XmlNode list) = task {
|
||||||
let! messages = getCurrentMessages ctx
|
let! messages = getCurrentMessages ctx
|
||||||
let appCtx = generateViewContext pageTitle messages includeCsrf ctx
|
let appCtx = generateViewContext pageTitle messages includeCsrf ctx
|
||||||
let layout = if isHtmx ctx then Layout.partial else Layout.full
|
let layout = if isHtmx ctx then Layout.partial else Layout.full
|
||||||
@ -378,7 +379,7 @@ let adminPage pageTitle includeCsrf (content: AppViewContext -> XmlNode list) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Display a bare page for an admin endpoint
|
/// Display a bare page for an admin endpoint
|
||||||
let adminBarePage pageTitle includeCsrf (content: AppViewContext -> XmlNode list) : HttpHandler = fun next ctx -> task {
|
let adminBarePage pageTitle includeCsrf next ctx (content: AppViewContext -> XmlNode list) = task {
|
||||||
let! messages = getCurrentMessages ctx
|
let! messages = getCurrentMessages ctx
|
||||||
let appCtx = generateViewContext pageTitle messages includeCsrf ctx
|
let appCtx = generateViewContext pageTitle messages includeCsrf ctx
|
||||||
return!
|
return!
|
||||||
@ -471,13 +472,12 @@ let getCategoryIds slug ctx =
|
|||||||
|> Seq.map (fun c -> CategoryId c.Id)
|
|> Seq.map (fun c -> CategoryId c.Id)
|
||||||
|> List.ofSeq
|
|> List.ofSeq
|
||||||
|
|
||||||
open System
|
|
||||||
open System.Globalization
|
|
||||||
open NodaTime
|
open NodaTime
|
||||||
|
|
||||||
/// Parse a date/time to UTC
|
/// Parse a date/time to UTC
|
||||||
let parseToUtc (date: string) =
|
let parseToUtc (date: string) : Instant =
|
||||||
Instant.FromDateTimeUtc(DateTime.Parse(date, null, DateTimeStyles.AdjustToUniversal))
|
let result = roundTrip.Parse date
|
||||||
|
if result.Success then result.Value else raise result.Exception
|
||||||
|
|
||||||
open Microsoft.Extensions.DependencyInjection
|
open Microsoft.Extensions.DependencyInjection
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
|
@ -66,10 +66,9 @@ let editPermalinks pgId : HttpHandler = requireAccess Author >=> fun next ctx ->
|
|||||||
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
||||||
| Some pg when canEdit pg.AuthorId ctx ->
|
| Some pg when canEdit pg.AuthorId ctx ->
|
||||||
return!
|
return!
|
||||||
hashForPage "Manage Prior Permalinks"
|
ManagePermalinksModel.FromPage pg
|
||||||
|> withAntiCsrf ctx
|
|> Views.Helpers.managePermalinks
|
||||||
|> addToHash ViewContext.Model (ManagePermalinksModel.FromPage pg)
|
|> adminPage "Manage Prior Permalinks" true next ctx
|
||||||
|> adminView "permalinks" next ctx
|
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -95,15 +94,14 @@ let editRevisions pgId : HttpHandler = requireAccess Author >=> fun next ctx ->
|
|||||||
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
match! ctx.Data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
||||||
| Some pg when canEdit pg.AuthorId ctx ->
|
| Some pg when canEdit pg.AuthorId ctx ->
|
||||||
return!
|
return!
|
||||||
hashForPage "Manage Page Revisions"
|
ManageRevisionsModel.FromPage pg
|
||||||
|> withAntiCsrf ctx
|
|> Views.Helpers.manageRevisions
|
||||||
|> addToHash ViewContext.Model (ManageRevisionsModel.FromPage ctx.WebLog pg)
|
|> adminPage "Manage Page Revisions" true next ctx
|
||||||
|> adminView "revisions" next ctx
|
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/page/{id}/revisions/purge
|
// DELETE /admin/page/{id}/revisions
|
||||||
let purgeRevisions pgId : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
let purgeRevisions pgId : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
match! data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
match! data.Page.FindFullById (PageId pgId) ctx.WebLog.Id with
|
||||||
@ -158,7 +156,7 @@ let restoreRevision (pgId, revDate) : HttpHandler = requireAccess Author >=> fun
|
|||||||
| _, None -> return! Error.notFound next ctx
|
| _, None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/page/{id}/revision/{revision-date}/delete
|
// DELETE /admin/page/{id}/revision/{revision-date}
|
||||||
let deleteRevision (pgId, revDate) : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
let deleteRevision (pgId, revDate) : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
match! findPageRevision pgId revDate ctx with
|
match! findPageRevision pgId revDate ctx with
|
||||||
| Some pg, Some rev when canEdit pg.AuthorId ctx ->
|
| Some pg, Some rev when canEdit pg.AuthorId ctx ->
|
||||||
|
@ -254,7 +254,7 @@ let all pageNbr : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
|||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
let! posts = data.Post.FindPageOfPosts ctx.WebLog.Id pageNbr 25
|
let! posts = data.Post.FindPageOfPosts ctx.WebLog.Id pageNbr 25
|
||||||
let! hash = preparePostList ctx.WebLog posts AdminList "" pageNbr 25 data
|
let! hash = preparePostList ctx.WebLog posts AdminList "" pageNbr 25 data
|
||||||
return! adminPage "Posts" true (Views.Post.list (hash[ViewContext.Model] :?> PostDisplay)) next ctx
|
return! adminPage "Posts" true next ctx (Views.Post.list (hash[ViewContext.Model] :?> PostDisplay))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/post/{id}/edit
|
// GET /admin/post/{id}/edit
|
||||||
@ -305,10 +305,9 @@ let editPermalinks postId : HttpHandler = requireAccess Author >=> fun next ctx
|
|||||||
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
||||||
| Some post when canEdit post.AuthorId ctx ->
|
| Some post when canEdit post.AuthorId ctx ->
|
||||||
return!
|
return!
|
||||||
hashForPage "Manage Prior Permalinks"
|
ManagePermalinksModel.FromPost post
|
||||||
|> withAntiCsrf ctx
|
|> Views.Helpers.managePermalinks
|
||||||
|> addToHash ViewContext.Model (ManagePermalinksModel.FromPost post)
|
|> adminPage "Manage Prior Permalinks" true next ctx
|
||||||
|> adminView "permalinks" next ctx
|
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -334,15 +333,14 @@ let editRevisions postId : HttpHandler = requireAccess Author >=> fun next ctx -
|
|||||||
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
match! ctx.Data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
||||||
| Some post when canEdit post.AuthorId ctx ->
|
| Some post when canEdit post.AuthorId ctx ->
|
||||||
return!
|
return!
|
||||||
hashForPage "Manage Post Revisions"
|
ManageRevisionsModel.FromPost post
|
||||||
|> withAntiCsrf ctx
|
|> Views.Helpers.manageRevisions
|
||||||
|> addToHash ViewContext.Model (ManageRevisionsModel.FromPost ctx.WebLog post)
|
|> adminPage "Manage Post Revisions" true next ctx
|
||||||
|> adminView "revisions" next ctx
|
|
||||||
| Some _ -> return! Error.notAuthorized next ctx
|
| Some _ -> return! Error.notAuthorized next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /admin/post/{id}/revisions/purge
|
// DELETE /admin/post/{id}/revisions
|
||||||
let purgeRevisions postId : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
let purgeRevisions postId : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
let data = ctx.Data
|
let data = ctx.Data
|
||||||
match! data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
match! data.Post.FindFullById (PostId postId) ctx.WebLog.Id with
|
||||||
@ -398,7 +396,7 @@ let restoreRevision (postId, revDate) : HttpHandler = requireAccess Author >=> f
|
|||||||
| _, None -> return! Error.notFound next ctx
|
| _, None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /admin/post/{id}/revision/{revision-date}/delete
|
// DELETE /admin/post/{id}/revision/{revision-date}
|
||||||
let deleteRevision (postId, revDate) : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
let deleteRevision (postId, revDate) : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
match! findPostRevision postId revDate ctx with
|
match! findPostRevision postId revDate ctx with
|
||||||
| Some post, Some rev when canEdit post.AuthorId ctx ->
|
| Some post, Some rev when canEdit post.AuthorId ctx ->
|
||||||
@ -418,7 +416,8 @@ let manageChapters postId : HttpHandler = requireAccess Author >=> fun next ctx
|
|||||||
&& Option.isSome post.Episode.Value.Chapters
|
&& Option.isSome post.Episode.Value.Chapters
|
||||||
&& canEdit post.AuthorId ctx ->
|
&& canEdit post.AuthorId ctx ->
|
||||||
return!
|
return!
|
||||||
adminPage "Manage Chapters" true (Views.Post.chapters false (ManageChaptersModel.Create post)) next ctx
|
Views.Post.chapters false (ManageChaptersModel.Create post)
|
||||||
|
|> adminPage "Manage Chapters" true next ctx
|
||||||
| Some _ | None -> return! Error.notFound next ctx
|
| Some _ | None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,9 +436,8 @@ let editChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex
|
|||||||
match chapter with
|
match chapter with
|
||||||
| Some chap ->
|
| Some chap ->
|
||||||
return!
|
return!
|
||||||
adminBarePage
|
Views.Post.chapterEdit (EditChapterModel.FromChapter post.Id index chap)
|
||||||
(if index = -1 then "Add a Chapter" else "Edit Chapter") true
|
|> adminBarePage (if index = -1 then "Add a Chapter" else "Edit Chapter") true next ctx
|
||||||
(Views.Post.chapterEdit (EditChapterModel.FromChapter post.Id index chap)) next ctx
|
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
| Some _ | None -> return! Error.notFound next ctx
|
| Some _ | None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
@ -466,9 +464,8 @@ let saveChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex
|
|||||||
do! data.Post.Update updatedPost
|
do! data.Post.Update updatedPost
|
||||||
do! addMessage ctx { UserMessage.Success with Message = "Chapter saved successfully" }
|
do! addMessage ctx { UserMessage.Success with Message = "Chapter saved successfully" }
|
||||||
return!
|
return!
|
||||||
adminPage
|
Views.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)
|
||||||
"Manage Chapters" true
|
|> adminPage "Manage Chapters" true next ctx
|
||||||
(Views.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)) next ctx
|
|
||||||
with
|
with
|
||||||
| ex -> return! Error.server ex.Message next ctx
|
| ex -> return! Error.server ex.Message next ctx
|
||||||
else return! Error.notFound next ctx
|
else return! Error.notFound next ctx
|
||||||
@ -491,9 +488,8 @@ let deleteChapter (postId, index) : HttpHandler = requireAccess Author >=> fun n
|
|||||||
do! data.Post.Update updatedPost
|
do! data.Post.Update updatedPost
|
||||||
do! addMessage ctx { UserMessage.Success with Message = "Chapter deleted successfully" }
|
do! addMessage ctx { UserMessage.Success with Message = "Chapter deleted successfully" }
|
||||||
return!
|
return!
|
||||||
adminPage
|
Views.Post.chapterList false (ManageChaptersModel.Create updatedPost)
|
||||||
"Manage Chapters" true (Views.Post.chapterList false (ManageChaptersModel.Create updatedPost)) next
|
|> adminPage "Manage Chapters" true next ctx
|
||||||
ctx
|
|
||||||
else return! Error.notFound next ctx
|
else return! Error.notFound next ctx
|
||||||
| Some _ | None -> return! Error.notFound next ctx
|
| Some _ | None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
@ -176,17 +176,13 @@ let router : HttpHandler = choose [
|
|||||||
route "/save" >=> Page.save
|
route "/save" >=> Page.save
|
||||||
route "/permalinks" >=> Page.savePermalinks
|
route "/permalinks" >=> Page.savePermalinks
|
||||||
routef "/%s/delete" Page.delete
|
routef "/%s/delete" Page.delete
|
||||||
routef "/%s/revision/%s/delete" Page.deleteRevision
|
|
||||||
routef "/%s/revision/%s/restore" Page.restoreRevision
|
routef "/%s/revision/%s/restore" Page.restoreRevision
|
||||||
routef "/%s/revisions/purge" Page.purgeRevisions
|
|
||||||
])
|
])
|
||||||
subRoute "/post" (choose [
|
subRoute "/post" (choose [
|
||||||
route "/save" >=> Post.save
|
route "/save" >=> Post.save
|
||||||
route "/permalinks" >=> Post.savePermalinks
|
route "/permalinks" >=> Post.savePermalinks
|
||||||
routef "/%s/chapter/%i" Post.saveChapter
|
routef "/%s/chapter/%i" Post.saveChapter
|
||||||
routef "/%s/revision/%s/delete" Post.deleteRevision
|
|
||||||
routef "/%s/revision/%s/restore" Post.restoreRevision
|
routef "/%s/revision/%s/restore" Post.restoreRevision
|
||||||
routef "/%s/revisions/purge" Post.purgeRevisions
|
|
||||||
])
|
])
|
||||||
subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
|
subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
|
||||||
route "" >=> Admin.WebLog.saveSettings
|
route "" >=> Admin.WebLog.saveSettings
|
||||||
@ -214,9 +210,15 @@ let router : HttpHandler = choose [
|
|||||||
])
|
])
|
||||||
]
|
]
|
||||||
DELETE >=> validateCsrf >=> choose [
|
DELETE >=> validateCsrf >=> choose [
|
||||||
|
subRoute "/page" (choose [
|
||||||
|
routef "/%s/revision/%s" Page.deleteRevision
|
||||||
|
routef "/%s/revisions" Page.purgeRevisions
|
||||||
|
])
|
||||||
subRoute "/post" (choose [
|
subRoute "/post" (choose [
|
||||||
routef "/%s" Post.delete
|
routef "/%s" Post.delete
|
||||||
routef "/%s/chapter/%i" Post.deleteChapter
|
routef "/%s/chapter/%i" Post.deleteChapter
|
||||||
|
routef "/%s/revision/%s" Post.deleteRevision
|
||||||
|
routef "/%s/revisions" Post.purgeRevisions
|
||||||
])
|
])
|
||||||
subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
|
subRoute "/settings" (requireAccess WebLogAdmin >=> choose [
|
||||||
routef "/user/%s" User.delete
|
routef "/user/%s" User.delete
|
||||||
|
@ -35,7 +35,7 @@ let logOn returnUrl : HttpHandler = fun next ctx ->
|
|||||||
match returnUrl with
|
match returnUrl with
|
||||||
| Some _ -> returnUrl
|
| Some _ -> returnUrl
|
||||||
| None -> if ctx.Request.Query.ContainsKey "returnUrl" then Some ctx.Request.Query["returnUrl"].[0] else None
|
| None -> if ctx.Request.Query.ContainsKey "returnUrl" then Some ctx.Request.Query["returnUrl"].[0] else None
|
||||||
adminPage "Log On" true (Views.User.logOn { LogOnModel.Empty with ReturnTo = returnTo }) next ctx
|
adminPage "Log On" true next ctx (Views.User.logOn { LogOnModel.Empty with ReturnTo = returnTo })
|
||||||
|
|
||||||
|
|
||||||
open System.Security.Claims
|
open System.Security.Claims
|
||||||
@ -91,12 +91,12 @@ let private goAway : HttpHandler = RequestErrors.BAD_REQUEST "really?"
|
|||||||
// GET /admin/settings/users
|
// GET /admin/settings/users
|
||||||
let all : HttpHandler = fun next ctx -> task {
|
let all : HttpHandler = fun next ctx -> task {
|
||||||
let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id
|
let! users = ctx.Data.WebLogUser.FindByWebLog ctx.WebLog.Id
|
||||||
return! adminBarePage "User Administration" true (Views.User.userList users) next ctx
|
return! adminBarePage "User Administration" true next ctx (Views.User.userList users)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show the edit user page
|
/// Show the edit user page
|
||||||
let private showEdit (model: EditUserModel) : HttpHandler = fun next ctx ->
|
let private showEdit (model: EditUserModel) : HttpHandler = fun next ctx ->
|
||||||
adminBarePage (if model.IsNew then "Add a New User" else "Edit User") true (Views.User.edit model) next ctx
|
adminBarePage (if model.IsNew then "Add a New User" else "Edit User") true next ctx (Views.User.edit model)
|
||||||
|
|
||||||
// GET /admin/settings/user/{id}/edit
|
// GET /admin/settings/user/{id}/edit
|
||||||
let edit usrId : HttpHandler = fun next ctx -> task {
|
let edit usrId : HttpHandler = fun next ctx -> task {
|
||||||
@ -137,7 +137,9 @@ let delete userId : HttpHandler = fun next ctx -> task {
|
|||||||
let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
let myInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
||||||
match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with
|
match! ctx.Data.WebLogUser.FindById ctx.UserId ctx.WebLog.Id with
|
||||||
| Some user ->
|
| Some user ->
|
||||||
return! adminPage "Edit Your Information" true (Views.User.myInfo (EditMyInfoModel.FromUser user) user) next ctx
|
return!
|
||||||
|
Views.User.myInfo (EditMyInfoModel.FromUser user) user
|
||||||
|
|> adminPage "Edit Your Information" true next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,9 +163,8 @@ let saveMyInfo : HttpHandler = requireAccess Author >=> fun next ctx -> task {
|
|||||||
| Some user ->
|
| Some user ->
|
||||||
do! addMessage ctx { UserMessage.Error with Message = "Passwords did not match; no updates made" }
|
do! addMessage ctx { UserMessage.Error with Message = "Passwords did not match; no updates made" }
|
||||||
return!
|
return!
|
||||||
adminPage
|
Views.User.myInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user
|
||||||
"Edit Your Information" true
|
|> adminPage "Edit Your Information" true next ctx
|
||||||
(Views.User.myInfo { model with NewPassword = ""; NewPasswordConfirm = "" } user) next ctx
|
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,3 +293,91 @@ let tagMapList (model: TagMap list) app =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
|
|
||||||
|
|
||||||
|
/// Display a list of themes
|
||||||
|
let themeList (model: DisplayTheme list) app =
|
||||||
|
let themeCol = "col-12 col-md-6"
|
||||||
|
let slugCol = "d-none d-md-block col-md-3"
|
||||||
|
let tmplCol = "d-none d-md-block col-md-3"
|
||||||
|
div [ _id "theme_panel" ] [
|
||||||
|
a [ _href (relUrl app "admin/theme/new"); _class "btn btn-primary btn-sm mb-3"; _hxTarget "#theme_new" ] [
|
||||||
|
raw "Upload a New Theme"
|
||||||
|
]
|
||||||
|
div [ _class "container g-0" ] [
|
||||||
|
div [ _class "row mwl-table-heading" ] [
|
||||||
|
div [ _class themeCol ] [ raw "Theme" ]
|
||||||
|
div [ _class slugCol ] [ raw "Slug" ]
|
||||||
|
div [ _class tmplCol ] [ raw "Templates" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row mwl-table-detail"; _id "theme_new" ] []
|
||||||
|
form [ _method "post"; _id "themeList"; _class "container g-0"; _hxTarget "#theme_panel"
|
||||||
|
_hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
||||||
|
antiCsrf app
|
||||||
|
for theme in model do
|
||||||
|
let url = relUrl app $"admin/theme/{theme.Id}"
|
||||||
|
div [ _class "row mwl-table-detail"; _id $"theme_{theme.Id}" ] [
|
||||||
|
div [ _class $"{themeCol} no-wrap" ] [
|
||||||
|
txt theme.Name
|
||||||
|
if theme.IsInUse then span [ _class "badge bg-primary ms-2" ] [ raw "IN USE" ]
|
||||||
|
if not theme.IsOnDisk then
|
||||||
|
span [ _class "badge bg-warning text-dark ms-2" ] [ raw "NOT ON DISK" ]
|
||||||
|
br []
|
||||||
|
small [] [
|
||||||
|
span [ _class "text-muted" ] [ txt $"v{theme.Version}" ]
|
||||||
|
if not (theme.IsInUse || theme.Id = "default") then
|
||||||
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
|
a [ _href url; _hxDelete url; _class "text-danger"
|
||||||
|
_hxConfirm $"Are you sure you want to delete the theme “{theme.Name}”? This action cannot be undone." ] [
|
||||||
|
raw "Delete"
|
||||||
|
]
|
||||||
|
span [ _class "d-md-none text-muted" ] [
|
||||||
|
br []; raw "Slug: "; txt theme.Id; raw $" • {theme.TemplateCount} Templates"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class slugCol ] [ txt (string theme.Id) ]
|
||||||
|
div [ _class tmplCol ] [ txt (string theme.TemplateCount) ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|> List.singleton
|
||||||
|
|
||||||
|
|
||||||
|
/// Form to allow a theme to be uploaded
|
||||||
|
let themeUpload app =
|
||||||
|
div [ _class "col" ] [
|
||||||
|
h5 [ _class "mt-2" ] [ raw app.PageTitle ]
|
||||||
|
form [ _action (relUrl app "admin/theme/new"); _method "post"; _class "container"
|
||||||
|
_enctype "multipart/form-data"; _hxNoBoost ] [
|
||||||
|
antiCsrf app
|
||||||
|
div [ _class "row " ] [
|
||||||
|
div [ _class "col-12 col-sm-6 pb-3" ] [
|
||||||
|
div [ _class "form-floating" ] [
|
||||||
|
input [ _type "file"; _id "file"; _name "file"; _class "form-control"; _accept ".zip"
|
||||||
|
_placeholder "Theme File"; _required ]
|
||||||
|
label [ _for "file" ] [ raw "Theme File" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "col-12 col-sm-6 pb-3 d-flex justify-content-center align-items-center" ] [
|
||||||
|
div [ _class "form-check form-switch pb-2" ] [
|
||||||
|
input [ _type "checkbox"; _name "DoOverwrite"; _id "doOverwrite"; _class "form-check-input"
|
||||||
|
_value "true" ]
|
||||||
|
label [ _for "doOverwrite"; _class "form-check-label" ] [ raw "Overwrite" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row pb-3" ] [
|
||||||
|
div [ _class "col text-center" ] [
|
||||||
|
button [ _type "submit"; _class "btn btn-sm btn-primary" ] [ raw "Upload Theme" ]; raw " "
|
||||||
|
button [ _type "button"; _class "btn btn-sm btn-secondary ms-3"
|
||||||
|
_onclick "document.getElementById('theme_new').innerHTML = ''" ] [
|
||||||
|
raw "Cancel"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|> List.singleton
|
||||||
|
|
@ -232,3 +232,139 @@ module Layout =
|
|||||||
title [] []
|
title [] []
|
||||||
yield! content app
|
yield! content app
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
// ~~ SHARED TEMPLATES BETWEEN POSTS AND PAGES
|
||||||
|
open Giraffe.Htmx.Common
|
||||||
|
|
||||||
|
/// The round-trip instant pattern
|
||||||
|
let roundTrip = InstantPattern.CreateWithInvariantCulture "uuuu'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff"
|
||||||
|
|
||||||
|
/// Capitalize the first letter in the given string
|
||||||
|
let private capitalize (it: string) =
|
||||||
|
$"{(string it[0]).ToUpper()}{it[1..]}"
|
||||||
|
|
||||||
|
/// Form to manage permalinks for pages or posts
|
||||||
|
let managePermalinks (model: ManagePermalinksModel) app = [
|
||||||
|
let baseUrl = relUrl app $"admin/{model.Entity}/"
|
||||||
|
let linkDetail idx link =
|
||||||
|
div [ _id $"link_%i{idx}"; _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({idx})" ] [
|
||||||
|
raw "−"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "col-11" ] [
|
||||||
|
div [ _class "form-floating" ] [
|
||||||
|
input [ _type "text"; _name "Prior"; _id $"prior_{idx}"; _class "form-control"; _placeholder "Link"
|
||||||
|
_value link ]
|
||||||
|
label [ _for $"prior_{idx}" ] [ raw "Link" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||||
|
article [] [
|
||||||
|
form [ _action $"{baseUrl}permalinks"; _method "post"; _class "container" ] [
|
||||||
|
antiCsrf app
|
||||||
|
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||||
|
div [ _class "row" ] [
|
||||||
|
div [ _class "col" ] [
|
||||||
|
p [ _style "line-height:1.2rem;" ] [
|
||||||
|
strong [] [ txt model.CurrentTitle ]; br []
|
||||||
|
small [ _class "text-muted" ] [
|
||||||
|
span [ _class "fst-italic" ] [ txt model.CurrentPermalink ]; br []
|
||||||
|
a [ _href $"{baseUrl}{model.Id}/edit" ] [
|
||||||
|
raw $"« Back to Edit {capitalize model.Entity}"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row mb-3" ] [
|
||||||
|
div [ _class "col" ] [
|
||||||
|
button [ _type "button"; _class "btn btn-sm btn-secondary"; _onclick "Admin.addPermalink()" ] [
|
||||||
|
raw "Add a Permalink"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row mb-3" ] [
|
||||||
|
div [ _class "col" ] [
|
||||||
|
div [ _id "permalinks"; _class "container g-0" ] [
|
||||||
|
yield! Array.mapi linkDetail model.Prior
|
||||||
|
script [] [
|
||||||
|
raw """document.addEventListener(\"DOMContentLoaded\", """
|
||||||
|
raw $"() => Admin.setPermalinkIndex({model.Prior.Length}))"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row pb-3" ] [
|
||||||
|
div [ _class "col " ] [
|
||||||
|
button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save Changes" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Form to manage revisions for pages or posts
|
||||||
|
let manageRevisions (model: ManageRevisionsModel) app = [
|
||||||
|
let revUrlBase = relUrl app $"admin/{model.Entity}/{model.Id}/revision"
|
||||||
|
let revDetail idx (rev: Revision) =
|
||||||
|
let asOfString = roundTrip.Format rev.AsOf
|
||||||
|
let asOfId = $"""rev_{asOfString.Replace(".", "_").Replace(":", "-")}"""
|
||||||
|
div [ _id asOfId; _class "row pb-3 mwl-table-detail" ] [
|
||||||
|
div [ _class "col-12 mb-1" ] [
|
||||||
|
longDate app rev.AsOf; raw " at "; shortTime app rev.AsOf; raw " "
|
||||||
|
span [ _class "badge bg-secondary text-uppercase ms-2" ] [ txt (string rev.Text.SourceType) ]
|
||||||
|
if idx = 0 then span [ _class "badge bg-primary text-uppercase ms-2" ] [ raw "Current Revision" ]
|
||||||
|
br []
|
||||||
|
if idx > 0 then
|
||||||
|
let revUrlPrefix = $"{revUrlBase}/{asOfString}"
|
||||||
|
let revRestore = $"{revUrlPrefix}/restore"
|
||||||
|
small [] [
|
||||||
|
a [ _href $"{revUrlPrefix}/preview"; _hxTarget $"#{asOfId}_preview" ] [ raw "Preview" ]
|
||||||
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
|
a [ _href revRestore; _hxPost revRestore ] [ raw "Restore as Current" ]
|
||||||
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
|
a [ _href revUrlPrefix; _hxDelete revUrlPrefix; _hxTarget $"#{asOfId}"
|
||||||
|
_hxSwap HxSwap.OuterHtml; _class "text-danger" ] [
|
||||||
|
raw "Delete"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
if idx > 0 then div [ _id $"{asOfId}_preview"; _class "col-12" ] []
|
||||||
|
]
|
||||||
|
|
||||||
|
h2 [ _class "my-3" ] [ raw app.PageTitle ]
|
||||||
|
article [] [
|
||||||
|
form [ _method "post"; _hxTarget "body"; _class "container mb-3" ] [
|
||||||
|
antiCsrf app
|
||||||
|
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||||
|
div [ _class "row" ] [
|
||||||
|
div [ _class "col" ] [
|
||||||
|
p [ _style "line-height:1.2rem;" ] [
|
||||||
|
strong [] [ txt model.CurrentTitle ]; br []
|
||||||
|
small [ _class "text-muted" ] [
|
||||||
|
a [ _href (relUrl app $"admin/{model.Entity}/{model.Id}/edit") ] [
|
||||||
|
raw $"« Back to Edit {(string model.Entity[0]).ToUpper()}{model.Entity[1..]}"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
if model.Revisions.Length > 1 then
|
||||||
|
div [ _class "row mb-3" ] [
|
||||||
|
div [ _class "col" ] [
|
||||||
|
button [ _type "button"; _class "btn btn-sm btn-danger"; _hxDelete $"{revUrlBase}s/purge"
|
||||||
|
_hxConfirm "This will remove all revisions but the current one; are you sure this is what you wish to do?" ] [
|
||||||
|
raw "Delete All Prior Revisions"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _class "row mwl-table-heading" ] [ div [ _class "col" ] [ raw "Revision" ] ]
|
||||||
|
yield! List.mapi revDetail model.Revisions
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
@ -13,7 +13,7 @@ let edit (model: EditUserModel) app =
|
|||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
h5 [ _class "my-3" ] [ txt app.PageTitle ]
|
h5 [ _class "my-3" ] [ txt app.PageTitle ]
|
||||||
form [ _hxPost (relUrl app "admin/settings/user/save"); _method "post"; _class "container"
|
form [ _hxPost (relUrl app "admin/settings/user/save"); _method "post"; _class "container"
|
||||||
_hxTarget "#userList"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
_hxTarget "#user_panel"; _hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
||||||
antiCsrf app
|
antiCsrf app
|
||||||
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
input [ _type "hidden"; _name "Id"; _value model.Id ]
|
||||||
div [ _class "row" ] [
|
div [ _class "row" ] [
|
||||||
@ -163,56 +163,77 @@ let logOn (model: LogOnModel) (app: AppViewContext) = [
|
|||||||
|
|
||||||
/// The list of users for a web log (part of web log settings page)
|
/// The list of users for a web log (part of web log settings page)
|
||||||
let userList (model: WebLogUser list) app =
|
let userList (model: WebLogUser list) app =
|
||||||
let badge = "ms-2 badge bg"
|
let userCol = "col-12 col-md-4 col-xl-3"
|
||||||
div [ _id "userList" ] [
|
let emailCol = "col-12 col-md-4 col-xl-4"
|
||||||
div [ _class "container g-0" ] [
|
let cre8Col = "d-none d-xl-block col-xl-2"
|
||||||
div [ _class "row mwl-table-detail"; _id "user_new" ] []
|
let lastCol = "col-12 col-md-4 col-xl-3"
|
||||||
]
|
let badge = "ms-2 badge bg"
|
||||||
form [ _method "post"; _class "container g-0"; _hxTarget "this"
|
let userDetail (user: WebLogUser) =
|
||||||
_hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
div [ _class "row mwl-table-detail"; _id $"user_{user.Id}" ] [
|
||||||
antiCsrf app
|
div [ _class $"{userCol} no-wrap" ] [
|
||||||
for user in model do
|
txt user.PreferredName; raw " "
|
||||||
div [ _class "row mwl-table-detail"; _id $"user_{user.Id}" ] [
|
match user.AccessLevel with
|
||||||
div [ _class "col-12 col-md-4 col-xl-3 no-wrap" ] [
|
| Administrator -> span [ _class $"{badge}-success" ] [ raw "ADMINISTRATOR" ]
|
||||||
txt user.PreferredName; raw " "
|
| WebLogAdmin -> span [ _class $"{badge}-primary" ] [ raw "WEB LOG ADMIN" ]
|
||||||
match user.AccessLevel with
|
| Editor -> span [ _class $"{badge}-secondary" ] [ raw "EDITOR" ]
|
||||||
| Administrator -> span [ _class $"{badge}-success" ] [ raw "ADMINISTRATOR" ]
|
| Author -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ]
|
||||||
| WebLogAdmin -> span [ _class $"{badge}-primary" ] [ raw "WEB LOG ADMIN" ]
|
br []
|
||||||
| Editor -> span [ _class $"{badge}-secondary" ] [ raw "EDITOR" ]
|
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
|
||||||
| Author -> span [ _class $"{badge}-dark" ] [ raw "AUTHOR" ]
|
let userUrl = relUrl app $"admin/settings/user/{user.Id}"
|
||||||
br []
|
small [] [
|
||||||
if app.IsAdministrator || (app.IsWebLogAdmin && not (user.AccessLevel = Administrator)) then
|
a [ _href $"{userUrl}/edit"; _hxTarget $"#user_{user.Id}"
|
||||||
let userUrl = relUrl app $"admin/settings/user/{user.Id}"
|
_hxSwap $"{HxSwap.InnerHtml} show:#user_{user.Id}:top" ] [
|
||||||
small [] [
|
raw "Edit"
|
||||||
a [ _href $"{userUrl}/edit"; _hxTarget $"#user_{user.Id}"
|
]
|
||||||
_hxSwap $"{HxSwap.InnerHtml} show:#user_{user.Id}:top" ] [
|
if app.UserId.Value <> user.Id then
|
||||||
raw "Edit"
|
span [ _class "text-muted" ] [ raw " • " ]
|
||||||
]
|
a [ _href userUrl; _hxDelete userUrl; _class "text-danger"
|
||||||
if app.UserId.Value <> user.Id then
|
_hxConfirm $"Are you sure you want to delete the user “{user.PreferredName}”? This action cannot be undone. (This action will not succeed if the user has authored any posts or pages.)" ] [
|
||||||
span [ _class "text-muted" ] [ raw " • " ]
|
raw "Delete"
|
||||||
a [ _href userUrl; _hxDelete userUrl; _class "text-danger"
|
|
||||||
_hxConfirm $"Are you sure you want to delete the user “{user.PreferredName}”? This action cannot be undone. (This action will not succeed if the user has authored any posts or pages.)" ] [
|
|
||||||
raw "Delete"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
div [ _class "col-12 col-md-4 col-xl-4" ] [
|
]
|
||||||
txt $"{user.FirstName} {user.LastName}"; br []
|
div [ _class emailCol ] [
|
||||||
small [ _class "text-muted" ] [
|
txt $"{user.FirstName} {user.LastName}"; br []
|
||||||
txt user.Email
|
small [ _class "text-muted" ] [
|
||||||
if Option.isSome user.Url then
|
txt user.Email
|
||||||
br []; txt user.Url.Value
|
if Option.isSome user.Url then
|
||||||
]
|
br []; txt user.Url.Value
|
||||||
]
|
|
||||||
div [ _class "d-none d-xl-block col-xl-2" ] [
|
|
||||||
if user.CreatedOn = Noda.epoch then raw "N/A" else longDate app user.CreatedOn
|
|
||||||
]
|
|
||||||
div [ _class "col-12 col-md-4 col-xl-3" ] [
|
|
||||||
match user.LastSeenOn with
|
|
||||||
| Some it -> longDate app it; raw " at "; shortTime app it
|
|
||||||
| None -> raw "--"
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
]
|
||||||
|
div [ _class "d-none d-xl-block col-xl-2" ] [
|
||||||
|
if user.CreatedOn = Noda.epoch then raw "N/A" else longDate app user.CreatedOn
|
||||||
|
]
|
||||||
|
div [ _class "col-12 col-md-4 col-xl-3" ] [
|
||||||
|
match user.LastSeenOn with
|
||||||
|
| Some it -> longDate app it; raw " at "; shortTime app it
|
||||||
|
| None -> raw "--"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _id "user_panel" ] [
|
||||||
|
a [ _href (relUrl app "admin/settings/user/new/edit"); _class "btn btn-primary btn-sm mb-3"
|
||||||
|
_hxTarget "#user_new" ] [
|
||||||
|
raw "Add a New User"
|
||||||
|
]
|
||||||
|
div [ _class "container g-0" ] [
|
||||||
|
div [ _class "row mwl-table-heading" ] [
|
||||||
|
div [ _class userCol ] [
|
||||||
|
raw "User"; span [ _class "d-md-none" ] [ raw "; Full Name / E-mail; Last Log On" ]
|
||||||
|
]
|
||||||
|
div [ _class $"{emailCol} d-none d-md-inline-block" ] [ raw "Full Name / E-mail" ]
|
||||||
|
div [ _class cre8Col ] [ raw "Created" ]
|
||||||
|
div [ _class $"{lastCol} d-none d-md-block" ] [ raw "Last Log On" ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
div [ _id "userList" ] [
|
||||||
|
div [ _class "container g-0" ] [
|
||||||
|
div [ _class "row mwl-table-detail"; _id "user_new" ] []
|
||||||
|
]
|
||||||
|
form [ _method "post"; _class "container g-0"; _hxTarget "#user_panel"
|
||||||
|
_hxSwap $"{HxSwap.OuterHtml} show:window:top" ] [
|
||||||
|
antiCsrf app
|
||||||
|
yield! List.map userDetail model
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|> List.singleton
|
|> List.singleton
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{%- assign theme_col = "col-12 col-md-6" -%}
|
|
||||||
{%- assign slug_col = "d-none d-md-block col-md-3" -%}
|
|
||||||
{%- assign tmpl_col = "d-none d-md-block col-md-3" -%}
|
|
@ -1,4 +0,0 @@
|
|||||||
{%- assign user_col = "col-12 col-md-4 col-xl-3" -%}
|
|
||||||
{%- assign email_col = "col-12 col-md-4 col-xl-4" -%}
|
|
||||||
{%- assign cre8_col = "d-none d-xl-block col-xl-2" -%}
|
|
||||||
{%- assign last_col = "col-12 col-md-4 col-xl-3" -%}
|
|
@ -2,19 +2,7 @@
|
|||||||
<article>
|
<article>
|
||||||
<fieldset class="container mb-3 pb-0">
|
<fieldset class="container mb-3 pb-0">
|
||||||
<legend>Themes</legend>
|
<legend>Themes</legend>
|
||||||
<a href="{{ "admin/theme/new" | relative_link }}" class="btn btn-primary btn-sm mb-3" hx-target=#theme_new>
|
<span hx-get="{{ "admin/theme/list" | relative_link }}" hx-trigger="load" hx-swap="outerHTML"></span>
|
||||||
Upload a New Theme
|
|
||||||
</a>
|
|
||||||
<div class="container g-0">
|
|
||||||
{% include_template "_theme-list-columns" %}
|
|
||||||
<div class="row mwl-table-heading">
|
|
||||||
<div class="{{ theme_col }}">Theme</div>
|
|
||||||
<div class="{{ slug_col }} d-none d-md-inline-block">Slug</div>
|
|
||||||
<div class="{{ tmpl_col }} d-none d-md-inline-block">Templates</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mwl-table-detail" id="theme_new"></div>
|
|
||||||
{{ theme_list }}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="container mb-3 pb-0">
|
<fieldset class="container mb-3 pb-0">
|
||||||
{%- assign cache_base_url = "admin/cache/" -%}
|
{%- assign cache_base_url = "admin/cache/" -%}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
<h2 class=my-3>{{ page_title }}</h2>
|
|
||||||
<article>
|
|
||||||
{%- assign base_url = "admin/" | append: model.entity | append: "/" -%}
|
|
||||||
<form action="{{ base_url | append: "permalinks" | relative_link }}" 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="{{ base_url | append: model.id | append: "/edit" | relative_link }}">
|
|
||||||
« Back to Edit {{ model.entity | capitalize }}
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</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>
|
|
@ -1,65 +0,0 @@
|
|||||||
<h2 class=my-3>{{ page_title }}</h2>
|
|
||||||
<article>
|
|
||||||
<form method=post hx-target=body>
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
<input type=hidden name=Id value="{{ model.id }}">
|
|
||||||
<div class="container mb-3">
|
|
||||||
<div class=row>
|
|
||||||
<div class=col>
|
|
||||||
<p style="line-height:1.2rem;">
|
|
||||||
<strong>{{ model.current_title }}</strong><br>
|
|
||||||
<small class=text-muted>
|
|
||||||
<a href="{{ "admin/" | append: model.entity | append: "/" | append: model.id | append: "/edit" | relative_link }}">
|
|
||||||
« Back to Edit {{ model.entity | capitalize }}
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- assign revision_count = model.revisions | size -%}
|
|
||||||
{%- assign rev_url_base = "admin/" | append: model.entity | append: "/" | append: model.id | append: "/revision" -%}
|
|
||||||
{%- if revision_count > 1 %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class=col>
|
|
||||||
<button type=button class="btn btn-sm btn-danger"
|
|
||||||
hx-post="{{ rev_url_base | append: "s/purge" | relative_link }}"
|
|
||||||
hx-confirm="This will remove all revisions but the current one; are you sure this is what you wish to do?">
|
|
||||||
Delete All Prior Revisions
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
<div class="row mwl-table-heading"><div class=col>Revision</div></div>
|
|
||||||
{% for rev in model.revisions %}
|
|
||||||
{%- assign as_of_string = rev.as_of | date: "o" -%}
|
|
||||||
{%- assign as_of_id = "rev_" | append: as_of_string | replace: "\.", "_" | replace: ":", "-" -%}
|
|
||||||
<div id="{{ as_of_id }}" class="row pb-3 mwl-table-detail">
|
|
||||||
<div class="col-12 mb-1">
|
|
||||||
{{ rev.as_of_local | date: "MMMM d, yyyy" }} at {{ rev.as_of_local | date: "h:mmtt" | downcase }}
|
|
||||||
<span class="badge bg-secondary text-uppercase ms-2">{{ rev.format }}</span>
|
|
||||||
{%- if forloop.first %}
|
|
||||||
<span class="badge bg-primary text-uppercase ms-2">Current Revision</span>
|
|
||||||
{%- endif %}<br>
|
|
||||||
{% unless forloop.first %}
|
|
||||||
{%- assign rev_url_prefix = rev_url_base | append: "/" | append: as_of_string -%}
|
|
||||||
{%- assign rev_restore = rev_url_prefix | append: "/restore" | relative_link -%}
|
|
||||||
{%- assign rev_delete = rev_url_prefix | append: "/delete" | relative_link -%}
|
|
||||||
<small>
|
|
||||||
<a href="{{ rev_url_prefix | append: "/preview" | relative_link }}" hx-target="#{{ as_of_id }}_preview">
|
|
||||||
Preview
|
|
||||||
</a>
|
|
||||||
<span class=text-muted> • </span>
|
|
||||||
<a href="{{ rev_restore }}" hx-post="{{ rev_restore }}">Restore as Current</a>
|
|
||||||
<span class=text-muted> • </span>
|
|
||||||
<a href="{{ rev_delete }}" hx-post="{{ rev_delete }}" hx-target="#{{ as_of_id }}" hx-swap=outerHTML
|
|
||||||
class=text-danger>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
{% endunless %}
|
|
||||||
</div>
|
|
||||||
{% unless forloop.first %}<div id="{{ as_of_id }}_preview" class=col-12></div>{% endunless %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</article>
|
|
@ -109,19 +109,6 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset id=users class="container mb-3 pb-0">
|
<fieldset id=users class="container mb-3 pb-0">
|
||||||
<legend>Users</legend>
|
<legend>Users</legend>
|
||||||
{% include_template "_user-list-columns" %}
|
|
||||||
<a href="{{ "admin/settings/user/new/edit" | relative_link }}" class="btn btn-primary btn-sm mb-3"
|
|
||||||
hx-target=#user_new>
|
|
||||||
Add a New User
|
|
||||||
</a>
|
|
||||||
<div class="container g-0">
|
|
||||||
<div class="row mwl-table-heading">
|
|
||||||
<div class="{{ user_col }}">User<span class=d-md-none>; Full Name / E-mail; Last Log On</span></div>
|
|
||||||
<div class="{{ email_col }} d-none d-md-inline-block">Full Name / E-mail</div>
|
|
||||||
<div class="{{ cre8_col }}">Created</div>
|
|
||||||
<div class="{{ last_col }} d-none d-md-block">Last Log On</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span hx-get="{{ "admin/settings/users" | relative_link }}" hx-trigger="load" hx-swap="outerHTML"></span>
|
<span hx-get="{{ "admin/settings/users" | relative_link }}" hx-trigger="load" hx-swap="outerHTML"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset id=rss-settings class="container mb-3 pb-0">
|
<fieldset id=rss-settings class="container mb-3 pb-0">
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<form method=post id=themeList class="container g-0" hx-target=this hx-swap="outerHTML show:window:top">
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
{% include_template "_theme-list-columns" %}
|
|
||||||
{% for theme in themes -%}
|
|
||||||
<div class="row mwl-table-detail" id="theme_{{ theme.id }}">
|
|
||||||
<div class="{{ theme_col }} no-wrap">
|
|
||||||
{{ theme.name }}
|
|
||||||
{%- if theme.is_in_use %}
|
|
||||||
<span class="badge bg-primary ms-2">IN USE</span>
|
|
||||||
{%- endif %}
|
|
||||||
{%- unless theme.is_on_disk %}
|
|
||||||
<span class="badge bg-warning text-dark ms-2">NOT ON DISK</span>
|
|
||||||
{%- endunless %}<br>
|
|
||||||
<small>
|
|
||||||
<span class=text-muted>v{{ theme.version }}</span>
|
|
||||||
{% unless theme.is_in_use or theme.id == "default" %}
|
|
||||||
<span class=text-muted> • </span>
|
|
||||||
{%- assign theme_del_link = "admin/theme/" | append: theme.id | append: "/delete" | relative_link -%}
|
|
||||||
<a href="{{ theme_del_link }}" hx-post="{{ theme_del_link }}" class=text-danger
|
|
||||||
hx-confirm="Are you sure you want to delete the theme “{{ theme.name }}”? This action cannot be undone.">
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
{% endunless %}
|
|
||||||
<span class="d-md-none text-muted">
|
|
||||||
<br>Slug: {{ theme.id }} • {{ theme.template_count }} Templates
|
|
||||||
</span>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="{{ slug_col }}">{{ theme.id }}</div>
|
|
||||||
<div class="{{ tmpl_col }}">{{ theme.template_count }}</div>
|
|
||||||
</div>
|
|
||||||
{%- endfor %}
|
|
||||||
</form>
|
|
@ -1,30 +0,0 @@
|
|||||||
<div class=col>
|
|
||||||
<h5 class=mt-2>{{ page_title }}</h5>
|
|
||||||
<form action="{{ "admin/theme/new" | relative_link }}" method=post class=container enctype=multipart/form-data
|
|
||||||
hx-boost=false>
|
|
||||||
<input type=hidden name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
|
||||||
<div class=row>
|
|
||||||
<div class="col-12 col-sm-6 pb-3">
|
|
||||||
<div class=form-floating>
|
|
||||||
<input type=file id=file name=file class=form-control accept=.zip placeholder="Theme File" required>
|
|
||||||
<label for=file>Theme File</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-sm-6 pb-3 d-flex justify-content-center align-items-center">
|
|
||||||
<div class="form-check form-switch pb-2">
|
|
||||||
<input type=checkbox name=DoOverwrite id=doOverwrite class=form-check-input value=true>
|
|
||||||
<label for=doOverwrite class=form-check-label>Overwrite</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row pb-3">
|
|
||||||
<div class="col text-center">
|
|
||||||
<button type=submit class="btn btn-sm btn-primary">Upload Theme</button>
|
|
||||||
<button type=button class="btn btn-sm btn-secondary ms-3"
|
|
||||||
onclick="document.getElementById('theme_new').innerHTML = ''">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
Loading…
Reference in New Issue
Block a user