parent
							
								
									43a700eead
								
							
						
					
					
						commit
						641a7499cc
					
				| @ -419,7 +419,7 @@ type EditChapterModel = { | |||||||
|             let pattern = |             let pattern = | ||||||
|                 match value |> Seq.fold (fun count chr -> if chr = ':' then count + 1 else count) 0 with |                 match value |> Seq.fold (fun count chr -> if chr = ':' then count + 1 else count) 0 with | ||||||
|                 | 0 -> "S" |                 | 0 -> "S" | ||||||
|                 | 1 -> "MM:ss" |                 | 1 -> "M:ss" | ||||||
|                 | 2 -> "H:mm:ss" |                 | 2 -> "H:mm:ss" | ||||||
|                 | _ -> invalidArg name "Max time format is H:mm:ss" |                 | _ -> invalidArg name "Max time format is H:mm:ss" | ||||||
|                 |> function |                 |> function | ||||||
|  | |||||||
| @ -6,8 +6,8 @@ open System.IO | |||||||
| open System.Web | open System.Web | ||||||
| open DotLiquid | open DotLiquid | ||||||
| open Giraffe.ViewEngine | open Giraffe.ViewEngine | ||||||
| open MyWebLog.AdminViews.Helpers |  | ||||||
| open MyWebLog.ViewModels | open MyWebLog.ViewModels | ||||||
|  | open MyWebLog.Views | ||||||
| 
 | 
 | ||||||
| /// Extensions on the DotLiquid Context object | /// Extensions on the DotLiquid Context object | ||||||
| type Context with | type Context with | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ module Dashboard = | |||||||
|               ListedPages        = listed |               ListedPages        = listed | ||||||
|               Categories         = cats |               Categories         = cats | ||||||
|               TopLevelCategories = topCats } |               TopLevelCategories = topCats } | ||||||
|         return! adminPage "Dashboard" false (AdminViews.Admin.dashboard model) next ctx |         return! adminPage "Dashboard" false (Views.Admin.dashboard model) next ctx | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // GET /admin/administration |     // GET /admin/administration | ||||||
|  | |||||||
| @ -3,8 +3,7 @@ module private MyWebLog.Handlers.Helpers | |||||||
| 
 | 
 | ||||||
| open System.Text.Json | open System.Text.Json | ||||||
| open Microsoft.AspNetCore.Http | open Microsoft.AspNetCore.Http | ||||||
| open MyWebLog.AdminViews | open MyWebLog.Views | ||||||
| open MyWebLog.AdminViews.Helpers |  | ||||||
| 
 | 
 | ||||||
| /// Session extensions to get and set objects | /// Session extensions to get and set objects | ||||||
| type ISession with | type ISession with | ||||||
|  | |||||||
| @ -379,7 +379,7 @@ let chapters postId : HttpHandler = requireAccess Author >=> fun next ctx -> tas | |||||||
|              && 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 (AdminViews.Post.chapters false (ManageChaptersModel.Create post)) next ctx |             adminPage "Manage Chapters" true (Views.Post.chapters false (ManageChaptersModel.Create post)) next ctx | ||||||
|     | Some _ | None -> return! Error.notFound next ctx |     | Some _ | None -> return! Error.notFound next ctx | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -398,9 +398,9 @@ let editChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex | |||||||
|         match chapter with |         match chapter with | ||||||
|         | Some chap -> |         | Some chap -> | ||||||
|             return! |             return! | ||||||
|                 adminPage |                 adminBarePage | ||||||
|                     (if index = -1 then "Add a Chapter" else "Edit Chapter") true |                     (if index = -1 then "Add a Chapter" else "Edit Chapter") true | ||||||
|                     (AdminViews.Post.chapterEdit (EditChapterModel.FromChapter post.Id index chap)) 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 | ||||||
| } | } | ||||||
| @ -418,7 +418,7 @@ let saveChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex | |||||||
|         if index >= -1 && index < List.length chapters then |         if index >= -1 && index < List.length chapters then | ||||||
|             try |             try | ||||||
|                 let chapter     = form.ToChapter() |                 let chapter     = form.ToChapter() | ||||||
|                 let existing    = if index = -1 then chapters else chapters |> List.removeAt index |                 let existing    = if index = -1 then chapters else List.removeAt index chapters | ||||||
|                 let updatedPost = |                 let updatedPost = | ||||||
|                     { post with |                     { post with | ||||||
|                         Episode = Some |                         Episode = Some | ||||||
| @ -429,9 +429,32 @@ let saveChapter (postId, index) : HttpHandler = requireAccess Author >=> fun nex | |||||||
|                 return! |                 return! | ||||||
|                     adminPage |                     adminPage | ||||||
|                         "Manage Chapters" true |                         "Manage Chapters" true | ||||||
|                         (AdminViews.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)) next ctx |                         (Views.Post.chapterList form.AddAnother (ManageChaptersModel.Create updatedPost)) next ctx | ||||||
|             with |             with | ||||||
|             | ex -> return! Error.notFound next ctx // TODO: return error |             | ex -> return! Error.server ex.Message next ctx | ||||||
|  |         else return! Error.notFound next ctx | ||||||
|  |     | Some _ | None -> return! Error.notFound next ctx | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DELETE /admin/post/{id}/chapter/{idx} | ||||||
|  | let deleteChapter (postId, index) : HttpHandler = requireAccess Author >=> fun next ctx -> task { | ||||||
|  |     let data = ctx.Data | ||||||
|  |     match! data.Post.FindById (PostId postId) ctx.WebLog.Id with | ||||||
|  |     | Some post | ||||||
|  |         when    Option.isSome post.Episode | ||||||
|  |              && Option.isSome post.Episode.Value.Chapters | ||||||
|  |              && canEdit post.AuthorId ctx -> | ||||||
|  |         let  chapters = post.Episode.Value.Chapters.Value | ||||||
|  |         if index >= 0 && index < List.length chapters then | ||||||
|  |             let updatedPost = | ||||||
|  |                 { post with | ||||||
|  |                     Episode = Some { post.Episode.Value with Chapters = Some (List.removeAt index chapters) } } | ||||||
|  |             do! data.Post.Update updatedPost | ||||||
|  |             do! addMessage ctx { UserMessage.Success with Message = "Chapter deleted successfully" } | ||||||
|  |             return! | ||||||
|  |                 adminPage | ||||||
|  |                     "Manage Chapters" true (Views.Post.chapterList false (ManageChaptersModel.Create updatedPost)) next | ||||||
|  |                     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 | ||||||
| } | } | ||||||
|  | |||||||
| @ -216,6 +216,11 @@ let router : HttpHandler = choose [ | |||||||
|                 routef  "/%s/delete"       Upload.deleteFromDb |                 routef  "/%s/delete"       Upload.deleteFromDb | ||||||
|             ]) |             ]) | ||||||
|         ] |         ] | ||||||
|  |         DELETE >=> validateCsrf >=> choose [ | ||||||
|  |             subRoute "/post" (choose [ | ||||||
|  |                 routef "/%s/chapter/%i" Post.deleteChapter | ||||||
|  |             ]) | ||||||
|  |         ] | ||||||
|     ]) |     ]) | ||||||
|     GET_HEAD >=> routexp "/category/(.*)"  Post.pageOfCategorizedPosts |     GET_HEAD >=> routexp "/category/(.*)"  Post.pageOfCategorizedPosts | ||||||
|     GET_HEAD >=> routef  "/page/%i"        Post.pageOfPosts |     GET_HEAD >=> routef  "/page/%i"        Post.pageOfPosts | ||||||
|  | |||||||
| @ -36,7 +36,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 (AdminViews.User.logOn { LogOnModel.Empty with ReturnTo = returnTo }) next ctx |     adminPage "Log On" true (Views.User.logOn { LogOnModel.Empty with ReturnTo = returnTo }) next ctx | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| open System.Security.Claims | open System.Security.Claims | ||||||
| @ -93,7 +93,7 @@ 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 (AdminViews.User.userList users) next ctx |     return! adminBarePage "User Administration" true (Views.User.userList users) next ctx | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Show the edit user page | /// Show the edit user page | ||||||
|  | |||||||
| @ -9,10 +9,10 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Content Include="appsettings*.json" CopyToOutputDirectory="Always" /> |     <Content Include="appsettings*.json" CopyToOutputDirectory="Always" /> | ||||||
|     <Compile Include="Caches.fs" /> |     <Compile Include="Caches.fs" /> | ||||||
|     <Compile Include="AdminViews\Helpers.fs" /> |     <Compile Include="Views\Helpers.fs" /> | ||||||
|     <Compile Include="AdminViews\Admin.fs" /> |     <Compile Include="Views\Admin.fs" /> | ||||||
|     <Compile Include="AdminViews\Post.fs" /> |     <Compile Include="Views\Post.fs" /> | ||||||
|     <Compile Include="AdminViews\User.fs" /> |     <Compile Include="Views\User.fs" /> | ||||||
|     <Compile Include="Handlers\Helpers.fs" /> |     <Compile Include="Handlers\Helpers.fs" /> | ||||||
|     <Compile Include="Handlers\Admin.fs" /> |     <Compile Include="Handlers\Admin.fs" /> | ||||||
|     <Compile Include="Handlers\Feed.fs" /> |     <Compile Include="Handlers\Feed.fs" /> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| module MyWebLog.AdminViews.Admin | module MyWebLog.Views.Admin | ||||||
| 
 | 
 | ||||||
| open Giraffe.ViewEngine | open Giraffe.ViewEngine | ||||||
| open MyWebLog.ViewModels | open MyWebLog.ViewModels | ||||||
| @ -1,5 +1,5 @@ | |||||||
| [<AutoOpen>] | [<AutoOpen>] | ||||||
| module MyWebLog.AdminViews.Helpers | module MyWebLog.Views.Helpers | ||||||
| 
 | 
 | ||||||
| open Microsoft.AspNetCore.Antiforgery | open Microsoft.AspNetCore.Antiforgery | ||||||
| open Giraffe.ViewEngine | open Giraffe.ViewEngine | ||||||
| @ -1,4 +1,4 @@ | |||||||
| module MyWebLog.AdminViews.Post | module MyWebLog.Views.Post | ||||||
| 
 | 
 | ||||||
| open Giraffe.ViewEngine | open Giraffe.ViewEngine | ||||||
| open Giraffe.ViewEngine.Htmx | open Giraffe.ViewEngine.Htmx | ||||||
| @ -9,6 +9,7 @@ open NodaTime.Text | |||||||
| /// The pattern for chapter start times | /// The pattern for chapter start times | ||||||
| let startTimePattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF" | let startTimePattern = DurationPattern.CreateWithInvariantCulture "H:mm:ss.FF" | ||||||
| 
 | 
 | ||||||
|  | /// The form to add or edit a chapter | ||||||
| let chapterEdit (model: EditChapterModel) app = [ | let chapterEdit (model: EditChapterModel) app = [ | ||||||
|     let postUrl = relUrl app $"admin/post/{model.PostId}/chapter/{model.Index}" |     let postUrl = relUrl app $"admin/post/{model.PostId}/chapter/{model.Index}" | ||||||
|     h3 [ _class "my-3" ] [ raw (if model.Index < 0 then "Add" else "Edit"); raw " Chapter" ] |     h3 [ _class "my-3" ] [ raw (if model.Index < 0 then "Add" else "Edit"); raw " Chapter" ] | ||||||
| @ -128,6 +129,7 @@ let chapterEdit (model: EditChapterModel) app = [ | |||||||
|                 else |                 else | ||||||
|                     input [ _type "hidden"; _name "AddAnother"; _value "false" ] |                     input [ _type "hidden"; _name "AddAnother"; _value "false" ] | ||||||
|                 button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save" ] |                 button [ _type "submit"; _class "btn btn-primary" ] [ raw "Save" ] | ||||||
|  |                 raw "   " | ||||||
|                 a [ _href cancelLink; _hxGet cancelLink; _class "btn btn-secondary"; _hxTarget "body" ] [ raw "Cancel" ] |                 a [ _href cancelLink; _hxGet cancelLink; _class "btn btn-secondary"; _hxTarget "body" ] [ raw "Cancel" ] | ||||||
|             ] |             ] | ||||||
|         ] |         ] | ||||||
| @ -146,20 +148,37 @@ let chapterList withNew (model: ManageChaptersModel) app = | |||||||
|             div [ _class "col" ] [ raw "Location?" ] |             div [ _class "col" ] [ raw "Location?" ] | ||||||
|         ] |         ] | ||||||
|         yield! model.Chapters |> List.mapi (fun idx chapter -> |         yield! model.Chapters |> List.mapi (fun idx chapter -> | ||||||
|             div [ _class "row pb-3 mwl-table-detail"; _id $"chapter{idx}" ] [ |             div [ _class "row mwl-table-detail"; _id $"chapter{idx}" ] [ | ||||||
|                 div [ _class "col" ] [ txt (startTimePattern.Format chapter.StartTime) ] |                 div [ _class "col" ] [ txt (startTimePattern.Format chapter.StartTime) ] | ||||||
|                 div [ _class "col" ] [ txt (defaultArg chapter.Title "") ] |                 div [ _class "col" ] [ | ||||||
|  |                     txt (defaultArg chapter.Title ""); br [] | ||||||
|  |                     small [] [ | ||||||
|  |                         if withNew then | ||||||
|  |                             raw " " | ||||||
|  |                         else | ||||||
|  |                             let chapterUrl = relUrl app $"admin/post/{model.Id}/chapter/{idx}" | ||||||
|  |                             a [ _href chapterUrl; _hxGet chapterUrl; _hxTarget $"#chapter{idx}" | ||||||
|  |                                 _hxSwap $"innerHTML show:#chapter{idx}:top" ] [ | ||||||
|  |                                 raw "Edit" | ||||||
|  |                             ] | ||||||
|  |                             span [ _class "text-muted" ] [ raw " • " ] | ||||||
|  |                             a [ _href chapterUrl; _hxDelete chapterUrl; _class "text-danger" ] [ | ||||||
|  |                                 raw "Delete" | ||||||
|  |                             ] | ||||||
|  |                     ] | ||||||
|  |                 ] | ||||||
|                 div [ _class "col" ] [ raw (if Option.isSome chapter.ImageUrl then "Y" else "N") ] |                 div [ _class "col" ] [ raw (if Option.isSome chapter.ImageUrl then "Y" else "N") ] | ||||||
|                 div [ _class "col" ] [ raw (if Option.isSome chapter.Location then "Y" else "N") ] |                 div [ _class "col" ] [ raw (if Option.isSome chapter.Location then "Y" else "N") ] | ||||||
|             ]) |             ]) | ||||||
|         div [ _class "row pb-3"; _id "chapter-1" ] [ |         div [ _class "row pb-3"; _id "chapter-1" ] [ | ||||||
|             if withNew then |  | ||||||
|                 yield! chapterEdit (EditChapterModel.FromChapter (PostId model.Id) -1 Chapter.Empty) app |  | ||||||
|             else |  | ||||||
|             let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1" |             let newLink = relUrl app $"admin/post/{model.Id}/chapter/-1" | ||||||
|  |             if withNew then | ||||||
|  |                 span [ _hxGet newLink; _hxTarget "#chapter-1"; _hxTrigger "load"; _hxSwap "show:#chapter-1:top" ] [] | ||||||
|  |             else | ||||||
|                 div [ _class "row pb-3 mwl-table-detail" ] [ |                 div [ _class "row pb-3 mwl-table-detail" ] [ | ||||||
|                     div [ _class "col-12" ] [ |                     div [ _class "col-12" ] [ | ||||||
|                         a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1" ] [ |                         a [ _class "btn btn-primary"; _href newLink; _hxGet newLink; _hxTarget "#chapter-1" | ||||||
|  |                             _hxSwap "show:#chapter-1:top" ] [ | ||||||
|                             raw "Add a New Chapter" |                             raw "Add a New Chapter" | ||||||
|                         ] |                         ] | ||||||
|                     ] |                     ] | ||||||
| @ -1,4 +1,4 @@ | |||||||
| module MyWebLog.AdminViews.User | module MyWebLog.Views.User | ||||||
| 
 | 
 | ||||||
| open Giraffe.ViewEngine | open Giraffe.ViewEngine | ||||||
| open Giraffe.ViewEngine.Htmx | open Giraffe.ViewEngine.Htmx | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user