diff --git a/README.md b/README.md index 112372e..5c93514 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ htmx uses attributes and HTTP headers to attain its interactivity; the libraries `Giraffe.Htmx` provides extensions that facilitate using htmx on the server side, primarily reading and setting headers. `Giraffe.ViewEngine.Htmx` provides attributes and helpers to produce views that utilize htmx. Both can be installed from NuGet via standard methods. -| Server Side | View Engine | -|---|---| -|[![Nuget](https://img.shields.io/nuget/v/Giraffe.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.Htmx/)|[![Nuget](https://img.shields.io/nuget/v/Giraffe.ViewEngine.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/)| +| Server Side | View Engine | +|------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| +| [![Nuget](https://img.shields.io/nuget/vpre/Giraffe.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.Htmx/) | [![Nuget](https://img.shields.io/nuget/vpre/Giraffe.ViewEngine.Htmx?style=plastic)](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx/) | -Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_. +Both of these packages will also install `Giraffe.Htmx.Common`, which has some common definitions and provides a local-to-your-project version of the htmx JavaScript _(as of v2.0.8)_ and htmax htmx-plus-extensions bundle _(as of v4.0.0-beta4)_. ## Server Side (`Giraffe.Htmx`) @@ -74,7 +74,7 @@ If you want to use the package-provided htmx library, `Htmx.Script.local` will c ## Feedback / Help -The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel of the [F# Software Foundation's Slack server](https://fsharp.org/guides/slack/). +The author hangs out in the #dotnet-htmx channel (and most others) of the [htmx Discord server](https://htmx.org/discord) and the #web channel (and most others) of the [F# Community Discord server](https://discord.gg/R6n7c54). ## Thanks |[Giraffe logo](https://giraffe.wiki)| [htmx logo](https://htmx.org) |[JetBrains Logo (Main)](https://jb.gg/OpenSource)| diff --git a/src/Common/Common.fs b/src/Common/Common.fs index 79500cc..a0946c8 100644 --- a/src/Common/Common.fs +++ b/src/Common/Common.fs @@ -1,111 +1,113 @@ -/// Common definitions shared between attribute values and response headers -[] -module Giraffe.Htmx.Common - -/// The version of htmx embedded in the package -let HtmxVersion = "4.0.0-beta4" - -/// The path for the provided htmx script -let internal htmxLocalScript = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}" - -/// The path for the provided htmax script -let internal htmaxLocalScript = $"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}" - -/// Serialize a list of key/value pairs to JSON (very rudimentary) -/// The key/value pairs to be serialized to JSON -/// A string with the key/value pairs serialized to JSON -let internal toJson (pairs: (string * string) list) = - pairs - |> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\""))) - |> String.concat ", " - |> sprintf "{ %s }" - -/// Convert a boolean to lowercase "true" or "false" -/// The boolean value to convert -/// "true" for true, "false" for false -let internal toLowerBool (boolValue: bool) = - (string boolValue).ToLowerInvariant() - - -/// Valid values for the hx-swap attribute / HX-Reswap header -/// May be combined with swap / scroll / show config) -/// Documentation -[] -module HxSwap = - - /// The default, replace the inner HTML of the target element - [] - let InnerHtml = "innerHTML" - - /// Replace the entire target element with the response - [] - let OuterHtml = "outerHTML" - - /// Morph the inner HTML of the target to the new content - [] - let InnerMorph = "innerMorph" - - /// Morph the outer HTML of the target to the new content - [] - let OuterMorph = "outerMorph" - - /// Morph the outer HTML of the target to the new content, recreating all children - /// This is used internally by the new history extension, but can be used by others if desired - [] - let OuterSync = "outerSync" - - /// Replace the text content of the target without parsing the response as HTML - [] - let TextContent = "textContent" - - /// Insert the response before the target element - [] - let Before = "before" - - /// Insert the response before the target element (pre-v4 name) - [] - let BeforeBegin = Before - - /// Insert the response before the first child of the target element - [] - let Prepend = "prepend" - - /// Insert the response before the first child of the target element (pre-v4 name) - [] - let AfterBegin = Prepend - - /// Insert the response after the last child of the target element - [] - let Append = "append" - - /// Insert the response after the last child of the target element (pre-v4 name) - [] - let BeforeEnd = Append - - /// Insert the response after the target element - [] - let After = "after" - - /// Insert the response after the target element (pre-v4 name) - [] - let AfterEnd = After - - /// Delete the target element regardless of response - [] - let Delete = "delete" - - /// Does not append content from response (out of band items will still be processed) - [] - let None = "none" - - /// Update existing elements by id and add new ones - /// This requires the upsert extension - /// Extension - [] - let Upsert = "upsert" - - /// Specify that the target of the htmx request should be downloaded - /// This requires the hx-download extension (included in the htmax bundle) - /// Documentation - [] - let Download = "download" +/// Common definitions shared between attribute values and response headers +[] +module Giraffe.Htmx.Common + +/// The version of htmx embedded in the package +let HtmxVersion = "4.0.0-beta5" + +/// URLs for the included htmx library static web assets +module StaticAssetUrl = + /// The path for the provided htmx script + let htmx = $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}" + + /// The path for the provided htmax script + let htmax = $"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}" + +/// Serialize a list of key/value pairs to JSON (very rudimentary) +/// The key/value pairs to be serialized to JSON +/// A string with the key/value pairs serialized to JSON +let internal toJson (pairs: (string * string) list) = + pairs + |> List.map (fun pair -> sprintf "\"%s\": \"%s\"" (fst pair) ((snd pair).Replace ("\"", "\\\""))) + |> String.concat ", " + |> sprintf "{ %s }" + +/// Convert a boolean to lowercase "true" or "false" +/// The boolean value to convert +/// "true" for true, "false" for false +let internal toLowerBool (boolValue: bool) = + (string boolValue).ToLowerInvariant() + + +/// Valid values for the hx-swap attribute / HX-Reswap header +/// May be combined with swap / scroll / show config) +/// Documentation +[] +module HxSwap = + + /// The default, replace the inner HTML of the target element + [] + let InnerHtml = "innerHTML" + + /// Replace the entire target element with the response + [] + let OuterHtml = "outerHTML" + + /// Morph the inner HTML of the target to the new content + [] + let InnerMorph = "innerMorph" + + /// Morph the outer HTML of the target to the new content + [] + let OuterMorph = "outerMorph" + + /// Morph the outer HTML of the target to the new content, recreating all children + /// This is used internally by the new history extension, but can be used by others if desired + [] + let OuterSync = "outerSync" + + /// Replace the text content of the target without parsing the response as HTML + [] + let TextContent = "textContent" + + /// Insert the response before the target element + [] + let Before = "before" + + /// Insert the response before the target element (pre-v4 name) + [] + let BeforeBegin = Before + + /// Insert the response before the first child of the target element + [] + let Prepend = "prepend" + + /// Insert the response before the first child of the target element (pre-v4 name) + [] + let AfterBegin = Prepend + + /// Insert the response after the last child of the target element + [] + let Append = "append" + + /// Insert the response after the last child of the target element (pre-v4 name) + [] + let BeforeEnd = Append + + /// Insert the response after the target element + [] + let After = "after" + + /// Insert the response after the target element (pre-v4 name) + [] + let AfterEnd = After + + /// Delete the target element regardless of response + [] + let Delete = "delete" + + /// Does not append content from response (out of band items will still be processed) + [] + let None = "none" + + /// Update existing elements by id and add new ones + /// This requires the upsert extension + /// Extension + [] + let Upsert = "upsert" + + /// Specify that the target of the htmx request should be downloaded + /// This requires the hx-download extension (included in the htmax bundle) + /// Documentation + [] + let Download = "download" diff --git a/src/Common/README.md b/src/Common/README.md index 42d4898..9e750f1 100644 --- a/src/Common/README.md +++ b/src/Common/README.md @@ -1,7 +1,7 @@ ## Giraffe.Htmx.Common -This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. It also contains htmx as a static web asset, allowing it to be loaded from your local (or published) project. +This package contains common code shared between [`Giraffe.Htmx`](https://www.nuget.org/packages/Giraffe.Htmx) and [`Giraffe.ViewEngine.Htmx`](https://www.nuget.org/packages/Giraffe.ViewEngine.Htmx), and will be automatically installed when you install either one. It also contains htmx and htmax as static web assets, allowing them to be loaded from your local (or published) project. -**htmx version: 4.0.0-beta4** +**htmx version: 4.0.0-beta5** _**NOTE:** Pay special attention to breaking changes highlighted in the packages listed above._ \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3ec5f62..c761399 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,15 +3,14 @@ net8.0;net9.0;net10.0 4.0.0 - beta4 + beta5 true - Update htmx 4 to beta4 -- [Common] Update provided htmx 4 to 4.0.0-beta4 -- [Common] Add htmax bundle to provided version -- [Server] Update HX-Target header to return tag and ID (similar to HX-Source) -- [Server] Add support for HX-Request-Type header -- [View Engine] Add hx-status attribute, outerSync swap -- [View Engine] Updated script tags to pull htmx 4.0.0-beta4, added "max" bundle links for local and CDN + Update htmx 4 to beta5 +- [Common] Update provided htmx/htmax 4 to 4.0.0-beta5 +- [Common] Add StaticAssetUrl module with static asset paths for htmx and htmax +- [Server] Unobsolete HX-Prompt header, note that it requires hx-prompt extension +- [View Engine] Unobsolete hx-prompt attribute, note that it requires hx-prompt extension +- [View Engine] Updated CDN script tags to pull htmx / htmax 4.0.0-beta5 See package and prior alpha release READMEs; v2 to v4 is not an update-and-forget-it release diff --git a/src/Htmx/Htmx.fs b/src/Htmx/Htmx.fs index e4bbab6..1433c84 100644 --- a/src/Htmx/Htmx.fs +++ b/src/Htmx/Htmx.fs @@ -8,7 +8,7 @@ open System type HxRequestTypes = /// A request targeting the body tag or using an hx-select attribute | HxFullRequest - + /// A request for partial content | HxPartialRequest @@ -18,15 +18,15 @@ let private hdr (headers : IHeaderDictionary) hdr = /// Extensions to the header dictionary type IHeaderDictionary with - + /// Indicates that the request is via an element using hx-boost member this.HxBoosted with get () = hdr this "HX-Boosted" |> Option.map bool.Parse - + /// The current URL of the browser (note that this does not update until after settle) member this.HxCurrentUrl with get () = hdr this "HX-Current-URL" |> Option.map Uri - + /// true if the request is for history restoration after a miss in the local history cache member this.HxHistoryRestoreRequest with get () = hdr this "HX-History-Restore-Request" |> Option.map bool.Parse @@ -35,9 +35,9 @@ type IHeaderDictionary with /// preload is part of the htmax htmx-plus-extensions bundle member this.HxPreloaded with get () = hdr this "HX-Preloaded" |> Option.map bool.Parse - + /// The user response to an hx-prompt - [] + /// NEW IN v4: This functionality is dependent on the hx-prompt extension being loaded member this.HxPrompt with get () = hdr this "HX-Prompt" @@ -49,7 +49,7 @@ type IHeaderDictionary with /// hx-ws is part of the htmax htmx-plus-extensions bundle member this.HxRequestId with get () = hdr this "HX-Request-ID" - + /// The request type sent by htmx /// member this.HxRequestType @@ -59,7 +59,7 @@ type IHeaderDictionary with | Some typ when typ = "partial" -> Some HxPartialRequest | Some _ -> None | None -> None - + /// The tag name (fst) and id attribute (snd) of the element triggering this request member this.HxSource with get () = @@ -119,7 +119,7 @@ type HttpRequest with module Handlers = open Giraffe.Htmx.Common - + /// Instruct htmx to download a response from another path / URL /// The path or URL where the downloadable content is found /// An HTTP handler with the HX-Download header set @@ -127,14 +127,14 @@ module Handlers = /// Documentation let withHxDownload (path: string) : HttpHandler = setHttpHeader "HX-Download" path - + /// Instruct htmx to perform a client-side redirect for content /// The path where the content should be found /// An HTTP handler with the HX-Location header set /// Documentation let withHxLocation (path: string) : HttpHandler = setHttpHeader "HX-Location" path - + /// Pushes a new url into the history stack /// The URL to be pushed /// An HTTP handler with the HX-Push-Url header set @@ -148,7 +148,7 @@ module Handlers = /// Documentation let withHxNoPushUrl : HttpHandler = toLowerBool false |> withHxPushUrl - + /// Can be used to do a client-side redirect to a new location /// The URL to which the client should be redirected /// An HTTP handler with the HX-Redirect header set @@ -175,13 +175,13 @@ module Handlers = /// Documentation let withHxNoReplaceUrl : HttpHandler = toLowerBool false |> withHxReplaceUrl - + /// Override which portion of the response will be swapped into the target document /// The selector for the new response target /// An HTTP handler with the HX-Reselect header set let withHxReselect (target: string) : HttpHandler = setHttpHeader "HX-Reselect" target - + /// Override the hx-swap attribute from the initiating element /// The swap value to override /// An HTTP handler with the HX-Reswap header set @@ -245,10 +245,12 @@ module Handlers = /// Load the package-provided version of the htmx script [] module HtmxScript = - + open Giraffe.Htmx.Common open Microsoft.AspNetCore.Html /// script tag to load the package-provided version of the htmx script - let local = HtmlString $"""""" - \ No newline at end of file + let local = HtmlString $"""""" + + /// script tag to load the package-provided version of the htmax script + let localMax = HtmlString $"""""" diff --git a/src/Htmx/README.md b/src/Htmx/README.md index e696684..b10d3e9 100644 --- a/src/Htmx/README.md +++ b/src/Htmx/README.md @@ -2,7 +2,7 @@ This package enables server-side support for [htmx](https://htmx.org) within [Giraffe](https://giraffe.wiki) and ASP.NET's `HttpContext`. -**htmx version: 4.0.0-beta4** +**htmx version: 4.0.0-beta5** _Upgrading from v2.x: the [migration guide](https://four.htmx.org/docs/get-started/migration) lists changes for v4. For this package, the `HX-Trigger` and `HX-Trigger-Name` headers are marked obsolete. They are replaced by `HX-Source`, which provides the triggering tag name and `id` attribute. The `HX-Prompt` header has also been marked as obsolete, as the `hx-prompt` attribute which generated its content has been removed._ @@ -36,7 +36,7 @@ let myHandler : HttpHander = The `HxSwap` module has constants to use for the `HX-Reswap` header. These may be extended with settle, show, and other qualifiers; see the htmx documentation for the `hx-swap` attribute for more information. -To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`. +To load the package-provided htmx library without using Giraffe.ViewEngine, use `HtmxScript.local`; to load the htmax bundle, use `HtmxScript.localMax`. ### Learn diff --git a/src/Tests/Common.fs b/src/Tests/Common.fs index a760e01..0942e42 100644 --- a/src/Tests/Common.fs +++ b/src/Tests/Common.fs @@ -6,9 +6,25 @@ open Giraffe.Htmx /// Test to ensure the version was updated let version = test "HtmxVersion is correct" { - Expect.equal HtmxVersion "4.0.0-beta4" "htmx version incorrect" + Expect.equal HtmxVersion "4.0.0-beta5" "htmx version incorrect" } +let staticAssetUrl = + testList "StaticAssetUrl" [ + test "htmx is correct" { + Expect.equal + StaticAssetUrl.htmx + $"/_content/Giraffe.Htmx.Common/htmx.min.js?ver={HtmxVersion}" + "Static htmx URL incorrect" + } + test "htmax is correct" { + Expect.equal + StaticAssetUrl.htmax + $"/_content/Giraffe.Htmx.Common/htmax.min.js?ver={HtmxVersion}" + "Static htmx URL incorrect" + } + ] + /// Tests for the HxSwap module let swap = testList "HxSwap" [ @@ -69,4 +85,4 @@ let swap = ] /// All tests for this module -let allTests = testList "Htmx.Common" [ version; swap ] +let allTests = testList "Htmx.Common" [ version; staticAssetUrl; swap ] diff --git a/src/Tests/Htmx.fs b/src/Tests/Htmx.fs index abbd0ab..4c29c5c 100644 --- a/src/Tests/Htmx.fs +++ b/src/Tests/Htmx.fs @@ -97,6 +97,21 @@ let dictExtensions = Expect.isFalse ctx.Request.Headers.HxPreloaded.Value "The header should have been false" } ] + testList "HxPrompt" [ + test "succeeds when the header is not present" { + let ctx = Substitute.For() + ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore + Expect.isNone ctx.Request.Headers.HxPrompt "There should not have been a header returned" + } + test "succeeds when the header is present" { + let ctx = Substitute.For() + let dic = HeaderDictionary() + dic.Add("HX-Prompt", "of course") + ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore + Expect.isSome ctx.Request.Headers.HxPrompt "There should be a header present" + Expect.equal ctx.Request.Headers.HxPrompt.Value "of course" "The header value was incorrect" + } + ] testList "HxRequest" [ test "succeeds when the header is not present" { let ctx = Substitute.For() @@ -216,7 +231,7 @@ let dictExtensions = ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" - Expect.equal (fst hdr.Value) "div" "The target tag was incorrect" + Expect.equal (fst hdr.Value) "div" "The target tag was incorrect" Expect.isSome (snd hdr.Value) "There should be a target ID present" Expect.equal (snd hdr.Value).Value "leItem" "The header value was incorrect" } @@ -227,7 +242,7 @@ let dictExtensions = ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" - Expect.equal (fst hdr.Value) "span" "The target tag was incorrect" + Expect.equal (fst hdr.Value) "span" "The target tag was incorrect" Expect.isNone (snd hdr.Value) "There should not be a target ID present" } test "succeeds when the header is present and ID is missing" { @@ -237,7 +252,7 @@ let dictExtensions = ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore let hdr = ctx.Request.Headers.HxTarget Expect.isSome hdr "There should be a header present" - Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect" + Expect.equal (fst hdr.Value) "aside" "The target tag was incorrect" Expect.isNone (snd hdr.Value) "There should not be a target ID present" } ] @@ -251,7 +266,7 @@ let reqExtensions = let ctx = Substitute.For() ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore Expect.isFalse ctx.Request.IsHtmx "The request should not be an htmx request" - } + } test "succeeds when request is from htmx" { let ctx = Substitute.For() let dic = HeaderDictionary() @@ -288,7 +303,7 @@ open System.Threading.Tasks /// Dummy "next" parameter to get the pipeline to execute/terminate let next (ctx: HttpContext) = Task.FromResult(Some ctx) - + /// Tests for the HttpHandler functions provided in the Handlers module let handlers = testList "HandlerTests" [ @@ -373,7 +388,7 @@ let handlers = let! _ = withHxReselect "#test" next ctx Expect.isTrue (dic.ContainsKey "HX-Reselect") "The HX-Reselect header should be present" Expect.equal dic["HX-Reselect"].[0] "#test" "The HX-Reselect value was incorrect" - } + } testTask "withHxReswap succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() @@ -381,7 +396,7 @@ let handlers = let! _ = withHxReswap HxSwap.BeforeEnd next ctx Expect.isTrue (dic.ContainsKey "HX-Reswap") "The HX-Reswap header should be present" Expect.equal dic["HX-Reswap"].[0] HxSwap.BeforeEnd "The HX-Reswap value was incorrect" - } + } testTask "withHxRetarget succeeds" { let ctx = Substitute.For() let dic = HeaderDictionary() @@ -418,26 +433,17 @@ let script = $"""""" "htmx script link is incorrect" } + test "localMax generates correct link" { + Expect.equal + (string HtmxScript.localMax) + $"""""" + "htmx script link is incorrect" + } ] #nowarn 44 // Obsolete items still have tests let dictExtensionsObs = testList "IHeaderDictionaryExtensions (Obsolete)" [ - testList "HxPrompt" [ - test "succeeds when the header is not present" { - let ctx = Substitute.For() - ctx.Request.Headers.ReturnsForAnyArgs(HeaderDictionary()) |> ignore - Expect.isNone ctx.Request.Headers.HxPrompt "There should not have been a header returned" - } - test "succeeds when the header is present" { - let ctx = Substitute.For() - let dic = HeaderDictionary() - dic.Add("HX-Prompt", "of course") - ctx.Request.Headers.ReturnsForAnyArgs dic |> ignore - Expect.isSome ctx.Request.Headers.HxPrompt "There should be a header present" - Expect.equal ctx.Request.Headers.HxPrompt.Value "of course" "The header value was incorrect" - } - ] testList "HxTrigger" [ test "succeeds when the header is not present" { let ctx = Substitute.For() diff --git a/src/Tests/ViewEngine.fs b/src/Tests/ViewEngine.fs index cb5f789..af633a9 100644 --- a/src/Tests/ViewEngine.fs +++ b/src/Tests/ViewEngine.fs @@ -395,7 +395,7 @@ let hxTrigger = testList "FromDocument" [ test "succeeds when it is the first modifier" { Expect.equal (HxTrigger.FromDocument "") "from:document" "FromDocument modifier incorrect" - } + } test "succeeds when it is not the first modifier" { Expect.equal (HxTrigger.FromDocument "click") "click from:document" "FromDocument modifier incorrect" } @@ -500,7 +500,7 @@ let hxVals = ] ] -/// Pipe-able assertion for a rendered node +/// Pipe-able assertion for a rendered node let shouldRender expected node = Expect.equal (RenderView.AsString.htmlNode node) expected "Rendered HTML incorrect" @@ -569,6 +569,10 @@ let attributes = test "_hxPreserve succeeds" { img [ _hxPreserve ] |> shouldRender """""" } + test "_hxPrompt succeeds" { + strong [ _hxPrompt "Who goes there?" ] [] + |> shouldRender """""" + } test "_hxPushUrl succeeds" { dl [ _hxPushUrl "/a-b-c" ] [] |> shouldRender """
""" } @@ -677,14 +681,14 @@ let script = let html = RenderView.AsString.htmlNode Script.cdnMinified Expect.equal html - $"""""" + $"""""" "CDN minified script tag is incorrect" } test "cdnUnminified succeeds" { let html = RenderView.AsString.htmlNode Script.cdnUnminified Expect.equal html - $"""""" + $"""""" "CDN unminified script tag is incorrect" } testList "Max" [ @@ -699,14 +703,14 @@ let script = let html = RenderView.AsString.htmlNode Script.Max.cdnMinified Expect.equal html - $"""""" + $"""""" "CDN minified script tag is incorrect" } test "cdnMaxUnminified succeeds" { let html = RenderView.AsString.htmlNode Script.Max.cdnUnminified Expect.equal html - $"""""" + $"""""" "CDN unminified script tag is incorrect" } ] @@ -717,11 +721,11 @@ open System.Text /// Tests for the RenderFragment module let renderFragment = testList "RenderFragment" [ - + /// Validate that the two object references are the same object let isSame obj1 obj2 message = Expect.isTrue (obj.ReferenceEquals(obj1, obj2)) message - + testList "findIdNode" [ test "fails with a Text node" { Expect.isNone (RenderFragment.findIdNode "blue" (Text "")) "There should not have been a node found" @@ -787,7 +791,7 @@ let renderFragment = ] ] testList "AsBytes" [ - + /// Alias for UTF-8 encoding let utf8 = Encoding.UTF8 @@ -1314,10 +1318,6 @@ let attributesObs = test "_hxParams succeeds" { br [ _hxParams "[p1,p2]" ] |> shouldRender """
""" } - test "_hxPrompt succeeds" { - strong [ _hxPrompt "Who goes there?" ] [] - |> shouldRender """""" - } test "_hxRequest succeeds" { u [ _hxRequest "noHeaders" ] [] |> shouldRender """""" } diff --git a/src/ViewEngine.Htmx/Htmx.fs b/src/ViewEngine.Htmx/Htmx.fs index 0d8e03c..ccbba1d 100644 --- a/src/ViewEngine.Htmx/Htmx.fs +++ b/src/ViewEngine.Htmx/Htmx.fs @@ -5,9 +5,9 @@ module Giraffe.ViewEngine.Htmx /// Documentation [] module HxConfig = - + open Giraffe.Htmx.Common - + /// Configure the request with various options /// The options to configure /// A string with the configured options @@ -15,19 +15,19 @@ module HxConfig = opts |> String.concat ", " |> sprintf "{ %s }" - + /// Set a timeout (in milliseconds) /// The milliseconds for the request timeout /// A string with the configured request timeout let Timeout (ms: int) = $"\"timeout\": {ms}" - + /// Include or exclude credentials from the request /// true if credentials should be sent, false if not /// A string with the configured credential options let Credentials send = (toLowerBool >> sprintf "\"credentials\": %s") send - + /// Exclude or include headers from the request /// /// true if no headers should be sent; false if headers should be sent @@ -40,11 +40,11 @@ module HxConfig = /// Valid values for the hx-encoding attribute [] module HxEncoding = - + /// A standard HTTP form [] let Form = "application/x-www-form-urlencoded" - + /// A multipart form (used for file uploads) [] let MultipartForm = "multipart/form-data" @@ -55,200 +55,200 @@ module HxEncoding = /// Documentation [] type HxEvent = - + /// Send this event to an element to abort a request | Abort - + /// Triggered after htmx disables an element or removes it from the DOM | AfterCleanup - + /// Triggered after content is saved to the history cache | AfterHistoryUpdate - + /// Triggered after a request has initialized | AfterInit - + /// Triggered after an AJAX request has completed processing a successful response | [] AfterOnLoad - + /// Triggered after htmx has initialized a DOM node or subtree | AfterProcess - + /// Triggered after htmx has initialized a node | [] AfterProcessNode - + /// Triggered after new content is saved to the history cache | AfterPushIntoHistory - + /// Triggered after updated content is saved to the history cache | AfterReplaceIntoHistory - + /// Triggered after an HTTP request has completed successfully | AfterRequest - + /// Triggered after the DOM has settled | AfterSettle - + /// Triggered after a Server Sent Events (SSE) message is read | [] AfterSseMessage - + /// Triggered after a Server Sent Events (SSE) stream is closed | [] AfterSseStream - + /// Triggered after new content has been swapped in | AfterSwap - + /// Triggered after a CSS view transition completes | AfterViewTransition - + /// Triggered before htmx disables an element or removes it from the DOM | BeforeCleanup - + /// Triggered before htmx disables an element or removes it from the DOM | [] BeforeCleanupElement - + /// Triggered before content is saved to the history cache | [] BeforeHistorySave - + /// Triggered before content is saved to the history cache | BeforeHistoryUpdate - + /// Triggered before htmx initializes a node | BeforeInit - + /// Triggered before any response processing occurs | [] BeforeOnLoad - + /// Triggered before htmx begins processing a DOM node or subtree | BeforeProcess - + /// Triggered before htmx initializes a node | [] BeforeProcessNode - + /// Triggered before an HTTP request is made | BeforeRequest - + /// Triggered before an HTTP response is processed (cancelable) | BeforeResponse - + /// Triggered before a history restore request is made | BeforeRestoreHistory - + /// Triggered just before an ajax request is sent | [] BeforeSend - + /// Triggered before a Server Sent Events (SSE) message is read | [] BeforeSseMessage - + /// Triggered before a Server Sent Events (SSE) connection is reconnected | [] BeforeSseReconnect - + /// Triggered before a Server Sent Events (SSE) stream is opened | [] BeforeSseStream - + /// Triggered before a swap is done, allows you to configure the swap | BeforeSwap - + /// Triggered before a CSS view transition starts | BeforeViewTransition - + /// Triggered before the request, allows you to customize parameters, headers | ConfigRequest - + /// /// Triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request /// | Confirm - + /// Triggered when an error occurs | Error - + /// Triggered after an HTTP request is made, whether it was successful or not | FinallyRequest - + /// Triggered on an error during cache writing | [] HistoryCacheError - + /// Triggered on a cache miss in the history subsystem | [] HistoryCacheMiss - + /// Triggered on a unsuccessful remote retrieval | [] HistoryCacheMissError - + /// Triggered on a successful remote retrieval | [] HistoryCacheMissLoad - + /// Triggered when htmx handles a history restoration action | [] HistoryRestore - + /// Triggered when new content is added to the DOM | [] Load - + /// /// Triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined /// | [] NoSseSourceError - + /// Triggered when an exception occurs during the onLoad handling in htmx | [] OnLoadError - + /// Triggered after an out of band element as been swapped in | [] OobAfterSwap - + /// Triggered before an out of band element swap is done, allows you to configure the swap | [] OobBeforeSwap - + /// Triggered when an out of band element does not have a matching ID in the current DOM | [] OobErrorNoTarget - + /// Triggered after a prompt is shown | [] Prompt - + /// Triggered after an url is pushed into history | [] PushedIntoHistory - + /// Triggered when an HTTP response error (non-200 or 300 response code) occurs | ResponseError - + /// Triggered when a network error prevents an HTTP request from happening | [] SendError - + /// Triggered when an error occurs with a SSE source | [] SseError - + /// Triggered when an SSE source is opened | [] SseOpen - + /// Triggered when an error occurs during the swap phase | [] SwapError - + /// Triggered when an invalid target is specified | [] TargetError - + /// Triggered when a request timeout occurs | [] Timeout - + /// Triggered before an element is validated | [] ValidationValidate - + /// Triggered when an element fails validation | [] ValidationFailed - + /// Triggered when a request is halted due to validation errors | [] ValidationHalted - + /// Triggered when an ajax request aborts | [] XhrAbort - + /// Triggered when an ajax request ends | [] XhrLoadEnd - + /// Triggered when an ajax request starts | [] XhrLoadStart - + /// Triggered periodically during an ajax request that supports progress events | [] XhrProgress - + /// The htmx event name (fst) and kebab-case name (snd, for use with hx-on) static member private Values = Map [ Abort, ("abort", "abort") @@ -315,9 +315,9 @@ type HxEvent = XhrProgress, ("xhr:progress", "xhr:progress") ] - /// The htmx event name + /// The htmx event name override this.ToString() = fst HxEvent.Values[this] - + /// The hx-on variant of the htmx event name member this.ToHxOnString() = snd HxEvent.Values[this] #warn 44 // restore obsolete warning @@ -326,7 +326,7 @@ type HxEvent = /// Helper to create the hx-headers attribute [] module HxHeaders = - + /// Create headers from a list of key/value pairs let From = Giraffe.Htmx.Common.toJson @@ -336,21 +336,21 @@ module HxHeaders = [] [] module HxParams = - + /// Include all parameters [] let All = "*" - + /// Include no parameters [] let None = "none" - + /// Include the specified parameters /// One or more fields to include in the request /// The list of fields for the hx-params attribute value let With fields = match fields with [] -> "" | _ -> fields |> List.reduce (fun acc it -> $"{acc},{it}") - + /// Exclude the specified parameters /// One or more fields to exclude from the request /// The list of fields for the hx-params attribute value prefixed with "not" @@ -363,9 +363,9 @@ module HxParams = [] [] module HxRequest = - + open Giraffe.Htmx.Common - + /// Configure the request with various options /// The options to configure /// A string with the configured options @@ -373,19 +373,19 @@ module HxRequest = opts |> String.concat ", " |> sprintf "{ %s }" - + /// Set a timeout (in milliseconds) /// The milliseconds for the request timeout /// A string with the configured request timeout let Timeout (ms: int) = $"\"timeout\": {ms}" - + /// Include or exclude credentials from the request /// true if credentials should be sent, false if not /// A string with the configured credential options let Credentials send = (toLowerBool >> sprintf "\"credentials\": %s") send - + /// Exclude or include headers from the request /// /// true if no headers should be sent; false if headers should be sent @@ -399,35 +399,35 @@ module HxRequest = /// Documentation [] module HxSync = - + /// Drop (ignore) this request if a request is already in flight /// This is the default for hx-sync [] let Drop = "drop" - + /// /// Drop (ignore) this request if a request is already in flight, and if another request occurs while this one is in /// flight, abort this request /// [] let Abort = "abort" - + /// Abort any current in-flight request and replace it with this one [] let Replace = "replace" - + /// Place this request in an element-associated queue [] let Queue = "queue" - + /// Queue only the first request received while another request is in flight [] let QueueFirst = "queue first" - + /// Queue only the last request received while another request is in flight [] let QueueLast = "queue last" - + /// Queue all requests received while another request is in flight [] let QueueAll = "queue all" @@ -437,7 +437,7 @@ module HxSync = /// Documentation [] module HxTrigger = - + /// Append a filter to a trigger let private appendFilter filter (trigger : string) = match trigger.Contains "[" with @@ -445,125 +445,125 @@ module HxTrigger = let parts = trigger.Split ('[', ']') $"{parts[0]}[{parts[1]}&&{filter}]" | false -> $"{trigger}[{filter}]" - + /// Trigger the event on a click [] let Click = "click" - + /// Trigger the event on page load [] let Load = "load" - + /// Trigger the event when the item is visible [] let Revealed = "revealed" - + /// Trigger this event every [timing declaration] /// The duration on which this trigger should fire (e.g., "1s", "500ms") /// A trigger timing specification let Every duration = $"every %s{duration}" - + /// Helpers for defining filters module Filter = /// Only trigger the event if the ALT key is pressed let Alt = appendFilter "altKey" - + /// Only trigger the event if the CTRL key is pressed let Ctrl = appendFilter "ctrlKey" - + /// Only trigger the event if the SHIFT key is pressed let Shift = appendFilter "shiftKey" - + /// Only trigger the event if CTRL and ALT are pressed let CtrlAlt = Ctrl >> Alt - + /// Only trigger the event if CTRL and SHIFT are pressed let CtrlShift = Ctrl >> Shift - + /// Only trigger the event if CTRL, ALT, and SHIFT are pressed let CtrlAltShift = CtrlAlt >> Shift - + /// Only trigger the event if ALT and SHIFT are pressed let AltShift = Alt >> Shift - + /// Append a modifier to the current trigger let private appendModifier modifier current = if current = "" then modifier else $"{current} {modifier}" - + /// Only trigger once /// The action which should only be fired once /// A trigger spec to fire the given action once let Once action = appendModifier "once" action - + /// Trigger when changed /// The element from which the onchange event will be emitted /// A trigger spec to fire when the given element changes let Changed elt = appendModifier "changed" elt - + /// Delay execution; resets every time the event is seen /// The duration for the delay (e.g., "1s", "500ms") /// The action which should be fired after the given delay /// A trigger spec to fire the given action after the specified delay let Delay duration action = appendModifier $"delay:%s{duration}" action - + /// Throttle execution; ignore other events, fire when duration passes /// The duration for the throttling (e.g., "1s", "500ms") /// The action which should be fired after the given duration /// A trigger spec to fire the given action after the specified duration let Throttle duration action = appendModifier $"throttle:%s{duration}" action - + /// Trigger this event from a CSS selector /// A CSS selector to identify elements which may fire this trigger /// The action to be fired /// A trigger spec to fire from the given element(s) let From selector action = appendModifier $"from:%s{selector}" action - + /// Trigger this event from the document object /// The action to be fired /// A trigger spec to fire from the document element let FromDocument action = From "document" action - + /// Trigger this event from the window object /// The action to be fired /// A trigger spec to fire from the window object let FromWindow action = From "window" action - + /// Trigger this event from the closest parent CSS selector /// The CSS selector from which the action should be fired /// The action to be fired /// A trigger spec to fire from the closest element let FromClosest selector action = From $"closest %s{selector}" action - + /// Trigger this event from the closest child CSS selector /// The child CSS selector from which the action should be fired /// The action to be fired /// A trigger spec to fire from the closest child element let FromFind selector action = From $"find %s{selector}" action - + /// Target the given CSS selector with the results of this event /// The CSS selector to which the result of the action will be applied /// The action to be fired /// A trigger spec to target the given selector let Target selector action = appendModifier $"target:%s{selector}" action - + /// Prevent any further events from occurring after this one fires /// The action to be fired /// A trigger spec to fire the given action and prevent further events let Consume action = appendModifier "consume" action - + /// /// Configure queueing when events fire when others are in flight; if unspecified, the default is last /// @@ -576,25 +576,25 @@ module HxTrigger = let Queue how action = let qSpec = if how = "" then "" else $":{how}" appendModifier $"queue{qSpec}" action - + /// Queue the first event, discard all others (i.e., a FIFO queue of 1) /// The action to be fired /// A trigger spec to queue the given action let QueueFirst action = Queue "first" action - + /// Queue the last event; discards current when another is received (i.e., a LIFO queue of 1) /// The action to be fired /// A trigger spec to queue the given action let QueueLast action = Queue "last" action - + /// Queue all events; discard none /// The action to be fired /// A trigger spec to queue the given action let QueueAll action = Queue "all" action - + /// Queue no events; discard all /// The action to be fired /// A trigger spec to queue the given action @@ -606,7 +606,7 @@ module HxTrigger = /// Documentation [] module HxVals = - + /// Create values from a list of key/value pairs let From = Giraffe.Htmx.Common.toJson @@ -617,40 +617,40 @@ open Giraffe.Htmx /// Attributes and flags for htmx [] module HtmxAttrs = - + /// The URL which will receive the request (use with _hxMethod) /// The URL for the attribute /// Documentation let _hxAction url = attr "hx-action" url - + /// Progressively enhances anchors and forms to use AJAX requests /// Use _hxNoBoost to set to false /// Documentation let _hxBoost = attr "hx-boost" "true" - + /// Configure request behavior /// The configuration parameters to use for the request /// A configured hx-config attribute /// Documentation let _hxConfig config = attr "hx-config" (toJson config) - + /// Shows a confirm() dialog before issuing a request /// The prompt to present to the user when seeking their confirmation /// A configured hx-confirm attribute /// Documentation let _hxConfirm prompt = attr "hx-confirm" prompt - + /// Issues a DELETE to the specified URL /// The URL to which the DELETE request should be sent /// A configured hx-delete attribute /// Documentation let _hxDelete url = attr "hx-delete" url - + /// Specifies elements that should be disabled when an htmx request is in flight /// The element to disable when an htmx request is in flight /// A configured hx-disable attribute @@ -662,7 +662,7 @@ module HtmxAttrs = /// Documentation let _hxDisable elt = attr "hx-disable" elt - + /// Specifies elements that should be disabled when an htmx request is in flight /// The element to disable when an htmx request is in flight /// A configured hx-disabled-elt attribute @@ -678,7 +678,7 @@ module HtmxAttrs = [] let _hxDisinherit hxAttrs = attr "hx-disinherit" hxAttrs - + /// Changes the request encoding type /// The encoding type (use HxEncoding constants) /// A configured hx-encoding attribute @@ -686,7 +686,7 @@ module HtmxAttrs = /// Documentation let _hxEncoding enc = attr "hx-encoding" enc - + /// Extensions to use for this element /// A list of extensions to apply to this element /// A configured hx-ext attribute @@ -694,21 +694,21 @@ module HtmxAttrs = [] let _hxExt exts = attr "hx-ext" exts - + /// Issues a GET to the specified URL /// The URL to which the GET request should be sent /// A configured hx-get attribute /// Documentation let _hxGet url = attr "hx-get" url - + /// Adds to the headers that will be submitted with the request /// The headers to include with the request /// A configured hx-headers attribute /// Documentation let _hxHeaders hdrs = attr "hx-headers" hdrs - + /// /// Set to "false" to prevent pages with sensitive information from being stored in the history cache /// @@ -718,43 +718,43 @@ module HtmxAttrs = [] let _hxHistory shouldStore = attr "hx-history" (toLowerBool shouldStore) - + /// The element to snapshot and restore during history navigation /// Documentation [] let _hxHistoryElt = flag "hx-history-elt" - + /// Disables htmx processing for the given node and any children nodes /// Documentation let _hxIgnore = flag "hx-ignore" - + /// Includes additional data in AJAX requests /// The specification of what should be included in the request /// A configured hx-include attribute /// Documentation let _hxInclude spec = attr "hx-include" spec - + /// The element to put the htmx-request class on during the AJAX request /// The selector for the indicator element /// A configured hx-indicator attribute /// Documentation let _hxIndicator selector = attr "hx-indicator" selector - + /// The HTTP method to use for the request(use with _hxAction) /// The method to use /// Documentation let _hxMethod (method: HttpMethod) = attr "hx-method" (method.Method.ToLowerInvariant()) - + /// Overrides a previous hx-boost (hx-boost="false") /// Documentation let _hxNoBoost = attr "hx-boost" "false" - + /// Attach an event handler for DOM events /// The name of the event /// The script to be executed when the event occurs @@ -781,35 +781,39 @@ module HtmxAttrs = [] let _hxParams toInclude = attr "hx-params" toInclude - + /// Issues a PATCH to the specified URL /// The URL to which the request should be directed /// A configured hx-patch attribute /// Documentation let _hxPatch url = attr "hx-patch" url - + /// Issues a POST to the specified URL /// The URL to which the request should be directed /// A configured hx-post attribute /// Documentation let _hxPost url = attr "hx-post" url - + /// Preserves an element between requests /// Documentation let _hxPreserve = flag "hx-preserve" - + /// Shows a prompt() dialog before submitting a request /// The text for the prompt /// A configured hx-prompt attribute - /// The value provided will be in the HX-Prompt request header + /// + ///
    + ///
  • NEW IN v4: This functionality is dependent on the hx-prompt extension being loaded
  • + ///
  • The value provided will be in the HX-Prompt request header
  • + ///
+ ///
/// Documentation - [] let _hxPrompt text = attr "hx-prompt" text - + /// Pushes the URL into the location bar, creating a new history entry /// ///
    @@ -822,14 +826,14 @@ module HtmxAttrs = /// Documentation let _hxPushUrl spec = attr "hx-push-url" spec - + /// Issues a PUT to the specified URL /// The URL to which the request should be directed /// A configured hx-put attribute /// Documentation let _hxPut url = attr "hx-put" url - + /// Replaces the current URL in the browser's history stack /// ///
      @@ -842,7 +846,7 @@ module HtmxAttrs = /// Documentation let _hxReplaceUrl spec = attr "hx-replace-url" spec - + /// Configures various aspects of the request /// The configuration spec (use HxRequest.Configure to create value) /// A configured hx-request attribute @@ -851,21 +855,21 @@ module HtmxAttrs = [] let _hxRequest spec = attr "hx-request" spec - + /// Selects a subset of the server response to process /// A CSS selector for the content to be selected /// A configured hx-select attribute /// Documentation let _hxSelect selector = attr "hx-select" selector - + /// Selects a subset of an out-of-band server response /// One or more comma-delimited CSS selectors for the content to be selected /// A configured hx-select-oob attribute /// Documentation let _hxSelectOob selectors = attr "hx-select-oob" selectors - + /// Define a response to an HTTP status code or code range /// /// The code or code range; for a code, pass the full code as a string (ex. 404), and for a range, pass a @@ -882,7 +886,7 @@ module HtmxAttrs = /// updates with the current URL, and a string URL will be pushed into history as-is ///
    • replace: and true, false, or a URL; same as push:, but replaces in history /// instead
    • - ///
    • transition: and true or false; whether to use view transitions
    • + ///
    • transition: and true or false; whether to use view transitions
    • ///
    /// If only the type of swap is being changed, pass an empty string to this parameter. /// @@ -890,7 +894,7 @@ module HtmxAttrs = let _hxStatus code swap action = let range = $"%s{code}xx".Substring(0, 3) attr $"hx-status:{range}" ($"swap:%s{swap} %s{action}".Trim()) - + /// /// Controls how the response content is swapped into the DOM (e.g. outerHTML or beforeEnd) /// @@ -899,7 +903,7 @@ module HtmxAttrs = /// Documentation let _hxSwap swap = attr "hx-swap" swap - + /// /// Controls how the response content is swapped into the DOM (e.g. outerHTML or beforeEnd), enabling /// CSS transitions @@ -909,7 +913,7 @@ module HtmxAttrs = /// Documentation let _hxSwapWithTransition swap = _hxSwap $"%s{swap} transition:true" - + /// /// Marks content in a response as being "Out of Band", i.e. swapped somewhere other than the target /// @@ -924,7 +928,7 @@ module HtmxAttrs = /// Documentation let _hxSwapOob swap = attr "hx-swap-oob" swap - + /// Synchronize events based on another element /// A CSS selector for the element with which this one should sync /// The request synchronization action to perform (use HxSync values) @@ -933,14 +937,14 @@ module HtmxAttrs = /// Documentation let _hxSync selector action = attr "hx-sync" $"%s{selector}:%s{action}" - + /// Specifies the target element to be swapped /// A CSS selector or relative reference (or both) to identify the target /// A configured hx-target attribute /// Documentation let _hxTarget selector = attr "hx-target" selector - + /// Specifies the event that triggers the request /// The trigger specification (use HxTrigger to create) /// A configured hx-trigger attribute @@ -948,12 +952,12 @@ module HtmxAttrs = /// Documentation let _hxTrigger spec = attr "hx-trigger" spec - + /// Validate an input element (uses HTML5 validation API) /// Documentation let _hxValidate = flag "hx-validate" - + /// Adds to the parameters that will be submitted with the request /// The values for the parameters (use HxVals.From to create) /// A configured hx-vals attribute @@ -961,7 +965,7 @@ module HtmxAttrs = /// Documentation let _hxVals values = attr "hx-vals" values - + /// The URL of the SSE server /// The URL from which events will be received /// A configured sse-connect attribute @@ -980,7 +984,7 @@ module HtmxAttrs = /// Modifiers for htmx attributes [] module HxModifiers = - + /// Append this value to any inherited value of the attribute /// The attribute to which :append should be applied /// The given attribute with :append applied @@ -988,7 +992,7 @@ module HxModifiers = match attrib with | KeyValue (name, value) -> attr $"{name}:append" value | Boolean name -> flag $"{name}:append" - + /// Explicitly propagate inheritance for the value for this attribute /// The attribute whose value should be inherited /// The given attribute with :inherited applied @@ -1001,7 +1005,7 @@ module HxModifiers = /// htmx-specific HTML tags [] module HxTags = - + /// An hx-partial tag let hxPartial = tag "hx-partial" @@ -1012,40 +1016,40 @@ module Script = open System /// Script tag to load the package-provided version of htmx - let local = script [ _src htmxLocalScript ] [] - + let local = script [ _src StaticAssetUrl.htmx ] [] + /// Script tag to load the minified version from jsdelivr.net /// Ensure cdn.jsdelivr.net is in your CSP script-src list (if applicable) let cdnMinified = script [ _src $"https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.min.js" - _integrity "sha384-aWZK1NtOs/aWb/+YZdTM8q2JkWEshlMc9mgZ189numT9bwFhyAyYEoO4nO/2dTXt" + _integrity "sha384-5dnhUXCt1hXGvYrjAnKwgNX3I8xtIJiW6eIHIbeo7oWyXv2XpWYC/rl+ZiWfuYO5" _crossorigin "anonymous" ] [] /// Script tag to load the unminified version from jsdelivr.net /// Ensure cdn.jsdelivr.net is in your CSP script-src list (if applicable) let cdnUnminified = script [ _src $"https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmx.js" - _integrity "sha384-OFLRIZpuqI2wwFozxvDGcuF3TQ36ySMgp44WEthOiR4wFzRkhZbK72HFaBo2C3cx" + _integrity "sha384-RZoQSZlu2BAuZMuM5lTKAWXXSKC+7X6eVzP1pwkUBcyfPmOswexqVOsUqQMKbAFA" _crossorigin "anonymous" ] [] - + /// Script tags to load the htmax bundle module Max = - + /// Script tag to load the package-provided version of the htmx-plus-extensions bundle - let local = script [ _src htmaxLocalScript ] [] - + let local = script [ _src StaticAssetUrl.htmax ] [] + /// Script tag to load the minified htmx-plus-extensions bundle from jsdelivr.net /// Ensure cdn.jsdelivr.net is in your CSP script-src list (if applicable) let cdnMinified = script [ _src $"https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.min.js" - _integrity "sha384-Qoqie5IRtOE79SDFFRSb/yKi+pkzpSnfjgwr1KksyP14OaHkLHar0KrLVxUwlsJF" + _integrity "sha384-VVbrNR6a+H8puV17ZlJ8aUUMTgbcDiqM1vlLYEmaaU4oFANllvTK2pLynNonElP+" _crossorigin "anonymous" ] [] /// Script tag to load the unminified htmx-plus-extensions bundle from jsdelivr.net /// Ensure cdn.jsdelivr.net is in your CSP script-src list (if applicable) let cdnUnminified = script [ _src $"https://cdn.jsdelivr.net/npm/htmx.org@{HtmxVersion}/dist/htmax.js" - _integrity "sha384-gGi3Urue6ZkE4NrJCmXWIZkfNkrt1IrdP3fr0kb/v06GWg3V1RnD9Pg/Ul3qhtAK" + _integrity "sha384-kjhVuvnX3/TsR1qH4JaIcHR6muh/WLMU5CTQRacCQZERzQlP4/r9p/TK7ucFwqvV" _crossorigin "anonymous" ] [] /// Script tag to load the minified version from jsdelivr.net @@ -1060,7 +1064,7 @@ module Script = /// Functions to extract and render an HTML fragment from a document [] module RenderFragment = - + /// Does this element have an ID matching the requested ID name? let private isIdElement nodeId (elt: XmlElement) = snd elt @@ -1072,7 +1076,7 @@ module RenderFragment = /// Generate a message if the requested ID node is not found let private nodeNotFound (nodeId: string) = $"– ID {nodeId} not found –" - + /// Find the node with the named ID /// The id attribute to find /// The node tree to search @@ -1083,7 +1087,7 @@ module RenderFragment = | VoidElement elt -> if isIdElement nodeId elt then Some node else None | ParentNode (elt, children) -> if isIdElement nodeId elt then Some node else children |> List.tryPick (findIdNode nodeId) - + /// Functions to render a fragment as a string [] module AsString = diff --git a/src/ViewEngine.Htmx/README.md b/src/ViewEngine.Htmx/README.md index f1bcf4d..ae64d82 100644 --- a/src/ViewEngine.Htmx/README.md +++ b/src/ViewEngine.Htmx/README.md @@ -2,7 +2,7 @@ This package enables [htmx](https://htmx.org) support within the [Giraffe](https://giraffe.wiki) view engine. -**htmx version: 4.0.0-beta4** +**htmx version: 4.0.0-beta5** _Upgrading from v2.x: see [the migration guide](https://four.htmx.org/docs/get-started/migration) for changes, which are plentiful. htmx switches from `XMLHTTPRequest` to `fetch`, and many changes are related to the new event cycle._