Version 3 #40

Merged
danieljsummers merged 67 commits from version-2-3 into main 2023-02-02 23:47:28 +00:00
3 changed files with 44 additions and 10 deletions
Showing only changes of commit 7096368b6e - Show all commits

View File

@ -77,6 +77,7 @@ module Helpers =
open System.Security.Claims open System.Security.Claims
open System.Text.Json open System.Text.Json
open System.Text.RegularExpressions
open Microsoft.AspNetCore.Antiforgery open Microsoft.AspNetCore.Antiforgery
open Microsoft.Extensions.Configuration open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.DependencyInjection
@ -198,15 +199,14 @@ module Helpers =
/// Require a user to be logged on for a route /// Require a user to be logged on for a route
let requireUser = requiresAuthentication Error.notAuthorized let requireUser = requiresAuthentication Error.notAuthorized
/// Regular expression to validate that a URL is a local URL
let isLocal = Regex """^/[^\/\\].*"""
/// Redirect to another page, saving the session before redirecting /// Redirect to another page, saving the session before redirecting
let redirectToGet url next ctx = task { let redirectToGet (url : string) next ctx = task {
do! saveSession ctx do! saveSession ctx
let action = let action =
if Option.isSome (noneIfEmpty url) if Option.isSome (noneIfEmpty url) && isLocal.IsMatch url then
// "/" or "/foo" but not "//" or "/\"
&& ( (url[0] = '/' && (url.Length = 1 || (url[1] <> '/' && url[1] <> '\\')))
// "~/" or "~/foo"
|| (url.Length > 1 && url[0] = '~' && url[1] = '/')) then
if isHtmx ctx then withHxRedirect url else redirectTo false url if isHtmx ctx then withHxRedirect url else redirectTo false url
else RequestErrors.BAD_REQUEST "Invalid redirect URL" else RequestErrors.BAD_REQUEST "Invalid redirect URL"
return! action next ctx return! action next ctx

View File

@ -44,7 +44,7 @@ type EditProfileViewModel =
Experience : string option Experience : string option
/// The skills for the user /// The skills for the user
Skills : SkillForm list Skills : SkillForm array
} }
/// Support functions for the ProfileForm type /// Support functions for the ProfileForm type
@ -60,7 +60,7 @@ module EditProfileViewModel =
FullTime = false FullTime = false
Biography = "" Biography = ""
Experience = None Experience = None
Skills = [ { Id = ""; Description = ""; Notes = None } ] Skills = [||]
} }
/// Create an instance of this form from the given profile /// Create an instance of this form from the given profile
@ -79,6 +79,7 @@ module EditProfileViewModel =
Description = s.Description Description = s.Description
Notes = s.Notes Notes = s.Notes
}) })
|> Array.ofList
} }

View File

@ -7,6 +7,40 @@ open Giraffe.ViewEngine
open Giraffe.ViewEngine.Htmx open Giraffe.ViewEngine.Htmx
open JobsJobsJobs.ViewModels open JobsJobsJobs.ViewModels
let skillEdit (skills : SkillForm array) =
let mapToInputs (idx : int) (skill : SkillForm) =
div [ _class "row pb-3" ] [
div [ _class "col-2 col-md-1 align-self-center" ] [
button [ _class "btn btn-sm btn-outline-danger rounded-pill"; _title "Delete"
_onclick $"jjj.removeSkill('{skill.Id}')" ] [
rawText "&nbsp;&minus;&nbsp;"
]
]
div [ _class "col-10 col-md-6" ] [
div [ _class "form-floating" ] [
input [ _type "text"; _id $"skillDesc{skill.Id}"; _name $"Skills[{idx}].Description"
_class "form-control"; _placeholder "A skill (language, design technique, process, etc.)"
_maxlength "200"; _value skill.Description; _required ]
label [ _class "jjj-label"; _for $"skillDesc{skill.Id}" ] [ rawText "Skill" ]
]
div [ _class "form-text" ] [ rawText "A skill (language, design technique, process, etc.)" ]
]
div [ _class "col-12 col-md-5" ] [
div [ _class "form-floating" ] [
input [ _type "text"; _id $"skillNotes{skill.Id}"; _name $"Skills[{idx}].Notes";
_class "form-control"; _maxlength "1000"
_placeholder "A further description of the skill (1,000 characters max)"
_value (defaultArg skill.Notes "") ]
label [ _class "jjj-label"; _for $"skillNotes{skill.Id}" ] [ rawText "Notes" ]
]
div [ _class "form-text" ] [ rawText "A further description of the skill" ]
]
]
template [ _id "newSkill" ] [ mapToInputs -1 { Id = ""; Description = ""; Notes = None } ]
:: (skills
|> Array.mapi mapToInputs
|> List.ofArray)
/// The profile edit page /// The profile edit page
let edit (m : EditProfileViewModel) continents isNew csrf = let edit (m : EditProfileViewModel) continents isNew csrf =
article [] [ article [] [
@ -68,8 +102,7 @@ let edit (m : EditProfileViewModel) continents isNew csrf =
] ]
] ]
] ]
//<profile-skill-edit v-for="(skill, idx) in profile.skills" :key="skill.id" v-model="profile.skills[idx]" yield! skillEdit m.Skills
// @remove="removeSkill(skill.id)" @input="v$.skills.$touch" />
div [ _class "col-12" ] [ div [ _class "col-12" ] [
hr [] hr []
h4 [] [ rawText "Experience" ] h4 [] [ rawText "Experience" ]