├── .envrc ├── .gitignore ├── README.md ├── cabal.project ├── flake.lock ├── flake.nix ├── fourmolu.yaml ├── htmx-lucid ├── README.md ├── hie.yaml ├── htmx-lucid.cabal └── src │ └── Htmx │ └── Lucid │ ├── Core.hs │ ├── Extension │ ├── IncludeVals.hs │ └── ServerSentEvents.hs │ ├── Extra.hs │ └── Head.hs ├── htmx-servant ├── README.md ├── hie.yaml ├── htmx-servant.cabal └── src │ └── Htmx │ └── Servant │ ├── Lucid.hs │ ├── Request.hs │ └── Response.hs ├── htmx ├── README.md ├── hie.yaml ├── htmx.cabal └── src │ └── Htmx │ ├── Event.hs │ ├── Extension.hs │ ├── Render.hs │ └── Swap.hs └── scripts ├── fmt.sh └── hkg.sh /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | source .env 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | dist/ 3 | dist-newstyle/ 4 | *~ 5 | .direnv/ 6 | .env 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # htmx for Haskell 2 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: 2 | htmx/htmx.cabal 3 | htmx-lucid/htmx-lucid.cabal 4 | htmx-servant/htmx-servant.cabal 5 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1717868076, 6 | "narHash": "sha256-c83Y9t815Wa34khrux81j8K8ET94ESmCuwORSKm2bQY=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "cd18e2ae9ab8e2a0a8d715b60c91b54c0ac35ff9", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "htmx"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 6 | }; 7 | 8 | outputs = { 9 | self, 10 | nixpkgs, 11 | }: let 12 | forAllSystems = function: 13 | nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"] (system: 14 | function rec { 15 | inherit system; 16 | compilerVersion = "ghc964"; 17 | pkgs = nixpkgs.legacyPackages.${system}; 18 | hsPkgs = pkgs.haskell.packages.${compilerVersion}.override { 19 | overrides = hfinal: hprev: 20 | with pkgs.haskell.lib; { 21 | # Internal Packages 22 | htmx = hfinal.callCabal2nix "htmx" ./htmx {}; 23 | htmx-lucid = hfinal.callCabal2nix "htmx-lucid" ./htmx-lucid {}; 24 | htmx-servant = hfinal.callCabal2nix "htmx-servant" ./htmx-servant {}; 25 | }; 26 | }; 27 | }); 28 | in { 29 | formatter = forAllSystems ({pkgs, ...}: pkgs.alejandra); 30 | 31 | # nix build 32 | packages = forAllSystems ( 33 | {hsPkgs, ...}: { 34 | htmx = hsPkgs.htmx; 35 | default = hsPkgs.htmx; 36 | } 37 | ); 38 | 39 | checks = {}; 40 | 41 | # nix develop 42 | devShells = forAllSystems ({ 43 | system, 44 | hsPkgs, 45 | pkgs, 46 | ... 47 | }: let 48 | hkg = pkgs.writeShellScriptBin "hkg" (builtins.readFile ./scripts/hkg.sh); 49 | packages = ["htmx" "htmx-lucid" "htmx-servant"]; 50 | format = pkgs.writeShellScriptBin "format" ( 51 | pkgs.lib.strings.concatMapStringsSep "\n" ( 52 | package: '' 53 | fourmolu -i ./${package} 54 | cabal-fmt -i ./${package}/${package}.cabal 55 | '' 56 | ) 57 | packages 58 | ); 59 | in { 60 | default = hsPkgs.shellFor { 61 | name = "htmx"; 62 | shellHook = '' 63 | export LOCALE_ARCHIVE="${pkgs.glibcLocales}/lib/locale/locale-archive" 64 | export LC_ALL=C.UTF-8 65 | ''; 66 | packages = p: [ 67 | p.htmx 68 | p.htmx-lucid 69 | p.htmx-servant 70 | ]; 71 | buildInputs = with pkgs; [ 72 | hsPkgs.haskell-language-server 73 | cabal2nix 74 | haskellPackages.ghcid 75 | haskellPackages.fourmolu 76 | haskellPackages.cabal-fmt 77 | haskellPackages.cabal-install 78 | hlint 79 | hkg 80 | format 81 | ]; 82 | }; 83 | }); 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /fourmolu.yaml: -------------------------------------------------------------------------------- 1 | haddock-style: single-line 2 | haddock-style-module: multi-line 3 | column-limit: 80 4 | -------------------------------------------------------------------------------- /htmx-lucid/README.md: -------------------------------------------------------------------------------- 1 | # htmx-lucid 2 | -------------------------------------------------------------------------------- /htmx-lucid/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./src" 4 | component: "lib:htmx-lucid" 5 | -------------------------------------------------------------------------------- /htmx-lucid/htmx-lucid.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.6 2 | name: htmx-lucid 3 | version: 0.2.0.1 4 | synopsis: Use htmx with lucid 5 | description: 6 | Please see the README on GitHub at 7 | 8 | license: MIT 9 | category: Web, HTML 10 | author: Jonathan Lorimer 11 | maintainer: jonathanlorimer@pm.me 12 | build-type: Simple 13 | extra-source-files: README.md 14 | 15 | source-repository head 16 | type: git 17 | location: https://github.com/JonathanLorimer/htmx 18 | 19 | common def-exts 20 | default-extensions: 21 | LambdaCase 22 | OverloadedStrings 23 | 24 | library 25 | import: def-exts 26 | 27 | -- cabal-fmt: expand src 28 | exposed-modules: 29 | Htmx.Lucid.Core 30 | Htmx.Lucid.Extension.IncludeVals 31 | Htmx.Lucid.Extension.ServerSentEvents 32 | Htmx.Lucid.Extra 33 | Htmx.Lucid.Head 34 | 35 | hs-source-dirs: src 36 | build-depends: 37 | , base >=4.7 && <5 38 | , htmx == 0.1.0.2 39 | , lucid2 >=0.0.20221012 40 | , text >=2 && <3 41 | 42 | default-language: Haskell2010 43 | -------------------------------------------------------------------------------- /htmx-lucid/src/Htmx/Lucid/Core.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Lucid.Core 3 | Description : Provides core htmx tags 4 | 5 | This module defines the "core" 11 HTMX attributes 6 | 7 | -} 8 | module Htmx.Lucid.Core where 9 | 10 | import Data.Text (Text, pack) 11 | import Htmx.Event 12 | import Htmx.Render 13 | import Htmx.Swap (Swap) 14 | import Lucid (Html, HtmlT, script_, src_) 15 | import Lucid.Base (Attributes, makeAttributes) 16 | 17 | -- | 18 | -- issues a GET to the specified URL 19 | hxGet_ :: Text -> Attributes 20 | hxGet_ = makeAttributes "hx-get" 21 | 22 | -- | 23 | -- issues a POST to the specified URL 24 | hxPost_ :: Text -> Attributes 25 | hxPost_ = makeAttributes "hx-post" 26 | 27 | -- | 28 | -- push a URL into the browser location bar to create history 29 | hxPushUrl_ :: Text -> Attributes 30 | hxPushUrl_ = makeAttributes "hx-push-url" 31 | 32 | -- | 33 | -- select content to swap in from a response 34 | hxSelect_ :: Text -> Attributes 35 | hxSelect_ = makeAttributes "hx-select" 36 | 37 | -- | 38 | -- select content to swap in from a response, somewhere other than the target 39 | -- (out of band) 40 | hxSelectOob_ :: Text -> Attributes 41 | hxSelectOob_ = makeAttributes "hx-select-oob" 42 | 43 | -- | 44 | -- controls how content will swap in (outerHTML, beforeend, afterend, …) 45 | hxSwap_ :: Text -> Attributes 46 | hxSwap_ = makeAttributes "hx-swap" 47 | 48 | -- | Like 'hxSwap_' but takes a strongly typed swap style. 49 | -- This doesn't allow [modifiers](https://htmx.org/attributes/hx-swap/#modifiers) to be applied. 50 | hxSwapS_ :: Swap -> Attributes 51 | hxSwapS_ = makeAttributes "hx-swap" . render 52 | 53 | -- | 54 | -- mark element to swap in from a response (out of band) 55 | hxSwapOob_ :: Text -> Attributes 56 | hxSwapOob_ = makeAttributes "hx-swap-oob" 57 | 58 | -- | 59 | -- specifies the target element to be swapped 60 | hxTarget_ :: Text -> Attributes 61 | hxTarget_ = makeAttributes "hx-target" 62 | 63 | -- | 64 | -- specifies the event that triggers the request 65 | hxTrigger_ :: Text -> Attributes 66 | hxTrigger_ = makeAttributes "hx-trigger" 67 | 68 | -- | 69 | -- add values to submit with the request (JSON format) 70 | hxVals_ :: Text -> Attributes 71 | hxVals_ = makeAttributes "hx-vals" 72 | 73 | -- | Indicates whether you are handling an arbitrary DOM event 74 | -- or on of the bespoke 'HtmxEvent' (defined by the htmx js bundle) 75 | data OnEvent = DomOnEvent Text | HtmxOnEvent HtmxEvent 76 | 77 | -- | 78 | -- handle events with inline scripts on elements 79 | hxOn_ :: OnEvent -> Text -> Attributes 80 | hxOn_ = \case 81 | DomOnEvent event -> makeAttributes $ "hx-on:" <> event 82 | HtmxOnEvent htmxEvent -> makeAttributes $ "hx-on::" <> render htmxEvent 83 | -------------------------------------------------------------------------------- /htmx-lucid/src/Htmx/Lucid/Extension/IncludeVals.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Lucid.Extension.IncludeVals 3 | Description : Attribute for adding values to a request 4 | 5 | This module defines an attribute that allows you to include additional values in a request 6 | 7 | -} 8 | module Htmx.Lucid.Extension.IncludeVals where 9 | 10 | import Data.Text (Text) 11 | import Lucid.Base (makeAttributes, Attributes) 12 | 13 | -- | 14 | -- The value of this attribute is one or more name/value pairs, which will be evaluated as the fields in a javascript object literal. 15 | -- i.e. "included:true, computed: computeValue()" 16 | includeVals_ :: Text -> Attributes 17 | includeVals_ = makeAttributes "include-vals" 18 | -------------------------------------------------------------------------------- /htmx-lucid/src/Htmx/Lucid/Extension/ServerSentEvents.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Lucid.Extension.ServerSentEvents 3 | Description : Attribute for connecting to an sse stream 4 | 5 | The Server Sent Events extension connects to an EventSource directly from HTML. 6 | It manages the connections to your web server, listens for server events, and 7 | then swaps their contents into your htmx webpage in real-time. 8 | 9 | -} 10 | module Htmx.Lucid.Extension.ServerSentEvents where 11 | 12 | import Data.Text (Text) 13 | import Lucid.Base (makeAttributes, Attributes) 14 | 15 | -- | 16 | -- Provide the url to connect to, in order to establish an SSE channel. 17 | sseConnect_ :: Text -> Attributes 18 | sseConnect_ = makeAttributes "sse-connect" 19 | 20 | -- | A stronger type for the different kinds of events permitted by 'sseSwap_' 21 | data SseEventKind = Named Text | UnNamed 22 | 23 | -- | 24 | -- event name to listen for in an SSE message, the contents of the message will 25 | -- be swapped into the tag this attribute is on. For named events the message structure is as follows: 26 | -- 27 | -- @ 28 | -- event: EventName 29 | -- data:
Content to swap into your HTML page.
sseSwap_ :: Text -> Attributes 30 | -- @ 31 | -- 32 | -- For unnamed events the message structure should look like this: 33 | -- 34 | -- @ 35 | -- data:
Content to swap into your HTML page.
sseSwap_ :: Text -> Attributes 36 | -- @ 37 | sseSwap_ :: SseEventKind -> Attributes 38 | sseSwap_ = \case 39 | Named eventName -> makeAttributes "sse-swap" eventName 40 | UnNamed -> makeAttributes "sse-swap" "message" 41 | -------------------------------------------------------------------------------- /htmx-lucid/src/Htmx/Lucid/Extra.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Lucid.Extra 3 | Description : Provides extra htmx tags 4 | 5 | This module defines additional attributes that can be used to get additional 6 | behaviour 7 | 8 | -} 9 | module Htmx.Lucid.Extra where 10 | 11 | import Data.Foldable 12 | import Data.List (intersperse) 13 | import Data.Text (Text, pack) 14 | import Htmx.Extension 15 | import Htmx.Render 16 | import Lucid (Html, HtmlT, script_, src_) 17 | import Lucid.Base (Attributes, makeAttributes) 18 | 19 | -- | 20 | -- add progressive enhancement for links and forms 21 | hxBoost_ :: Text -> Attributes 22 | hxBoost_ = makeAttributes "hx-boost" 23 | 24 | -- | 25 | -- shows a confirm() dialog before issuing a request 26 | hxConfirm_ :: Text -> Attributes 27 | hxConfirm_ = makeAttributes "hx-confirm" 28 | 29 | -- | 30 | -- issues a DELETE to the specified URL 31 | hxDelete_ :: Text -> Attributes 32 | hxDelete_ = makeAttributes "hx-delete" 33 | 34 | -- | 35 | -- disables htmx processing for the given node and any children nodes 36 | hxDisable_ :: Attributes 37 | hxDisable_ = makeAttributes "hx-disable" mempty 38 | 39 | -- | 40 | -- adds the disabled attribute to the specified elements while a request is in flight 41 | hxDisabledElt_ :: Text -> Attributes 42 | hxDisabledElt_ = makeAttributes "hx-disabled-elt" 43 | 44 | -- | 45 | -- control and disable automatic attribute inheritance for child nodes 46 | hxDisinherit_ :: Text -> Attributes 47 | hxDisinherit_ = makeAttributes "hx-disinherit" 48 | 49 | -- | 50 | -- changes the request encoding type 51 | hxEncoding_ :: Text -> Attributes 52 | hxEncoding_ = makeAttributes "hx-encoding" 53 | 54 | -- | 55 | -- extensions to use for this element 56 | hxExt_ :: Text -> Attributes 57 | hxExt_ = makeAttributes "hx-ext" 58 | 59 | -- | A typesafe version of 'hxExt_' that works with the "included" extensions 60 | -- that the htmx codebase is tested against 61 | hxExtension_ :: HtmxExtension -> Attributes 62 | hxExtension_ = makeAttributes "hx-ext" . render 63 | 64 | -- | Include multiple extensions in one declaration 65 | hxExtensions_ :: [HtmxExtension] -> Attributes 66 | hxExtensions_ = makeAttributes "hx-ext" . fold . intersperse "," . fmap render 67 | 68 | -- | 69 | -- adds to the headers that will be submitted with the request 70 | hxHeaders_ :: Text -> Attributes 71 | hxHeaders_ = makeAttributes "hx-headers" 72 | 73 | -- | 74 | -- prevent sensitive data being saved to the history cache 75 | hxHistory_ :: Text -> Attributes 76 | hxHistory_ = makeAttributes "hx-history" 77 | 78 | -- | 79 | -- the element to snapshot and restore during history navigation 80 | hxHistoryElt_ :: Attributes 81 | hxHistoryElt_ = makeAttributes "hx-history-elt" mempty 82 | 83 | -- | 84 | -- include additional data in requests 85 | hxInclude_ :: Text -> Attributes 86 | hxInclude_ = makeAttributes "hx-include" 87 | 88 | -- | 89 | -- the element to put the htmx-request class on during the request 90 | hxIndicator_ :: Text -> Attributes 91 | hxIndicator_ = makeAttributes "hx-indicator" 92 | 93 | -- | An enumeration of the filter types based on the documentation here: 94 | -- 95 | data ParamsFilter 96 | = -- | Include all parameters (default) 97 | All 98 | | -- | Include no parameters 99 | None 100 | | -- | Include all except the list of parameter names 101 | Exclude [Text] 102 | | -- | Include all the list of parameter names 103 | Include [Text] 104 | 105 | -- | 106 | -- filters the parameters that will be submitted with a request 107 | hxParams_ :: ParamsFilter -> Attributes 108 | hxParams_ = \case 109 | All -> makeAttributes "hx-params" "*" 110 | None -> makeAttributes "hx-params" "none" 111 | Exclude ps -> makeAttributes "hx-params" $ "not " <> (fold . intersperse "," $ ps) 112 | Include ps -> makeAttributes "hx-params" $ fold . intersperse "," $ ps 113 | 114 | -- | 115 | -- issues a PATCH to the specified URL 116 | hxPatch_ :: Text -> Attributes 117 | hxPatch_ = makeAttributes "hx-patch" 118 | 119 | -- | 120 | -- specifies elements to keep unchanged between requests 121 | hxPreserve_ :: Attributes 122 | hxPreserve_ = makeAttributes "hx-preserve" mempty 123 | 124 | -- | 125 | -- shows a prompt() before submitting a request 126 | hxPrompt_ :: Text -> Attributes 127 | hxPrompt_ = makeAttributes "hx-prompt" 128 | 129 | -- | 130 | -- issues a PUT to the specified URL 131 | hxPut_ :: Text -> Attributes 132 | hxPut_ = makeAttributes "hx-put" 133 | 134 | -- | 135 | -- replace the URL in the browser location bar 136 | hxReplaceUrl_ :: Text -> Attributes 137 | hxReplaceUrl_ = makeAttributes "hx-replace-url" 138 | 139 | -- | 140 | -- configures various aspects of the request 141 | hxRequest_ :: Text -> Attributes 142 | hxRequest_ = makeAttributes "hx-request" 143 | 144 | {-# DEPRECATED 145 | hxSse_ 146 | "Don't use hx-sse directly, please use the server sent events extension instead https://htmx.org/extensions/server-sent-events/" 147 | #-} 148 | 149 | -- | 150 | -- has been moved to an extension. Documentation for older versions 151 | hxSse_ :: Text -> Attributes 152 | hxSse_ = makeAttributes "hx-sse" 153 | 154 | -- | An enumeration of the sync strategies based on the documentation here: 155 | -- 156 | data SyncStrategy 157 | = -- | drop (ignore) this request if an existing request is in flight (the default) 158 | SyncDrop 159 | | -- | drop (ignore) this request if an existing request is in flight, and, if 160 | -- that is not the case, abort this request if another request occurs while it is 161 | -- still in flight 162 | SyncAbort 163 | | -- | abort the current request, if any, and replace it with this request 164 | SyncReplace 165 | | -- | queue the first request to show up while a request is in flight 166 | SyncQueueFirst 167 | | -- | queue the last request to show up while a request is in flight 168 | SyncQueueLast 169 | | -- | queue all requests that show up while a request is in flight 170 | SyncQueueAll 171 | 172 | -- | 173 | -- control how requests made by different elements are synchronized 174 | hxSync_ :: Text -> Attributes 175 | hxSync_ = makeAttributes "hx-sync" 176 | 177 | -- | 178 | -- the same as 'hxSync_' but accepts a strongly typed htmx 'SyncStrategy' 179 | hxSyncStrategy_ :: Text -> SyncStrategy -> Attributes 180 | hxSyncStrategy_ selector = \case 181 | SyncDrop -> makeAttributes "hx-sync" $ selector <> ":" <> "drop" 182 | SyncAbort -> makeAttributes "hx-sync" $ selector <> ":" <> "abort" 183 | SyncReplace -> makeAttributes "hx-sync" $ selector <> ":" <> "replace" 184 | SyncQueueFirst -> makeAttributes "hx-sync" $ selector <> ":" <> "queue first" 185 | SyncQueueLast -> makeAttributes "hx-sync" $ selector <> ":" <> "queue last" 186 | SyncQueueAll -> makeAttributes "hx-sync" $ selector <> ":" <> "queue all" 187 | 188 | -- | 189 | -- force elements to validate themselves before a request 190 | hxValidate_ :: Text -> Attributes 191 | hxValidate_ = makeAttributes "hx-validate" 192 | 193 | -- | 194 | -- adds values dynamically to the parameters to submit with the request (deprecated, please use hx-vals) 195 | hxVars_ :: Text -> Attributes 196 | hxVars_ = makeAttributes "hx-vars" 197 | 198 | {-# DEPRECATED 199 | hxWs_ 200 | "Don't use hx-ws directly, please use the web sockets extension instead https://htmx.org/extensions/server-sent-events/https://htmx.org/extensions/web-sockets/" 201 | #-} 202 | 203 | -- | 204 | -- has been moved to an extension. Documentation for older versions 205 | hxWs_ :: Text -> Attributes 206 | hxWs_ = makeAttributes "hx-ws" 207 | -------------------------------------------------------------------------------- /htmx-lucid/src/Htmx/Lucid/Head.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Lucid.Head 3 | Description : Utilities for including HTMX in the html head tag 4 | 5 | This module defines utilities for installing HTMX and HTMX extensions 6 | via the head tag in your html document 7 | 8 | -} 9 | module Htmx.Lucid.Head ( 10 | useHtmx, 11 | useHtmxVersion, 12 | useHtmxExtension, 13 | useHtmxExtensionV, 14 | useHtmxExtensions, 15 | useHtmxExtensionsV, 16 | recommendedVersion, 17 | htmxSrc, 18 | htmxSrcWithSemVer, 19 | htmxExtSrc, 20 | ) where 21 | 22 | import Data.Foldable (forM_) 23 | import Data.Text (Text, pack) 24 | import GHC.Natural (Natural) 25 | import Htmx.Extension 26 | import Htmx.Render 27 | import Lucid (Html, HtmlT, script_, src_) 28 | import Lucid.Base (Attributes, makeAttributes) 29 | 30 | -- | Place in your template after @useHtmx@, but before where the extension is used via @hxExt_@ 31 | -- NOTE: This uses 'recommendedVersion' as the version section of the URL 32 | useHtmxExtension :: (Monad m) => HtmxExtension -> HtmlT m () 33 | useHtmxExtension = useHtmxExtensionV recommendedVersion 34 | 35 | -- | Same as 'useHtmxExtension' but lets you choose the version url 36 | useHtmxExtensionV :: 37 | (Monad m) => (Natural, Natural, Natural) -> HtmxExtension -> HtmlT m () 38 | useHtmxExtensionV v ext = script_ [src_ $ htmxExtSrc v (render ext)] ("" :: Html ()) 39 | 40 | -- | A version of 'useHtmxExtension' that works on a list of extensions 41 | -- NOTE: This uses 'recommendedVersion' as the version section of the URL 42 | useHtmxExtensions :: (Monad m) => [HtmxExtension] -> HtmlT m () 43 | useHtmxExtensions exts = forM_ exts useHtmxExtension 44 | 45 | -- | Same as 'useHtmxExtensions' but with a versioned url 46 | useHtmxExtensionsV :: 47 | (Monad m) => (Natural, Natural, Natural) -> [HtmxExtension] -> HtmlT m () 48 | useHtmxExtensionsV v exts = forM_ exts (useHtmxExtensionV v) 49 | 50 | -- | Place in your @head_@ tag to use htmx attributes in your lucid template 51 | useHtmx :: (Monad m) => HtmlT m () 52 | useHtmx = useHtmxVersion recommendedVersion 53 | 54 | -- | Choose the version of htmx to use using a 3-tuple representing semantic versioning 55 | useHtmxVersion :: (Monad m) => (Natural, Natural, Natural) -> HtmlT m () 56 | useHtmxVersion semVer = script_ [src_ $ htmxSrcWithSemVer semVer] ("" :: Html ()) 57 | 58 | -- | This is the recommended version of htmx for using this library 59 | -- (lucid-htmx). It is the version of the documentation that the implementation 60 | -- is based off of. 61 | recommendedVersion :: (Natural, Natural, Natural) 62 | recommendedVersion = (2, 0, 0) 63 | 64 | -- | constant for the htmx cdn 65 | htmxSrc :: Text 66 | htmxSrc = "https://unpkg.com/htmx.org" 67 | 68 | showT :: (Show a) => a -> Text 69 | showT = pack . show 70 | 71 | showSemVer :: (Natural, Natural, Natural) -> Text 72 | showSemVer (major, minor, patch) = 73 | "@" 74 | <> showT major 75 | <> "." 76 | <> showT minor 77 | <> "." 78 | <> showT patch 79 | 80 | -- | Creates a string with the htmx CDN specified to version 81 | htmxSrcWithSemVer :: (Natural, Natural, Natural) -> Text 82 | htmxSrcWithSemVer ver = 83 | htmxSrc <> showSemVer ver 84 | 85 | -- | Creates a string with the htmx extension CDN specified to version 86 | htmxExtSrc :: (Natural, Natural, Natural) -> Text -> Text 87 | htmxExtSrc ver ext = 88 | "https://unpkg.com/htmx-ext-" 89 | <> ext 90 | <> showSemVer ver 91 | <> "/" 92 | <> ext 93 | <> ".js" 94 | -------------------------------------------------------------------------------- /htmx-servant/README.md: -------------------------------------------------------------------------------- 1 | # htmx-servant 2 | -------------------------------------------------------------------------------- /htmx-servant/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./src" 4 | component: "lib:htmx-servant" 5 | -------------------------------------------------------------------------------- /htmx-servant/htmx-servant.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.6 2 | name: htmx-servant 3 | version: 0.2.0.2 4 | synopsis: Use htmx with servant 5 | description: 6 | Please see the README on GitHub at 7 | 8 | license: MIT 9 | category: Web, HTML 10 | author: Jonathan Lorimer 11 | maintainer: jonathanlorimer@pm.me 12 | build-type: Simple 13 | extra-source-files: README.md 14 | 15 | source-repository head 16 | type: git 17 | location: https://github.com/JonathanLorimer/htmx 18 | 19 | common def-exts 20 | default-extensions: 21 | DataKinds 22 | LambdaCase 23 | OverloadedStrings 24 | 25 | library 26 | import: def-exts 27 | 28 | -- cabal-fmt: expand src 29 | exposed-modules: 30 | Htmx.Servant.Lucid 31 | Htmx.Servant.Request 32 | Htmx.Servant.Response 33 | 34 | hs-source-dirs: src 35 | build-depends: 36 | , base >=4.7 && <5 37 | , htmx == 0.1.0.2 38 | , htmx-lucid == 0.2.0.1 39 | , lucid2 >=0.0.20221012 40 | , servant >=0.19 && <0.30 41 | , text >=2 && <3 42 | 43 | default-language: Haskell2010 44 | -------------------------------------------------------------------------------- /htmx-servant/src/Htmx/Servant/Lucid.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Servant.Lucid 3 | Description : Typesafe versions of HTMX request tags 4 | 5 | This module exports Lucid combinators that leverage the Servant 'Link' 6 | type to guarantee that they are live URLs, therefore making the requests 7 | "safe". 8 | -} 9 | module Htmx.Servant.Lucid ( 10 | hxDeleteSafe_, 11 | hxGetSafe_, 12 | hxPatchSafe_, 13 | hxPostSafe_, 14 | hxPushUrlSafe_, 15 | hxPutSafe_, 16 | ) 17 | where 18 | 19 | import Data.Text (Text) 20 | import Htmx.Lucid.Core ( 21 | hxGet_, 22 | hxPost_, 23 | hxPushUrl_, 24 | ) 25 | import Htmx.Lucid.Extra ( 26 | hxDelete_, 27 | hxPatch_, 28 | hxPut_, 29 | ) 30 | import Lucid.Base (Attributes) 31 | import Servant.API (ToHttpApiData (..), toUrlPiece) 32 | import Servant.Links (Link) 33 | 34 | -- | Type-safe version of 'hxDelete_' 35 | hxDeleteSafe_ :: Link -> Attributes 36 | hxDeleteSafe_ = hxDelete_ . toUrl 37 | 38 | -- | Type-safe version of 'hxGet_' 39 | hxGetSafe_ :: Link -> Attributes 40 | hxGetSafe_ = hxGet_ . toUrl 41 | 42 | -- | Type-safe version of 'hxPatch_' 43 | hxPatchSafe_ :: Link -> Attributes 44 | hxPatchSafe_ = hxPatch_ . toUrl 45 | 46 | -- | Type-safe version of 'hxPatch_' 47 | hxPostSafe_ :: Link -> Attributes 48 | hxPostSafe_ = hxPost_ . toUrl 49 | 50 | -- | Type-safe version of 'hxPushUrl_' 51 | hxPushUrlSafe_ :: Either Bool Link -> Attributes 52 | hxPushUrlSafe_ boolOrUrl = hxPushUrl_ $ case boolOrUrl of 53 | Left bool -> if bool then "true" else "false" 54 | Right url -> toUrl url 55 | 56 | -- | Type-safe version of 'hxPut_' 57 | hxPutSafe_ :: Link -> Attributes 58 | hxPutSafe_ = hxPut_ . toUrl 59 | 60 | toUrl :: (ToHttpApiData a) => a -> Text 61 | toUrl = ("/" <>) . toUrlPiece 62 | -------------------------------------------------------------------------------- /htmx-servant/src/Htmx/Servant/Request.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Servant.RequestHeaders 3 | Description : Helper types for HTMX request headers 4 | 5 | 6 | -} 7 | module Htmx.Servant.Request where 8 | 9 | import Data.Text (Text) 10 | import Servant.API.Header (Header) 11 | 12 | -- | indicates that the request is via an element using hx-boost 13 | type HXBoosted = Header "HX-Boosted" Bool 14 | 15 | -- | the current URL of the browser 16 | type HXCurrentURL = Header "HX-Current-URL" Text 17 | 18 | -- | “true” if the request is for history restoration after a miss in the local history cache 19 | type HXHistoryRestoreRequest = Header "HX-History-Restore-Request" Bool 20 | 21 | -- | the user response to an hx-prompt 22 | type HXPrompt a = Header "HX-Prompt" a 23 | 24 | -- | always “true” 25 | type HXRequest = Header "HX-Request" Bool 26 | 27 | -- | the id of the target element if it exists 28 | type HXTarget = Header "HX-Target" Text 29 | 30 | -- | the name of the triggered element if it exists 31 | type HXTriggerName = Header "HX-Trigger-Name" Text 32 | 33 | -- | the id of the triggered element if it exists 34 | type HXTrigger = Header "HX-Trigger" Text 35 | -------------------------------------------------------------------------------- /htmx-servant/src/Htmx/Servant/Response.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Servant.ResponseHeaders 3 | Description : Helper types for HTMX response headers 4 | 5 | 6 | -} 7 | module Htmx.Servant.Response where 8 | 9 | import Data.Text (Text) 10 | import Htmx.Swap (Swap) 11 | import Servant.API.Header (Header) 12 | 13 | -- | allows you to do a client-side redirect that does not do a full page reload 14 | type HXLocation = Header "HX-Location" Text 15 | 16 | -- | pushes a new url into the history stack 17 | type HXPushURL = Header "HX-Push-Url" Text 18 | 19 | -- | can be used to do a client-side redirect to a new location 20 | type HXRedirect = Header "HX-Redirect" Text 21 | 22 | -- | if set to “true” the client-side will do a full refresh of the page 23 | type HXRefresh = Header "HX-Refresh" Bool 24 | 25 | -- | replaces the current URL in the location bar 26 | type HXReplaceUrl = Header "HX-Replace-Url" Bool 27 | 28 | -- | replaces the current URL in the location bar 29 | type HXReswap = Header "HX-Reswap" Swap 30 | 31 | -- | a CSS selector that updates the target of the 32 | -- content update to a different element on the page 33 | type HXRetarget = Header "HX-Retarget" Text 34 | 35 | -- | a CSS selector that allows you to choose which part of the response is used 36 | -- to be swapped in. Overrides an existing hx-select on the triggering element 37 | type HXReselect = Header "HX-Reselect" Text 38 | 39 | -- | allows you to trigger client-side events 40 | type HXTrigger = Header "HX-Trigger" Text 41 | 42 | -- | allows you to trigger client-side events after the settle step 43 | type HXTriggerAfterSettle = Header "HX-Trigger-After-Settle" Text 44 | 45 | -- | allows you to trigger client-side events after the swap stepallows you to 46 | -- trigger client-side events after the settle step 47 | type HXTriggerAfterSwap = Header "HX-Trigger-After-Swap" Text 48 | -------------------------------------------------------------------------------- /htmx/README.md: -------------------------------------------------------------------------------- 1 | # htmx 2 | -------------------------------------------------------------------------------- /htmx/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./src" 4 | component: "lib:htmx" 5 | -------------------------------------------------------------------------------- /htmx/htmx.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.6 2 | name: htmx 3 | version: 0.1.0.2 4 | synopsis: Use htmx with various haskell libraries 5 | description: 6 | Please see the README on GitHub at 7 | 8 | license: MIT 9 | category: Web, HTML 10 | author: Jonathan Lorimer 11 | maintainer: jonathanlorimer@pm.me 12 | build-type: Simple 13 | extra-source-files: README.md 14 | 15 | source-repository head 16 | type: git 17 | location: https://github.com/JonathanLorimer/htmx 18 | 19 | common def-exts 20 | default-extensions: 21 | LambdaCase 22 | OverloadedStrings 23 | 24 | library 25 | import: def-exts 26 | 27 | -- cabal-fmt: expand src 28 | exposed-modules: 29 | Htmx.Event 30 | Htmx.Extension 31 | Htmx.Render 32 | Htmx.Swap 33 | 34 | hs-source-dirs: src 35 | build-depends: 36 | , base >=4.7 && <5 37 | , http-api-data >=0.5 && <0.7 38 | , text >=2 && <3 39 | 40 | default-language: Haskell2010 41 | -------------------------------------------------------------------------------- /htmx/src/Htmx/Event.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Event 3 | Description : Enumerates htmx specific events 4 | 5 | This module defines a type that represents events that originate from the HTMX 6 | library itself 7 | 8 | -} 9 | module Htmx.Event where 10 | 11 | import Data.Text (Text) 12 | import Htmx.Render 13 | 14 | -- | 15 | -- A sum type that represents possible events originating from the HTMX 16 | -- javascript library 17 | data HtmxEvent 18 | = -- | send this event to an element to abort a request 19 | Abort 20 | | -- | triggered after an AJAX request has completed processing a successful 21 | -- response 22 | AfterOnLoad 23 | | -- | triggered after htmx has initialized a node 24 | AfterProcessNode 25 | | -- | triggered after an AJAX request has completed 26 | AfterRequest 27 | | -- | triggered after the DOM has settled 28 | AfterSettle 29 | | -- | triggered after new content has been swapped in 30 | AfterSwap 31 | | -- | triggered before htmx disables an element or removes it from the DOM 32 | BeforeCleanupElement 33 | | -- | triggered before any response processing occurs 34 | BeforeOnLoad 35 | | -- | triggered before htmx initializes a node 36 | BeforeProcessNode 37 | | -- | triggered before an AJAX request is made 38 | BeforeRequest 39 | | -- | triggered before a swap is done, allows you to configure the swap 40 | BeforeSwap 41 | | -- | triggered just before an ajax request is sent 42 | BeforeSend 43 | | -- | triggered before the request, allows you to customize parameters, 44 | -- headers 45 | ConfigRequest 46 | | -- | triggered after a trigger occurs on an element, allows you to cancel 47 | -- (or delay) issuing the AJAX request 48 | Confirm 49 | | -- | triggered on an error during cache writing 50 | HistoryCacheError 51 | | -- | triggered on a cache miss in the history subsystem 52 | HistoryCacheMiss 53 | | -- | triggered on a unsuccessful remote retrieval 54 | HistoryCacheMissError 55 | | -- | triggered on a successful remote retrieval 56 | HistoryCacheMissLoad 57 | | -- | triggered when htmx handles a history restoration action 58 | HistoryRestore 59 | | -- | triggered before content is saved to the history cache 60 | BeforeHistorySave 61 | | -- | triggered when new content is added to the DOM 62 | Load 63 | | -- | triggered when an element refers to a SSE event in its trigger, but 64 | -- no parent SSE source has been defined 65 | NoSSESourceError 66 | | -- | triggered when an exception occurs during the onLoad handling in htmx 67 | OnLoadError 68 | | -- | triggered after an out of band element as been swapped in 69 | OobAfterSwap 70 | | -- | triggered before an out of band element swap is done, allows you to 71 | -- configure the swap 72 | OobBeforeSwap 73 | | -- | triggered when an out of band element does not have a matching ID in 74 | -- the current DOM 75 | OobErrorNoTarget 76 | | -- | triggered after a prompt is shown 77 | Prompt 78 | | -- | triggered after an url is pushed into history 79 | PushedIntoHistory 80 | | -- | triggered when an HTTP response error (non-200 or 300 response code) 81 | -- occurs 82 | ResponseError 83 | | -- | triggered when a network error prevents an HTTP request from happening 84 | SendError 85 | | -- | triggered when an error occurs with a SSE source 86 | SseError 87 | | -- | triggered when a SSE source is opened 88 | SseOpen 89 | | -- | triggered when an error occurs during the swap phase 90 | SwapError 91 | | -- | triggered when an invalid target is specified 92 | TargetError 93 | | -- | triggered when a request timeout occurs 94 | Timeout 95 | | -- | triggered before an element is validated 96 | ValidationValidate 97 | | -- | triggered when an element fails validation 98 | ValidationFailed 99 | | -- | triggered when a request is halted due to validation errors 100 | ValidationHalted 101 | | -- | triggered when an ajax request aborts 102 | XhrAbort 103 | | -- | triggered when an ajax request ends 104 | XhrLoadend 105 | | -- | triggered when an ajax request starts 106 | XhrLoadstart 107 | | -- | triggered periodically during an ajax request that supports progress 108 | -- events 109 | XhrProgress 110 | 111 | -- | We render htmx events as kebab case since browsers require lower casing, 112 | -- and both camelCase / kebab-case are supported by htmx 113 | -- 114 | -- - [HTMX events reference](https://htmx.org/reference/#events) 115 | -- - [hx-on documentation](https://htmx.org/attributes/hx-on/) which explains the preference for kebab case event names 116 | -- - [HTMX event naming docs](https://htmx.org/docs/#event_naming) 117 | instance Render HtmxEvent where 118 | render = \case 119 | Abort -> "abort" 120 | AfterOnLoad -> "after-on-load" 121 | AfterProcessNode -> "after-process-node" 122 | AfterRequest -> "after-request" 123 | AfterSettle -> "after-settle" 124 | AfterSwap -> "after-swap" 125 | BeforeCleanupElement -> "before-cleanup-element" 126 | BeforeOnLoad -> "before-on-load" 127 | BeforeProcessNode -> "before-process-node" 128 | BeforeRequest -> "before-request" 129 | BeforeSwap -> "before-swap" 130 | BeforeSend -> "before-send" 131 | ConfigRequest -> "config-request" 132 | Confirm -> "confirm" 133 | HistoryCacheError -> "history-cache-error" 134 | HistoryCacheMiss -> "history-cache-miss" 135 | HistoryCacheMissError -> "history-cache-miss-error" 136 | HistoryCacheMissLoad -> "history-cache-miss-load" 137 | HistoryRestore -> "history-restore" 138 | BeforeHistorySave -> "before-history-save" 139 | Load -> "load" 140 | NoSSESourceError -> "no-sse-source-error" 141 | OnLoadError -> "on-load-error" 142 | OobAfterSwap -> "oob-after-swap" 143 | OobBeforeSwap -> "oob-beforeSwap" 144 | OobErrorNoTarget -> "oob-error-no-target" 145 | Prompt -> "prompt" 146 | PushedIntoHistory -> "pushed-into-history" 147 | ResponseError -> "response-error" 148 | SendError -> "send-error" 149 | SseError -> "sse-error" 150 | SseOpen -> "sse-open" 151 | SwapError -> "swap-error" 152 | TargetError -> "target-error" 153 | Timeout -> "timeout" 154 | ValidationValidate -> "validation:validate" 155 | ValidationFailed -> "validation:failed" 156 | ValidationHalted -> "validation:halted" 157 | XhrAbort -> "xhr:abort" 158 | XhrLoadend -> "xhr:loadend" 159 | XhrLoadstart -> "xhr:loadstart" 160 | XhrProgress -> "xhr:progress" 161 | -------------------------------------------------------------------------------- /htmx/src/Htmx/Extension.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Extension 3 | Description : Enumerates official HTMX extensions 4 | 5 | This module defines a sum type that represents the "included" HTMX extensions 6 | 7 | -} 8 | module Htmx.Extension where 9 | 10 | import Data.Text (Text) 11 | import Htmx.Render 12 | 13 | -- | 14 | -- 15 | -- htmx includes a set of extensions out of the box that address common 16 | -- developer needs. These extensions are tested against htmx in each distribution. 17 | -- 18 | -- You can find the source for the bundled extensions at https://unpkg.com/ 19 | -- browse/htmx.org@1.9.12/dist/ext/. You will need to include the javascript file 20 | -- for the extension and then install it using the hx-ext attributes. 21 | -- See the individual extension documentation for more details. 22 | data HtmxExtension 23 | = -- | includes the commonly-used X-Requested-With header that identifies ajax requests in many backend frameworks 24 | AjaxHeader 25 | | -- | an extension for using the Alpine.js morph plugin as the swapping mechanism in htmx. 26 | AlpineMorph 27 | | -- | an extension for manipulating timed addition and removal of classes on HTML elements 28 | ClassTools 29 | | -- | support for client side template processing of JSON/XML responses 30 | ClientSideTemplates 31 | | -- | an extension for debugging of a particular element using htmx 32 | Debug 33 | | -- | includes a JSON serialized version of the triggering event, if any 34 | EventHeader 35 | | -- | support for merging the head tag from responses into the existing documents head 36 | HeadSupport 37 | | -- | allows you to include additional values in a request 38 | IncludeVals 39 | | -- | use JSON encoding in the body of requests, rather than the default x-www-form-urlencoded 40 | JsonEnc 41 | | -- | an extension for using the idiomorph morphing algorithm as a swapping mechanism 42 | Idiomorph 43 | | -- | allows you to disable inputs, add and remove CSS classes to any element while a request is in-flight. 44 | LoadingStates 45 | | -- | use the X-HTTP-Method-Override header for non-GET and POST requests 46 | MethodOverride 47 | | -- | an extension for using the morphdom library as the swapping mechanism in htmx. 48 | MorphdomSwap 49 | | -- | allows to swap multiple elements with different swap methods 50 | MultiSwap 51 | | -- | an extension for expressing path-based dependencies similar to intercoolerjs 52 | PathDeps 53 | | -- | preloads selected href and hx-get targets based on rules you control. 54 | Preload 55 | | -- | allows you to remove an element after a given amount of time 56 | RemoveMe 57 | | -- | allows to specify different target elements to be swapped when different HTTP response codes are received 58 | ResponseTargets 59 | | -- | allows you to trigger events when the back button has been pressed 60 | Restored 61 | | -- | uni-directional server push messaging via EventSource 62 | ServerSentEvents 63 | | -- | bi-directional connection to WebSocket servers 64 | WebSockets 65 | | -- | allows to use parameters for path variables instead of sending them in query or body 66 | PathParams 67 | deriving (Eq, Ord, Show) 68 | 69 | instance Render HtmxExtension where 70 | render = \case 71 | AjaxHeader -> "ajax-header" 72 | AlpineMorph -> "alpine-morph" 73 | ClassTools -> "class-tools" 74 | ClientSideTemplates -> "client-side-templates" 75 | Debug -> "debug" 76 | EventHeader -> "event-header" 77 | HeadSupport -> "head-support" 78 | IncludeVals -> "include-vals" 79 | JsonEnc -> "json-enc" 80 | Idiomorph -> "idiomorph" 81 | LoadingStates -> "loading-states" 82 | MethodOverride -> "method-override" 83 | MorphdomSwap -> "morphdom-swap" 84 | MultiSwap -> "multi-swap" 85 | PathDeps -> "path-deps" 86 | Preload -> "preload" 87 | RemoveMe -> "remove-me" 88 | ResponseTargets -> "response-targets" 89 | Restored -> "restored" 90 | ServerSentEvents -> "sse" 91 | WebSockets -> "ws" 92 | PathParams -> "path-params" 93 | -------------------------------------------------------------------------------- /htmx/src/Htmx/Render.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Render 3 | Description : Typeclass for rendering domain types as HTMX compatible 'Text' 4 | 5 | This module defines a typeclass that doesn't have the historical baggage or 6 | connotations of other text serialization typeclasses (like 'Show' or Display). 7 | The semantics of this class are supposed to be HTMX specific, i.e. serializing 8 | attribute values 9 | -} 10 | module Htmx.Render where 11 | 12 | import Data.Text (Text) 13 | 14 | -- | A typeclass for rendering domain types into attribute values 15 | class Render a where 16 | render :: a -> Text 17 | -------------------------------------------------------------------------------- /htmx/src/Htmx/Swap.hs: -------------------------------------------------------------------------------- 1 | {- | 2 | Module : Htmx.Swap 3 | Description : Provides a type for swap styles 4 | 5 | Provides a type and utilities for the "swap style" for hx-swap 6 | 7 | -} 8 | module Htmx.Swap where 9 | 10 | import Data.Text (Text, pack) 11 | import Htmx.Render 12 | import Web.HttpApiData (FromHttpApiData (..), ToHttpApiData (..)) 13 | 14 | -- | 15 | -- The different styles that can be used for swapping in content. 16 | -- Usually defaults to 'InnerHTML' 17 | data Swap 18 | = -- | Replace the inner html of the target element 19 | InnerHTML 20 | | -- | Replace the entire target element with the response 21 | OuterHTML 22 | | -- | Replace the text content of the target element, without parsing the response as HTML 23 | TextContent 24 | | -- | Insert the response before the target element 25 | BeforeBegin 26 | | -- | Insert the response before the first child of the target element 27 | AfterBegin 28 | | -- | Insert the response after the last child of the target element 29 | BeforeEnd 30 | | -- | Insert the response after the target element 31 | AfterEnd 32 | | -- | Deletes the target element regardless of the response 33 | Delete 34 | | -- | Does not append content from response (out of band items will still be processed). 35 | None 36 | 37 | instance Render Swap where 38 | render = \case 39 | InnerHTML -> "innerHTML" 40 | OuterHTML -> "outerHTML" 41 | TextContent -> "textContent" 42 | BeforeBegin -> "beforeBegin" 43 | AfterBegin -> "afterBegin" 44 | BeforeEnd -> "beforeEnd" 45 | AfterEnd -> "afterEnd" 46 | Delete -> "delete" 47 | None -> "none" 48 | 49 | instance ToHttpApiData Swap where 50 | toUrlPiece = render 51 | 52 | instance FromHttpApiData Swap where 53 | parseUrlPiece = \case 54 | "innerHTML" -> Right InnerHTML 55 | "outerHTML" -> Right OuterHTML 56 | "textContent" -> Right TextContent 57 | "beforeBegin" -> Right BeforeBegin 58 | "afterBegin" -> Right AfterBegin 59 | "beforeEnd" -> Right BeforeEnd 60 | "afterEnd" -> Right AfterEnd 61 | "delete" -> Right Delete 62 | "none" -> Right None 63 | t -> Left $ "Could not parse " <> t <> ". Expected a valid Swap" 64 | -------------------------------------------------------------------------------- /scripts/fmt.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanLorimer/htmx/a9592b2627a75f522cd710d6bcfb7670a4a587dd/scripts/fmt.sh -------------------------------------------------------------------------------- /scripts/hkg.sh: -------------------------------------------------------------------------------- 1 | set -u # or set -o nounset 2 | set -x 3 | set -e 4 | : "$HACKAGE_USERNAME" 5 | : "$HACKAGE_PASSWORD" 6 | 7 | # Upload package 8 | SDIST=$(cabal sdist | tail -1) 9 | cabal upload -u "$HACKAGE_USERNAME" -p "$HACKAGE_PASSWORD" --publish "$SDIST" 10 | rm "$SDIST" 11 | 12 | # Upload docs 13 | DOCS=$(cabal haddock --haddock-html-location='https://hackage.haskell.org/package/$pkg-$version/docs' --haddock-hyperlink-source --haddock-quickjump --haddock-for-hackage --verbose=1 | tail -1) 14 | cabal upload -d -u "$HACKAGE_USERNAME" -p "$HACKAGE_PASSWORD" --publish "$DOCS" 15 | rm "$DOCS" 16 | --------------------------------------------------------------------------------