hyperbole 0.5.0 → 0.6.0
raw patch · 47 files changed
+846/−1086 lines, 47 filesdep ~casingdep ~data-default
Dependency ranges changed: casing, data-default
Files
- CHANGELOG.md +12/−0
- README.md +21/−16
- client/dist/hyperbole.js +1/−1
- client/dist/hyperbole.js.map +1/−1
- hyperbole.cabal +8/−7
- src/Web/Hyperbole.hs +12/−472
- src/Web/Hyperbole/Application.hs +20/−17
- src/Web/Hyperbole/Data/Encoded.hs +28/−10
- src/Web/Hyperbole/Data/Param.hs +17/−4
- src/Web/Hyperbole/Data/QueryData.hs +2/−2
- src/Web/Hyperbole/Document.hs +9/−6
- src/Web/Hyperbole/Effect/Client.hs +11/−9
- src/Web/Hyperbole/Effect/GenRandom.hs +1/−1
- src/Web/Hyperbole/Effect/Hyperbole.hs +5/−25
- src/Web/Hyperbole/Effect/OAuth2.hs +0/−6
- src/Web/Hyperbole/Effect/Query.hs +11/−6
- src/Web/Hyperbole/Effect/Response.hs +27/−13
- src/Web/Hyperbole/Effect/Session.hs +8/−8
- src/Web/Hyperbole/HyperView.hs +9/−4
- src/Web/Hyperbole/HyperView/Event.hs +29/−28
- src/Web/Hyperbole/HyperView/Forms.hs +50/−130
- src/Web/Hyperbole/HyperView/Handled.hs +11/−31
- src/Web/Hyperbole/HyperView/Hyper.hs +51/−0
- src/Web/Hyperbole/HyperView/Input.hs +23/−23
- src/Web/Hyperbole/HyperView/Types.hs +47/−8
- src/Web/Hyperbole/HyperView/ViewAction.hs +0/−40
- src/Web/Hyperbole/HyperView/ViewId.hs +0/−56
- src/Web/Hyperbole/Page.hs +3/−3
- src/Web/Hyperbole/Route.hs +2/−2
- src/Web/Hyperbole/Server/Handler.hs +18/−16
- src/Web/Hyperbole/Server/Message.hs +46/−17
- src/Web/Hyperbole/Server/Options.hs +11/−10
- src/Web/Hyperbole/Server/Socket.hs +100/−18
- src/Web/Hyperbole/Server/Wai.hs +44/−29
- src/Web/Hyperbole/Types/Event.hs +8/−7
- src/Web/Hyperbole/Types/Request.hs +1/−1
- src/Web/Hyperbole/Types/Response.hs +8/−5
- src/Web/Hyperbole/View.hs +5/−1
- src/Web/Hyperbole/View/CSS.hs +2/−2
- src/Web/Hyperbole/View/Render.hs +9/−3
- src/Web/Hyperbole/View/Tag.hs +30/−3
- src/Web/Hyperbole/View/Types.hs +66/−43
- src/Web/Hyperbole/View/ViewAction.hs +40/−0
- src/Web/Hyperbole/View/ViewId.hs +33/−0
- test/Test/EncodedSpec.hs +5/−0
- test/Test/ViewActionSpec.hs +1/−1
- test/Test/ViewIdSpec.hs +0/−1
CHANGELOG.md view
@@ -1,5 +1,17 @@ # Revision history for hyperbole +## 0.6.0 -- 2026-01-15++Improvements:+* `ViewState` - built in threaded state, defaults to `()`, for folks who really miss Elm+* `Concurrency` Controls - `Drop` vs `Replace` for overlapping updates+* `pushUpdate` - server push an update to an arbitrary view+* Long-running actions can be interrupted / cancelled+* https://hyperbole.live now has inline documentation, code snippets, and live examples++Breaking Changes:+* A few functions now require state, such as `trigger` and `target`+ ## 0.5.0 -- 2025-09-26 Improvements
README.md view
@@ -1,28 +1,29 @@-+ [](https://hackage.haskell.org/package/hyperbole) Create interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView. -+[▶️ Simple Example](https://hyperbole.live/simple) ```haskell+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}+ module Main where import Data.Text (Text)-import Web.Hyperbole import Web.Atomic.CSS-+import Web.Hyperbole main :: IO () main = do run 3000 $ do liveApp quickStartDocument (runPage page) -page :: (Hyperbole :> es) => Eff es (Page '[Message])+page :: (Hyperbole :> es) => Page es '[Message] page = do pure $ do hyper Message1 $ messageView "Hello"@@ -32,7 +33,8 @@ deriving (Generic, ViewId) instance HyperView Message es where- data Action Message = Louder Text+ data Action Message+ = Louder Text deriving (Generic, ViewAction) update (Louder msg) = do@@ -46,16 +48,17 @@ -Examples----------+Documentation+------------- -The examples directory contains an app demonstrating many features. See them in action at [hyperbole.live](https://hyperbole.live)+Visit [hyperbole.live](https://hyperbole.live) for documentation and live examples. Also see the [Hackage Documentation](https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html) <a href="https://hyperbole.live">- <!--<img alt="Hyperbole Examples" src="https://github.com/seanhess/hyperbole/raw/main/examples/static/examples.png"/>-->- <img alt="Hyperbole Examples" src="https://github.com/seanhess/hyperbole/raw/main/examples/static/examples.png"/>+ <img alt="Hyperbole Documentation" src="https://github.com/seanhess/hyperbole/raw/main/demo/static/demo-screenshot.jpg"/> </a> ++ <!-- out of date! * [HaskRead](https://github.com/tusharad/Reddit-Clone-Haskell) - A Reddit Clone -->@@ -76,9 +79,11 @@ base , hyperbole , text++ default-language: GHC2021 ``` -Paste the above example into Main.hs, and run:+Paste the above example into Main.hs, then run it: $ cabal run @@ -88,13 +93,13 @@ Learn More ---------- -<a href="https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html" target="_blank" style="border-radius: 20px; Background-color:#f8f8f8; gap: 20px; display: flex; flex-direction: row; align-items: center">- <img src="https://github.com/seanhess/hyperbole/raw/main/docs/hackage.svg">-</a>+<!-- <a href="https://hackage.haskell.org/package/hyperbole/docs/Web-Hyperbole.html" target="_blank" style="border-radius: 20px; Background-color:#f8f8f8; gap: 20px; display: flex; flex-direction: row; align-items: center"> -->+<!-- <img src="https://github.com/seanhess/hyperbole/raw/main/docs/hackage.svg"> -->+<!-- </a> --> -* [Using NIX](./docs/nix.md) * [Local Development](./docs/dev.md) * [Comparison with Similar Frameworks](./docs/comparison.md)+* [Using NIX](./docs/nix.md) In the Wild ---------------------
client/dist/hyperbole.js view
@@ -1,3 +1,3 @@ /*! For license information please see hyperbole.js.LICENSE.txt */-(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:r}="boolean"==typeof n?{immediate:n}:n;let o,i,a,u,c;function s(){const t=o,n=i;return o=void 0,i=void 0,c=e.apply(t,n),c}function l(){const e=Date.now()-u;e<t&&e>=0?a=setTimeout(l,t-e):(a=void 0,r||(c=s()))}const d=function(...e){if(o&&this!==o&&Object.getPrototypeOf(this)===Object.getPrototypeOf(o))throw new Error("Debounced method called with different contexts of the same prototype.");o=this,i=e,u=Date.now();const n=r&&!a;return a||(a=setTimeout(l,t)),n&&(c=s()),c};return Object.defineProperty(d,"isPending",{get:()=>void 0!==a}),d.clear=()=>{a&&(clearTimeout(a),a=void 0)},d.flush=()=>{a&&d.trigger()},d.trigger=()=>{c=s(),d.clear()},d}e.exports.debounce=t,e.exports=t},147:e=>{"use strict";e.exports=JSON.parse('{"name":"web-ui","version":"0.5.0","description":"Development -----------","main":"index.js","directories":{"client":"client"},"scripts":{"build":"npx webpack"},"author":"","license":"ISC","devDependencies":{"ts-loader":"^9.4.1","typescript":"^4.8.3","uglify":"^0.1.5","webpack":"^5.88.2","webpack-cli":"^4.10.0"},"dependencies":{"omdomdom":"^0.3.2","debounce":"^2.2.0"}}')}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,r=-1;if(n)for(;++r<n&&!1!==t(e[r],r););},r=function(e,t,n){return e.node.insertBefore(t.node,n)};function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i,a,u=[],c=!0,s=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;c=!1}else for(;!(c=(r=i.call(n)).done)&&(u.push(r.value),u.length!==t);c=!0);}catch(e){s=!0,o=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw o}}return u}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}var a="string",u="number",c="boolean",s={},l=function(e,t,n){return{attrName:e,propName:t,type:n}};t([["style","cssText"],["class","className"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r||n,a)})),t(["autofocus","draggable","hidden","checked","multiple","muted","selected"],(function(e){s[e]=l(e,e,c)})),t([["tabindex","tabIndex"]],(function(e){var t=o(e,2),n=t[0],r=t[1];s[n]=l(n,r,u)}));var d="xlink:",f="http://www.w3.org/1999/xlink",v="xml:",p="http://www.w3.org/XML/1998/namespace",h=function(e,t,n,r){switch(t){case a:n===s.style.propName?e.style[n]=null===r?"":r:e[n]=null===r?"":r;break;case u:if(null===r){var o=n.toLowerCase();e.removeAttribute(o)}else if("0"===r)e[n]=0;else if("-1"===r)e[n]=-1;else{var i=parseInt(r,10);isNaN(i)||(e[n]=i)}break;case c:["","true"].indexOf(r)<0?e[n]=!1:e[n]=!0}},y=function n(o,i,u){if(o&&i){u=u||i.node.parentNode;var c=o.content&&o.content!==i.content;if(o.type!==i.type||c)return u.replaceChild(o.node,i.node),function(e,t){for(var n in e)t[n]=e[n]}(o,i);(function(n,r){var o=[],i={};for(var u in r.attributes){var c=r.attributes[u],l=n.attributes[u];c!==l&&void 0===l&&o.push(u)}for(var y in n.attributes){var m=r.attributes[y],b=n.attributes[y];m!==b&&void 0!==b&&(i[y]=b)}!function(n,r){t(r,(function(t){if(e(s,t)){var r=s[t];h(n.node,r.type,r.propName,null)}else t in n.node&&h(n.node,a,t,null),n.node.removeAttribute(t);delete n.attributes[t]}))}(r,o),function(t,n){for(var r in n){var o=n[r];if(t.attributes[r]=o,e(s,r)){var i=s[r];h(t.node,i.type,i.propName,o)}else r.startsWith(d)?t.node.setAttributeNS(f,r,o):r.startsWith(v)?t.node.setAttributeNS(p,r,o):(r in t.node&&h(t.node,a,r,o),t.node.setAttribute(r,o||""))}}(r,i)})(o,i),function(n,o,i){var a=n.children.length,u=o.children.length;if(a||u){var c=function(n){var r={};for(var o in t(n,(function(t){var n=t.attributes["data-key"];(function(t,n){return!(!n||e(t,n)&&(console.warn("[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed."),1))})(r,n)&&(r[n]=t)})),r)return r}(o.children),s=Array(a);t(n.children,void 0!==c?function(e,t){var n=o.node.childNodes,a=e.attributes["data-key"];if(Object.prototype.hasOwnProperty.call(c,a)){var u=c[a];Array.prototype.indexOf.call(n,u.node)!==t&&r(o,u,n[t]),s[t]=u,delete c[a],i(e,s[t])}else r(o,e,n[t]),s[t]=e}:function(e,t){var n=o.children[t];void 0!==n?(i(e,n),s[t]=n):(o.node.appendChild(e.node),s[t]=e)}),o.children=s;var l=o.node.childNodes.length,d=l-a;if(d>0)for(;d>0;)o.node.removeChild(o.node.childNodes[l-1]),l--,d--}}(o,i,n)}},m=function n(r){var o,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof r&&(o=r.trim().replace(/\s+</g,"<").replace(/>\s+/g,">"),r=(new DOMParser).parseFromString(o,"text/html").body);var a="BODY"===r.tagName,u=r.childNodes,c=u?u.length:0;if(a){if(c>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===c)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(u[0])}var l=3===r.nodeType?"text":8===r.nodeType?"comment":r.tagName.toLowerCase(),d=i||"svg"===l,f=1===r.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(s,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in s){var r=s[n].propName,o=e.getAttribute(n);n===s.style.attrName?t[n]=e.style[r]:"string"==typeof o&&(t[n]=o)}}(t,n),n}(r):{},v=c>0?null:r.textContent,p=Array(c);return t(u,(function(e,t){p[t]=n(e,d)})),{type:l,attributes:f,children:p,content:v,node:r,isSVGContext:d}};function b(e){if(e){var t=new URLSearchParams;return e.forEach((function(e,n){t.append(n,e)})),t}}function w(e){return g(e.trim().split("\n")).metadata}function g(e){var t,n,r,o,i,a,u,c=function(e,t){for(var n=[],r=0,o=t;r<o.length;r++){var i=e(o[r]);if(!i)break;n.push(i)}return n}(k,e),s=e.slice(c.length);return{metadata:(t=c,u=null===(n=t.find((function(e){return"RequestId"==e.key})))||void 0===n?void 0:n.value,{cookies:t.filter((function(e){return"Cookie"==e.key})).map((function(e){return e.value})),redirect:null===(r=t.find((function(e){return"Redirect"==e.key})))||void 0===r?void 0:r.value,error:null===(o=t.find((function(e){return"Error"==e.key})))||void 0===o?void 0:o.value,query:null===(i=t.find((function(e){return"Query"==e.key})))||void 0===i?void 0:i.value,events:t.filter((function(e){return"Event"==e.key})).map((function(e){return function(e){var t=E(e),n=t[0],r=t[1];return{name:n,detail:JSON.parse(r)}}(e.value)})),actions:t.filter((function(e){return"Trigger"==e.key})).map((function(e){return function(e){var t=E(e);return[t[0],t[1]]}(e.value)})),pageTitle:null===(a=t.find((function(e){return"PageTitle"==e.key})))||void 0===a?void 0:a.value,requestId:u}),rest:s}}function E(e){var t=e.indexOf("|");if(-1===t){var n=new Error("Bad Encoding, Expected Segment");throw n.message=e,n}return[e.slice(0,t),e.slice(t+1)]}function k(e){var t=e.match(/^(\w+)\: (.*)$/);if(t)return{key:t[1],value:t[2]}}function x(e,t){return e+" "+function(e){return""==e?"|":e.replace(/_/g,"\\_").replace(/\s+/g,"_")}(t)}var I,S=(I=function(e,t){return I=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},I(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}I(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),C=function(e){function t(t,n,r){var o=e.call(this,n)||this;return o.viewId=t,o.name="Fetch Error",o.body=r,o}return S(t,e),t}(Error),A=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},T=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},O="https:"===window.location.protocol?"wss:":"ws:",q="".concat(O,"//").concat(window.location.host).concat(window.location.pathname),L=function(){function e(){this.reconnectDelay=0}return e.prototype.connect=function(e){var t=this;void 0===e&&(e=q);var n=new WebSocket(e);function r(e){console.log("Connection Error",e)}this.socket=n,n.addEventListener("error",r),n.addEventListener("open",(function(e){console.log("Websocket Connected"),t.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),t.isConnected=!0,t.hasEverConnected=!0,t.reconnectDelay=1e3,t.socket.removeEventListener("error",r),document.dispatchEvent(new Event("hyp-socket-connect"))})),n.addEventListener("close",(function(n){t.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),t.isConnected=!1,t.hasEverConnected&&(console.log("Reconnecting in "+t.reconnectDelay/1e3+"s"),setTimeout((function(){return t.connect(e)}),t.reconnectDelay))}))},e.prototype.sendAction=function(e){return A(this,void 0,void 0,(function(){var t,n,r,o;return T(this,(function(i){switch(i.label){case 0:return t=function(e){var t,n;return[["|ACTION|","ViewId: "+e.viewId,"Action: "+e.action,"RequestId: "+e.requestId].join("\n"),(n=e.meta,n.map((function(e){return e.key+": "+e.value})).join("\n"))].join("\n")+((t=e.form)?"\n\n"+t:"")}(e),[4,this.fetch(e.requestId,e.viewId,t)];case 1:return n=i.sent(),r=n.metadata,o=n.rest,[2,{meta:r,body:o.join("\n")}]}}))}))},e.prototype.fetch=function(e,t,n){return A(this,void 0,void 0,(function(){return T(this,(function(r){switch(r.label){case 0:return this.sendMessage(n),[4,this.waitMessage(e,t)];case 1:return[2,r.sent()]}}))}))},e.prototype.sendMessage=function(e){this.socket.send(e)},e.prototype.waitMessage=function(e,t){return A(this,void 0,void 0,(function(){var n=this;return T(this,(function(r){return[2,new Promise((function(r,o){var i=function(a){var u=g(a.data.split("\n").slice(1)),c=u.metadata;c.requestId?c.requestId==e&&(n.socket.removeEventListener("message",i),c.cookies.forEach((function(e){document.cookie=e})),c.error?o(new C(t,c.error,u.rest.join("\n"))):r(u)):console.error("Missing RequestId!",c,a.data)};n.socket.addEventListener("message",i),n.socket.addEventListener("error",o)}))]}))}))},e.prototype.disconnect=function(){this.socket.close()},e}(),D=n(296);function N(e,t){document.addEventListener(e.toLowerCase(),(function(n){var r=n.target,o="on"+e+n.key,i=r.dataset[o];i&&(n.preventDefault(),t(R(r),i))}))}function M(e,t){document.addEventListener(e,(function(n){var r=n.target.closest("[data-on"+e+"]");if(r){n.preventDefault();var o=R(r);t(o,r.dataset["on"+e])}}))}function j(e){e.querySelectorAll("[data-onload]").forEach((function(e){var t=parseInt(e.dataset.delay)||0,n=e.dataset.onload;setTimeout((function(){var t=R(e);if(e.dataset.onload==n){var r=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(r)}}),t)}))}function P(e){e.querySelectorAll("[data-onmouseenter]").forEach((function(e){var t=e.dataset.onmouseenter,n=R(e);e.onmouseenter=function(){var r=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(r)}}))}function H(e){e.querySelectorAll("[data-onmouseleave]").forEach((function(e){var t=e.dataset.onmouseleave,n=R(e);e.onmouseleave=function(){var r=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(r)}}))}function R(e){var t=function(e){var t,n=e.closest("[data-target]");return(null==n?void 0:n.dataset.target)||(null===(t=e.closest("[id]"))||void 0===t?void 0:t.id)}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",t,e)}var _=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},V=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}};function B(e){return _(this,void 0,void 0,(function(){var t,n,r,o,i,a;return V(this,(function(u){switch(u.label){case 0:return t=window.location.href,[4,fetch(t,{method:"POST",headers:{Accept:"text/html","Content-Type":"application/x-www-form-urlencoded","Hyp-RequestId":e.requestId,"Hyp-ViewId":e.viewId,"Hyp-Action":e.action},body:e.form,redirect:"manual"})];case 1:return[4,(n=u.sent()).text()];case 2:if(r=u.sent(),o=function(e){var t=g(e.split("\n").slice(1));return{metadata:t.metadata,rest:t.rest.slice(2)}}(r),i=o.metadata,a=o.rest,!n.ok)throw new C(e.viewId,r,r);return[2,{meta:i,body:a.join("\n")}]}}))}))}var G,U=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function u(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,u)}c((r=r.apply(e,t||[])).next())}))},W=function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(u){return function(c){return function(u){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,u[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&u[0]?r.return:u[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,u[1])).done)return o;switch(r=0,o&&(u=[2&u[0],o.value]),u[0]){case 0:case 1:o=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,r=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==u[0]&&2!==u[0])){a=0;continue}if(3===u[0]&&(!o||u[1]>o[0]&&u[1]<o[3])){a.label=u[1];break}if(6===u[0]&&a.label<o[1]){a.label=o[1],o=u;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(u);break}o[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],r=0}finally{n=o=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}([u,c])}}},F=n(147);console.log("Hyperbole "+F.version);var J=new Set;function K(e){return U(this,void 0,void 0,(function(){return W(this,(function(t){return Z.isConnected?[2,Z.sendAction(e)]:[2,B(e)]}))}))}function Y(e,t,n){return U(this,void 0,void 0,(function(){var r,o,i,a,u,c,s,l,d,f;return W(this,(function(v){switch(v.label){case 0:if(void 0===e)return console.error("Undefined HyperView!"),[2];if(void 0===t)return console.error("Undefined Action!",e,"this is a bug, please report: https://github.com/seanhess/hyperbole"),[2];if(r=setTimeout((function(){e.classList.add("hyp-loading")}),100),o=Math.random().toString(36).substring(2,8),i=function(e,t,n,r){return{viewId:e,action:t,requestId:n,meta:[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}],form:b(r)}}(e.id,t,o,n),e.dataset.requestId)return console.warn("Action overlaps with active request ("+e.dataset.requestId+")",t),[2];e.dataset.requestId=o,v.label=1;case 1:return v.trys.push([1,3,,4]),[4,K(i)];case 2:if((a=v.sent()).meta.requestId!=e.dataset.requestId)throw(u=new Error).name="Concurrency Error",u.message="Stale Action ("+o+"):"+t,u;return delete e.dataset.requestId,$(a.meta)?[2]:(c=function(e){var t=(new DOMParser).parseFromString(e,"text/html"),n=t.querySelector("style");return{content:t.querySelector("div"),css:n}}(a.body),c.content?(function(e){if(e)for(var t=0,n=e.sheet.cssRules;t<n.length;t++){var r=n[t];0==J.has(r.cssText)&&(G.sheet.insertRule(r.cssText),J.add(r.cssText))}}(c.css),s=m(e),(l=m(c.content)).attributes=s.attributes,y(l,s),z(d=document.getElementById(e.id)),d?(Q(a.meta,d),j(d),P(d),H(d),function(e){var t=e.querySelector("[autofocus]");(null==t?void 0:t.focus)&&t.focus(),e.querySelectorAll("input[value]").forEach((function(e){var t=e.getAttribute("value");void 0!==t&&(e.value=t)})),e.querySelectorAll("input[type=checkbox]").forEach((function(e){var t="True"==e.dataset.checked;e.checked=t}))}(d),X(d)):console.warn("Target Missing: ",e.id),[3,4]):(console.error("Empty Response!",a.body),[2]));case 3:return f=v.sent(),console.error("Caught Error in HyperView ("+e.id+"):\n",f),e.innerHTML=f.body||"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>",[3,4];case 4:return clearTimeout(r),e.classList.remove("hyp-loading"),[2]}}))}))}function $(e){if(e.redirect)return window.location.href=e.redirect,!0;null!=e.query&&function(e){if(e!=function(){var e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);var t=location.pathname+e;window.history.replaceState({},"",t)}}(e.query),null!=e.pageTitle&&(document.title=e.pageTitle)}function Q(e,t){for(var n=0,r=e.events;n<r.length;n++){var o=r[n];setTimeout((function(){var e=new CustomEvent(o.name,{bubbles:!0,detail:o.detail});(t||document).dispatchEvent(e)}),10)}e.actions.forEach((function(e){var t=e[0],n=e[1];setTimeout((function(){var e=window.Hyperbole.hyperView(t);e&&Y(e,n)}),10)}))}function X(e){e.querySelectorAll("[id]").forEach((function(t){t.runAction=function(e){Y(this,e)}.bind(t),z(e)}))}function z(e){var t=new Event("hyp-content",{bubbles:!0});e.dispatchEvent(t)}document.addEventListener("DOMContentLoaded",(function(){var e,t=w(document.getElementById("hyp.metadata").innerText);$(t),Q(t),G=document.querySelector("style"),e=function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))},document.addEventListener("hyp-load",(function(t){var n=t.detail.onLoad,r=t.detail.target;e(r,n)})),document.addEventListener("hyp-mouseenter",(function(t){var n=t.detail.onMouseEnter,r=t.detail.target;e(r,n)})),document.addEventListener("hyp-mouseleave",(function(t){var n=t.detail.onMouseLeave,r=t.detail.target;e(r,n)})),j(document.body),P(document.body),H(document.body),X(document.body),M("click",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),M("dblclick",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),N("Keydown",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),N("Keyup",(function(e,t){return U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))})),document.addEventListener("submit",(function(e){var t=e.target;if(null==t?void 0:t.dataset.onsubmit){e.preventDefault();var n=R(t),r=new FormData(t);!function(e,t,n){U(this,void 0,void 0,(function(){return W(this,(function(r){return Y(e,t,n),[2]}))}))}(n,t.dataset.onsubmit,r)}else console.error("Missing onSubmit: ",t)})),document.addEventListener("change",(function(e){var t=e.target.closest("[data-onchange]");t&&(e.preventDefault(),null!=t.value?function(e,t){U(this,void 0,void 0,(function(){return W(this,(function(n){return console.log("CHANGE",e.id,"("+t+")"),Y(e,t),[2]}))}))}(R(t),x(t.dataset.onchange,t.value)):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){var t=e.target.closest("[data-oninput]");if(t){var n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 250 can result in poor performance."),null==t?void 0:t.dataset.oninput){e.preventDefault();var r=R(t);t.debouncedCallback||(t.debouncedCallback=D((function(){var e=x(t.dataset.oninput,t.value);!function(e,t){U(this,void 0,void 0,(function(){return W(this,(function(n){return Y(e,t),[2]}))}))}(r,e)}),n)),t.debouncedCallback()}else console.error("Missing onInput: ",t)}}))}));var Z=new L;Z.connect(),window.Hyperbole={runAction:Y,parseMetadata:w,action:function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];return e+t.reduce((function(e,t){return e+" "+JSON.stringify(t)}),"")},hyperView:function(e){var t=document.getElementById(e);if(null==t?void 0:t.runAction)return t;console.error("Element id="+e+" was not a HyperView")},socket:Z}})()})();+(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:o}="boolean"==typeof n?{immediate:n}:n;let r,i,a,s,c;function u(){const t=r,n=i;return r=void 0,i=void 0,c=e.apply(t,n),c}function d(){const e=Date.now()-s;e<t&&e>=0?a=setTimeout(d,t-e):(a=void 0,o||(c=u()))}const l=function(...e){if(r&&this!==r&&Object.getPrototypeOf(this)===Object.getPrototypeOf(r))throw new Error("Debounced method called with different contexts of the same prototype.");r=this,i=e,s=Date.now();const n=o&&!a;return a||(a=setTimeout(d,t)),n&&(c=u()),c};return Object.defineProperty(l,"isPending",{get:()=>void 0!==a}),l.clear=()=>{a&&(clearTimeout(a),a=void 0)},l.flush=()=>{a&&l.trigger()},l.trigger=()=>{c=u(),l.clear()},l}e.exports.debounce=t,e.exports=t},147:e=>{"use strict";e.exports=JSON.parse('{"name":"web-ui","version":"0.6.0","description":"Development -----------","main":"index.js","directories":{"client":"client"},"scripts":{"build":"npx webpack"},"author":"","license":"ISC","devDependencies":{"ts-loader":"^9.4.1","typescript":"^4.8.3","uglify":"^0.1.5","webpack":"^5.88.2","webpack-cli":"^4.10.0"},"dependencies":{"omdomdom":"^0.3.2","debounce":"^2.2.0"}}')}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,o=-1;if(n)for(;++o<n&&!1!==t(e[o],o););},o=function(e,t,n){return e.node.insertBefore(t.node,n)};function r(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var o,r,i,a,s=[],c=!0,u=!1;try{if(i=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;c=!1}else for(;!(c=(o=i.call(n)).done)&&(s.push(o.value),s.length!==t);c=!0);}catch(e){u=!0,r=e}finally{try{if(!c&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(u)throw r}}return s}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n<t;n++)o[n]=e[n];return o}var a="string",s="number",c="boolean",u={},d=function(e,t,n){return{attrName:e,propName:t,type:n}};t([["style","cssText"],["class","className"]],(function(e){var t=r(e,2),n=t[0],o=t[1];u[n]=d(n,o||n,a)})),t(["autofocus","draggable","hidden","checked","multiple","muted","selected"],(function(e){u[e]=d(e,e,c)})),t([["tabindex","tabIndex"]],(function(e){var t=r(e,2),n=t[0],o=t[1];u[n]=d(n,o,s)}));var l="xlink:",f="http://www.w3.org/1999/xlink",v="xml:",p="http://www.w3.org/XML/1998/namespace",h=function(e,t,n,o){switch(t){case a:n===u.style.propName?e.style[n]=null===o?"":o:e[n]=null===o?"":o;break;case s:if(null===o){var r=n.toLowerCase();e.removeAttribute(r)}else if("0"===o)e[n]=0;else if("-1"===o)e[n]=-1;else{var i=parseInt(o,10);isNaN(i)||(e[n]=i)}break;case c:["","true"].indexOf(o)<0?e[n]=!1:e[n]=!0}},m=function n(r,i,s){if(r&&i){s=s||i.node.parentNode;var c=r.content&&r.content!==i.content;if(r.type!==i.type||c)return s.replaceChild(r.node,i.node),function(e,t){for(var n in e)t[n]=e[n]}(r,i);(function(n,o){var r=[],i={};for(var s in o.attributes){var c=o.attributes[s],d=n.attributes[s];c!==d&&void 0===d&&r.push(s)}for(var m in n.attributes){var y=o.attributes[m],g=n.attributes[m];y!==g&&void 0!==g&&(i[m]=g)}!function(n,o){t(o,(function(t){if(e(u,t)){var o=u[t];h(n.node,o.type,o.propName,null)}else t in n.node&&h(n.node,a,t,null),n.node.removeAttribute(t);delete n.attributes[t]}))}(o,r),function(t,n){for(var o in n){var r=n[o];if(t.attributes[o]=r,e(u,o)){var i=u[o];h(t.node,i.type,i.propName,r)}else o.startsWith(l)?t.node.setAttributeNS(f,o,r):o.startsWith(v)?t.node.setAttributeNS(p,o,r):(o in t.node&&h(t.node,a,o,r),t.node.setAttribute(o,r||""))}}(o,i)})(r,i),function(n,r,i){var a=n.children.length,s=r.children.length;if(a||s){var c=function(n){var o={};for(var r in t(n,(function(t){var n=t.attributes["data-key"];(function(t,n){return!(!n||e(t,n)&&(console.warn("[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed."),1))})(o,n)&&(o[n]=t)})),o)return o}(r.children),u=Array(a);t(n.children,void 0!==c?function(e,t){var n=r.node.childNodes,a=e.attributes["data-key"];if(Object.prototype.hasOwnProperty.call(c,a)){var s=c[a];Array.prototype.indexOf.call(n,s.node)!==t&&o(r,s,n[t]),u[t]=s,delete c[a],i(e,u[t])}else o(r,e,n[t]),u[t]=e}:function(e,t){var n=r.children[t];void 0!==n?(i(e,n),u[t]=n):(r.node.appendChild(e.node),u[t]=e)}),r.children=u;var d=r.node.childNodes.length,l=d-a;if(l>0)for(;l>0;)r.node.removeChild(r.node.childNodes[d-1]),d--,l--}}(r,i,n)}},y=function n(o){var r,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof o&&(r=o.trim().replace(/\s+</g,"<").replace(/>\s+/g,">"),o=(new DOMParser).parseFromString(r,"text/html").body);var a="BODY"===o.tagName,s=o.childNodes,c=s?s.length:0;if(a){if(c>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===c)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(s[0])}var d=3===o.nodeType?"text":8===o.nodeType?"comment":o.tagName.toLowerCase(),l=i||"svg"===d,f=1===o.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(u,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in u){var o=u[n].propName,r=e.getAttribute(n);n===u.style.attrName?t[n]=e.style[o]:"string"==typeof r&&(t[n]=r)}}(t,n),n}(o):{},v=c>0?null:o.textContent,p=Array(c);return t(s,(function(e,t){p[t]=n(e,l)})),{type:d,attributes:f,children:p,content:v,node:o,isSVGContext:l}};function g(e,t){var n=[];for(var o of t){let t=e(o);if(!t)break;n.push(t)}return n}function w(e){return{cookies:e.filter((e=>"Cookie"==e.key)).map((e=>e.value)),error:E("Error",e),query:E("Query",e),pageTitle:E("PageTitle",e),events:C("Event",e).map(q),actions:C("Trigger",e).map(I)}}function b(e){return w(g(k,e.trim().split("\n")))}function E(e,t){return t.find((t=>t.key==e))?.value}function C(e,t){return t.filter((t=>t.key==e)).map((e=>e.value))}function k(e){let t=e.match(/^(\w+)\: (.*)$/);if(t)return{key:t[1],value:t[2]}}function q(e){let[t,n]=S(e);return{name:t,detail:JSON.parse(n)}}function I(e){let[t,n]=S(e);return[t,n]}function S(e){let t=e.indexOf("|");if(-1===t){let t=new Error("Bad Encoding, Expected Segment");throw t.message=e,t}return[e.slice(0,t),e.slice(t+1)]}function A(e){if(!e)return;const t=new URLSearchParams;return e.forEach(((e,n)=>{t.append(n,e)})),t}let x=0;function L(e,t){return e+" "+function(e){return""==e?"|":e.replace(/_/g,"\\_").replace(/\s+/g,"_")}(t)}const T=`${"https:"===window.location.protocol?"wss:":"ws:"}//${window.location.host}${window.location.pathname}`;class D extends Error{constructor(e,t){super(e+"\n"+t),this.name="ProtocolError"}}var R=n(296);function O(e,t){document.addEventListener(e.toLowerCase(),(function(n){let o=n.target,r="on"+e+n.key,i=o.dataset[r];i&&(n.preventDefault(),t(V(o),i))}))}function N(e,t){document.addEventListener(e,(function(n){let o=n.target.closest("[data-on"+e+"]");if(!o)return;n.preventDefault();let r=V(o);t(r,o.dataset["on"+e])}))}function M(e){e.querySelectorAll("[data-onload]").forEach((e=>{let t=parseInt(e.dataset.delay)||0,n=e.dataset.onload;setTimeout((()=>{let t=V(e);if(e.dataset.onload!=n)return;const o=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(o)}),t)}))}function j(e){e.querySelectorAll("[data-onmouseenter]").forEach((e=>{let t=e.dataset.onmouseenter,n=V(e);e.onmouseenter=()=>{const o=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(o)}}))}function P(e){e.querySelectorAll("[data-onmouseleave]").forEach((e=>{let t=e.dataset.onmouseleave,n=V(e);e.onmouseleave=()=>{const o=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(o)}}))}function V(e){let t=function(e){let t=e.closest("[data-target]");return t?.dataset.target||e.closest("[id]")?.id}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",t,e)}let B,U=n(147);console.log("Hyperbole "+U.version+"b");let Q=new Set;async function $(e,t,n){if(void 0===e)return void console.error("Undefined HyperView!",t);if(void 0===t)return void console.error("Undefined Action!",e.id);if(e.activeRequest&&!e.activeRequest?.isCancelled&&"Drop"==e.concurrency)return void console.warn("Drop action overlapping with active request ("+e.activeRequest+")",t);e._timeout=setTimeout((()=>{e.classList.add("hyp-loading")}),100);let o=e.dataset.state,r={requestId:++x,isCancelled:!1},i=function(e,t,n,o,r){return{viewId:e,action:t,state:n,requestId:o,meta:[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}],form:A(r)}}(e.id,t,o,r.requestId,n);e.activeRequest=r,Y.sendAction(i)}function H(e){let t=e.targetViewId||e.viewId,n=document.getElementById(t);if(!n)return console.error("Missing Update Target: ",t,e),n;if(e.requestId<n.activeRequest?.requestId)return console.warn("Ignore Stale Action ("+e.requestId+") vs ("+n.activeRequest.requestId+"): "+e.action),n;if(n.activeRequest?.isCancelled)return console.warn("Cancelled request",n.activeRequest?.requestId),delete n.activeRequest,n;let o=function(e){const t=(new DOMParser).parseFromString(e,"text/html"),n=t.querySelector("style");return{content:t.querySelector("div"),css:n}}(e.body);if(!o.content)return console.error("Empty Response!",e.body),n;!function(e){if(!e)return;const t=e.sheet.cssRules;for(const e of t)0==Q.has(e.cssText)&&(B.sheet.insertRule(e.cssText),Q.add(e.cssText))}(o.css);const r=y(n);let i=y(o.content),a=i.attributes["data-state"];i.attributes=r.attributes,m(i,r);let s=document.getElementById(n.id);return J(s),s?(s.dataset.state=a,_(e.meta,s),W(e.meta.cookies),M(s),j(s),P(s),function(e){let t=e.querySelector("[autofocus]");t?.focus&&t.focus(),e.querySelectorAll("input[value]").forEach((e=>{let t=e.getAttribute("value");void 0!==t&&(e.value=t)})),e.querySelectorAll("input[type=checkbox]").forEach((e=>{let t="True"==e.dataset.checked;e.checked=t}))}(s),F(s),n):(console.warn("Target Missing: ",n.id),n)}function W(e){e.forEach((e=>{console.log("SetCookie: ",e),document.cookie=e}))}function _(e,t){null!=e.query&&function(e){if(e!=function(){const e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);let t=location.pathname+e;window.history.replaceState({},"",t)}}(e.query),null!=e.pageTitle&&(document.title=e.pageTitle),e.events.forEach((e=>{setTimeout((()=>{let n=new CustomEvent(e.name,{bubbles:!0,detail:e.detail});(t||document).dispatchEvent(n)}),10)})),e.actions.forEach((([e,t])=>{setTimeout((()=>{let n=window.Hyperbole.hyperView(e);n&&$(n,t)}),10)}))}function F(e){e.querySelectorAll("[id]").forEach((t=>{t.runAction=function(e){$(this,e)}.bind(t),t.concurrency=t.dataset.concurrency||"Drop",t.cancelActiveRequest=function(){t.activeRequest&&!t.activeRequest?.isCancelled&&(t.activeRequest.isCancelled=!0)},J(e)}))}function J(e){let t=new Event("hyp-content",{bubbles:!0});e.dispatchEvent(t)}document.addEventListener("DOMContentLoaded",(function(){var e;_(b(document.getElementById("hyp.metadata").innerText)),B=document.body.querySelector("style"),B||(console.warn("rootStyles missing from page, creating..."),B=document.createElement("style"),B.type="text/css",document.body.appendChild(B)),e=async function(e,t){$(e,t)},document.addEventListener("hyp-load",(function(t){let n=t.detail.onLoad,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseenter",(function(t){let n=t.detail.onMouseEnter,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseleave",(function(t){let n=t.detail.onMouseLeave,o=t.detail.target;e(o,n)})),M(document.body),j(document.body),P(document.body),F(document.body),N("click",(async function(e,t){$(e,t)})),N("dblclick",(async function(e,t){$(e,t)})),O("keydown",(async function(e,t){$(e,t)})),O("keyup",(async function(e,t){$(e,t)})),document.addEventListener("submit",(function(e){let t=e.target;if(!t?.dataset.onsubmit)return void console.error("Missing onSubmit: ",t);e.preventDefault();let n=V(t);const o=new FormData(t);!async function(e,t,n){$(e,t,n)}(n,t.dataset.onsubmit,o)})),document.addEventListener("change",(function(e){let t=e.target.closest("[data-onchange]");t&&(e.preventDefault(),null!=t.value?async function(e,t){$(e,t)}(V(t),L(t.dataset.onchange,t.value)):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){let t=e.target.closest("[data-oninput]");if(!t)return;let n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 250 can result in poor performance."),!t?.dataset.oninput)return void console.error("Missing onInput: ",t);e.preventDefault();let o=V(t);(function(e){"Replace"==e.concurrency&&e.cancelActiveRequest()})(o),t.debouncedCallback||(t.debouncedCallback=R((()=>{let e=L(t.dataset.oninput,t.value);!async function(e,t){$(e,t)}(o,e)}),n)),t.debouncedCallback()}))}));const Y=new class{constructor(){this.hasEverConnected=!1,this.isConnected=!1,this.reconnectDelay=0,this.queue=[],this.events=new EventTarget}connect(e=T){const t=new WebSocket(e);function n(e){console.error("Connect Error",e)}function o(e){console.error("Socket Error",e)}this.socket=t,t.addEventListener("error",n),t.addEventListener("open",(e=>{console.log("Websocket Connected"),this.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),this.isConnected=!0,this.hasEverConnected=!0,this.reconnectDelay=1e3,t.removeEventListener("error",n),t.addEventListener("error",o),document.dispatchEvent(new Event("hyp-socket-connect")),this.runQueue()})),t.addEventListener("close",(n=>{console.log("CLOSE SOCKET"),this.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),this.isConnected=!1,t.removeEventListener("error",o),this.hasEverConnected&&(console.log("Reconnecting in "+this.reconnectDelay/1e3+"s"),setTimeout((()=>this.connect(e)),this.reconnectDelay)),t.removeEventListener("error",o)})),t.addEventListener("message",(e=>this.onMessage(e)))}async sendAction(e){if(this.isConnected){let t=function(e){let t=["|ACTION|","ViewId: "+e.viewId,"Action: "+e.action];return e.state&&t.push("State: "+e.state),t.push("RequestId: "+e.requestId),[t.join("\n"),(o=e.meta,o.map((e=>e.key+": "+e.value)).join("\n"))].join("\n")+((n=e.form)?"\n\n"+n:"");var n,o}(e);this.socket.send(t)}else this.queue.push(e)}runQueue(){let e=this.queue.pop();e&&(console.log("runQueue: ",e),this.sendAction(e),this.runQueue())}onMessage(e){let{command:t,metas:n,rest:o}=function(e){let t=e.split("\n"),n=t[0],o=g(k,t.slice(1));return{command:n,metas:o,rest:function(e,t){let n=0;for(;n<t.length&&""==t[n];)n++;return t.slice(n)}(0,t.slice(o.length+1))}}(e.data),r=parseInt(i("RequestId"),0);function i(t){let o=E(t,n);if(!o)throw new D("Missing Required Metadata: "+t,e.data);return o}function a(e){let t=i("ViewId"),o=i("Action");return{requestId:r,targetViewId:void 0,viewId:t,action:o,meta:w(n),body:e.join("\n")}}switch(t){case"|UPDATE|":return this.dispatchEvent(new CustomEvent("update",{detail:function(e){let t=a(e);return t.targetViewId=E("TargetViewId",n),t}(o)}));case"|RESPONSE|":return this.dispatchEvent(new CustomEvent("response",{detail:a(o)}));case"|REDIRECT|":return this.dispatchEvent(new CustomEvent("redirect",{detail:function(e){let t=e[0];return{requestId:r,meta:w(n),url:t}}(o)}))}}addEventListener(e,t){this.events.addEventListener(e,t)}dispatchEvent(e){this.events.dispatchEvent(e)}disconnect(){this.isConnected=!1,this.hasEverConnected=!1,this.socket.close()}};Y.connect(),Y.addEventListener("update",(e=>H(e.detail))),Y.addEventListener("response",(e=>function(e){let t=H(e);delete t.activeRequest,clearTimeout(t._timeout),t.classList.remove("hyp-loading")}(e.detail))),Y.addEventListener("redirect",(e=>{return t=e.detail,console.log("REDIRECT",t),W(t.meta.cookies),void(window.location.href=t.url);var t})),window.Hyperbole={runAction:$,parseMetadata:b,action:function(e,...t){return e+t.reduce(((e,t)=>e+" "+JSON.stringify(t)),"")},hyperView:function(e){let t=document.getElementById(e);if(t?.runAction)return t;console.error("Element id="+e+" was not a HyperView")},socket:Y}})()})(); //# sourceMappingURL=hyperbole.js.map
client/dist/hyperbole.js.map view
@@ -1,1 +1,1 @@-{"version":3,"file":"hyperbole.js","mappings":"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","sources":["webpack://web-ui/./node_modules/debounce/index.js","webpack://web-ui/webpack/bootstrap","webpack://web-ui/./node_modules/omdomdom/lib/omdomdom.es.js","webpack://web-ui/./src/action.ts","webpack://web-ui/./src/lib.ts","webpack://web-ui/./src/response.ts","webpack://web-ui/./src/sockets.ts","webpack://web-ui/./src/events.ts","webpack://web-ui/./src/http.ts","webpack://web-ui/./src/index.ts","webpack://web-ui/./src/browser.ts"],"sourcesContent":["function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\t// TODO: Deprecate the boolean parameter at some point.\n\tconst {immediate} = typeof options === 'boolean' ? {immediate: options} : options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n\n// Adds compatibility for ES modules\nmodule.exports.debounce = debounce;\n\nmodule.exports = debounce;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/*!\n * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n * Copyright 2023 George Treviranus <geowtrev@gmail.com>\n */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n if (!key) return false;\n if (hasProperty(map, key)) {\n console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n return false;\n }\n return true;\n};\nvar forEach = function forEach(items, fn) {\n var length = items.length;\n var idx = -1;\n if (!length) return;\n while (++idx < length) {\n if (fn(items[idx], idx) === false) break;\n }\n};\nvar createKeyMap = function createKeyMap(children) {\n var map = {};\n forEach(children, function (child) {\n var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n if (keyIsValid(map, key)) {\n map[key] = child;\n }\n });\n for (var _ in map) {\n return map;\n }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n for (var property in template) {\n vNode[property] = template[property];\n }\n};\n\nvar toHTML = function toHTML(htmlString) {\n var processedDOMString = htmlString.trim().replace(/\\s+</g, \"<\").replace(/>\\s+/g, \">\");\n var parser = new DOMParser();\n var context = parser.parseFromString(processedDOMString, \"text/html\");\n return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n if (null != _i) {\n var _s,\n _e,\n _x,\n _r,\n _arr = [],\n _n = !0,\n _d = !1;\n try {\n if (_x = (_i = _i.call(arr)).next, 0 === i) {\n if (Object(_i) !== _i) return;\n _n = !1;\n } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n } catch (err) {\n _d = !0, _e = err;\n } finally {\n try {\n if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n } finally {\n if (_d) throw _e;\n }\n }\n return _arr;\n }\n}\nfunction _slicedToArray(arr, i) {\n return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n return arr2;\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n STRING: \"string\",\n INT: \"number\",\n BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n return {\n attrName: attrName,\n propName: propName,\n type: type\n };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n attr = _ref2[0],\n property = _ref2[1];\n DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n attr = _ref4[0],\n property = _ref4[1];\n DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n xlink: {\n prefix: \"xlink:\",\n resource: \"http://www.w3.org/1999/xlink\"\n },\n xml: {\n prefix: \"xml:\",\n resource: \"http://www.w3.org/XML/1998/namespace\"\n }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n switch (type) {\n case Types.STRING:\n {\n if (prop === DomProperties.style.propName) {\n if (value === null) {\n element.style[prop] = \"\";\n } else {\n element.style[prop] = value;\n }\n } else if (value === null) {\n element[prop] = \"\";\n } else {\n element[prop] = value;\n }\n break;\n }\n case Types.INT:\n {\n if (value === null) {\n var attr = prop.toLowerCase();\n element.removeAttribute(attr);\n } else if (value === \"0\") {\n element[prop] = 0;\n } else if (value === \"-1\") {\n element[prop] = -1;\n } else {\n var parsed = parseInt(value, 10);\n if (!isNaN(parsed)) {\n element[prop] = parsed;\n }\n }\n break;\n }\n case Types.BOOL:\n {\n if ([\"\", \"true\"].indexOf(value) < 0) {\n element[prop] = false;\n } else {\n element[prop] = true;\n }\n break;\n }\n }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n forEach(attributes, function (attrName) {\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n } else {\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, null);\n }\n vNode.node.removeAttribute(attrName);\n }\n delete vNode.attributes[attrName];\n });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n for (var attrName in attributes) {\n var value = attributes[attrName];\n vNode.attributes[attrName] = value;\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xlink.prefix)) {\n vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xml.prefix)) {\n vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n continue;\n }\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, value);\n }\n vNode.node.setAttribute(attrName, value || \"\");\n }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n for (var attrName in DomProperties) {\n var propertyRecord = DomProperties[attrName];\n var propName = propertyRecord.propName;\n var attrValue = element.getAttribute(attrName);\n if (attrName === DomProperties.style.attrName) {\n attributes[attrName] = element.style[propName];\n } else if (typeof attrValue === \"string\") {\n attributes[attrName] = attrValue;\n }\n }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n if (!hasProperty(DomProperties, attrName.name)) {\n attributes[attrName.name] = attrName.value;\n }\n return attributes;\n }, {});\n};\nvar getAttributes = function getAttributes(element) {\n var attributes = getBaseAttributes(element);\n getPropertyValues(element, attributes);\n return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n var removedAttributes = [];\n var changedAttributes = {};\n for (var attrName in vNode.attributes) {\n var oldValue = vNode.attributes[attrName];\n var nextValue = template.attributes[attrName];\n if (oldValue === nextValue) continue;\n if (typeof nextValue === \"undefined\") {\n removedAttributes.push(attrName);\n }\n }\n for (var _attrName in template.attributes) {\n var _oldValue = vNode.attributes[_attrName];\n var _nextValue = template.attributes[_attrName];\n if (_oldValue === _nextValue) continue;\n if (typeof _nextValue !== \"undefined\") {\n changedAttributes[_attrName] = _nextValue;\n }\n }\n removeAttributes(vNode, removedAttributes);\n setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n var templateChildrenLength = template.children.length;\n var vNodeChildrenLength = vNode.children.length;\n if (!templateChildrenLength && !vNodeChildrenLength) return;\n var vNodeKeyMap = createKeyMap(vNode.children);\n var nextChildren = Array(templateChildrenLength);\n if (vNodeKeyMap !== undefined) {\n forEach(template.children, function (templateChild, idx) {\n var childNodes = vNode.node.childNodes;\n var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n var keyedChild = vNodeKeyMap[key];\n if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n insertBefore(vNode, keyedChild, childNodes[idx]);\n }\n nextChildren[idx] = keyedChild;\n delete vNodeKeyMap[key];\n patch(templateChild, nextChildren[idx]);\n } else {\n insertBefore(vNode, templateChild, childNodes[idx]);\n nextChildren[idx] = templateChild;\n }\n });\n } else {\n forEach(template.children, function (templateChild, idx) {\n var vNodeChild = vNode.children[idx];\n if (typeof vNodeChild !== \"undefined\") {\n patch(templateChild, vNodeChild);\n nextChildren[idx] = vNodeChild;\n } else {\n vNode.node.appendChild(templateChild.node);\n nextChildren[idx] = templateChild;\n }\n });\n }\n vNode.children = nextChildren;\n var childNodesLength = vNode.node.childNodes.length;\n var delta = childNodesLength - templateChildrenLength;\n if (delta > 0) {\n while (delta > 0) {\n vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n childNodesLength--;\n delta--;\n }\n }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n if (!template || !vNode) return;\n rootNode = rootNode || vNode.node.parentNode;\n var contentChanged = template.content && template.content !== vNode.content;\n if (template.type !== vNode.type || contentChanged) {\n rootNode.replaceChild(template.node, vNode.node);\n return assignVNode(template, vNode);\n }\n updateAttributes(template, vNode);\n patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n if (typeof node === \"string\") {\n node = toHTML(node);\n }\n var isRoot = node.tagName === \"BODY\";\n var childNodes = node.childNodes;\n var numChildNodes = childNodes ? childNodes.length : 0;\n if (isRoot) {\n if (numChildNodes > 1) {\n throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n } else if (numChildNodes === 0) {\n throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n } else {\n return create(childNodes[0]);\n }\n }\n var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n var isSVG = isSVGContext || type === \"svg\";\n var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n var content = numChildNodes > 0 ? null : node.textContent;\n var children = Array(numChildNodes);\n forEach(childNodes, function (child, idx) {\n children[idx] = create(child, isSVG);\n });\n return {\n type: type,\n attributes: attributes,\n children: children,\n content: content,\n node: node,\n isSVGContext: isSVG\n };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","\nimport { takeWhileMap } from \"./lib\"\n\n\ntype EncodedAction = string\n\nexport type ActionMessage = {\n viewId: ViewId\n action: EncodedAction\n requestId: RequestId\n meta: Meta[]\n form: URLSearchParams | undefined\n}\n\nexport type ViewId = string\nexport type RequestId = string\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, reqId: RequestId, form?: FormData): ActionMessage {\n let meta: Meta[] = [\n { key: \"Cookie\", value: decodeURI(document.cookie) },\n { key: \"Query\", value: window.location.search }\n ]\n\n return { viewId: id, action, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n if (!form) return undefined\n\n const params = new URLSearchParams()\n\n form.forEach((value, key) => {\n params.append(key, value as string)\n })\n\n return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n let header = [\n \"|ACTION|\",\n \"ViewId: \" + msg.viewId,\n \"Action: \" + msg.action,\n \"RequestId: \" + msg.requestId\n ]\n\n\n return [\n header.join('\\n'),\n renderMetadata(msg.meta),\n ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n if (!form) return \"\"\n return \"\\n\\n\" + form\n}\n\n\nexport function requestId(): RequestId {\n return Math.random().toString(36).substring(2, 8)\n}\n\n\n// Metadata -------------------------------------\n\n\ntype Meta = { key: string, value: string }\ntype RemoteEvent = { name: string, detail: any }\n\nexport type Metadata = {\n requestId: string\n cookies: string[]\n redirect?: string\n error?: string\n query?: string\n events?: RemoteEvent[]\n actions?: [ViewId, string][],\n pageTitle?: string\n}\n\n\nexport type ParsedResponse = {\n metadata: Metadata,\n rest: string[]\n}\n\nexport function renderMetadata(meta: Meta[]): string {\n return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport function parseMetas(meta: Meta[]): Metadata {\n\n let requestId = meta.find(m => m.key == \"RequestId\")?.value\n\n return {\n cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n redirect: meta.find(m => m.key == \"Redirect\")?.value,\n error: meta.find(m => m.key == \"Error\")?.value,\n query: meta.find(m => m.key == \"Query\")?.value,\n events: meta.filter(m => m.key == \"Event\").map((m) => parseRemoteEvent(m.value)),\n actions: meta.filter(m => m.key == \"Trigger\").map((m) => parseAction(m.value)),\n pageTitle: meta.find(m => m.key == \"PageTitle\")?.value,\n requestId\n }\n}\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\n// decode entities\nexport function parseMetadata(input: string): Metadata {\n return splitMetadata(input.trim().split(\"\\n\")).metadata\n\n}\n\nexport function splitMetadata(lines: string[]): ParsedResponse {\n let metas: Meta[] = takeWhileMap(parseMeta, lines)\n // console.log(\"Split Metadata\", lines.length)\n // console.log(\" [0]\", lines[0])\n // console.log(\" [1]\", lines[1])\n let rest = lines.slice(metas.length)\n\n return {\n metadata: parseMetas(metas),\n rest: rest\n }\n\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n let [name, data] = breakNextSegment(input)\n return {\n name,\n detail: JSON.parse(data)\n }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n let [viewId, action] = breakNextSegment(input)\n return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] | undefined {\n let ix = input.indexOf('|')\n if (ix === -1) {\n let err = new Error(\"Bad Encoding, Expected Segment\")\n err.message = input\n throw err\n }\n return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n let match = line.match(/^(\\w+)\\: (.*)$/)\n if (match) {\n return {\n key: match[1],\n value: match[2]\n }\n }\n}\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n if (param == \"\") {\n return \"|\"\n }\n\n return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n","\n\nexport function takeWhileMap<T, A>(pred: (val: T) => A | undefined, lines: T[]): A[] {\n var output = []\n for (var line of lines) {\n let a = pred(line)\n if (a)\n output.push(a)\n else\n break;\n }\n\n return output\n}\n\n// export function dropWhile<T, A>(pred: (val: T) => A | undefined, lines: T[]): T[] {\n// let index = 0;\n// while (index < lines.length && pred(lines[index])) {\n// index++;\n// }\n// return lines.slice(index);\n// }\n//\n","\nimport { ViewId } from './action'\nimport { Metadata } from \"./action\"\n\n\n\nexport type Response = {\n meta: Metadata\n body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n const parser = new DOMParser()\n const doc = parser.parseFromString(res, 'text/html')\n const css = doc.querySelector(\"style\") as HTMLStyleElement\n const content = doc.querySelector(\"div\") as HTMLElement\n\n return {\n content: content,\n css: css\n }\n}\n\nexport type LiveUpdate = {\n content: HTMLElement\n css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n viewId: ViewId\n body: string\n constructor(viewId: ViewId, msg: string, body: string) {\n super(msg)\n this.viewId = viewId\n this.name = \"Fetch Error\"\n this.body = body\n }\n}\n","import { ActionMessage, ViewId, RequestId, renderActionMessage } from './action'\nimport { Metadata, ParsedResponse, splitMetadata } from \"./action\"\nimport { Response, FetchError } from \"./response\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\n\n\nexport class SocketConnection {\n socket: WebSocket\n\n hasEverConnected: Boolean\n isConnected: Boolean\n reconnectDelay: number = 0\n\n constructor() {\n }\n\n // we need to faithfully transmit the \n connect(addr = defaultAddress) {\n const sock = new WebSocket(addr)\n this.socket = sock\n\n function onConnectError(ev: Event) {\n console.log(\"Connection Error\", ev)\n }\n\n sock.addEventListener('error', onConnectError)\n\n sock.addEventListener('open', (_event) => {\n console.log(\"Websocket Connected\")\n\n if (this.hasEverConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n }\n\n this.isConnected = true\n this.hasEverConnected = true\n this.reconnectDelay = 1000\n this.socket.removeEventListener('error', onConnectError)\n document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n })\n\n sock.addEventListener('close', _ => {\n if (this.isConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n }\n\n this.isConnected = false\n\n // attempt to reconnect in 1s\n if (this.hasEverConnected) {\n console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n setTimeout(() => this.connect(addr), this.reconnectDelay)\n }\n\n })\n }\n\n async sendAction(action: ActionMessage): Promise<Response> {\n let msg = renderActionMessage(action)\n let { metadata, rest } = await this.fetch(action.requestId, action.viewId, msg)\n\n return {\n meta: metadata,\n body: rest.join('\\n')\n }\n\n }\n\n async fetch(reqId: RequestId, id: ViewId, msg: string): Promise<ParsedResponse> {\n this.sendMessage(msg)\n let res = await this.waitMessage(reqId, id)\n return res\n }\n\n private sendMessage(msg: string) {\n this.socket.send(msg)\n }\n\n private async waitMessage(reqId: RequestId, id: ViewId): Promise<ParsedResponse> {\n return new Promise((resolve, reject) => {\n const onMessage = (event: MessageEvent) => {\n let data: string = event.data\n let lines = data.split(\"\\n\").slice(1) // drop the command line\n\n let parsed = splitMetadata(lines)\n let metadata: Metadata = parsed.metadata\n\n if (!metadata.requestId) {\n console.error(\"Missing RequestId!\", metadata, event.data)\n return\n }\n\n if (metadata.requestId != reqId) {\n // skip, it's not us!\n return\n }\n\n\n // We have found our message. Remove the listener\n this.socket.removeEventListener('message', onMessage)\n\n // set the cookies. These happen automatically in http\n metadata.cookies.forEach((cookie: string) => {\n document.cookie = cookie\n })\n\n if (metadata.error) {\n reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n return\n }\n\n resolve(parsed)\n }\n\n this.socket.addEventListener('message', onMessage)\n this.socket.addEventListener('error', reject)\n })\n }\n\n disconnect() {\n this.socket.close()\n }\n}\n\n\n\n\n\n\n","\nimport * as debounce from 'debounce'\nimport { encodedParam } from './action'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"Keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"Keyup\", cb)\n}\n\nexport function listenKeyEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event.toLowerCase(), function(e: KeyboardEvent) {\n let source = e.target as HTMLInputElement\n\n let datasetKey = \"on\" + event + e.key\n let action = source.dataset[datasetKey]\n if (!action) return\n\n e.preventDefault()\n cb(nearestTarget(source), action)\n })\n}\n\nexport function listenBubblingEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event, function(e) {\n let el = e.target as HTMLInputElement\n\n // clicks can fire on internal elements. Find the parent with a click handler\n let source = el.closest(\"[data-on\" + event + \"]\") as HTMLElement\n if (!source) return\n\n e.preventDefault()\n let target = nearestTarget(source)\n cb(target, source.dataset[\"on\" + event])\n })\n}\n\nexport function listenClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n let action = e.detail.onLoad\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n let action = e.detail.onMouseEnter\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n let action = e.detail.onMouseLeave\n let target = e.detail.target\n cb(target, action)\n })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n // it doesn't really matter WHO runs this except that it should have target\n node.querySelectorAll(\"[data-onload]\").forEach((load: HTMLElement) => {\n let delay = parseInt(load.dataset.delay) || 0\n let onLoad = load.dataset.onload\n // console.log(\"load start\", load.dataset.onLoad)\n\n // load no longer exists!\n // we should clear the timeout or back out if the dom is replaced in the interem\n setTimeout(() => {\n let target = nearestTarget(load)\n // console.log(\"load go\", load.dataset.onLoad)\n\n if (load.dataset.onload != onLoad) {\n // the onLoad no longer exists\n return\n }\n\n const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n load.dispatchEvent(event)\n }, delay)\n })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseenter]\").forEach((node: HTMLElement) => {\n let onMouseEnter = node.dataset.onmouseenter\n\n let target = nearestTarget(node)\n\n node.onmouseenter = () => {\n const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n node.dispatchEvent(event)\n }\n })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseleave]\").forEach((node: HTMLElement) => {\n let onMouseLeave = node.dataset.onmouseleave\n\n let target = nearestTarget(node)\n\n node.onmouseleave = () => {\n const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n node.dispatchEvent(event)\n }\n })\n}\n\n\nexport function listenChange(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"change\", function(e) {\n let el = e.target as HTMLElement\n\n let source = el.closest(\"[data-onchange]\") as HTMLInputElement\n\n if (!source) return\n e.preventDefault()\n\n if (source.value == null) {\n console.error(\"Missing input value:\", source)\n return\n }\n\n let target = nearestTarget(source)\n let action = encodedParam(source.dataset.onchange, source.value)\n cb(target, action)\n })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n debouncedCallback?: Function;\n}\n\nexport function listenInput(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"input\", function(e) {\n let el = e.target as HTMLElement\n let source = el.closest(\"[data-oninput]\") as LiveInputElement\n\n if (!source) return\n\n let delay = parseInt(source.dataset.delay) || 250\n if (delay < 250) {\n console.warn(\"Input delay < 250 can result in poor performance.\")\n }\n\n if (!source?.dataset.oninput) {\n console.error(\"Missing onInput: \", source)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(source)\n\n if (!source.debouncedCallback) {\n source.debouncedCallback = debounce(() => {\n let action = encodedParam(source.dataset.oninput, source.value)\n cb(target, action)\n }, delay)\n }\n\n source.debouncedCallback()\n })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HTMLElement, action: string, form: FormData) => void): void {\n document.addEventListener(\"submit\", function(e) {\n let form = e.target as HTMLFormElement\n\n if (!form?.dataset.onsubmit) {\n console.error(\"Missing onSubmit: \", form)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(form)\n const formData = new FormData(form)\n cb(target, form.dataset.onsubmit, formData)\n })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n let targetData = node.closest(\"[data-target]\") as HTMLElement | undefined\n return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestTarget(node: HTMLElement): HTMLElement {\n let targetId = nearestTargetId(node)\n let target = document.getElementById(targetId)\n\n if (!target) {\n console.error(\"Cannot find target: \", targetId, node)\n return\n }\n\n return target\n}\n","import { ActionMessage, splitMetadata, ParsedResponse } from './action'\nimport { Response, FetchError } from \"./response\"\n\nexport async function sendActionHttp(msg: ActionMessage): Promise<Response> {\n // console.log(\"HTTP sendAction\", msg.url.toString())\n let url = window.location.href\n let res = await fetch(url, {\n method: \"POST\",\n headers:\n {\n 'Accept': 'text/html',\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Hyp-RequestId': msg.requestId,\n 'Hyp-ViewId': msg.viewId,\n 'Hyp-Action': msg.action\n },\n body: msg.form,\n // we never want this to be redirected\n redirect: \"manual\"\n })\n\n let body = await res.text()\n let { metadata, rest } = parseMetadataHttp(body)\n\n if (!res.ok) {\n throw new FetchError(msg.viewId, body, body)\n }\n\n let response: Response = {\n meta: metadata,\n body: rest.join('\\n')\n }\n\n return response\n}\n\n\nexport function parseMetadataHttp(inp: string): ParsedResponse {\n let lines = inp.split(\"\\n\")\n // drop the <script> start line\n let { metadata, rest } = splitMetadata(lines.slice(1))\n // drop the </script> end line and 2x whitespace\n return { metadata, rest: rest.slice(2) }\n}\n\n\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, ActionMessage, requestId, ViewId, parseMetadata, Metadata } from './action'\nimport { sendActionHttp } from './http'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, Response, LiveUpdate } from './response'\n\nlet PACKAGE = require('../package.json');\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version)\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\nasync function sendAction(msg: ActionMessage): Promise<Response> {\n if (sock.isConnected) {\n return sock.sendAction(msg)\n }\n else {\n return sendActionHttp(msg)\n }\n}\n\n\n\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n // console.log(\"runAction\", target.id, action)\n\n if (target === undefined) {\n console.error(\"Undefined HyperView!\")\n return\n }\n\n if (action === undefined) {\n console.error(\"Undefined Action!\", target, \"this is a bug, please report: https://github.com/seanhess/hyperbole\")\n return\n }\n\n let timeout = setTimeout(() => {\n // add loading after 100ms, not right away\n // if it runs shorter than that we probably don't want to add loading effects\n target.classList.add(\"hyp-loading\")\n }, 100)\n\n let reqId = requestId()\n let msg = actionMessage(target.id, action, reqId, form)\n\n // Ignore any request if a requestId is active\n if (target.dataset.requestId) {\n console.warn(\"Action overlaps with active request (\" + target.dataset.requestId + \")\", action)\n return\n }\n\n // Set the requestId\n target.dataset.requestId = reqId\n\n try {\n let res: Response = await sendAction(msg)\n\n if (res.meta.requestId != target.dataset.requestId) {\n let err = new Error()\n err.name = \"Concurrency Error\"\n err.message = \"Stale Action (\" + reqId + \"):\" + action\n throw err\n }\n else {\n delete target.dataset.requestId\n }\n\n let shouldStop = runMetadataImmediate(res.meta)\n\n if (shouldStop) {\n return\n }\n\n let update: LiveUpdate = parseResponse(res.body)\n\n if (!update.content) {\n console.error(\"Empty Response!\", res.body)\n return\n }\n\n // First, update the stylesheet\n addCSS(update.css)\n\n\n // Patch the node\n const old: VNode = create(target)\n let next: VNode = create(update.content)\n next.attributes = old.attributes\n patch(next, old)\n\n\n // Emit relevant events\n let newTarget = document.getElementById(target.id)\n dispatchContent(newTarget)\n\n if (newTarget) {\n // execute the metadata, anything that doesn't interrupt the dom update\n runMetadataDOM(res.meta, newTarget)\n\n // now way for these to bubble)\n listenLoad(newTarget)\n listenMouseEnter(newTarget)\n listenMouseLeave(newTarget)\n fixInputs(newTarget)\n enrichHyperViews(newTarget)\n }\n else {\n console.warn(\"Target Missing: \", target.id)\n }\n\n }\n catch (err) {\n console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n\n // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n // but if you manage to crash your parent server process somehow, the response may be empty\n target.innerHTML = err.body || \"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>\"\n }\n\n\n // Remove loading and clear add timeout\n clearTimeout(timeout)\n target.classList.remove(\"hyp-loading\")\n}\n\nfunction runMetadataImmediate(meta: Metadata): boolean {\n if (meta.redirect) {\n // perform a redirect immediately\n window.location.href = meta.redirect\n return true\n }\n\n if (meta.query != null) {\n setQuery(meta.query)\n }\n\n if (meta.pageTitle != null) {\n document.title = meta.pageTitle\n }\n}\n\nfunction runMetadataDOM(meta: Metadata, target?: HTMLElement) {\n for (var remoteEvent of meta.events) {\n setTimeout(() => {\n // console.log(\"dipsatching custom event\", remoteEvent)\n let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n let eventTarget = target || document\n eventTarget.dispatchEvent(event)\n }, 10)\n }\n\n meta.actions.forEach(([viewId, action]) => {\n setTimeout(() => {\n let view = window.Hyperbole.hyperView(viewId)\n if (view) {\n runAction(view, action)\n }\n }, 10)\n })\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n let focused = target.querySelector(\"[autofocus]\") as HTMLInputElement\n if (focused?.focus) {\n focused.focus()\n }\n\n target.querySelectorAll(\"input[value]\").forEach((input: HTMLInputElement) => {\n let val = input.getAttribute(\"value\")\n if (val !== undefined) {\n input.value = val\n }\n })\n\n target.querySelectorAll(\"input[type=checkbox]\").forEach((checkbox: HTMLInputElement) => {\n let checked = checkbox.dataset.checked == \"True\"\n checkbox.checked = checked\n })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n if (!src) return;\n const rules: any = src.sheet.cssRules\n for (const rule of rules) {\n if (addedRulesIndex.has(rule.cssText) == false) {\n rootStyles.sheet.insertRule(rule.cssText);\n addedRulesIndex.add(rule.cssText);\n }\n }\n}\n\n\n\n\nfunction init() {\n // metadata attached to initial page loads need to be executed\n let meta = parseMetadata(document.getElementById(\"hyp.metadata\").innerText)\n runMetadataImmediate(meta)\n runMetadataDOM(meta)\n\n rootStyles = document.querySelector('style')\n\n listenTopLevel(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n listenLoad(document.body)\n listenMouseEnter(document.body)\n listenMouseLeave(document.body)\n enrichHyperViews(document.body)\n\n\n listenClick(async function(target: HyperView, action: string) {\n // console.log(\"CLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenDblClick(async function(target: HyperView, action: string) {\n // console.log(\"DBLCLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenKeydown(async function(target: HyperView, action: string) {\n // console.log(\"KEYDOWN\", target.id, action)\n runAction(target, action)\n })\n\n listenKeyup(async function(target: HyperView, action: string) {\n // console.log(\"KEYUP\", target.id, action)\n runAction(target, action)\n })\n\n listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n // console.log(\"FORM\", target.id, action, form)\n runAction(target, action, form)\n })\n\n listenChange(async function(target: HyperView, action: string) {\n console.log(\"CHANGE\", target.id, \"(\" + action + \")\")\n runAction(target, action)\n })\n\n listenInput(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n}\n\n\n\nfunction enrichHyperViews(node: HTMLElement): void {\n // enrich all the hyperviews\n node.querySelectorAll(\"[id]\").forEach((element: HyperView) => {\n element.runAction = function(action: string) {\n runAction(this, action)\n }.bind(element)\n\n dispatchContent(node)\n })\n}\n\nfunction dispatchContent(node: HTMLElement): void {\n let event = new Event(\"hyp-content\", { bubbles: true })\n node.dispatchEvent(event)\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\n// Should we connect to the socket or not?\nconst sock = new SocketConnection()\nsock.connect()\n\n\n\n\n\n\ntype VNode = {\n // One of three value types are used:\n // - The tag name of the element\n // - \"text\" if text node\n // - \"comment\" if comment node\n type: string\n\n // An object whose key/value pairs are the attribute\n // name and value, respectively\n attributes: [string: string]\n\n // Is set to `true` if a node is an `svg`, which tells\n // Omdomdom to treat it, and its children, as such\n isSVGContext: Boolean\n\n // The content of a \"text\" or \"comment\" node\n content: string\n\n // An array of virtual node children\n children: Array<VNode>\n\n // The real DOM node\n node: Node\n}\n\n\n\n\n\ndeclare global {\n interface Window {\n Hyperbole?: HyperboleAPI;\n }\n}\n\nexport interface HyperboleAPI {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n action(con: string, ...params: any[]): string\n hyperView(viewId: ViewId): HyperView | undefined\n parseMetadata(input: string): Metadata\n socket: SocketConnection\n}\n\n\n\n\nexport interface HyperView extends HTMLElement {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n}\n\n\n\nwindow.Hyperbole =\n{\n runAction: runAction,\n parseMetadata: parseMetadata,\n action: function(con, ...params: any[]) {\n let ps = params.reduce((str, param) => str + \" \" + JSON.stringify(param), \"\")\n return con + ps\n },\n hyperView: function(viewId) {\n let element = document.getElementById(viewId) as any\n if (!element?.runAction) {\n console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n return undefined\n }\n return element\n },\n socket: sock\n}\n","\nexport function setQuery(query: string) {\n if (query != currentQuery()) {\n if (query != \"\") query = \"?\" + query\n let url = location.pathname + query\n // console.log(\"history.replaceState(\", url, \")\")\n window.history.replaceState({}, \"\", url)\n }\n}\n\nfunction currentQuery(): string {\n const query = window.location.search;\n return query.startsWith('?') ? query.substring(1) : query;\n}\n"],"names":["debounce","function_","wait","options","TypeError","RangeError","immediate","storedContext","storedArguments","timeoutId","timestamp","result","run","callContext","callArguments","undefined","apply","later","last","Date","now","setTimeout","debounced","arguments_","this","Object","getPrototypeOf","Error","callNow","defineProperty","get","clear","clearTimeout","flush","trigger","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","hasProperty","obj","prop","prototype","hasOwnProperty","call","forEach","items","fn","length","idx","insertBefore","parent","child","refNode","node","_slicedToArray","arr","i","Array","isArray","_arrayWithHoles","_i","Symbol","iterator","_s","_e","_x","_r","_arr","_n","_d","next","done","push","value","err","return","_iterableToArrayLimit","o","minLen","_arrayLikeToArray","n","toString","slice","constructor","name","from","test","_unsupportedIterableToArray","_nonIterableRest","len","arr2","Types","DomProperties","createRecord","attrName","propName","type","_ref","_ref2","attr","property","_ref3","_ref4","Namespace","setProperty","element","style","toLowerCase","removeAttribute","parsed","parseInt","isNaN","indexOf","patch","template","vNode","rootNode","parentNode","contentChanged","content","replaceChild","assignVNode","removedAttributes","changedAttributes","attributes","oldValue","nextValue","_attrName","_oldValue","_nextValue","propertyRecord","removeAttributes","startsWith","setAttributeNS","setAttribute","setAttributes","updateAttributes","templateChildrenLength","children","vNodeChildrenLength","vNodeKeyMap","map","_","key","console","warn","keyIsValid","createKeyMap","nextChildren","templateChild","childNodes","keyedChild","vNodeChild","appendChild","childNodesLength","delta","removeChild","patchChildren","create","processedDOMString","isSVGContext","arguments","trim","replace","DOMParser","parseFromString","body","isRoot","tagName","numChildNodes","nodeType","isSVG","reduce","getBaseAttributes","attrValue","getAttribute","getPropertyValues","getAttributes","textContent","toSearch","form","params","URLSearchParams","append","parseMetadata","input","splitMetadata","split","metadata","lines","meta","requestId","metas","pred","output","a","takeWhileMap","parseMeta","rest","find","m","cookies","filter","redirect","error","query","events","breakNextSegment","data","detail","JSON","parse","parseRemoteEvent","actions","parseAction","pageTitle","ix","message","line","match","encodedParam","action","param","sanitizeParam","viewId","msg","protocol","window","location","defaultAddress","host","pathname","reconnectDelay","connect","addr","sock","WebSocket","onConnectError","ev","log","socket","addEventListener","_event","hasEverConnected","document","dispatchEvent","Event","isConnected","removeEventListener","sendAction","join","renderActionMessage","fetch","reqId","id","sendMessage","waitMessage","send","Promise","resolve","reject","onMessage","event","cookie","FetchError","disconnect","close","listenKeyEvent","cb","e","source","target","datasetKey","dataset","preventDefault","nearestTarget","listenBubblingEvent","closest","listenLoad","querySelectorAll","load","delay","onLoad","onload","CustomEvent","bubbles","listenMouseEnter","onMouseEnter","onmouseenter","listenMouseLeave","onMouseLeave","onmouseleave","targetId","targetData","nearestTargetId","getElementById","sendActionHttp","url","href","method","headers","res","text","inp","parseMetadataHttp","ok","rootStyles","PACKAGE","version","addedRulesIndex","Set","runAction","timeout","classList","add","Math","random","substring","decodeURI","search","actionMessage","runMetadataImmediate","update","doc","css","querySelector","parseResponse","src","sheet","cssRules","rule","has","cssText","insertRule","addCSS","old","dispatchContent","newTarget","runMetadataDOM","focused","focus","val","checkbox","checked","fixInputs","enrichHyperViews","innerHTML","remove","currentQuery","history","replaceState","setQuery","title","remoteEvent","view","Hyperbole","hyperView","bind","innerText","onsubmit","formData","FormData","onchange","oninput","debouncedCallback","SocketConnection","con","str","stringify"],"sourceRoot":""}+{"version":3,"file":"hyperbole.js","mappings":"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","sources":["webpack://web-ui/./node_modules/debounce/index.js","webpack://web-ui/webpack/bootstrap","webpack://web-ui/./node_modules/omdomdom/lib/omdomdom.es.js","webpack://web-ui/./src/lib.ts","webpack://web-ui/./src/message.ts","webpack://web-ui/./src/action.ts","webpack://web-ui/./src/sockets.ts","webpack://web-ui/./src/events.ts","webpack://web-ui/./src/index.ts","webpack://web-ui/./src/response.ts","webpack://web-ui/./src/browser.ts"],"sourcesContent":["function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\t// TODO: Deprecate the boolean parameter at some point.\n\tconst {immediate} = typeof options === 'boolean' ? {immediate: options} : options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n\n// Adds compatibility for ES modules\nmodule.exports.debounce = debounce;\n\nmodule.exports = debounce;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/*!\n * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n * Copyright 2023 George Treviranus <geowtrev@gmail.com>\n */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n if (!key) return false;\n if (hasProperty(map, key)) {\n console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n return false;\n }\n return true;\n};\nvar forEach = function forEach(items, fn) {\n var length = items.length;\n var idx = -1;\n if (!length) return;\n while (++idx < length) {\n if (fn(items[idx], idx) === false) break;\n }\n};\nvar createKeyMap = function createKeyMap(children) {\n var map = {};\n forEach(children, function (child) {\n var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n if (keyIsValid(map, key)) {\n map[key] = child;\n }\n });\n for (var _ in map) {\n return map;\n }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n for (var property in template) {\n vNode[property] = template[property];\n }\n};\n\nvar toHTML = function toHTML(htmlString) {\n var processedDOMString = htmlString.trim().replace(/\\s+</g, \"<\").replace(/>\\s+/g, \">\");\n var parser = new DOMParser();\n var context = parser.parseFromString(processedDOMString, \"text/html\");\n return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n if (null != _i) {\n var _s,\n _e,\n _x,\n _r,\n _arr = [],\n _n = !0,\n _d = !1;\n try {\n if (_x = (_i = _i.call(arr)).next, 0 === i) {\n if (Object(_i) !== _i) return;\n _n = !1;\n } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n } catch (err) {\n _d = !0, _e = err;\n } finally {\n try {\n if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n } finally {\n if (_d) throw _e;\n }\n }\n return _arr;\n }\n}\nfunction _slicedToArray(arr, i) {\n return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n return arr2;\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n STRING: \"string\",\n INT: \"number\",\n BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n return {\n attrName: attrName,\n propName: propName,\n type: type\n };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n attr = _ref2[0],\n property = _ref2[1];\n DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n attr = _ref4[0],\n property = _ref4[1];\n DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n xlink: {\n prefix: \"xlink:\",\n resource: \"http://www.w3.org/1999/xlink\"\n },\n xml: {\n prefix: \"xml:\",\n resource: \"http://www.w3.org/XML/1998/namespace\"\n }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n switch (type) {\n case Types.STRING:\n {\n if (prop === DomProperties.style.propName) {\n if (value === null) {\n element.style[prop] = \"\";\n } else {\n element.style[prop] = value;\n }\n } else if (value === null) {\n element[prop] = \"\";\n } else {\n element[prop] = value;\n }\n break;\n }\n case Types.INT:\n {\n if (value === null) {\n var attr = prop.toLowerCase();\n element.removeAttribute(attr);\n } else if (value === \"0\") {\n element[prop] = 0;\n } else if (value === \"-1\") {\n element[prop] = -1;\n } else {\n var parsed = parseInt(value, 10);\n if (!isNaN(parsed)) {\n element[prop] = parsed;\n }\n }\n break;\n }\n case Types.BOOL:\n {\n if ([\"\", \"true\"].indexOf(value) < 0) {\n element[prop] = false;\n } else {\n element[prop] = true;\n }\n break;\n }\n }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n forEach(attributes, function (attrName) {\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n } else {\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, null);\n }\n vNode.node.removeAttribute(attrName);\n }\n delete vNode.attributes[attrName];\n });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n for (var attrName in attributes) {\n var value = attributes[attrName];\n vNode.attributes[attrName] = value;\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xlink.prefix)) {\n vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xml.prefix)) {\n vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n continue;\n }\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, value);\n }\n vNode.node.setAttribute(attrName, value || \"\");\n }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n for (var attrName in DomProperties) {\n var propertyRecord = DomProperties[attrName];\n var propName = propertyRecord.propName;\n var attrValue = element.getAttribute(attrName);\n if (attrName === DomProperties.style.attrName) {\n attributes[attrName] = element.style[propName];\n } else if (typeof attrValue === \"string\") {\n attributes[attrName] = attrValue;\n }\n }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n if (!hasProperty(DomProperties, attrName.name)) {\n attributes[attrName.name] = attrName.value;\n }\n return attributes;\n }, {});\n};\nvar getAttributes = function getAttributes(element) {\n var attributes = getBaseAttributes(element);\n getPropertyValues(element, attributes);\n return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n var removedAttributes = [];\n var changedAttributes = {};\n for (var attrName in vNode.attributes) {\n var oldValue = vNode.attributes[attrName];\n var nextValue = template.attributes[attrName];\n if (oldValue === nextValue) continue;\n if (typeof nextValue === \"undefined\") {\n removedAttributes.push(attrName);\n }\n }\n for (var _attrName in template.attributes) {\n var _oldValue = vNode.attributes[_attrName];\n var _nextValue = template.attributes[_attrName];\n if (_oldValue === _nextValue) continue;\n if (typeof _nextValue !== \"undefined\") {\n changedAttributes[_attrName] = _nextValue;\n }\n }\n removeAttributes(vNode, removedAttributes);\n setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n var templateChildrenLength = template.children.length;\n var vNodeChildrenLength = vNode.children.length;\n if (!templateChildrenLength && !vNodeChildrenLength) return;\n var vNodeKeyMap = createKeyMap(vNode.children);\n var nextChildren = Array(templateChildrenLength);\n if (vNodeKeyMap !== undefined) {\n forEach(template.children, function (templateChild, idx) {\n var childNodes = vNode.node.childNodes;\n var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n var keyedChild = vNodeKeyMap[key];\n if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n insertBefore(vNode, keyedChild, childNodes[idx]);\n }\n nextChildren[idx] = keyedChild;\n delete vNodeKeyMap[key];\n patch(templateChild, nextChildren[idx]);\n } else {\n insertBefore(vNode, templateChild, childNodes[idx]);\n nextChildren[idx] = templateChild;\n }\n });\n } else {\n forEach(template.children, function (templateChild, idx) {\n var vNodeChild = vNode.children[idx];\n if (typeof vNodeChild !== \"undefined\") {\n patch(templateChild, vNodeChild);\n nextChildren[idx] = vNodeChild;\n } else {\n vNode.node.appendChild(templateChild.node);\n nextChildren[idx] = templateChild;\n }\n });\n }\n vNode.children = nextChildren;\n var childNodesLength = vNode.node.childNodes.length;\n var delta = childNodesLength - templateChildrenLength;\n if (delta > 0) {\n while (delta > 0) {\n vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n childNodesLength--;\n delta--;\n }\n }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n if (!template || !vNode) return;\n rootNode = rootNode || vNode.node.parentNode;\n var contentChanged = template.content && template.content !== vNode.content;\n if (template.type !== vNode.type || contentChanged) {\n rootNode.replaceChild(template.node, vNode.node);\n return assignVNode(template, vNode);\n }\n updateAttributes(template, vNode);\n patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n if (typeof node === \"string\") {\n node = toHTML(node);\n }\n var isRoot = node.tagName === \"BODY\";\n var childNodes = node.childNodes;\n var numChildNodes = childNodes ? childNodes.length : 0;\n if (isRoot) {\n if (numChildNodes > 1) {\n throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n } else if (numChildNodes === 0) {\n throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n } else {\n return create(childNodes[0]);\n }\n }\n var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n var isSVG = isSVGContext || type === \"svg\";\n var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n var content = numChildNodes > 0 ? null : node.textContent;\n var children = Array(numChildNodes);\n forEach(childNodes, function (child, idx) {\n children[idx] = create(child, isSVG);\n });\n return {\n type: type,\n attributes: attributes,\n children: children,\n content: content,\n node: node,\n isSVGContext: isSVG\n };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","\n\nexport function takeWhileMap<T, A>(pred: (val: T) => A | undefined, lines: T[]): A[] {\n var output = []\n for (var line of lines) {\n let a = pred(line)\n if (a)\n output.push(a)\n else\n break;\n }\n\n return output\n}\n\nexport function dropWhile<T, A>(pred: (val: T) => A | undefined, lines: T[]): T[] {\n let index = 0;\n while (index < lines.length && pred(lines[index])) {\n index++;\n }\n return lines.slice(index);\n}\n\n","\nimport { takeWhileMap, dropWhile } from \"./lib\"\n\n\n\nexport type Meta = { key: string, value: string }\nexport type ViewId = string\nexport type RequestId = number\nexport type EncodedAction = string\nexport type ViewState = string\n\ntype RemoteEvent = { name: string, detail: any }\n\n\nexport function renderMetas(meta: Meta[]): string {\n return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport type Metadata = {\n cookies?: string[]\n // redirect?: string\n error?: string\n query?: string\n events?: RemoteEvent[]\n actions?: [ViewId, string][],\n pageTitle?: string\n}\n\n\nexport function toMetadata(meta: Meta[]): Metadata {\n\n return {\n cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n // redirect: metaValue(\"Redirect\", meta),\n error: metaValue(\"Error\", meta),\n query: metaValue(\"Query\", meta),\n pageTitle: metaValue(\"PageTitle\", meta),\n events: metaValuesAll(\"Event\", meta).map(parseRemoteEvent),\n actions: metaValuesAll(\"Trigger\", meta).map(parseAction),\n }\n}\n\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\nexport function parseMetadata(input: string): Metadata {\n let metas = takeWhileMap(parseMeta, input.trim().split(\"\\n\"))\n return toMetadata(metas)\n}\n\n\nexport function metaValue(key: string, metas: Meta[]): string | undefined {\n return metas.find(m => m.key == key)?.value\n}\n\nexport function metaValuesAll(key: string, metas: Meta[]): string[] {\n return metas.filter(m => m.key == key).map(m => m.value)\n}\n\nexport type SplitMessage = {\n command: string,\n metas: Meta[],\n rest: string[]\n}\n\n\nexport function splitMessage(message: string): SplitMessage {\n let lines = message.split(\"\\n\")\n let command: string = lines[0]\n let metas: Meta[] = takeWhileMap(parseMeta, lines.slice(1))\n // console.log(\"Split Metadata\", lines.length)\n // console.log(\" [0]\", lines[0])\n // console.log(\" [1]\", lines[1])\n let rest = dropWhile(l => l == \"\", lines.slice(metas.length + 1))\n\n return { command, metas, rest }\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n let match = line.match(/^(\\w+)\\: (.*)$/)\n if (match) {\n return {\n key: match[1],\n value: match[2]\n }\n }\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n let [name, data] = breakNextSegment(input)\n return {\n name,\n detail: JSON.parse(data)\n }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n let [viewId, action] = breakNextSegment(input)\n return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] | undefined {\n let ix = input.indexOf('|')\n if (ix === -1) {\n let err = new Error(\"Bad Encoding, Expected Segment\")\n err.message = input\n throw err\n }\n return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\n","\nimport { takeWhileMap } from \"./lib\"\nimport { Meta, ViewId, RequestId, EncodedAction, ViewState } from \"./message\"\nimport * as message from \"./message\"\n\n\n\nexport type ActionMessage = {\n viewId: ViewId\n action: EncodedAction\n requestId: RequestId\n state?: ViewState\n meta: Meta[]\n form: URLSearchParams | undefined\n}\n\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, state: ViewState | undefined, reqId: RequestId, form?: FormData): ActionMessage {\n let meta: Meta[] = [\n { key: \"Cookie\", value: decodeURI(document.cookie) },\n { key: \"Query\", value: window.location.search }\n ]\n\n return { viewId: id, action, state, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n if (!form) return undefined\n\n const params = new URLSearchParams()\n\n form.forEach((value, key) => {\n params.append(key, value as string)\n })\n\n return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n let header = [\n \"|ACTION|\",\n \"ViewId: \" + msg.viewId,\n \"Action: \" + msg.action,\n ]\n\n\n if (msg.state) {\n header.push(\"State: \" + msg.state)\n }\n\n header.push(\"RequestId: \" + msg.requestId)\n\n return [\n header.join('\\n'),\n message.renderMetas(msg.meta),\n ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n if (!form) return \"\"\n return \"\\n\\n\" + form\n}\n\nlet globalRequestId: RequestId = 0\n\nexport type Request = {\n requestId: RequestId\n isCancelled: boolean\n}\n\nexport function newRequest(): Request {\n let requestId = ++globalRequestId\n return { requestId, isCancelled: false }\n}\n\n\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n if (param == \"\") {\n return \"|\"\n }\n\n return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n","import { ActionMessage, renderActionMessage } from './action'\nimport { ResponseBody } from \"./response\"\nimport * as message from \"./message\"\nimport { ViewId, RequestId, EncodedAction, metaValue, Metadata } from \"./message\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\n\n\nexport class SocketConnection {\n socket: WebSocket\n\n hasEverConnected: Boolean = false\n isConnected: Boolean = false\n reconnectDelay: number = 0\n queue: ActionMessage[] = []\n events: EventTarget\n\n constructor() {\n this.events = new EventTarget()\n }\n\n connect(addr = defaultAddress) {\n const sock = new WebSocket(addr)\n this.socket = sock\n\n function onConnectError(ev: Event) {\n console.error(\"Connect Error\", ev)\n }\n\n function onSocketError(ev: Event) {\n console.error(\"Socket Error\", ev)\n }\n\n\n // initial connection errors\n sock.addEventListener('error', onConnectError)\n\n sock.addEventListener('open', (_event) => {\n console.log(\"Websocket Connected\")\n\n if (this.hasEverConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n }\n\n this.isConnected = true\n this.hasEverConnected = true\n this.reconnectDelay = 1000\n sock.removeEventListener('error', onConnectError)\n sock.addEventListener('error', onSocketError)\n\n document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n\n this.runQueue()\n })\n\n sock.addEventListener('close', _ => {\n console.log(\"CLOSE SOCKET\")\n if (this.isConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n }\n\n this.isConnected = false\n sock.removeEventListener('error', onSocketError)\n\n // attempt to reconnect in 1s\n if (this.hasEverConnected) {\n console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n setTimeout(() => this.connect(addr), this.reconnectDelay)\n }\n\n sock.removeEventListener('error', onSocketError)\n })\n\n sock.addEventListener('message', ev => this.onMessage(ev))\n }\n\n async sendAction(action: ActionMessage) {\n if (this.isConnected) {\n let msg = renderActionMessage(action)\n this.socket.send(msg)\n }\n else {\n this.queue.push(action)\n }\n }\n\n private runQueue() {\n // send all messages queued while disconnected \n let next: ActionMessage | null = this.queue.pop()\n if (next) {\n console.log(\"runQueue: \", next)\n this.sendAction(next)\n this.runQueue()\n }\n }\n\n\n // full responses will never be sent over!\n private onMessage(event: MessageEvent) {\n let { command, metas, rest } = message.splitMessage(event.data)\n // console.log(\"MESSAGE\", command, metas, rest)\n\n let requestId = parseInt(requireMeta(\"RequestId\"), 0)\n\n function requireMeta(key: string): string {\n let val = metaValue(key, metas)\n if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, event.data)\n return val\n }\n\n function parseResponse(rest: string[]): Update {\n let viewId = requireMeta(\"ViewId\")\n let action = requireMeta(\"Action\")\n return {\n requestId,\n targetViewId: undefined,\n viewId,\n action,\n meta: message.toMetadata(metas),\n body: rest.join(\"\\n\"),\n }\n }\n\n function parseUpdate(rest: string[]): Update {\n let up = parseResponse(rest)\n // add the TargetViewId\n up.targetViewId = metaValue(\"TargetViewId\", metas)\n return up\n }\n\n function parseRedirect(rest: string[]): Redirect {\n let url = rest[0]\n return {\n requestId,\n meta: message.toMetadata(metas),\n url\n }\n }\n\n switch (command) {\n\n case \"|UPDATE|\":\n return this.dispatchEvent(new CustomEvent(\"update\", { detail: parseUpdate(rest) }))\n\n case \"|RESPONSE|\":\n return this.dispatchEvent(new CustomEvent(\"response\", { detail: parseResponse(rest) }))\n\n case \"|REDIRECT|\":\n return this.dispatchEvent(new CustomEvent(\"redirect\", { detail: parseRedirect(rest) }))\n }\n }\n\n\n // so what if they send remote events in the page? trigger, redirect, page title, etc...\n // we aren't connected yet on a page thing\n\n // private async waitMessage(reqId: RequestId, id: ViewId): Promise<ParsedResponse> {\n // return new Promise((resolve, reject) => {\n // const onMessage = (event: MessageEvent) => {\n // let data: string = event.data\n // let lines = data.split(\"\\n\").slice(1) // drop the command line\n //\n // let parsed = splitMetadata(lines)\n // let metadata: Metadata = parsed.metadata\n //\n // if (!metadata.requestId) {\n // console.error(\"Missing RequestId!\", metadata, event.data)\n // return\n // }\n //\n // if (metadata.requestId != reqId) {\n // // skip, it's not us!\n // return\n // }\n //\n //\n // // We have found our message. Remove the listener\n // this.socket.removeEventListener('message', onMessage)\n //\n // // set the cookies. These happen automatically in http\n // metadata.cookies.forEach((cookie: string) => {\n // document.cookie = cookie\n // })\n //\n // if (metadata.error) {\n // reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n // return\n // }\n //\n // resolve(parsed)\n // }\n //\n // this.socket.addEventListener('message', onMessage)\n // this.socket.addEventListener('error', reject)\n // })\n // }\n\n addEventListener(e: string, cb: EventListenerOrEventListenerObject) {\n this.events.addEventListener(e, cb)\n }\n\n dispatchEvent(e: Event) {\n this.events.dispatchEvent(e)\n }\n\n disconnect() {\n this.isConnected = false\n this.hasEverConnected = false\n this.socket.close()\n }\n}\n\n\nexport type Update = {\n requestId: RequestId\n meta: Metadata\n viewId: ViewId\n targetViewId?: ViewId\n action: EncodedAction\n body: ResponseBody\n}\n\nexport type Redirect = {\n requestId: RequestId\n meta: Metadata\n url: string\n}\n\nexport type MessageType = string\n\n\n// PARSING MESSAGE ---------------------------------------\n\nexport class ProtocolError extends Error {\n constructor(description: string, body: string) {\n super(description + \"\\n\" + body)\n this.name = \"ProtocolError\"\n }\n}\n","\nimport * as debounce from 'debounce'\nimport { encodedParam } from './action'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keyup\", cb)\n}\n\nexport function listenKeyEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event.toLowerCase(), function(e: KeyboardEvent) {\n let source = e.target as HTMLInputElement\n\n let datasetKey = \"on\" + event + e.key\n let action = source.dataset[datasetKey]\n if (!action) return\n\n e.preventDefault()\n cb(nearestTarget(source), action)\n })\n}\n\nexport function listenBubblingEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event, function(e) {\n let el = e.target as HTMLInputElement\n\n // clicks can fire on internal elements. Find the parent with a click handler\n let source = el.closest(\"[data-on\" + event + \"]\") as HTMLElement\n if (!source) return\n\n e.preventDefault()\n let target = nearestTarget(source)\n cb(target, source.dataset[\"on\" + event])\n })\n}\n\nexport function listenClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n let action = e.detail.onLoad\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n let action = e.detail.onMouseEnter\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n let action = e.detail.onMouseLeave\n let target = e.detail.target\n cb(target, action)\n })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n // it doesn't really matter WHO runs this except that it should have target\n node.querySelectorAll(\"[data-onload]\").forEach((load: HTMLElement) => {\n let delay = parseInt(load.dataset.delay) || 0\n let onLoad = load.dataset.onload\n // console.log(\"load start\", load.dataset.onLoad)\n\n // load no longer exists!\n // we should clear the timeout or back out if the dom is replaced in the interem\n setTimeout(() => {\n let target = nearestTarget(load)\n // console.log(\"load go\", load.dataset.onLoad)\n\n if (load.dataset.onload != onLoad) {\n // the onLoad no longer exists\n return\n }\n\n const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n load.dispatchEvent(event)\n }, delay)\n })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseenter]\").forEach((node: HTMLElement) => {\n let onMouseEnter = node.dataset.onmouseenter\n\n let target = nearestTarget(node)\n\n node.onmouseenter = () => {\n const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n node.dispatchEvent(event)\n }\n })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseleave]\").forEach((node: HTMLElement) => {\n let onMouseLeave = node.dataset.onmouseleave\n\n let target = nearestTarget(node)\n\n node.onmouseleave = () => {\n const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n node.dispatchEvent(event)\n }\n })\n}\n\n\nexport function listenChange(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"change\", function(e) {\n let el = e.target as HTMLElement\n\n let source = el.closest(\"[data-onchange]\") as HTMLInputElement\n\n if (!source) return\n e.preventDefault()\n\n if (source.value == null) {\n console.error(\"Missing input value:\", source)\n return\n }\n\n let target = nearestTarget(source)\n let action = encodedParam(source.dataset.onchange, source.value)\n cb(target, action)\n })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n debouncedCallback?: Function;\n}\n\nexport function listenInput(startedTyping: (target: HTMLElement) => void, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"input\", function(e) {\n let el = e.target as HTMLElement\n let source = el.closest(\"[data-oninput]\") as LiveInputElement\n\n if (!source) return\n\n let delay = parseInt(source.dataset.delay) || 250\n if (delay < 250) {\n console.warn(\"Input delay < 250 can result in poor performance.\")\n }\n\n if (!source?.dataset.oninput) {\n console.error(\"Missing onInput: \", source)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(source)\n\n // I want to CANCEL the active request as soon as we start typing\n startedTyping(target)\n\n if (!source.debouncedCallback) {\n source.debouncedCallback = debounce(() => {\n let action = encodedParam(source.dataset.oninput, source.value)\n cb(target, action)\n }, delay)\n }\n\n source.debouncedCallback()\n })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HTMLElement, action: string, form: FormData) => void): void {\n document.addEventListener(\"submit\", function(e) {\n let form = e.target as HTMLFormElement\n\n if (!form?.dataset.onsubmit) {\n console.error(\"Missing onSubmit: \", form)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(form)\n const formData = new FormData(form)\n cb(target, form.dataset.onsubmit, formData)\n })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n let targetData = node.closest(\"[data-target]\") as HTMLElement | undefined\n return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestTarget(node: HTMLElement): HTMLElement {\n let targetId = nearestTargetId(node)\n let target = document.getElementById(targetId)\n\n if (!target) {\n console.error(\"Cannot find target: \", targetId, node)\n return\n }\n\n return target\n}\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection, Update, Redirect } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, ActionMessage, Request, newRequest } from './action'\nimport { ViewId, Metadata, parseMetadata, ViewState } from './message'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, Response, LiveUpdate } from './response'\n\nlet PACKAGE = require('../package.json');\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version + \"b\")\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\n\n\n\n// Run an action in a given HyperView\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n if (target === undefined) {\n console.error(\"Undefined HyperView!\", action)\n return\n }\n\n if (action === undefined) {\n console.error(\"Undefined Action!\", target.id)\n return\n }\n\n if (target.activeRequest && !target.activeRequest?.isCancelled) {\n // Active Request!\n if (target.concurrency == \"Drop\") {\n console.warn(\"Drop action overlapping with active request (\" + target.activeRequest + \")\", action)\n return\n }\n }\n\n target._timeout = setTimeout(() => {\n // add loading after 100ms, not right away\n // if it runs shorter than that we probably don't want to show the user any loading feedback\n target.classList.add(\"hyp-loading\")\n }, 100)\n\n let state = target.dataset.state\n\n let req = newRequest()\n let msg = actionMessage(target.id, action, state, req.requestId, form)\n\n // Set the requestId\n target.activeRequest = req\n\n sock.sendAction(msg)\n}\n\n\n// TODO: redirect concurrency\nfunction handleRedirect(red: Redirect) {\n console.log(\"REDIRECT\", red)\n\n // the other metdata doesn't apply, they are all specific to the page\n applyCookies(red.meta.cookies)\n\n window.location.href = red.url\n}\n\n// in-process update\nfunction handleResponse(res: Update) {\n // console.log(\"Handle Response\", res)\n let target = handleUpdate(res)\n\n // clean up the request\n delete target.activeRequest\n clearTimeout(target._timeout)\n target.classList.remove(\"hyp-loading\")\n}\n\nfunction handleUpdate(res: Update): HyperView {\n // console.log(\"|UPDATE|\", res)\n\n let targetViewId = res.targetViewId || res.viewId\n let target = document.getElementById(targetViewId) as HyperView\n\n\n if (!target) {\n console.error(\"Missing Update Target: \", targetViewId, res)\n return target\n }\n\n if (res.requestId < target.activeRequest?.requestId) {\n // this should only happen on Replace, since other requests should be dropped\n // but it's safe to assume we never want to apply an old requestId\n console.warn(\"Ignore Stale Action (\" + res.requestId + \") vs (\" + target.activeRequest.requestId + \"): \" + res.action)\n return target\n }\n else if (target.activeRequest?.isCancelled) {\n console.warn(\"Cancelled request\", target.activeRequest?.requestId)\n delete target.activeRequest\n return target\n }\n\n let update: LiveUpdate = parseResponse(res.body)\n\n if (!update.content) {\n console.error(\"Empty Response!\", res.body)\n return target\n }\n\n // First, update the stylesheet\n addCSS(update.css)\n\n\n // Patch the node\n const old: VNode = create(target)\n let next: VNode = create(update.content)\n let state = (next.attributes as any)[\"data-state\"]\n next.attributes = old.attributes\n patch(next, old)\n\n\n // Emit relevant events\n let newTarget = document.getElementById(target.id)\n dispatchContent(newTarget)\n\n if (!newTarget) {\n console.warn(\"Target Missing: \", target.id)\n return target\n }\n\n // re-add state attribute \n newTarget.dataset.state = state\n\n // execute the metadata, anything that doesn't interrupt the dom update\n runMetadata(res.meta, newTarget)\n applyCookies(res.meta.cookies)\n\n // now way for these to bubble)\n listenLoad(newTarget)\n listenMouseEnter(newTarget)\n listenMouseLeave(newTarget)\n fixInputs(newTarget)\n enrichHyperViews(newTarget)\n\n return target\n}\n// catch (err) {\n// console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n//\n// // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n// // but if you manage to crash your parent server process somehow, the response may be empty\n// target.innerHTML = err.body || \"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>\"\n// }\n\nfunction applyCookies(cookies: string[]) {\n cookies.forEach((cookie: string) => {\n console.log(\"SetCookie: \", cookie)\n document.cookie = cookie\n })\n}\n\nfunction runMetadata(meta: Metadata, target?: HTMLElement) {\n if (meta.query != null) {\n setQuery(meta.query)\n }\n\n if (meta.pageTitle != null) {\n document.title = meta.pageTitle\n }\n\n meta.events.forEach((remoteEvent) => {\n setTimeout(() => {\n let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n let eventTarget = target || document\n eventTarget.dispatchEvent(event)\n }, 10)\n })\n\n meta.actions.forEach(([viewId, action]) => {\n setTimeout(() => {\n let view = window.Hyperbole.hyperView(viewId)\n if (view) {\n runAction(view, action)\n }\n }, 10)\n })\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n let focused = target.querySelector(\"[autofocus]\") as HTMLInputElement\n if (focused?.focus) {\n focused.focus()\n }\n\n target.querySelectorAll(\"input[value]\").forEach((input: HTMLInputElement) => {\n let val = input.getAttribute(\"value\")\n if (val !== undefined) {\n input.value = val\n }\n })\n\n target.querySelectorAll(\"input[type=checkbox]\").forEach((checkbox: HTMLInputElement) => {\n let checked = checkbox.dataset.checked == \"True\"\n checkbox.checked = checked\n })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n if (!src) return;\n const rules: any = src.sheet.cssRules\n for (const rule of rules) {\n if (addedRulesIndex.has(rule.cssText) == false) {\n rootStyles.sheet.insertRule(rule.cssText);\n addedRulesIndex.add(rule.cssText);\n }\n }\n}\n\n\n\n\nfunction init() {\n // metadata attached to initial page loads need to be executed\n let meta = parseMetadata(document.getElementById(\"hyp.metadata\").innerText)\n // runMetadataImmediate(meta)\n runMetadata(meta)\n\n rootStyles = document.body.querySelector('style')\n\n if (!rootStyles) {\n console.warn(\"rootStyles missing from page, creating...\")\n rootStyles = document.createElement(\"style\")\n rootStyles.type = \"text/css\"\n document.body.appendChild(rootStyles)\n }\n\n listenTopLevel(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n listenLoad(document.body)\n listenMouseEnter(document.body)\n listenMouseLeave(document.body)\n enrichHyperViews(document.body)\n\n\n listenClick(async function(target: HyperView, action: string) {\n // console.log(\"CLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenDblClick(async function(target: HyperView, action: string) {\n // console.log(\"DBLCLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenKeydown(async function(target: HyperView, action: string) {\n // console.log(\"KEYDOWN\", target.id, action)\n runAction(target, action)\n })\n\n listenKeyup(async function(target: HyperView, action: string) {\n // console.log(\"KEYUP\", target.id, action)\n runAction(target, action)\n })\n\n listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n // console.log(\"FORM\", target.id, action, form)\n runAction(target, action, form)\n })\n\n listenChange(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n function onStartedTyping(target: HyperView) {\n if (target.concurrency == \"Replace\") {\n target.cancelActiveRequest()\n }\n }\n\n listenInput(onStartedTyping, async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n}\n\n\n\nfunction enrichHyperViews(node: HTMLElement): void {\n // enrich all the hyperviews\n node.querySelectorAll(\"[id]\").forEach((element: HyperView) => {\n element.runAction = function(action: string) {\n runAction(this, action)\n }.bind(element)\n\n element.concurrency = element.dataset.concurrency || \"Drop\"\n\n element.cancelActiveRequest = function() {\n if (element.activeRequest && !element.activeRequest?.isCancelled) {\n element.activeRequest.isCancelled = true\n }\n }\n\n dispatchContent(node)\n })\n}\n\nfunction dispatchContent(node: HTMLElement): void {\n let event = new Event(\"hyp-content\", { bubbles: true })\n node.dispatchEvent(event)\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\n// Should we connect to the socket or not?\nconst sock = new SocketConnection()\nsock.connect()\nsock.addEventListener(\"update\", (ev: CustomEvent<Update>) => handleUpdate(ev.detail))\nsock.addEventListener(\"response\", (ev: CustomEvent<Update>) => handleResponse(ev.detail))\nsock.addEventListener(\"redirect\", (ev: CustomEvent<Redirect>) => handleRedirect(ev.detail))\n\n\n\n\n\ntype VNode = {\n // One of three value types are used:\n // - The tag name of the element\n // - \"text\" if text node\n // - \"comment\" if comment node\n type: string\n\n // An object whose key/value pairs are the attribute\n // name and value, respectively\n attributes: [string: string]\n\n // Is set to `true` if a node is an `svg`, which tells\n // Omdomdom to treat it, and its children, as such\n isSVGContext: Boolean\n\n // The content of a \"text\" or \"comment\" node\n content: string\n\n // An array of virtual node children\n children: Array<VNode>\n\n // The real DOM node\n node: Node\n}\n\n\n\n\n\ndeclare global {\n interface Window {\n Hyperbole?: HyperboleAPI;\n }\n}\n\nexport interface HyperboleAPI {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n action(con: string, ...params: any[]): string\n hyperView(viewId: ViewId): HyperView | undefined\n parseMetadata(input: string): Metadata\n socket: SocketConnection\n}\n\n\n\n\nexport interface HyperView extends HTMLElement {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n activeRequest?: Request\n cancelActiveRequest(): void\n concurrency: ConcurrencyMode\n _timeout?: any\n}\n\ntype ConcurrencyMode = string\n\n\nwindow.Hyperbole =\n{\n runAction: runAction,\n parseMetadata: parseMetadata,\n action: function(con, ...params: any[]) {\n let ps = params.reduce((str, param) => str + \" \" + JSON.stringify(param), \"\")\n return con + ps\n },\n hyperView: function(viewId) {\n let element = document.getElementById(viewId) as any\n if (!element?.runAction) {\n console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n return undefined\n }\n return element\n },\n socket: sock\n}\n","\nimport { ViewId, Metadata } from './message'\n\n\n\nexport type Response = {\n meta: Metadata\n body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n const parser = new DOMParser()\n const doc = parser.parseFromString(res, 'text/html')\n const css = doc.querySelector(\"style\") as HTMLStyleElement\n const content = doc.querySelector(\"div\") as HTMLElement\n\n return {\n content: content,\n css: css\n }\n}\n\nexport type LiveUpdate = {\n content: HTMLElement\n css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n viewId: ViewId\n body: string\n constructor(viewId: ViewId, msg: string, body: string) {\n super(msg)\n this.viewId = viewId\n this.name = \"Fetch Error\"\n this.body = body\n }\n}\n","\nexport function setQuery(query: string) {\n if (query != currentQuery()) {\n if (query != \"\") query = \"?\" + query\n let url = location.pathname + query\n // console.log(\"history.replaceState(\", url, \")\")\n window.history.replaceState({}, \"\", url)\n }\n}\n\nfunction currentQuery(): string {\n const query = window.location.search;\n return query.startsWith('?') ? query.substring(1) : query;\n}\n"],"names":["debounce","function_","wait","options","TypeError","RangeError","immediate","storedContext","storedArguments","timeoutId","timestamp","result","run","callContext","callArguments","undefined","apply","later","last","Date","now","setTimeout","debounced","arguments_","this","Object","getPrototypeOf","Error","callNow","defineProperty","get","clear","clearTimeout","flush","trigger","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","hasProperty","obj","prop","prototype","hasOwnProperty","call","forEach","items","fn","length","idx","insertBefore","parent","child","refNode","node","_slicedToArray","arr","i","Array","isArray","_arrayWithHoles","_i","Symbol","iterator","_s","_e","_x","_r","_arr","_n","_d","next","done","push","value","err","return","_iterableToArrayLimit","o","minLen","_arrayLikeToArray","n","toString","slice","constructor","name","from","test","_unsupportedIterableToArray","_nonIterableRest","len","arr2","Types","DomProperties","createRecord","attrName","propName","type","_ref","_ref2","attr","property","_ref3","_ref4","Namespace","setProperty","element","style","toLowerCase","removeAttribute","parsed","parseInt","isNaN","indexOf","patch","template","vNode","rootNode","parentNode","contentChanged","content","replaceChild","assignVNode","removedAttributes","changedAttributes","attributes","oldValue","nextValue","_attrName","_oldValue","_nextValue","propertyRecord","removeAttributes","startsWith","setAttributeNS","setAttribute","setAttributes","updateAttributes","templateChildrenLength","children","vNodeChildrenLength","vNodeKeyMap","map","_","key","console","warn","keyIsValid","createKeyMap","nextChildren","templateChild","childNodes","keyedChild","vNodeChild","appendChild","childNodesLength","delta","removeChild","patchChildren","create","processedDOMString","isSVGContext","arguments","trim","replace","DOMParser","parseFromString","body","isRoot","tagName","numChildNodes","nodeType","isSVG","reduce","getBaseAttributes","attrValue","getAttribute","getPropertyValues","getAttributes","textContent","takeWhileMap","pred","lines","output","line","a","toMetadata","meta","cookies","filter","m","error","metaValue","query","pageTitle","events","metaValuesAll","parseRemoteEvent","actions","parseAction","parseMetadata","input","parseMeta","split","metas","find","match","data","breakNextSegment","detail","JSON","parse","viewId","action","ix","message","toSearch","form","params","URLSearchParams","append","globalRequestId","encodedParam","param","sanitizeParam","defaultAddress","window","location","protocol","host","pathname","ProtocolError","description","super","listenKeyEvent","event","cb","document","addEventListener","e","source","target","datasetKey","dataset","preventDefault","nearestTarget","listenBubblingEvent","closest","listenLoad","querySelectorAll","load","delay","onLoad","onload","CustomEvent","bubbles","dispatchEvent","listenMouseEnter","onMouseEnter","onmouseenter","listenMouseLeave","onMouseLeave","onmouseleave","targetId","targetData","id","nearestTargetId","getElementById","rootStyles","PACKAGE","log","version","addedRulesIndex","Set","async","runAction","activeRequest","isCancelled","concurrency","_timeout","classList","add","state","req","requestId","msg","reqId","decodeURI","cookie","search","actionMessage","sock","sendAction","handleUpdate","res","targetViewId","update","doc","css","querySelector","parseResponse","src","rules","sheet","cssRules","rule","has","cssText","insertRule","addCSS","old","newTarget","dispatchContent","runMetadata","applyCookies","focused","focus","val","checkbox","checked","fixInputs","enrichHyperViews","substring","currentQuery","url","history","replaceState","setQuery","title","remoteEvent","view","Hyperbole","hyperView","bind","cancelActiveRequest","Event","innerText","createElement","onsubmit","formData","FormData","onchange","oninput","startedTyping","debouncedCallback","hasEverConnected","isConnected","reconnectDelay","queue","EventTarget","connect","addr","WebSocket","onConnectError","ev","onSocketError","socket","_event","removeEventListener","runQueue","onMessage","header","join","renderActionMessage","send","pop","command","rest","index","dropWhile","l","requireMeta","up","parseUpdate","parseRedirect","disconnect","close","remove","handleResponse","handleRedirect","red","href","con","str","stringify"],"sourceRoot":""}
hyperbole.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: hyperbole-version: 0.5.0+version: 0.6.0 synopsis: Interactive HTML apps using type-safe serverside Haskell description: Interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView. category: Web, Network@@ -54,10 +54,9 @@ Web.Hyperbole.HyperView.Event Web.Hyperbole.HyperView.Forms Web.Hyperbole.HyperView.Handled+ Web.Hyperbole.HyperView.Hyper Web.Hyperbole.HyperView.Input Web.Hyperbole.HyperView.Types- Web.Hyperbole.HyperView.ViewAction- Web.Hyperbole.HyperView.ViewId Web.Hyperbole.Page Web.Hyperbole.Route Web.Hyperbole.Server.Handler@@ -76,6 +75,8 @@ Web.Hyperbole.View.Render Web.Hyperbole.View.Tag Web.Hyperbole.View.Types+ Web.Hyperbole.View.ViewAction+ Web.Hyperbole.View.ViewId other-modules: Paths_hyperbole autogen-modules:@@ -99,10 +100,10 @@ , attoparsec-aeson >=2.1 && <2.3 , base >=4.16 && <5 , bytestring >=0.11 && <0.13- , casing >0.1 && <0.2+ , casing >=0.1.2 && <0.2 , containers >=0.6 && <1 , cookie >=0.4 && <0.6- , data-default >0.8 && <0.9+ , data-default ==0.8.* , effectful >=2.4 && <3 , file-embed >=0.0.10 && <0.1 , filepath >=1.4 && <2@@ -160,10 +161,10 @@ , attoparsec-aeson >=2.1 && <2.3 , base >=4.16 && <5 , bytestring >=0.11 && <0.13- , casing >0.1 && <0.2+ , casing >=0.1.2 && <0.2 , containers >=0.6 && <1 , cookie >=0.4 && <0.6- , data-default >0.8 && <0.9+ , data-default ==0.8.* , effectful >=2.4 && <3 , file-embed >=0.0.10 && <0.1 , filepath >=1.4 && <2
src/Web/Hyperbole.hs view
@@ -7,54 +7,12 @@ Portability: portable Create fully interactive HTML applications with type-safe serverside Haskell. Inspired by [HTMX](https://htmx.org/), [Elm](https://elm-lang.org/), and [Phoenix LiveView](https://www.phoenixframework.org/)++* [hyperbole.live](https://hyperbole.live) - documentation and examples+* [github](https://github.com/seanhess/hyperbole) - issues and source code -} module Web.Hyperbole- ( -- * Introduction #intro#- -- $use-- -- * Getting started #start#- -- $hello-- -- ** HTML Views #views#- -- $view-functions-intro-- -- ** Interactive HyperViews #hyperviews#- -- $interactive-- -- ** View Functions #viewfunctions#- -- $view-functions-- -- * Managing State #state#- -- $state-parameters-- -- ** Side Effects #side-effects#- -- $state-effects-- -- ** Databases and Custom Effects #databases#- -- $state-databases-- -- * Multiple HyperViews #hyperview-multi#- -- $practices-multi-- -- ** Same HyperView, Unique ViewId #hyperview-same#- -- $practices-same-- -- ** Different HyperViews- -- $practices-diff-- -- ** Nesting HyperViews #hyperview-nested#- -- $practices-nested-- -- * Functions, not Components #reusable#- -- $reusable-- -- * Pages and Routes #pages#- -- $practices-pages-- -- * Examples #examples#- -- $examples-- -- * Application #application#+ ( -- * Application #application# liveApp , Warp.run @@ -94,6 +52,8 @@ , FromQuery (..) , query , setQuery+ , modifyQuery+ , clearQuery , param , lookupParam , setParam@@ -114,12 +74,12 @@ , pageTitle , trigger , pushEvent+ , pushUpdate -- * HyperView #hyperview# , HyperView (..)- , ViewId- , ViewAction , hyper+ , hyperState , HasViewId (..) -- * Interactive Elements #interactive#@@ -186,6 +146,7 @@ , target , Response , Root+ , ConcurrencyMode (..) -- * Exports #exports# @@ -242,430 +203,9 @@ import Web.Hyperbole.View.Embed -{- $use--Single Page Applications (SPAs) require the programmer to write two programs: a Javascript client and a Server, which both must conform to a common API--Hyperbole allows us to instead write a single Haskell program which runs exclusively on the server. All user interactions are sent to the server for processing, and a sub-section of the page is updated with the resulting HTML.--There are frameworks that support this in different ways, including [HTMX](https://htmx.org/), [Phoenix LiveView](https://www.phoenixframework.org/), and others. Hyperbole has the following advantages--1. 100% Haskell-2. Type safe views, actions, routes, and forms-3. Elegant interface with little boilerplate-4. VirtualDOM updates over sockets, fallback to HTTP-5. Easy to use--Like [HTMX](https://htmx.org/), Hyperbole extends the capability of UI elements, but it uses Haskell's type-system to prevent common errors and provide default functionality. Specifically, a page has multiple update targets called 'HyperView's. These are automatically targeted by any UI element that triggers an action inside them. The compiler makes sure that actions and targets match--Like [Phoenix LiveView](https://www.phoenixframework.org/), it upgrades the page to a fast WebSocket connection and uses VirtualDOM for live updates--Like [Elm](https://elm-lang.org/), it uses an update function to process actions, but greatly simplifies the Elm Architecture by remaining stateless. Effects are handled by [Effectful](https://hackage.haskell.org/package/effectful). 'form's are easy to use with minimal boilerplate--Hyperbole depends heavily on the following frameworks--* [Effectful](https://hackage.haskell.org/package/effectful-core)-* [Atomic CSS](https://hackage.haskell.org/package/atomic-css)--}---{- $hello--#EXAMPLE /intro--Hyperbole applications run via [Warp](https://hackage.haskell.org/package/warp) and [WAI](https://hackage.haskell.org/package/wai)--They are divided into top-level 'Page's, which run side effects (such as loading data from a database), then respond with an HTML 'View'. The following application has a single 'Page' that displays a static "Hello World"--@-{\-# LANGUAGE OverloadedStrings #-\}--module Main where--import Web.Hyperbole--#EMBED Example/Docs/BasicPage.hs main--#EMBED Example/Docs/BasicPage.hs page-@--}---{- $interactive--#EXAMPLE /simple--We can embed one or more 'HyperView's to add type-safe interactivity to live subsections of our 'Page'. To start, first define a data type (a 'ViewId') that uniquely identifies that subsection of the page:--@-#EMBED Example/Docs/Interactive.hs data Message-@--Make our 'ViewId' an instance of 'HyperView':--* Create an 'Action' type with a constructor for every possible way that the user can interact with it-* Write an 'update' for each 'Action'--@-#EMBED Example/Docs/Interactive.hs instance HyperView Message-@--If an 'Action' occurs, the contents of our 'HyperView' will be replaced with the result of 'update'.--To use our new 'HyperView', add the 'ViewId' to the type-level list of 'Page', and then place it in the page view with 'hyper'.--@-#EMBED Example/Docs/Interactive.hs page-@--Now let's add a button to trigger the 'Action'. Note that we must now update the 'View'\'s 'context' to match our 'ViewId'. The compiler will tell us if we try to trigger actions that don't belong to our 'HyperView'--@-#EMBED Example/Docs/Interactive.hs messageView-@--If the user clicks the button, the contents of `hyper` will be replaced with the result of 'update', leaving the rest of the page untouched.--}---{- $view-functions-intro--'View's are HTML fragments with a 'context'--@-#EMBED Example/Docs/BasicPage.hs helloWorld-@-->>> renderText helloWorld-<div>Hello World</div>--We can factor 'View's into reusable functions:--#EXAMPLE /simple--@-#EMBED Example/Docs/BasicPage.hs messageView--#EMBED Example/Docs/BasicPage.hs page'-@--Using [atomic-css](https://hackage.haskell.org/package/atomic-css) we can use functions to factor styles as well--#EXAMPLE /css--@-import Web.Atomic.CSS--header = bold-h1 = header . fontSize 32-h2 = header . fontSize 24-page = gap 10--example = col page $ do- el h1 "My Page"-@--}---{- $view-functions--We showed above how we can factor 'View's into functions. It's best-practice to have a main 'View' function for each 'HyperView'. These take the form:--> state -> View viewId ()--There's nothing special about `state` or 'View' functions. They're just functions that take input data and return a view.--We can write multiple view functions with our 'HyperView' as the 'context', and factor them however is most convenient:--@-#EMBED Example/Docs/ViewFunctions.hs messageButton-@--Some 'View' functions can be used in any 'context':--@-#EMBED Example/Docs/ViewFunctions.hs header-@--With those two functions defined, we can refactor our main 'View' to use them and avoid repeating ourselves--@-#EMBED Example/Docs/ViewFunctions.hs messageView-@--}---{- $practices--We've mentioned most of the Architecture of a hyperbole application, but let's go over each layer here:--* [Pages](#g:pages) - routes map to completely independent web pages-* [HyperViews](#g:hyperviews) - independently updating live subsections of a 'Page'-* Main [View Functions](#g:viewfunctions) - A view function that renders and updates a 'HyperView'-* [Reusable View Functions](#g:reusable) - Generic view functions you can use in any 'HyperView'--}---{- $practices-multi--We can add as many 'HyperView's to a page as we want. These can be muliple copies of the same 'HyperView' with unique 'ViewId' values, or completely different 'HyperView's.--}---{- $practices-diff--Let's add both 'Count' and 'Message' 'HyperView's to the same page. Each will update independently:--@-#EMBED Example/Docs/MultiView.hs page-@--}---{- $practices-same--We can embed more than one of the same 'HyperView' as long as the value of 'ViewId' is unique. Let's update `Message` to allow for more than one value:--▶️ #EXAMPLE /simple--@-#EMBED Example/Docs/MultiCopies.hs data Message-@--Now we can embed multiple `Message` 'HyperView's into the same page. Each will update independently.--@-#EMBED Example/Docs/MultiCopies.hs page-@---This is especially useful if we put identifying information in our 'ViewId', such as a database id. The 'viewId' function can give us access to that info:--#EXAMPLE /data/loadmore--@-#EMBED Example/Page/DataLists/LoadMore.hs data Languages--#EMBED Example/Page/DataLists/LoadMore.hs instance HyperView Languages-@--}---{- $practices-pages--An app will usually have multiple 'Page's with different 'Route's that each map to a unique url path:--@-#EMBED Example/Docs/MultiPage.hs data AppRoute-@--When we create our app, we can add a router function which maps a 'Route' to a 'Page' with 'routeRequest'. The web page is completely reloaded each time you switch routes. Each 'Page' is completely isolated.--@-#EMBED Example/Docs/MultiPage.hs main-@--We can add type-safe links to other pages using 'route'--@-#EMBED Example/Docs/MultiPage.hs menu-@--If you need the same header or menu on all pages, use a view function:--@-#EMBED Example/Docs/MultiPage.hs exampleLayout--#EMBED Example/Docs/MultiPage.hs examplePage-@--As shown above, each 'Page' can contain multiple interactive 'HyperView's to add interactivity--}---{- $practices-nested--We can nest smaller, more specific 'HyperView's inside of a larger parent. You might need this technique to display a list of items which might also need to update themselves individually--Let's imagine we want to display a list of Todos. The user can mark individual todos complete, and have them update independently. The more specific 'HyperView' for each item might look like this:--#EXAMPLE /examples/todos--@-#EMBED Example/Docs/Nested.hs data TodoItem--#EMBED Example/Docs/Nested.hs instance HyperView TodoItem-@--But we also want the entire list to refresh when a user adds a new todo. We need to create a parent 'HyperView' for the whole list.--Add any nested 'HyperView's to 'Require' to make sure they are handled. The compiler will let you know if you forget--@-#EMBED Example/Docs/Nested.hs data AllTodos--#EMBED Example/Docs/Nested.hs instance HyperView AllTodos-@--Then we can embed the child 'HyperView' into the parent 'View' just like we do on a 'Page', by using 'hyper'--@-#EMBED Example/Docs/Nested.hs todosView-@--}---{- $reusable--You may be tempted to use 'HyperView's to create reusable \"Components\". This leads to object-oriented designs that don't compose well. We are using a functional language, so our main unit of reuse should be functions!--We showed earlier that we can write a [View Function](#g:view-functions) with a generic 'context' that we can reuse in any view. A function like this might help us reuse styles or layout:--@-#EMBED Example/Docs/ViewFunctions.hs header-@--But what if we want to reuse interactivity? We can pass an 'Action' into the view function as a parameter:--@-#EMBED Example/Docs/Component.hs styledButton-@--We can create more complex view functions by passing state in as a parameter. Here's a button that toggles between a checked and unchecked state for any 'HyperView':--@-#EMBED Example/View/Inputs.hs toggleCheckbox-@--View functions can be containers which wrap other Views:--@-#EMBED Example/View/Inputs.hs progressBar-@---Don't use 'HyperView's to keep your code DRY. Think about which subsections of a page ought to update independently. Those are 'HyperView's. If you need reusable interactivity, use [view functions](#g:viewfunctions) whenever possible. See the following example for a more complicated example.--#EXAMPLE /data/sortabletable--}---{- $examples-[hyperbole.live](https://hyperbole.live) is full of live examples demonstrating different features. Each example includes a link to the source code. Some highlights:--* ▶️ #EXAMPLE /simple-* ▶️ #EXAMPLE /counter-* ▶️ #EXAMPLE /concurrency-* ▶️ #EXAMPLE /state-* ▶️ #EXAMPLE /requests-* ▶️ #EXAMPLE /data-* ▶️ #EXAMPLE /forms-* ▶️ #EXAMPLE /interactivity-* ▶️ #EXAMPLE /errors-* ▶️ #EXAMPLE /oauth2-* ▶️ #EXAMPLE /javascript-* ▶️ #EXAMPLE /advanced--The [National Solar Observatory](https://nso.edu) uses Hyperbole to manage Level 2 Data pipelines for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It uses complex user interfaces, workers, databases, and more. [The entire codebase is open source](https://github.com/DKISTDC/level2/).--}---{- $state-parameters--'HyperView's are stateless. They 'update' based entirely on the 'Action'. However, we can track simple state by passing it back and forth between the 'Action' and the 'View'--#EXAMPLE /counter--@-#EMBED Example/Page/Counter.hs instance HyperView Counter--#EMBED Example/Page/Counter.hs viewCount-@--}---{- $state-effects--For any real application with more complex state and data persistence, we need side effects.--Hyperbole relies on [Effectful](https://hackage.haskell.org/package/effectful) to compose side effects. We can use effects in a 'Page' or an 'update'. The 'Hyperbole' effect gives us access to the 'request' and client state, including 'session's and the 'query' 'param's. In this example the page keeps the message in the 'query' 'param's--#EXAMPLE /state/query--@-#EMBED Example/Docs/SideEffects.hs page--#EMBED Example/Docs/SideEffects.hs instance HyperView Message-@---To use an 'Effect' other than 'Hyperbole', add it as a constraint to the 'Page' and any 'HyperView' instances that need it.--#EXAMPLE /state/effects--@-{\-# LANGUAGE UndecidableInstances #-\}--#EMBED Example/Page/State/Effects.hs instance (Reader-@--Then run the effect in your application--@-#EMBED Example/Page/State/Effects.hs app-@--See [Effectful](https://hackage.haskell.org/package/effectful) to read more about Effects--}---{- $state-databases--A database is no different from any other 'Effect'. It is recommended to create a custom effect to describe high-level data operations.--#EXAMPLE /examples/todos--@-#EMBED Example/Effects/Todos.hs data Todos--#EMBED Example/Effects/Todos.hs loadAll-@--Just like any effect, to use our custom 'Effect', we add it to any 'HyperView' or 'Page' as a constraint.--@-{\-# LANGUAGE UndecidableInstances #-\}--#EMBED Example/Page/Todos/Todo.hs simplePage-@--We run a custom effect in our Application just like any other. The TodoMVC example implements the Todos 'Effect' using 'Hyperbole' 'sessions', but you could write a different runner that connects to a database instead.--@-#EMBED Example/Page/Todos/Todo.hs main-@--Implementing a database runner for a custom 'Effect' is beyond the scope of this documentation, but see the following:--* [Effectful.Dynamic.Dispatch](https://hackage.haskell.org/package/effectful-core/docs/Effectful-Dispatch-Dynamic.html) - Introduction to Effects-* [NSO.Data.Datasets](https://github.com/DKISTDC/level2/blob/main/src/NSO/Data/Datasets.hs) - Production Data Effect with a database runner-* [Effectful.Rel8](https://github.com/DKISTDC/level2/blob/main/types/src/Effectful/Rel8.hs) - Effect for the [Rel8](https://hackage.haskell.org/package/rel8) Postgres Library--}---{- $query-#EXAMPLE /state/query--}-+{- $documentation -{- $sessions-#EXAMPLE /state/sessions+Please visit https://hyperbole.live for documentation and examples -} --{- $forms--Painless forms with type-checked field names, and support for validation.--#EXAMPLE /forms--}+-- TODO: NSO link
src/Web/Hyperbole/Application.hs view
@@ -17,6 +17,8 @@ import Data.ByteString.Lazy qualified as BL import Effectful import Effectful.Concurrent.Async+import Effectful.Concurrent.STM (TVar)+import GHC.Conc (newTVarIO) import Network.Wai qualified as Wai import Network.Wai.Handler.WebSockets (websocketsOr) import Network.WebSockets (ConnectionException (..), PendingConnection, defaultConnectionOptions, withPingThread)@@ -27,14 +29,14 @@ import Web.Hyperbole.Effect.Response (notFound) import Web.Hyperbole.Route import Web.Hyperbole.Server.Options-import Web.Hyperbole.Server.Socket (handleRequestSocket)+import Web.Hyperbole.Server.Socket (RunningActions, handleRequestSocket) import Web.Hyperbole.Server.Wai (handleRequestWai) import Web.Hyperbole.Types.Response {- | Turn one or more 'Page's into a Wai Application. Respond using both HTTP and WebSockets -> #EMBED Example/Docs/BasicPage.hs main+> #EMBED Example.Docs.BasicPage main -} liveApp :: (BL.ByteString -> BL.ByteString) -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application liveApp doc =@@ -47,26 +49,27 @@ -- | Run a Hyperbole application, customizing both the document and the format of server errors liveAppWith :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application-liveAppWith opts app req =+liveAppWith opts eff req = do websocketsOr defaultConnectionOptions- (\pend -> socketApp opts req app pend `catch` suppressMessages)- (waiApp opts app)+ (\pend -> socketApp opts req eff pend `catch` suppressMessages)+ (waiApp opts eff) req waiApp :: ServerOptions -> Eff '[Hyperbole, Concurrent, IOE] Response -> Wai.Application-waiApp opts actions req res = do- runEff $ runConcurrent $ handleRequestWai opts req res actions+waiApp opts eff req res = do+ runEff $ runConcurrent $ handleRequestWai opts req res eff -socketApp :: ServerOptions -> Wai.Request -> Eff '[Hyperbole, Concurrent, IOE] Response -> PendingConnection -> IO ()-socketApp opts req actions pend = do- conn <- liftIO $ WS.acceptRequest pend- -- ping to keep the socket alive+socketApp :: (MonadIO m) => ServerOptions -> Wai.Request -> Eff '[Hyperbole, Concurrent, IOE] Response -> PendingConnection -> m ()+socketApp opts req eff pend = liftIO $ do+ -- private TVar for each client+ actions :: TVar RunningActions <- liftIO $ newTVarIO mempty+ conn <- WS.acceptRequest pend withPingThread conn 25 (pure ()) $ do forever $ do- runEff $ runConcurrent $ handleRequestSocket opts req conn actions+ runEff $ runConcurrent $ handleRequestSocket opts actions req conn eff suppressMessages :: ConnectionException -> IO a@@ -87,15 +90,15 @@ @-#EMBED Example/Docs/App.hs import Example.Docs.Page+#EMBED Example.Docs.App type UserId -#EMBED Example/Docs/App.hs type UserId+#EMBED Example.Docs.App data AppRoute -#EMBED Example/Docs/App.hs data AppRoute+#EMBED Example.Docs.App instance Route -#EMBED Example/Docs/App.hs instance Route+#EMBED Example.Docs.App router -#EMBED Example/Docs/App.hs router+#EMBED Example.Docs.App app @ -} routeRequest :: (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
src/Web/Hyperbole/Data/Encoded.hs view
@@ -20,7 +20,7 @@ newtype ConName = ConName {text :: Text}- deriving newtype (Eq, Show, IsString)+ deriving newtype (Eq, Show, IsString, Ord) instance Semigroup ConName where -- Ignore the second constructor name c1 <> _ = c1@@ -34,7 +34,7 @@ MyConstructor 1 2 3 -} data Encoded = Encoded ConName [ParamValue]- deriving (Show, Eq)+ deriving (Show, Eq, Ord) instance Semigroup Encoded where@@ -69,10 +69,7 @@ -- | Basic Encoding encodedToText :: Encoded -> Text encodedToText (Encoded con values) =- let params = T.intercalate " " $ fmap encodeParam values- in case params of- "" -> con.text- _ -> con.text <> " " <> params+ T.intercalate " " (con.text : fmap encodeParam values) encodedParseText :: Text -> Either String Encoded@@ -83,8 +80,8 @@ encodedParser = do con <- AC.takeTill AC.isSpace AC.skipSpace- params <- paramParser `sepBy` AC.char ' '- pure $ Encoded (ConName (cs con)) params+ ps <- paramParser `sepBy` AC.char ' '+ pure $ Encoded (ConName (cs con)) ps genericToEncoded :: (Generic a, GToEncoded (Rep a)) => a -> Encoded@@ -112,6 +109,14 @@ instance ToEncoded Encoded where toEncoded = id+instance ToEncoded () where+ toEncoded _ = mempty+instance ToEncoded ParamValue where+ toEncoded p = Encoded mempty [toParam p]+instance ToEncoded Int where+ toEncoded = toEncoded . toParam+instance ToEncoded Text where+ toEncoded = toEncoded . toParam -- | Custom Encoding for embedding into web documents. Noteably used for 'ViewId' and 'ViewAction'@@ -123,6 +128,17 @@ instance FromEncoded Encoded where parseEncoded = pure+instance FromEncoded () where+ parseEncoded _ = pure ()+instance FromEncoded ParamValue where+ parseEncoded (Encoded _ ps) = do+ case ps of+ [p] -> parseParam p+ _ -> Left $ "Expected single param value [param] but got: " <> show ps+instance FromEncoded Int where+ parseEncoded enc = parseEncoded enc >>= parseParam+instance FromEncoded Text where+ parseEncoded enc = parseEncoded enc >>= parseParam fromResult :: A.Result a -> Either String a@@ -152,7 +168,7 @@ desanitizeParamText :: Text -> Text desanitizeParamText =- T.replace "\\ " "_" . T.replace "_" " "+ T.replace "\\ " "_" . T.replace "_" " " . T.replace "\\n" "\n" -- | T.isSuffixOf "\\" seg = T.dropEnd 1 seg <> "_" <> txt@@ -174,8 +190,10 @@ "" -> "|" _ -> sanitizeParamText t where+ -- Q: Should we also sanitize \r\n? sanitizeParamText :: Text -> Text- sanitizeParamText = T.replace " " "_" . T.replace "_" "\\_"+ sanitizeParamText =+ T.replace " " "_" . T.replace "_" "\\_" . T.replace "\n" "\\n" -- decodeParamValue :: (FromParam a) => Text -> Either String a
src/Web/Hyperbole/Data/Param.hs view
@@ -24,8 +24,9 @@ -- | Encode arbitrarily complex data into url form encoded data-data ParamValue = ParamValue {value :: Text}- deriving (Eq, Show)+newtype ParamValue = ParamValue {value :: Text}+ deriving newtype (Ord, Eq)+ deriving (Show, Generic) instance IsString ParamValue where@@ -37,7 +38,7 @@ This is equivalent to Web.HttpApiData, which is missing some instances and has some strange defaults @-#EMBED Example/Docs/Sessions.hs data AppColor+#EMBED Example.Docs.Sessions data AppColor @ -} class ToParam a where@@ -46,6 +47,8 @@ toParam = genericToParam +instance ToParam ParamValue where+ toParam = id instance ToParam Int where toParam = jsonParam instance ToParam Integer where@@ -78,12 +81,14 @@ toParam = toParam . uriToText instance ToParam Value where toParam = jsonParam+instance (ToParam a, ToParam b) => ToParam (a, b) where+ toParam (a, b) = toParam [toParam a, toParam b] {- | Decode data from a 'query', 'session', or 'form' parameter value @-#EMBED Example/Docs/Sessions.hs data AppColor+#EMBED Example.Docs.Sessions data AppColor @ -} class FromParam a where@@ -104,6 +109,8 @@ -- decodeParamValue = parseParam . decodeParam -- Permissive instances. Some of these come directly from forms!+instance FromParam ParamValue where+ parseParam = pure instance FromParam Int where parseParam "" = pure 0 parseParam p = jsonParse p@@ -118,6 +125,12 @@ parseParam p = jsonParse p instance FromParam Text where parseParam = parseQueryParam+instance (FromParam a, FromParam b) => FromParam (a, b) where+ parseParam p = do+ ps <- parseParam @[ParamValue] p+ case ps of+ [pa, pb] -> (,) <$> parseParam pa <*> parseParam pb+ _ -> Left $ "Expected [a,b] but got: " <> cs p.value -- -- we don't need to desanitize the text
src/Web/Hyperbole/Data/QueryData.hs view
@@ -117,7 +117,7 @@ {- | Decode a type from a 'QueryData'. Missing fields are set to 'Data.Default.def' @-#EMBED Example/Docs/Encoding.hs data Filters+#EMBED Example.Docs.Encoding data Filters @ >>> parseQuery $ QueryData.parse "active=true&search=asdf"@@ -139,7 +139,7 @@ {- | A page can store state in the browser 'query' string. ToQuery and 'FromQuery' control how a datatype is encoded to a full query string @-#EMBED Example/Docs/Encoding.hs data Filters+#EMBED Example.Docs.Encoding data Filters @ >>> QueryData.render $ toQuery $ Filter True "asdf"
src/Web/Hyperbole/Document.hs view
@@ -4,6 +4,7 @@ import Data.ByteString.Lazy qualified as BL import Data.String.Interpolate (i)+import GHC.Generics (Generic) import Web.Hyperbole.View @@ -12,13 +13,15 @@ {- | 'liveApp' requires a function which turns an html fragment into an entire html document. Use this to import javascript, css, etc. Use 'quickStartDocument' to get going quickly -> #EMBED Example/Docs/App.hs app+> #EMBED Example.Docs.App app -} document :: View DocumentHead () -> BL.ByteString -> BL.ByteString document docHead cnt =- [i|<html>+ [i|<!doctype html>+ <html> <head>- #{renderLazyByteString $ addContext DocumentHead docHead}+ <meta charset="UTF-8"/>+ #{renderLazyByteString $ runViewContext DocumentHead () docHead} </head> <body> #{cnt}@@ -30,11 +33,12 @@ > import Web.Hyperbole (scriptEmbed, cssEmbed) >-> #EMBED Example/Docs/App.hs documentHead+> #EMBED Example.Docs.App documentHead >-> #EMBED Example/Docs/App.hs app+> #EMBED Example.Docs.App app -} data DocumentHead = DocumentHead+ deriving (Generic, ViewId) {- | A simple mobile-friendly document with all required embeds and live reload@@ -59,5 +63,4 @@ -- | Set the viewport to handle mobile zoom mobileFriendly :: View DocumentHead () mobileFriendly = do- meta @ httpEquiv "Content-Type" . content "text/html" . charset "UTF-8" meta @ name "viewport" . content "width=device-width, initial-scale=1.0"
src/Web/Hyperbole/Effect/Client.hs view
@@ -9,31 +9,33 @@ import Web.Hyperbole.HyperView import Web.Hyperbole.Types.Client (clientSetPageTitle) import Web.Hyperbole.Types.Event+import Web.Hyperbole.View (toAction, toViewId) {- | Trigger an action for an arbitrary 'HyperView' -#EXAMPLE /advanced- @-#EMBED Example/Page/Advanced.hs instance HyperView Controls+#EMBED Example.Trigger instance HyperView Controls @ -} trigger :: (HyperView id es, HyperViewHandled id view, Hyperbole :> es) => id -> Action id -> Eff (Reader view : es) () trigger vid act = do- send $ TriggerAction (TargetViewId $ encodeViewId vid) (toAction act)+ send $ TriggerAction (TargetViewId $ toViewId vid) (toAction act) {- | Dispatch a custom javascript event. This is emitted on the current hyper view and bubbles up to the document -#EXAMPLE /javascript- @-#EMBED Example/Page/Javascript.hs instance HyperView Message+#EMBED Example.Javascript instance HyperView Message @ @-#EMBED static/custom.js function listenServerEvents+function listenServerEvents() {+ // you can listen on document instead, the event will bubble+ Hyperbole.hyperView("Message").addEventListener("server-message", function(e) {+ alert("Server Message: " + e.detail)+ })+} @ -} pushEvent :: (ToJSON a, Hyperbole :> es) => Text -> a -> Eff es ()@@ -44,7 +46,7 @@ {- | Set the document title @-#EMBED Example/Docs/Client.hs page+#EMBED Example.Docs.Client page @ -} pageTitle :: (Hyperbole :> es) => Text -> Eff es ()
src/Web/Hyperbole/Effect/GenRandom.hs view
@@ -53,4 +53,4 @@ newtype Token a = Token {value :: Text}- deriving newtype (FromJSON, ToJSON, FromParam, ToParam, Eq, Show, Read)+ deriving newtype (FromJSON, ToJSON, FromParam, ToParam, Eq, Show, Read, Ord)
src/Web/Hyperbole/Effect/Hyperbole.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE UndecidableInstances #-} module Web.Hyperbole.Effect.Hyperbole where@@ -6,7 +5,6 @@ import Data.Aeson (Value) import Data.Text (Text) import Effectful-import Effectful.Dispatch.Dynamic import Effectful.Error.Static import Effectful.State.Static.Local import Effectful.Writer.Static.Local@@ -21,6 +19,7 @@ data Hyperbole :: Effect where GetRequest :: Hyperbole m Request RespondNow :: Response -> Hyperbole m a+ PushUpdate :: ViewUpdate -> Hyperbole m () ModClient :: (Client -> Client) -> Hyperbole m () GetClient :: Hyperbole m Client -- TODO: this should actually execute the other view, and send the response to the client@@ -36,30 +35,11 @@ | RemoteEvent Text Value --- | Run the 'Hyperbole' effect to get a response-runHyperbole- :: Request- -> Eff (Hyperbole : es) Response- -> Eff es (Response, Client, [Remote])-runHyperbole req = reinterpret runLocal $ \_ -> \case- GetRequest -> do- pure req- RespondNow r -> do- throwError_ r- GetClient -> do- get @Client- ModClient f -> do- modify @Client f- TriggerAction vid act -> do- tell [RemoteAction vid act]- TriggerEvent name dat -> do- tell [RemoteEvent name dat]+runHyperboleLocal :: Request -> Eff (Error Response : State Client : Writer [Remote] : es) Response -> Eff es (Response, Client, [Remote])+runHyperboleLocal req eff = do+ ((eresp, client'), rmts) <- runWriter @[Remote] . runState (emptyClient req.requestId) . runErrorNoCallStack @Response $ eff+ pure (either id id eresp, client', rmts) where- runLocal :: Eff (Error Response : State Client : Writer [Remote] : es) Response -> Eff es (Response, Client, [Remote])- runLocal eff = do- ((eresp, client'), rmts) <- runWriter @[Remote] . runState (emptyClient req.requestId) . runErrorNoCallStack @Response $ eff- pure (either id id eresp, client', rmts)- emptyClient :: RequestId -> Client emptyClient requestId = Client
src/Web/Hyperbole/Effect/OAuth2.hs view
@@ -56,12 +56,6 @@ import Web.Hyperbole.Types.Response --- TODO: could all hyperbole applications have an oauth2 redirect endpoint?--- TVar (Map State (Eff es _))--- then you could execute it afterwards!---- High level effect interface- authUrl :: (OAuth2 :> es) => URI -> Scopes -> Eff es URI authUrl redirectUrl scopes = send $ AuthUrl redirectUrl scopes
src/Web/Hyperbole/Effect/Query.hs view
@@ -20,9 +20,9 @@ {- | Parse querystring from the 'Request' into a datatype. See 'FromQuery' @-#EMBED Example/Docs/Params.hs data Filters+#EMBED Example.Docs.Params data Filters -#EMBED Example/Docs/Params.hs page+#EMBED Example.Docs.Params page @ -} query :: (FromQuery a, Hyperbole :> es) => Eff es a@@ -36,7 +36,7 @@ {- | Update the client's querystring to an encoded datatype. See 'ToQuery' @-#EMBED Example/Docs/Params.hs instance HyperView Todos+#EMBED Example.Docs.Params instance HyperView Todos @ -} setQuery :: (ToQuery a, Hyperbole :> es) => a -> Eff es ()@@ -52,10 +52,15 @@ pure updated +clearQuery :: (Hyperbole :> es) => Eff es ()+clearQuery =+ setQuery (mempty :: QueryData)++ {- | Parse a single query parameter. Return a 400 status if missing or if parsing fails. See 'FromParam' @-#EMBED Example/Docs/Params.hs page'+#EMBED Example.Docs.Params page' @ -} param :: (FromParam a, Hyperbole :> es) => Param -> Eff es a@@ -70,7 +75,7 @@ @-#EMBED Example/Docs/SideEffects.hs page+#EMBED Example.Docs.SideEffects page @ -} lookupParam :: (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)@@ -81,7 +86,7 @@ {- | Modify the client's querystring to set a single parameter. See 'ToParam' @-#EMBED Example/Docs/Params.hs instance HyperView Message+#EMBED Example.Docs.Params instance HyperView Message @ -} setParam :: (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
src/Web/Hyperbole/Effect/Response.hs view
@@ -3,22 +3,36 @@ import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic+import Effectful.Reader.Dynamic+import Effectful.State.Dynamic import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Data.URI import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..))-import Web.Hyperbole.HyperView (HyperView (..), ViewId (..), hyperUnsafe)+import Web.Hyperbole.HyperView (ConcurrencyValue (..), HyperView (..), hyperUnsafe) import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Response-import Web.Hyperbole.View.Types+import Web.Hyperbole.View -- | Respond with the given hyperview-hyperView :: (HyperView id es) => id -> View id () -> Eff es Response-hyperView i vw = do- let vid = TargetViewId (encodedToText $ toViewId i)- pure $ Response vid $ hyperUnsafe i vw+hyperView :: (HyperView id es, ToEncoded (ViewState id)) => id -> ViewState id -> View id () -> Eff es Response+hyperView i st vw = do+ let vid = TargetViewId (toViewId i)+ pure $ Response $ ViewUpdate vid $ renderBody $ hyperUnsafe i st vw +pushUpdate :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => View id () -> Eff (Reader id : State (ViewState id) : es) ()+pushUpdate vw = do+ i <- viewId+ st <- get+ pushUpdateTo i st vw+++pushUpdateTo :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> Eff es ()+pushUpdateTo i st vw = do+ send $ PushUpdate $ ViewUpdate (TargetViewId $ toViewId i) $ renderBody $ hyperUnsafe i st vw++ -- | Abort execution and respond with an error respondError :: (Hyperbole :> es) => ResponseError -> Eff es a respondError err = do@@ -26,17 +40,17 @@ -- | Abort execution and respond with an error view-respondErrorView :: (Hyperbole :> es) => Text -> View Body () -> Eff es a+respondErrorView :: (Hyperbole :> es) => Text -> View () () -> Eff es a respondErrorView msg vw = do- send $ RespondNow $ Err $ ErrCustom $ ServerError msg vw+ send $ RespondNow $ Err $ ErrCustom $ ServerError msg $ renderBody vw {- | Abort execution and respond with 404 Not Found @-#EMBED Example/Docs/App.hs findUser+#EMBED Example.Docs.App findUser -#EMBED Example/Docs/App.hs userPage+#EMBED Example.Docs.App userPage @ -} notFound :: (Hyperbole :> es) => Eff es a@@ -54,6 +68,6 @@ -- | Respond with a generic view. Normally you will return a view from the page or handler instead of using this function-view :: View Body () -> Response-view =- Response (TargetViewId "")+view :: View () () -> Response+view v =+ Response $ ViewUpdate (TargetViewId mempty) (renderBody v)
src/Web/Hyperbole/Effect/Session.hs view
@@ -24,9 +24,9 @@ {- | Configure a data type to persist in the 'session' as a cookie. These are type-indexed, so only one of each can exist in the session @-#EMBED Example/Docs/Sessions.hs data Preferences+#EMBED Example.Docs.Sessions data Preferences -#EMBED Example/Docs/Sessions.hs instance Default Preferences+#EMBED Example.Docs.Sessions instance Default Preferences @ -} class Session a where@@ -58,11 +58,11 @@ {- | Load data from a browser cookie. If it doesn't exist, the 'Default' instance is used @-#EMBED Example/Docs/Sessions.hs data Preferences+#EMBED Example.Docs.Sessions data Preferences -#EMBED Example/Docs/Sessions.hs instance Default Preferences+#EMBED Example.Docs.Sessions instance Default Preferences -#EMBED Example/Docs/Sessions.hs page+#EMBED Example.Docs.Sessions page @ -} session :: (Session a, Default a, Hyperbole :> es) => Eff es a@@ -84,11 +84,11 @@ {- | Persist datatypes in browser cookies @-#EMBED Example/Docs/Sessions.hs data Preferences+#EMBED Example.Docs.Sessions data Preferences -#EMBED Example/Docs/Sessions.hs instance Default Preferences+#EMBED Example.Docs.Sessions instance Default Preferences -#EMBED Example/Docs/Sessions.hs instance HyperView Content+#EMBED Example.Docs.Sessions instance HyperView Content @ -} saveSession :: forall a es. (Session a, Hyperbole :> es) => a -> Eff es ()
src/Web/Hyperbole/HyperView.hs view
@@ -2,15 +2,20 @@ ( module Web.Hyperbole.HyperView.Types , module Web.Hyperbole.HyperView.Input , module Web.Hyperbole.HyperView.Event- , module Web.Hyperbole.HyperView.ViewId- , module Web.Hyperbole.HyperView.ViewAction , module Web.Hyperbole.HyperView.Handled+ , module Web.Hyperbole.HyperView.Hyper+ , get+ , put+ , gets+ , modify+ , state+ , State ) where +import Effectful.State.Dynamic import Web.Hyperbole.HyperView.Event import Web.Hyperbole.HyperView.Handled+import Web.Hyperbole.HyperView.Hyper import Web.Hyperbole.HyperView.Input import Web.Hyperbole.HyperView.Types-import Web.Hyperbole.HyperView.ViewAction-import Web.Hyperbole.HyperView.ViewId
src/Web/Hyperbole/HyperView/Event.hs view
@@ -7,43 +7,44 @@ import Web.Hyperbole.Data.Encoded import Web.Hyperbole.HyperView.Handled import Web.Hyperbole.HyperView.Types-import Web.Hyperbole.HyperView.ViewAction-import Web.Hyperbole.HyperView.ViewId import Web.Hyperbole.View-import Web.Hyperbole.View.Types (ViewContext) type DelayMs = Int -event :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Name -> Action id -> Attributes a -> Attributes a-event eventName a = att ("data-on" <> eventName) (encodedToText $ toAction a)+event :: (ViewAction (Action id), Attributable a) => Name -> Action id -> Attributes a -> Attributes a+event nm a = att (eventName nm) (encodedToText $ toAction a) -{- | Send the action after N milliseconds. Can be used to implement lazy loading or polling. See [Example.Page.Concurrent](https://docs.hyperbole.live/concurrent)+eventName :: Text -> Name+eventName t = "data-on" <> t ++{- | Send the action after N milliseconds. Can be used to implement lazy loading or polling.+ @-#EMBED Example/Page/Concurrency.hs viewTaskLoad+#EMBED Example.Concurrency.LazyLoading viewTaskLoad @ -}-onLoad :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a+onLoad :: (ViewAction (Action id), Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a onLoad a delay = do event "load" a . att "data-delay" (cs $ show delay) -onClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onClick :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onClick = event "click" -onDblClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onDblClick :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onDblClick = event "dblclick" -onMouseEnter :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onMouseEnter :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onMouseEnter = event "mouseenter" -onMouseLeave :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onMouseLeave :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onMouseLeave = event "mouseleave" @@ -53,31 +54,31 @@ > input (onInput OnSearch) 250 id -}-onInput :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a+onInput :: (ViewAction (Action id), Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a onInput a delay = do- att "data-oninput" (encodedToText $ toActionInput a) . att "data-delay" (cs $ show delay)+ att (eventName "input") (encodedToText $ toActionInput a) . att "data-delay" (cs $ show delay) -- WARNING: no way to do this generically right now, because toActionInput is specialized to Text -- the change event DOES assume that the target has a string value -- but, that doesn't let us implement dropdown-onChange :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (value -> Action id) -> Attributes a -> Attributes a+onChange :: (ViewAction (Action id), Attributable a) => (value -> Action id) -> Attributes a -> Attributes a onChange a = do- att "data-onchange" (encodedToText $ toActionInput a)+ att (eventName "change") (encodedToText $ toActionInput a) -onSubmit :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a+onSubmit :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onSubmit = event "submit" -onKeyDown :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a-onKeyDown key act = do- att ("data-on-keydown-" <> keyDataAttribute key) (encodedToText $ toAction act)+onKeyDown :: (ViewAction (Action id), Attributable a) => Key -> Action id -> Attributes a -> Attributes a+onKeyDown key = do+ event ("keydown-" <> keyDataAttribute key) -onKeyUp :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a-onKeyUp key act = do- att ("data-on-keyup-" <> keyDataAttribute key) (encodedToText $ toAction act)+onKeyUp :: (ViewAction (Action id), Attributable a) => Key -> Action id -> Attributes a -> Attributes a+onKeyUp key = do+ event ("keyup-" <> keyDataAttribute key) keyDataAttribute :: Key -> Text@@ -118,17 +119,17 @@ -- | Internal-dataTarget :: (ViewId id, ViewContext a ~ id, Attributable a) => id -> Attributes a -> Attributes a+dataTarget :: (ViewId id, Attributable a) => id -> Attributes a -> Attributes a dataTarget = att "data-target" . encodedToText . toViewId {- | Allow inputs to trigger actions for a different view @-#EMBED Example/Page/Advanced.hs targetView+#EMBED Example.Trigger targetView @ -}-target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx ()-target newId view = do- addContext newId $ do+target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> ViewState id -> View id () -> View ctx ()+target newId st view = do+ runViewContext newId st $ do view @ dataTarget newId
src/Web/Hyperbole/HyperView/Forms.hs view
@@ -16,7 +16,6 @@ , label , input , checkbox- , Radio (..) , radioGroup , radio , select@@ -60,7 +59,6 @@ import Web.Hyperbole.HyperView.Event (onSubmit) import Web.Hyperbole.HyperView.Input (Option (..), checked) import Web.Hyperbole.HyperView.Types-import Web.Hyperbole.HyperView.ViewAction import Web.Hyperbole.View @@ -71,7 +69,7 @@ {- | Simple types that be decoded from form data @-#EMBED Example/Page/FormSimple.hs data ContactForm+#EMBED Example.FormSimple data ContactForm @ -} class FromForm (form :: Type) where@@ -83,7 +81,7 @@ {- | A Higher-Kinded type that can be parsed from a 'Web.FormUrlEncoded.Form' @-#EMBED Example/Page/FormValidation.hs data UserForm+#EMBED Example.FormValidation data UserForm @ -} class FromFormF (f :: (Type -> Type) -> Type) where@@ -113,11 +111,11 @@ {- | Generate a Higher Kinded record with all selectors filled with default values. See 'GenField' @-#EMBED Example/Page/FormValidation.hs data UserForm+#EMBED Example.FormValidation data UserForm @ @-#EMBED Example/Page/Contacts.hs newContactForm+#EMBED Example.Contacts newContactForm @ -} class GenFields f (form :: (Type -> Type) -> Type) where@@ -130,9 +128,9 @@ #EXAMPLE /forms -> #EMBED Example/Page/Todos/Todo.hs data TodoForm+> #EMBED Example.Todos.Todo data TodoForm >-> #EMBED Example/Page/Todos/Todo.hs todoForm+> #EMBED Example.Todos.Todo todoForm -} fieldNames :: forall form. (GenFields FieldName form) => form FieldName fieldNames = genFields@@ -160,20 +158,21 @@ ------------------------------------------------------------------------------ -- | Context that allows form fields-data FormFields id = FormFields id+newtype FormFields id = FormFields id+ deriving (Generic)+ deriving newtype (ViewId) {- | Type-safe \<form\>. Calls (Action id) on submit @-#EMBED Example/Page/FormSimple.hs formView+#EMBED Example.FormSimple formView @ -} form :: (ViewAction (Action id)) => Action id -> View (FormFields id) () -> View id () form a cnt = do- vid <- context tag "form" @ onSubmit a $ do- addContext (FormFields vid) cnt+ runChildView FormFields cnt -- | Button that submits the 'form'@@ -182,8 +181,8 @@ -- | Form FieldName. This is embeded as the name attribute, and refers to the key need to parse the form when submitted. See 'fieldNames'-newtype FieldName a = FieldName Text- deriving newtype (Show, IsString)+newtype FieldName a = FieldName {value :: Text}+ deriving newtype (Show, IsString, FromParam, ToParam) -- | Display a 'FormField'. See 'form' and 'Form'@@ -193,7 +192,7 @@ -> View (Input id a) () -> View (FormFields id) () field fn =- addContext (Input fn)+ runChildView (\(FormFields i) -> Input i fn) -- | Choose one for 'input's to give the browser autocomplete hints@@ -217,8 +216,12 @@ data Input (id :: Type) (a :: Type) = Input- { inputName :: FieldName a+ { id :: id+ , inputName :: FieldName a }+ deriving (Generic)+instance (ViewId id, FromParam id, ToParam id) => ViewId (Input id a) where+ type ViewState (Input id a) = ViewState id {- | label for a 'field'@@ -229,10 +232,10 @@ -- | input for a 'field'-input :: InputType -> View (Input id a) ()+input :: forall id a. InputType -> View (Input id a) () input ft = do- Input (FieldName nm) <- context- tag "input" @ att "type" (inpType ft) . name nm . att "autocomplete" (auto ft) $ none+ inp :: Input id a <- viewId+ tag "input" @ att "type" (inpType ft) . name inp.inputName.value . att "autocomplete" (auto ft) $ none where inpType NewPassword = "password" inpType CurrentPassword = "password"@@ -242,51 +245,56 @@ inpType _ = "text" auto :: InputType -> Text- auto = pack . kebab . show+ auto TextInput = "off"+ auto inp = pack . kebab . show $ inp -checkbox :: Bool -> View (Input id a) ()+checkbox :: forall id a. Bool -> View (Input id a) () checkbox isChecked = do- Input (FieldName nm) <- context- tag "input" @ att "type" "checkbox" . name nm $ none @ checked isChecked+ inp :: Input id a <- viewId+ tag "input" @ att "type" "checkbox" . name inp.inputName.value $ none @ checked isChecked -- NOTE: Radio is a special type of selection different from list type or -- select. select or list input can be thought of one wrapper and multiple -- options whereas radio is multiple wrappers with options. The context required -- for radio is more than that required for select.-data Radio (id :: Type) (a :: Type) (b :: Type) = Selection- { inputCtx :: Input id a- , defaultOption :: b+data Radio (id :: Type) (a :: Type) (opt :: Type) = Radio+ { id :: id+ , inputName :: FieldName a+ , defaultOption :: opt }+ deriving (Generic)+instance (FromParam id, ToParam id, FromParam a, ToParam a, ToParam opt, FromParam opt) => ViewId (Radio id a opt) where+ type ViewState (Radio id a opt) = ViewState id -radioGroup :: b -> View (Radio id a b) () -> View (Input id a) ()-radioGroup defOpt = modifyContext $ \inp -> Selection inp defOpt+radioGroup :: opt -> View (Radio id a opt) () -> View (Input id a) ()+radioGroup defOpt = runChildView (\(inp :: Input id a) -> Radio inp.id inp.inputName defOpt) -radio :: (Eq b, ToParam b) => b -> View (Radio id a b) ()+radio :: forall id a opt. (Eq opt, ToParam opt) => opt -> View (Radio id a opt) () radio val = do- Selection (Input (FieldName nm)) defOpt <- context+ rd :: Radio id a opt <- viewId tag "input" @ att "type" "radio"- . name nm+ . name rd.inputName.value . value (toParam val).value- . checked (defOpt == val)+ . checked (rd.defaultOption == val) $ none -select :: (Eq opt) => opt -> View (Option opt id) () -> View (Input id a) ()+select :: forall opt id a. (Eq opt) => opt -> View (Option id opt) () -> View (Input id a) () select defOpt options = do- Input (FieldName nm) <- context- tag "select" @ name nm $ addContext (Option defOpt) options+ inp :: Input id a <- viewId+ tag "select" @ name inp.inputName.value $ runChildView (\_ -> Option inp.id defOpt) options -- | textarea for a 'field'-textarea :: Maybe Text -> View (Input id a) ()+textarea :: forall id a. Maybe Text -> View (Input id a) () textarea mDefaultText = do- Input (FieldName nm) <- context- tag "textarea" @ name nm $ text $ fromMaybe "" mDefaultText+ inp :: Input id a <- viewId+ tag "textarea" @ name inp.inputName.value $ text $ fromMaybe "" mDefaultText ------------------------------------------------------------------------------@@ -296,11 +304,11 @@ {- | Validation results for a 'Form'. See 'validate' @-#EMBED Example/Page/FormValidation.hs data UserForm+#EMBED Example.FormValidation data UserForm -#EMBED Example/Page/FormValidation.hs validateForm+#EMBED Example.FormValidation validateForm -#EMBED Example/Page/FormValidation.hs validateAge+#EMBED Example.FormValidation validateAge @ -} data Validated a = Invalid Text | NotInvalid | Valid@@ -360,7 +368,7 @@ {- | specify a check for a 'Validation' @-#EMBED Example/Page/FormValidation.hs validateAge+#EMBED Example.FormValidation validateAge @ -} validate :: Bool -> Text -> Validated a@@ -554,91 +562,3 @@ -- -- instance (GCollect f v) => GCollect (M1 C c f) v where -- gCollect (M1 f) = gCollect f------------------------------------------------------------------------------------ newtype User = User Text--- deriving newtype (FromParam)--------- data TestForm f = TestForm--- { name :: Field f Text--- , age :: Field f Int--- , user :: Field f User--- }--- deriving (Generic, FromFormF, GenFields Maybe, GenFields Validated)---- test :: (Hyperbole :> es) => Eff es (TestForm Identity)--- test = do--- tf <- formData--- pure tf---- formView :: (ViewAction (Action id)) => View id ()--- formView = do--- -- generate a ContactForm' FieldName--- let f = fieldNames @ContactForm--- form undefined (gap 10 . pad 10) $ do--- -- f.name :: FieldName Text--- -- f.name = FieldName "name"--- field f.name id $ do--- label "Contact Name"--- input Username (placeholder "contact name")------ -- f.age :: FieldName Int--- -- f.age = FieldName "age"--- field f.age id $ do--- label "Age"--- input Number (placeholder "age" . value "0")------ submit id "Submit"--------- formView' :: (ViewAction (Action id)) => ContactForm Validated -> View id ()--- formView' contact = do--- -- generate a ContactForm' FieldName--- let f = formFields @ContactForm contact--- form undefined (gap 10 . pad 10) $ do--- -- f.name :: FieldName Text--- -- f.name = FieldName "name"--- field f.name id $ do--- label "Contact Name"--- input Username (placeholder "contact name")------ -- f.age :: FieldName Int--- -- f.age = FieldName "age"--- field f.age id $ do--- label "Age"--- input Number (placeholder "age" . value "0")------ field f.age id $ do--- label "Username"--- input Username (placeholder "username")------ case f.age.value of--- Invalid t -> el_ (text t)--- Valid -> el_ "Username is available"--- _ -> none------ submit id "Submit"--- where--- valStyle (Invalid _) = id--- valStyle Valid = id--- valStyle _ = id--------- data ContactForm' = ContactForm'--- { name :: Text--- , age :: Int--- }--- deriving (Generic)--- instance FormParse ContactForm'--------- formView'' :: (ViewAction (Action id)) => View id ()--- formView'' = do--- form undefined (gap 10 . pad 10) $ do--- -- f.name :: FieldName Text--- -- f.name = FieldName "name"--- field (FieldName "name") id $ do--- label "Contact Name"--- input Username (placeholder "contact name")
src/Web/Hyperbole/HyperView/Handled.hs view
@@ -4,33 +4,9 @@ import Data.Kind (Constraint, Type) import GHC.TypeLits hiding (Mod)-import Web.Atomic.Types-import Web.Hyperbole.Data.Encoded as Encoded import Web.Hyperbole.HyperView.Types-import Web.Hyperbole.HyperView.ViewId import Web.Hyperbole.TypeList-import Web.Hyperbole.View (View, addContext, tag)---{- | Embed a 'HyperView' into a page or another 'View'--@-#EMBED Example/Docs/Interactive.hs page-@--}-hyper- :: forall id ctx- . (HyperViewHandled id ctx, ViewId id)- => id- -> View id ()- -> View ctx ()-hyper = hyperUnsafe---hyperUnsafe :: (ViewId id) => id -> View id () -> View ctx ()-hyperUnsafe vid vw = do- tag "div" @ att "id" (encodedToText $ toViewId vid) $- addContext vid vw+import Web.Hyperbole.View (View) type family ValidDescendents x :: [Type] where@@ -100,12 +76,16 @@ ) -type HyperViewHandled id ctx =- ( -- the id must be found in the children of the context- ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx))- , -- Make sure the descendents of id are in the context for the root page- CheckDescendents id ctx- )+type family HyperViewHandled id ctx :: Constraint where+ -- If you forget to pass the state into a function that expects it, you end up passing a view+ -- in as the ctx. Show an error to help with this+ HyperViewHandled id (View view ()) = TypeError ('Text "View c () is not a valid ViewState, did you forget to pass ViewState into target or runViewContext?")+ HyperViewHandled id ctx =+ ( -- the id must be found in the children of the context+ ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx))+ , -- Make sure the descendents of id are in the context for the root page+ CheckDescendents id ctx+ ) -- TODO: Report which view requires the missing one
+ src/Web/Hyperbole/HyperView/Hyper.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE UndecidableInstances #-}++module Web.Hyperbole.HyperView.Hyper where++import Web.Atomic.Types+import Web.Hyperbole.Data.Encoded as Encoded+import Web.Hyperbole.HyperView.Handled (HyperViewHandled)+import Web.Hyperbole.HyperView.Types+import Web.Hyperbole.View (View, runViewContext, tag)+import Web.Hyperbole.View.ViewId+++{- | Embed a 'HyperView' into a page or another 'View'++@+#EMBED Example.Docs.Interactive page+@+-}+hyper+ :: forall id ctx+ . (HyperViewHandled id ctx, ViewId id, ViewState id ~ (), ConcurrencyValue (Concurrency id))+ => id+ -> View id ()+ -> View ctx ()+hyper vid = hyperState vid ()+++hyperState+ :: forall id ctx+ . (HyperViewHandled id ctx, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id))+ => id+ -> ViewState id+ -> View id ()+ -> View ctx ()+hyperState = hyperUnsafe+++hyperUnsafe :: forall id ctx. (ViewId id, ViewState id ~ ViewState id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> View ctx ()+hyperUnsafe vid st vw = do+ tag "div" @ att "id" (encodedToText $ toViewId vid) . state . concurrency $+ runViewContext vid st vw+ where+ concurrency =+ case concurrencyMode @(Concurrency id) of+ Drop -> id+ Replace -> att "data-concurrency" (encode Replace)++ state =+ if encode st == mempty+ then id+ else att "data-state" (encode st)
src/Web/Hyperbole/HyperView/Input.hs view
@@ -2,11 +2,11 @@ import Data.String.Conversions (cs) import Data.Text (Text)+import GHC.Generics (Generic) import Web.Atomic.Types-import Web.Hyperbole.Data.Param (ParamValue (..), ToParam (..))+import Web.Hyperbole.Data.Param (FromParam, ParamValue (..), ToParam (..)) import Web.Hyperbole.HyperView.Event (DelayMs, onChange, onClick, onInput) import Web.Hyperbole.HyperView.Types (HyperView (..))-import Web.Hyperbole.HyperView.ViewAction (ViewAction (..)) import Web.Hyperbole.Route (Route (..), routeUri) import Web.Hyperbole.View @@ -14,7 +14,7 @@ {- | \<button\> HTML tag which sends the action when pressed @-#EMBED Example/Page/Simple.hs messageView+#EMBED Example.Simple messageView @ -} button :: (ViewAction (Action id)) => Action id -> View id () -> View id ()@@ -22,43 +22,37 @@ tag "button" cnt @ onClick action --- tag "button" @ att "whatber" "asdf" $ cnt---- {- | \<input type="checkbox"\> which toggles automatically------ > toggle True SetIsSelected id--- -}--- toggle :: (ViewAction (Action id)) => Bool -> (Bool -> Action id) -> Mod id -> View id ()--- toggle isSelected clickAction f = do--- tag "input" (att "type" "checkbox" . checked isSelected . onClick (clickAction (not isSelected)) . f) none- {- | Type-safe dropdown. Sends (opt -> Action id) when selected. The default will be selected. #EXAMPLE /data/filter @-#EMBED Example/Page/DataLists/Filter.hs familyDropdown+#EMBED Example.DataLists.Filter familyDropdown @ -} dropdown- :: (ViewAction (Action id))+ :: forall opt id+ . (ViewAction (Action id)) => (opt -> Action id) -> opt -- default option- -> View (Option opt id) ()+ -> View (Option id opt) () -> View id () dropdown act defOpt options = do+ st :: ViewState id <- viewState+ i :: id <- viewId tag "select" @ onChange act $ do- addContext (Option defOpt) options+ runViewContext (Option i defOpt) st options -- | An option for a 'dropdown' or 'select' option- :: (ViewAction (Action id), Eq opt, ToParam opt)+ :: forall opt id+ . (ViewAction (Action id), Eq opt, ToParam opt) => opt -> Text- -> View (Option opt id) ()+ -> View (Option id opt) () option opt cnt = do- os <- context+ os :: Option id opt <- viewId tag "option" @ att "value" (toParam opt).value @ selected (os.defaultOption == opt) $ text cnt @@ -68,15 +62,21 @@ -- | The view context for an 'option'-data Option opt id = Option- { defaultOption :: opt+data Option id opt = Option+ { id :: id+ , defaultOption :: opt }+ deriving (Generic) +instance (ToParam id, ToParam opt, FromParam id, FromParam opt) => ViewId (Option id opt) where+ type ViewState (Option id opt) = ViewState id++ {- | A live search field. Set a DelayMs to avoid hitting the server on every keystroke @-#EMBED Example/Page/Errors.hs viewSearchUsers+#EMBED Example.Errors viewSearchUsers @ -} search :: (ViewAction (Action id)) => (Text -> Action id) -> DelayMs -> View id ()
src/Web/Hyperbole/HyperView/Types.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE UndecidableInstances #-} module Web.Hyperbole.HyperView.Types where@@ -5,24 +7,26 @@ import Data.Kind (Type) import Effectful import Effectful.Reader.Dynamic+import Effectful.State.Dynamic import GHC.Generics+import Web.Hyperbole.Data.Encoded as Encoded import Web.Hyperbole.Effect.Hyperbole (Hyperbole)-import Web.Hyperbole.HyperView.ViewAction-import Web.Hyperbole.HyperView.ViewId-import Web.Hyperbole.View (View, none)+import Web.Hyperbole.View (View (..), ViewAction, ViewId (..), none) +-- HyperView --------------------------------------------+ {- | HyperViews are interactive subsections of a 'Page' Create an instance with a unique view id type and a sum type describing the actions the HyperView supports. The View Id can contain context (a database id, for example) @-#EMBED Example/Docs/Interactive.hs data Message+#EMBED Example.Simple data Message -#EMBED Example/Docs/Interactive.hs instance HyperView Message es where+#EMBED Example.Simple instance HyperView Message es where @ -}-class (ViewId id, ViewAction (Action id)) => HyperView id es where+class (ViewId id, ViewAction (Action id), ConcurrencyValue (Concurrency id)) => HyperView id es where -- | Outline all actions that are permitted in this HyperView -- -- > data Action Message = SetMessage Text | ClearMessage@@ -32,18 +36,53 @@ -- | Include any child hyperviews here. The compiler will make sure that the page knows how to handle them --- -- > type Require = '[ChildView]+ -- > type Require Messages = '[ChildView] type Require id :: [Type] type Require id = '[] + -- type ViewState id :: Type+ -- type ViewState id = ()++ -- | Control how overlapping actions are handled. 'Drop' by default+ --+ -- > type Concurrency Autocomplete = Replace+ type Concurrency id :: ConcurrencyMode+++ type Concurrency id = Drop++ -- | Specify how the view should be updated for each Action -- -- > update (SetMessage msg) = pure $ messageView msg -- > update ClearMessage = pure $ messageView ""- update :: (Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ())+ update :: (Hyperbole :> es) => Action id -> Eff (Reader id : State (ViewState id) : es) (View id ())+++instance HyperView () es where+ data Action () = TupleNone+ deriving (Generic, ViewAction)+ update _ = pure none+++-- convert the type to a value+class ConcurrencyValue a where+ concurrencyMode :: ConcurrencyMode+instance ConcurrencyValue 'Drop where+ concurrencyMode = Drop+instance ConcurrencyValue 'Replace where+ concurrencyMode = Replace+++data ConcurrencyMode+ = -- | Do not send any actions that occur while one is active. Prevents double-submitting writes or expensive operations+ Drop+ | -- | Ignore the results of older actions in favor of new ones. Use for read-only views with fast-firing interactions, like autocomplete, sliders, etc+ Replace+ deriving (Generic, ToEncoded, FromEncoded) -- | The top-level view returned by a 'Page'. It carries a type-level list of every 'HyperView' used in our 'Page' so the compiler can check our work and wire everything together.
− src/Web/Hyperbole/HyperView/ViewAction.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE DefaultSignatures #-}--module Web.Hyperbole.HyperView.ViewAction where--import Data.Text (Text)-import GHC.Generics-import Web.Hyperbole.Data.Encoded as Encoded---{- | Define every action possible for a given 'HyperView'--@-#EMBED Example/Page/Simple.hs instance HyperView Message-@--}-class ViewAction a where- toAction :: a -> Encoded- default toAction :: (Generic a, GToEncoded (Rep a)) => a -> Encoded- toAction = genericToEncoded--- parseAction :: Encoded -> Either String a- default parseAction :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a- parseAction = genericParseEncoded---instance ViewAction () where- toAction _ = mempty- parseAction _ = pure ()---encodeAction :: (ViewAction act) => act -> Text-encodeAction = encodedToText . toAction---decodeAction :: (ViewAction act) => Text -> Maybe act-decodeAction t = do- case parseAction =<< encodedParseText t of- Left _ -> Nothing- Right a -> pure a
− src/Web/Hyperbole/HyperView/ViewId.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE DefaultSignatures #-}--module Web.Hyperbole.HyperView.ViewId where--import Data.Text (Text)-import Effectful-import Effectful.Reader.Dynamic-import GHC.Generics-import Web.Hyperbole.Data.Encoded as Encoded-import Web.Hyperbole.View (View, context)---{- | A unique identifier for a 'HyperView'--@-#EMBED Example/Page/Simple.hs data Message-@--}-class ViewId a where- toViewId :: a -> Encoded- default toViewId :: (Generic a, GToEncoded (Rep a)) => a -> Encoded- toViewId = genericToEncoded--- parseViewId :: Encoded -> Either String a- default parseViewId :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a- parseViewId = genericParseEncoded---{- | Access the 'viewId' in a 'View' or 'update'--@-#EMBED Example/Page/Concurrency.hs data LazyData--#EMBED Example/Page/Concurrency.hs instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where-@--}-class HasViewId m view where- viewId :: m view---instance HasViewId (View ctx) ctx where- viewId = context-instance HasViewId (Eff (Reader view : es)) view where- viewId = ask---encodeViewId :: (ViewId id) => id -> Text-encodeViewId = encodedToText . toViewId---decodeViewId :: (ViewId id) => Text -> Maybe id-decodeViewId t = do- case parseViewId =<< encodedParseText t of- Left _ -> Nothing- Right a -> pure a
src/Web/Hyperbole/Page.hs view
@@ -13,7 +13,7 @@ {- | An application is divided into multiple [Pages](#g:pages). Each page module should have a 'Page' function, which returns a root 'View' @-#EMBED Example/Docs/MultiView.hs page+#EMBED Example.Docs.MultiView page @ -} type Page es (views :: [Type]) = Eff (Reader (Root views) : es) (View (Root views) ())@@ -22,9 +22,9 @@ {- | Run a 'Page' and return a 'Response' @-#EMBED Example/Docs/BasicPage.hs main+#EMBED Example.Docs.BasicPage main -#EMBED Example/Docs/BasicPage.hs page+#EMBED Example.Docs.BasicPage page @ -} runPage
src/Web/Hyperbole/Route.hs view
@@ -26,9 +26,9 @@ {- | Derive this class to use a sum type as a route. Constructors and Selectors map intuitively to url patterns @-#EMBED Example/Docs/App.hs data AppRoute+#EMBED Example.Docs.App data AppRoute -#EMBED Example/Docs/App.hs instance Route+#EMBED Example.Docs.App instance Route @ >>> routeUri Main
src/Web/Hyperbole/Server/Handler.hs view
@@ -7,6 +7,7 @@ import Effectful import Effectful.Dispatch.Dynamic import Effectful.Reader.Dynamic+import Effectful.State.Dynamic import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Effect.Response (hyperView, respondError)@@ -18,14 +19,14 @@ class RunHandlers (views :: [Type]) es where- runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded -> Eff es (Maybe Response)+ runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded Encoded -> Eff es (Maybe Response) instance RunHandlers '[] es where runHandlers _ = pure Nothing -instance (HyperView view es, RunHandlers views es) => RunHandlers (view : views) es where+instance (HyperView view es, ToEncoded (ViewState view), FromEncoded (ViewState view), RunHandlers views es) => RunHandlers (view : views) es where runHandlers rawEvent = do mr <- runHandler @view rawEvent (update @view) case mr of@@ -35,17 +36,17 @@ runHandler :: forall id es- . (HyperView id es, Hyperbole :> es)- => Event TargetViewId Encoded- -> (Action id -> Eff (Reader id : es) (View id ()))+ . (HyperView id es, ToEncoded (ViewState id), FromEncoded (ViewState id), Hyperbole :> es)+ => Event TargetViewId Encoded Encoded+ -> (Action id -> Eff (Reader id : State (ViewState id) : es) (View id ())) -> Eff es (Maybe Response) runHandler rawEvent run = do -- Get an event matching our type. If it doesn't match, skip to the next handler- mev <- decodeEvent @id rawEvent :: Eff es (Maybe (Event id (Action id)))+ mev <- decodeEvent @id rawEvent :: Eff es (Maybe (Event id (Action id) (ViewState id))) case mev of Just evt -> do- vw <- runReader evt.viewId $ run evt.action- res <- hyperView evt.viewId vw+ (vw, st) <- runStateLocal evt.state $ runReader evt.viewId $ run evt.action+ res <- hyperView evt.viewId st vw pure $ Just res _ -> do pure Nothing@@ -72,15 +73,16 @@ loadPageResponse :: Eff es (View (Root total) ()) -> Eff es Response loadPageResponse run = do vw <- run- let vid = TargetViewId (encodedToText $ toViewId Root)- let res = Response vid $ addContext Root vw+ let vid = TargetViewId $ toViewId Root+ let res = Response $ ViewUpdate vid $ renderBody $ runViewContext Root () vw pure res -- despite not needing any effects, this must be in Eff es to get `es` on the RHS-decodeEvent :: forall id es. (HyperView id es) => Event TargetViewId Encoded -> Eff es (Maybe (Event id (Action id)))-decodeEvent (Event (TargetViewId ti) eact) =- pure $ do- vid <- decodeViewId ti- act <- either (const Nothing) Just $ parseAction eact- pure $ Event vid act+decodeEvent :: forall id es. (HyperView id es, FromEncoded (ViewState id)) => Event TargetViewId Encoded Encoded -> Eff es (Maybe (Event id (Action id) (ViewState id)))+decodeEvent (Event (TargetViewId ti) eact est) =+ pure $ either (const Nothing) Just $ do+ vid <- parseViewId ti+ act <- parseAction eact+ st <- parseEncoded est+ pure $ Event vid act st
src/Web/Hyperbole/Server/Message.hs view
@@ -2,6 +2,7 @@ module Web.Hyperbole.Server.Message where +import Control.Applicative ((<|>)) import Control.Exception (Exception) import Data.Aeson qualified as Aeson import Data.Attoparsec.Text (Parser, char, endOfLine, isEndOfLine, parseOnly, sepBy, string, takeText, takeTill, takeWhile1)@@ -11,16 +12,18 @@ import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T+import GHC.Generics (Generic) import Web.Hyperbole.Data.Cookie (Cookie, Cookies) import Web.Hyperbole.Data.Cookie qualified as Cookie import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Data.QueryData (QueryData) import Web.Hyperbole.Data.QueryData qualified as QueryData-import Web.Hyperbole.Data.URI (Path, URI, uriToText)+import Web.Hyperbole.Data.URI (Path) import Web.Hyperbole.Effect.Hyperbole (Remote (..)) import Web.Hyperbole.Types.Client (Client (..)) import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Request+import Web.Hyperbole.View (ViewId) {-@@ -36,7 +39,7 @@ data Message = Message { messageType :: Text- , event :: Event TargetViewId Encoded+ , event :: Event TargetViewId Encoded Encoded , requestId :: RequestId , metadata :: Metadata , body :: MessageBody@@ -85,27 +88,43 @@ body = do MessageBody . cs . T.strip <$> takeText - event :: Parser (Event TargetViewId Encoded)+ event :: Parser (Event TargetViewId Encoded Encoded) event = do vid <- targetViewId- endOfLine act <- encodedAction- endOfLine- pure $ Event vid act+ st <- encodedState <|> pure mempty+ pure $ Event vid act st where targetViewId :: Parser TargetViewId targetViewId = do _ <- string "ViewId: "- TargetViewId <$> takeLine+ line <- takeLine+ v <- case encodedParseText line of+ Left e -> fail $ "Parse Encoded ViewId failed: " <> cs e <> " from " <> cs line+ Right a -> pure $ TargetViewId a+ endOfLine+ pure v encodedAction :: Parser Encoded encodedAction = do _ <- string "Action: " inp <- takeLine- case encodedParseText inp of+ v <- case encodedParseText inp of Left e -> fail $ "Parse Encoded ViewAction failed: " <> cs e <> " from " <> cs inp Right a -> pure a+ endOfLine+ pure v + encodedState :: Parser Encoded+ encodedState = do+ _ <- string "State: "+ inp <- takeLine+ v <- case encodedParseText inp of+ Left e -> fail $ "Parse Encoded ViewState failed: " <> cs e <> " from " <> cs inp+ Right a -> pure a+ endOfLine+ pure v+ requestId :: Parser RequestId requestId = do _ <- string "RequestId: "@@ -147,9 +166,15 @@ newtype Metadata = Metadata [(Text, Text)] deriving newtype (Semigroup, Monoid)- deriving (Show)+ deriving (Show, Generic)+ deriving anyclass (ViewId) +-- instance HyperView Metadata es where+-- data Action Metadata = MetaNone+-- deriving (Generic, ViewAction)+-- update _ = pure none+ metadata :: MetaKey -> Text -> Metadata metadata key value = Metadata [(key, value)] @@ -167,14 +192,18 @@ metaRequestId (RequestId reqId) = metadata "RequestId" (cs reqId) - eventMetadata :: Event TargetViewId Encoded -> Metadata+ eventMetadata :: Event TargetViewId Encoded Encoded -> Metadata eventMetadata event = Metadata- [ ("ViewId", event.viewId.text)+ [ ("ViewId", encodedToText event.viewId.encoded) , ("Action", encodedToText event.action) ] +targetViewMetadata :: TargetViewId -> Metadata+targetViewMetadata (TargetViewId vid) = Metadata [("TargetViewId", encodedToText vid)]++ responseMetadata :: Path -> Client -> [Remote] -> Metadata responseMetadata reqPath client remotes = clientMetadata reqPath client <> metaRemotes remotes@@ -207,7 +236,7 @@ where meta = \case RemoteAction (TargetViewId vid) act ->- metadata "Trigger" $ vid <> "|" <> encodedToText act+ metadata "Trigger" $ encodedToText vid <> "|" <> encodedToText act RemoteEvent ev dat -> metadata "Event" $ T.intercalate "|" [ev, cs $ Aeson.encode dat] @@ -216,14 +245,14 @@ metaError = metadata "Error" -metaRedirect :: URI -> Metadata-metaRedirect u = metadata "Redirect" (uriToText u)-+-- metaRedirect :: URI -> Metadata+-- metaRedirect u = metadata "Redirect" (uriToText u) data ContentType = ContentHtml | ContentText -newtype RenderedHtml = RenderedHtml BL.ByteString- deriving newtype (Semigroup, Monoid)+data RenderedMessage+ = MessageHtml BL.ByteString+ | MessageText Text
src/Web/Hyperbole/Server/Options.hs view
@@ -6,11 +6,11 @@ import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T+import Web.Atomic.CSS import Web.Hyperbole.Data.Encoded (Encoded, encodedToText) import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Response import Web.Hyperbole.View-import Web.Atomic.CSS data ServerOptions = ServerOptions@@ -29,26 +29,27 @@ e -> cs $ drop 3 $ show e -defaultErrorBody :: Text -> View Body ()-defaultErrorBody msg =- el ~ bg (HexColor "#F00") . color (HexColor "#FFF") $ do- text msg+defaultErrorBody :: Text -> Body+defaultErrorBody msg = Body $+ renderLazyByteString $ do+ el ~ bg (HexColor "#F00") . color (HexColor "#FFF") $ do+ text msg defaultError :: ResponseError -> ServerError defaultError = \case ErrCustom e -> e ErrNotHandled e -> errNotHandled e- err -> + err -> let msg = defaultErrorMessage err- in ServerError msg (defaultErrorBody msg)+ in ServerError msg (defaultErrorBody msg) where- errNotHandled :: Event TargetViewId Encoded -> ServerError+ errNotHandled :: Event TargetViewId Encoded Encoded -> ServerError errNotHandled ev =- ServerError "Action Not Handled" $ do+ ServerError "Action Not Handled" $ Body $ renderLazyByteString $ do el $ do text "No Handler for Event viewId: "- text ev.viewId.text+ text $ encodedToText ev.viewId.encoded text " action: " text $ encodedToText ev.action el $ do
src/Web/Hyperbole/Server/Socket.hs view
@@ -1,27 +1,39 @@+{-# LANGUAGE LambdaCase #-}+ module Web.Hyperbole.Server.Socket where import Control.Monad (void) import Data.Bifunctor (first) import Data.List qualified as L+import Data.Map (Map)+import Data.Map qualified as M import Data.Maybe (fromMaybe)+import Data.String (IsString) import Data.String.Conversions (cs) import Data.Text (Text) import Effectful import Effectful.Concurrent.Async+import Effectful.Concurrent.STM (TVar, atomically, modifyTVar, readTVar, writeTVar)+import Effectful.Dispatch.Dynamic+import Effectful.Error.Static (throwError_) import Effectful.Exception+import Effectful.State.Static.Local as Local (get, modify)+import Effectful.Writer.Static.Local (tell) import Network.HTTP.Types as HTTP (parseQuery) import Network.Wai qualified as Wai import Network.WebSockets (Connection) import Network.WebSockets qualified as WS import Web.Cookie qualified import Web.Hyperbole.Data.Cookie qualified as Cookie-import Web.Hyperbole.Data.URI (URI, path)+import Web.Hyperbole.Data.Encoded (Encoded, encodedToText)+import Web.Hyperbole.Data.URI (URI, path, uriToText) import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Server.Message import Web.Hyperbole.Server.Options+import Web.Hyperbole.Types.Client+import Web.Hyperbole.Types.Event (Event (..), TargetViewId (..)) import Web.Hyperbole.Types.Request import Web.Hyperbole.Types.Response-import Web.Hyperbole.View (View, addContext, renderLazyByteString) data SocketRequest = SocketRequest@@ -29,20 +41,49 @@ } +type RunningActions = Map TargetViewId (Encoded, Async ())+++runHyperboleSocket+ :: (IOE :> es)+ => ServerOptions+ -> Connection+ -> Request+ -> Eff (Hyperbole : es) Response+ -> Eff es (Response, Client, [Remote])+runHyperboleSocket _opts conn req = reinterpret (runHyperboleLocal req) $ \_ -> \case+ GetRequest -> do+ pure req+ RespondNow r -> do+ throwError_ r+ PushUpdate (ViewUpdate vid vw) -> do+ sendUpdate conn (targetViewMetadata vid <> requestMetadata req) vw+ GetClient -> do+ Local.get @Client+ ModClient f -> do+ Local.modify @Client f+ TriggerAction vid act -> do+ tell [RemoteAction vid act]+ TriggerEvent name dat -> do+ tell [RemoteEvent name dat]++ handleRequestSocket :: (IOE :> es, Concurrent :> es) => ServerOptions+ -> TVar RunningActions -> Wai.Request -> Connection -> Eff (Hyperbole : es) Response -> Eff es ()-handleRequestSocket opts wreq conn actions = do+handleRequestSocket opts actions wreq conn eff = do flip catch onMessageError $ do msg <- receiveMessage req <- parseMessageRequest msg - void $ async $ do- res <- trySync $ runHyperbole req actions+ a <- async $ do+ -- is one already running?+ res <- trySync $ runHyperboleSocket opts conn req eff case res of -- TODO: catch socket errors separately from SomeException? Left (ex :: SomeException) -> do@@ -56,12 +97,39 @@ Right (resp, clnt, rmts) -> do let meta = requestMetadata req <> responseMetadata req.path clnt rmts case resp of- (Response _ vw) -> do- sendUpdateView conn meta vw- -- (Err (ErrServer m)) -> sendError conn meta (opts.serverError m)+ (Response (ViewUpdate _ vw)) -> do+ sendResponse conn meta vw (Err err) -> sendError conn meta (opts.serverError err) (Redirect url) -> sendRedirect conn meta url++ addRunningAction a req.requestId req.event++ void $ async $ do+ _ <- waitCatch a+ clearRunningAction req.requestId req.event where+ addRunningAction :: (IOE :> es, Concurrent :> es) => Async () -> RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es ()+ addRunningAction a (RequestId reqId) = \case+ Nothing -> pure ()+ Just (Event vid act _) -> do+ -- liftIO $ putStrLn $ " [add] (" <> cs reqId <> ") " <> cs clientId.value <> "|" <> show vid+ maold <- atomically $ do+ m <- readTVar @RunningActions actions+ writeTVar actions $ M.insert vid (act, a) m+ pure $ M.lookup vid m+ case maold of+ Nothing -> pure ()+ Just (actold, aold) -> do+ liftIO $ putStrLn $ "CANCEL (" <> cs reqId <> ") " <> cs (encodedToText vid.encoded) <> ": " <> cs (encodedToText actold)+ cancel aold++ clearRunningAction :: (IOE :> es, Concurrent :> es) => RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es ()+ clearRunningAction (RequestId _) = \case+ Nothing -> pure ()+ Just (Event vid _ _) -> do+ _ <- atomically $ modifyTVar actions $ M.delete vid+ pure ()+ onMessageError :: (IOE :> es) => MessageError -> Eff es a onMessageError e = do liftIO $ do@@ -117,23 +185,37 @@ maybe (Left $ MissingMeta (cs key)) pure $ lookupMetadata key m -sendUpdateView :: (IOE :> es) => Connection -> Metadata -> View Body () -> Eff es ()-sendUpdateView conn meta vw = do- sendMessage conn meta (RenderedHtml $ renderLazyByteString $ addContext Body vw)+sendResponse :: (IOE :> es) => Connection -> Metadata -> Body -> Eff es ()+sendResponse conn meta (Body b) = do+ sendMessage "RESPONSE" conn meta (MessageHtml b) +sendUpdate :: (IOE :> es) => Connection -> Metadata -> Body -> Eff es ()+sendUpdate conn meta (Body b) = do+ sendMessage "UPDATE" conn meta (MessageHtml b)++ sendRedirect :: (IOE :> es) => Connection -> Metadata -> URI -> Eff es () sendRedirect conn meta u = do- sendMessage conn (metaRedirect u <> meta) mempty+ sendMessage "REDIRECT" conn meta (MessageText $ uriToText u) sendError :: (IOE :> es) => Connection -> Metadata -> ServerError -> Eff es ()-sendError conn meta (ServerError err body) = do- sendMessage conn (metadata "Error" err <> meta) (RenderedHtml $ renderLazyByteString $ addContext Body body)+sendError conn meta (ServerError err (Body body)) = do+ sendMessage "UPDATE" conn (metadata "Error" err <> meta) (MessageHtml body) +newtype Command = Command Text+ deriving newtype (IsString)++ -- low level message. Use sendResponse-sendMessage :: (MonadIO m) => Connection -> Metadata -> RenderedHtml -> m ()-sendMessage conn meta' (RenderedHtml html) = do- let out = "|UPDATE|\n" <> cs (renderMetadata meta') <> "\n\n" <> html- liftIO $ WS.sendTextData conn out+sendMessage :: (MonadIO m) => Command -> Connection -> Metadata -> RenderedMessage -> m ()+sendMessage (Command cmd) conn meta' msg = do+ let header = "|" <> cs cmd <> "|\n" <> cs (renderMetadata meta')++ let body = case msg of+ MessageHtml html -> html+ MessageText t -> cs t++ liftIO $ WS.sendTextData conn (header <> "\n\n" <> body)
src/Web/Hyperbole/Server/Wai.hs view
@@ -11,7 +11,11 @@ import Data.String.Conversions (cs) import Data.String.Interpolate (i) import Effectful+import Effectful.Dispatch.Dynamic+import Effectful.Error.Static (throwError_) import Effectful.Exception (throwIO)+import Effectful.State.Static.Local (get, modify)+import Effectful.Writer.Static.Local (tell) import Network.HTTP.Types (Header, HeaderName, status200, status400, status401, status404, status500) import Network.Wai qualified as Wai import Network.Wai.Internal (ResponseReceived (..))@@ -19,16 +23,16 @@ import Web.Cookie qualified import Web.Hyperbole.Data.Cookie (Cookie, Cookies) import Web.Hyperbole.Data.Cookie qualified as Cookie-import Web.Hyperbole.Data.Encoded (Encoded, encodedParseText)+import Web.Hyperbole.Data.Encoded (Encoded, decode) import Web.Hyperbole.Data.URI (path, uriToText) import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Server.Message import Web.Hyperbole.Server.Options import Web.Hyperbole.Types.Client-import Web.Hyperbole.Types.Event+import Web.Hyperbole.Types.Event (Event (..), TargetViewId (..)) import Web.Hyperbole.Types.Request import Web.Hyperbole.Types.Response-import Web.Hyperbole.View (View, addContext, renderLazyByteString, script', type_)+import Web.Hyperbole.View (View, renderLazyByteString, runViewContext, script', type_) handleRequestWai@@ -43,10 +47,33 @@ body <- liftIO $ Wai.consumeRequestBodyLazy req rq <- either throwIO pure $ do fromWaiRequest req body- (res, client, rmts) <- runHyperbole rq actions+ (res, client, rmts) <- runHyperboleWai rq actions liftIO $ sendResponse options rq client res rmts respond +-- | Run the 'Hyperbole' effect to get a response+runHyperboleWai+ :: Request+ -> Eff (Hyperbole : es) Response+ -> Eff es (Response, Client, [Remote])+runHyperboleWai req = reinterpret (runHyperboleLocal req) $ \_ -> \case+ GetRequest -> do+ pure req+ RespondNow r -> do+ throwError_ r+ PushUpdate _ -> do+ -- ignore! you can't push updates using WAI+ pure ()+ GetClient -> do+ get @Client+ ModClient f -> do+ modify @Client f+ TriggerAction vid act -> do+ tell [RemoteAction vid act]+ TriggerEvent name dat -> do+ tell [RemoteEvent name dat]++ sendResponse :: ServerOptions -> Request -> Client -> Response -> [Remote] -> (Wai.Response -> IO ResponseReceived) -> IO Wai.ResponseReceived sendResponse options req client res remotes respond = do let metas = requestMetadata req <> responseMetadata req.path client remotes@@ -56,27 +83,14 @@ response metas = \case (Err err) -> respondError (errStatus err) [] $ options.serverError err- (Response _ vw) -> do+ (Response (ViewUpdate _ vw)) -> do respondHtml status200 (clientHeaders client) $ renderViewResponse metas vw (Redirect u) -> do let url = uriToText u- -- We have to use a 200 javascript redirect because javascript- -- will redirect the fetch(), while we want to redirect the whole page- -- see index.ts sendAction() let hs = ("Location", cs url) : clientHeaders client- respondHtml status200 hs $ renderViewResponse (metaRedirect u <> metas) $ do+ respondHtml status200 hs $ renderViewResponse metas $ Body $ renderLazyByteString $ do script'- -- static script is safe to execute- [i|- let metaInput = document.getElementById("hyp.metadata").innerText;- let meta = Hyperbole.parseMetadata(metaInput)- if (meta.redirect) {- window.location = meta.redirect- }- else {- console.error("Invalid Redirect", meta.rediect)- }- |]+ [i|window.location = '#{uriToText u}'|] errStatus = \case NotFound -> status404@@ -93,9 +107,9 @@ Nothing -> options.toDocument body _ -> body - renderViewResponse :: Metadata -> View Body () -> BL.ByteString- renderViewResponse metas vw =- addDocument $ renderLazyByteString (addContext metas $ scriptMeta metas) <> "\n\n" <> renderLazyByteString (addContext Body vw)+ renderViewResponse :: Metadata -> Body -> BL.ByteString+ renderViewResponse metas (Body body) =+ addDocument $ renderLazyByteString (runViewContext metas () $ scriptMeta metas) <> "\n\n" <> body respondError s hs serr = respondHtml s hs $ renderViewResponse (metaError serr.message) serr.body respondHtml s hs = Wai.responseLBS s (contentType ContentHtml : hs)@@ -135,7 +149,6 @@ requestId = RequestId $ cs $ fromMaybe "" $ L.lookup "Hyp-RequestId" headers method = Wai.requestMethod wr event = lookupEvent headers- cookies <- fromCookieHeader cookie pure $@@ -150,13 +163,15 @@ , requestId } where- lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded)+ lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded Encoded) lookupEvent headers = do- viewId <- TargetViewId . cs <$> L.lookup "Hyp-ViewId" headers+ viewIdText <- cs <$> L.lookup "Hyp-ViewId" headers actText <- cs <$> L.lookup "Hyp-Action" headers- case encodedParseText actText of- Left _ -> Nothing- Right a -> pure $ Event viewId a+ stText <- cs <$> L.lookup "Hyp-State" headers+ act <- decode actText+ viewId <- TargetViewId <$> decode viewIdText+ st <- decode stText+ pure $ Event viewId act st -- Client only returns ONE Cookie header, with everything concatenated
src/Web/Hyperbole/Types/Event.hs view
@@ -2,24 +2,25 @@ import Data.Aeson (ToJSON) import Data.String.Conversions (cs)-import Data.Text (Text)+import Web.Hyperbole.Data.Encoded -- | Serialized ViewId-newtype TargetViewId = TargetViewId {text :: Text}- deriving newtype (ToJSON)+newtype TargetViewId = TargetViewId {encoded :: Encoded}+ deriving newtype (ToJSON, Ord, Eq) instance Show TargetViewId where- show (TargetViewId t) = "TargetViewId " <> cs t+ show (TargetViewId e) = "TargetViewId " <> cs (encodedToText e) -- | An action, with its corresponding id-data Event id act = Event+data Event id act st = Event { viewId :: id , action :: act+ , state :: st } -instance (Show act, Show id) => Show (Event id act) where- show e = "Event " <> show e.viewId <> " " <> show e.action+instance (Show act, Show id, Show st) => Show (Event id act st) where+ show e = "Event " <> show e.viewId <> " " <> show e.action <> " " <> show e.state
src/Web/Hyperbole/Types/Request.hs view
@@ -21,7 +21,7 @@ , body :: BL.ByteString , method :: Method , cookies :: Cookies- , event :: Maybe (Event TargetViewId Encoded)+ , event :: Maybe (Event TargetViewId Encoded Encoded) , requestId :: RequestId } deriving (Show)
src/Web/Hyperbole/Types/Response.hs view
@@ -2,21 +2,24 @@ module Web.Hyperbole.Types.Response where +import Data.ByteString.Lazy qualified as BL import Data.String (IsString (..)) import Data.String.Conversions (cs) import Data.Text (Text) import Web.Hyperbole.Data.Encoded (Encoded) import Web.Hyperbole.Data.URI (URI) import Web.Hyperbole.Types.Event-import Web.Hyperbole.View -data Body = Body+newtype Body = Body BL.ByteString +data ViewUpdate = ViewUpdate {viewId :: TargetViewId, body :: Body}++ -- | A processed response for the client, which might be a 'ResponseError' data Response- = Response TargetViewId (View Body ())+ = Response ViewUpdate | Redirect URI | Err ResponseError @@ -29,7 +32,7 @@ | ErrServer Text | ErrCustom ServerError | ErrInternal- | ErrNotHandled (Event TargetViewId Encoded)+ | ErrNotHandled (Event TargetViewId Encoded Encoded) | ErrAuth Text instance Show ResponseError where show = \case@@ -49,5 +52,5 @@ -- Serialized server error data ServerError = ServerError { message :: Text- , body :: View Body ()+ , body :: Body }
src/Web/Hyperbole/View.hs view
@@ -1,5 +1,7 @@ module Web.Hyperbole.View ( module Web.Hyperbole.View.Types+ , module Web.Hyperbole.View.ViewId+ , module Web.Hyperbole.View.ViewAction , module Web.Hyperbole.View.Embed , module Web.Hyperbole.View.Render , module Web.Hyperbole.View.Tag@@ -12,5 +14,7 @@ import Web.Hyperbole.View.Embed import Web.Hyperbole.View.Render import Web.Hyperbole.View.Tag hiding (form, input, label)-import Web.Hyperbole.View.Types (View (..), addContext, context, modifyContext, none, raw, tag, text)+import Web.Hyperbole.View.Types+import Web.Hyperbole.View.ViewAction+import Web.Hyperbole.View.ViewId
src/Web/Hyperbole/View/CSS.hs view
@@ -6,7 +6,7 @@ {- | Apply CSS only when a request is in flight. See [Example.Page.Contact](https://docs.hyperbole.live/contacts/1) @-#EMBED Example/Page/Contact.hs contactEditView+#EMBED Example.Contact contactEditView @ -} whenLoading :: (Styleable h) => (CSS h -> CSS h) -> CSS h -> CSS h@@ -18,7 +18,7 @@ disabled = utility "disabled"- [ "opacity" :. "0.7"+ [ "opacity" :. "0.5" , "pointer-events" :. "none" ]
src/Web/Hyperbole/View/Render.hs view
@@ -1,17 +1,23 @@ module Web.Hyperbole.View.Render ( renderText , renderLazyByteString+ , renderBody ) where import Data.ByteString.Lazy qualified as BL import Data.Text (Text) import Web.Atomic.Render qualified as Atomic-import Web.Hyperbole.View.Types (View, runView)+import Web.Hyperbole.Types.Response (Body (..))+import Web.Hyperbole.View.Types (View, execView) renderText :: View () () -> Text-renderText = Atomic.renderText . runView ()+renderText = Atomic.renderText . execView () () renderLazyByteString :: View () () -> BL.ByteString-renderLazyByteString = Atomic.renderLazyByteString . runView ()+renderLazyByteString = Atomic.renderLazyByteString . execView () ()+++renderBody :: View () () -> Body+renderBody v = Body $ renderLazyByteString v
src/Web/Hyperbole/View/Tag.hs view
@@ -8,13 +8,40 @@ import Data.Text (Text, pack) import Data.Text qualified as T import Effectful-import Effectful.State.Static.Local+import Effectful.State.Dynamic import Web.Atomic.CSS+import Web.Atomic.Html qualified as Atomic import Web.Atomic.Types import Web.Hyperbole.Data.URI import Web.Hyperbole.View.Types +-- Html ---------------------------------------------++tag :: Text -> View c () -> View c ()+tag = tag' False+++tag' :: Bool -> Text -> View c () -> View c ()+tag' inline n (View eff) = View $ do+ inner <- eff+ pure $ Atomic.tag' inline n inner+++text :: Text -> View c ()+text t = View $ pure $ Atomic.text t+++none :: View c ()+none = View $ pure Atomic.none+++raw :: Text -> View c ()+raw t = View $ pure $ Atomic.raw t+++---+ el :: View c () -> View c () el = tag "div" @@ -41,7 +68,7 @@ -- | A hyperlink to the given url link :: URI -> View c () -> View c ()-link u = tag "a" @ att "href" (uriToText u)+link u = tag' True "a" @ att "href" (uriToText u) img :: Text -> View c ()@@ -163,7 +190,7 @@ -} table :: [dt] -> TableColumns c dt () -> View c () table dts (TableColumns wcs) = do- let cols = runPureEff . execState [] $ wcs+ let cols = runPureEff . execStateLocal [] $ wcs tag "table" $ do tag "thead" $ do tag "tr" $ do
src/Web/Hyperbole/View/Types.hs view
@@ -2,14 +2,18 @@ module Web.Hyperbole.View.Types where -import Data.Kind (Type) import Data.String (IsString (..)) import Data.Text (Text, pack) import Effectful-import Effectful.Reader.Static+import Effectful.Reader.Dynamic+import Effectful.State.Dynamic+import GHC.Generics import Web.Atomic.Html (Html (..)) import Web.Atomic.Html qualified as Atomic import Web.Atomic.Types+import Web.Hyperbole.Data.Encoded (decodeEither, encodedToText)+import Web.Hyperbole.Data.Param (FromParam, ToParam (..))+import Web.Hyperbole.View.ViewId -- View ------------------------------------------------------------@@ -17,19 +21,19 @@ {- | 'View's are HTML fragments with a 'context' @-#EMBED Example/Docs/BasicPage.hs helloWorld+#EMBED Example.Docs.BasicPage helloWorld @ -}-newtype View c a = View {html :: Eff '[Reader c] (Html a)}+newtype View c a = View {html :: Eff '[Reader (c, ViewState c)] (Html a)} instance IsString (View c ()) where fromString s = View $ pure $ Atomic.text (pack s) -runView :: forall c a. c -> View c a -> Html a-runView c (View eff) = do- runPureEff $ runReader c eff+execView :: forall c a. c -> ViewState c -> View c a -> Html a+execView c st (View eff) = do+ runPureEff $ runReader (c, st) eff instance Functor (View c) where@@ -54,59 +58,49 @@ View ea >>= famb = View $ do a :: a <- (.value) <$> ea let View eb :: View ctx b = famb a- hb <- eb- pure $ hb+ eb -- Context ----------------------------------------- -type family ViewContext (v :: Type) where- ViewContext (View c x) = c- ViewContext (View c x -> View c x) = c+-- type family ViewContext (v :: Type) where+-- ViewContext (View c x) = c+-- ViewContext (View c x -> View c x) = c +newtype ChildView a = ChildView a+ deriving (Generic)+instance (ViewId a, FromParam a, ToParam a) => ViewId (ChildView a) where+ type ViewState (ChildView a) = ViewState a + -- TEST: appending Empty-context :: forall c. View c c+context :: forall c. View c (c, ViewState c) context = View $ do- c <- ask @c+ c <- ask @(c, ViewState c) pure $ pure c -addContext :: ctx -> View ctx () -> View c ()-addContext c (View eff) = View $ do- pure $ runPureEff $ runReader c eff---modifyContext- :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()-modifyContext f (View eff) = View $ do- ctx0 <- ask @ctx0- pure $ runPureEff $ runReader (f ctx0) eff----- Html -----------------------------------------------tag :: Text -> View c () -> View c ()-tag = tag' False---tag' :: Bool -> Text -> View c () -> View c ()-tag' inline n (View eff) = View $ do- content <- eff- pure $ Atomic.tag' inline n content+viewState :: View c (ViewState c)+viewState = snd <$> context -text :: Text -> View c ()-text t = View $ pure $ Atomic.text t+runViewContext :: ctx -> ViewState ctx -> View ctx () -> View c ()+runViewContext c st (View eff) = View $ do+ pure $ runPureEff $ runReader (c, st) eff -none :: View c ()-none = View $ pure Atomic.none+runChildView :: (ViewState ctx ~ ViewState c) => (c -> ctx) -> View ctx () -> View c ()+runChildView f v = do+ st <- viewState+ c <- viewId+ runViewContext (f c) st v -raw :: Text -> View c ()-raw t = View $ pure $ Atomic.raw t-+-- modifyContext+-- :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 ()+-- modifyContext f (View eff) = View $ do+-- ctx0 <- ask @ctx0+-- pure $ runPureEff $ runReader (f ctx0) eff -- Attributes ----------------------------------------- @@ -120,3 +114,32 @@ modCSS f (View eff) = View $ do h <- eff pure $ modCSS f h+++{- | Access the 'viewId' in a 'View' or 'update'++@+#EMBED Example.Concurrency.LazyLoading data LazyData++#EMBED Example.Concurrency.LazyLoading instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where+@+-}+class HasViewId m view where+ viewId :: m view+++instance HasViewId (View ctx) ctx where+ viewId = fst <$> context+instance (ViewState view ~ st) => HasViewId (Eff (Reader view : State st : es)) view where+ viewId = ask+++encodeViewId :: (ViewId id) => id -> Text+encodeViewId = encodedToText . toViewId+++decodeViewId :: (ViewId id) => Text -> Maybe id+decodeViewId t = do+ case parseViewId =<< decodeEither t of+ Left _ -> Nothing+ Right a -> pure a
+ src/Web/Hyperbole/View/ViewAction.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DefaultSignatures #-}++module Web.Hyperbole.View.ViewAction where++import Data.Text (Text)+import GHC.Generics+import Web.Hyperbole.Data.Encoded as Encoded+++{- | Define every action possible for a given 'HyperView'++@+#EMBED Example.Simple instance HyperView Message+@+-}+class ViewAction a where+ toAction :: a -> Encoded+ default toAction :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+ toAction = genericToEncoded+++ parseAction :: Encoded -> Either String a+ default parseAction :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+ parseAction = genericParseEncoded+++instance ViewAction () where+ toAction _ = mempty+ parseAction _ = pure ()+++encodeAction :: (ViewAction act) => act -> Text+encodeAction = encodedToText . toAction+++decodeAction :: (ViewAction act) => Text -> Maybe act+decodeAction t = do+ case parseAction =<< encodedParseText t of+ Left _ -> Nothing+ Right a -> pure a
+ src/Web/Hyperbole/View/ViewId.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DefaultSignatures #-}++module Web.Hyperbole.View.ViewId where++import Data.Kind (Type)+import GHC.Generics+import Web.Hyperbole.Data.Encoded as Encoded+++{- | A unique identifier for a 'HyperView'++@+#EMBED Example.Simple data Message+@+-}+class ViewId a where+ type ViewState a :: Type+ type ViewState a = ()+++ toViewId :: a -> Encoded+ default toViewId :: (Generic a, GToEncoded (Rep a)) => a -> Encoded+ toViewId = genericToEncoded+++ parseViewId :: Encoded -> Either String a+ default parseViewId :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a+ parseViewId = genericParseEncoded+++instance ViewId () where+ toViewId _ = mempty+ parseViewId _ = pure ()
test/Test/EncodedSpec.hs view
@@ -130,8 +130,10 @@ encode (Str " ") `shouldBe` "Str _" encode (Str "") `shouldBe` "Str |" encode (Str "_") `shouldBe` "Str \\_"+ encode (Str "\n") `shouldBe` "Str \\n" encode (Str "hello_world") `shouldBe` "Str hello\\_world" encode (Str "hello+world") `shouldBe` "Str hello+world"+ encode (Str "hello\nworld") `shouldBe` "Str hello\\nworld" it "should encode records`" $ do -- no field names for ourselves@@ -175,11 +177,14 @@ it "sanitizeText" $ do encodeParam "hello world" `shouldBe` "hello_world" encodeParam "hello_world" `shouldBe` "hello\\_world"+ encodeParam "hello\nworld" `shouldBe` "hello\\nworld" it "desanitizeText" $ do decodeParam "hello_world" `shouldBe` "hello world" decodeParam "hello\\_world" `shouldBe` "hello_world"+ decodeParam "hello\\nworld" `shouldBe` "hello\nworld" + -- TODO: Add more edge cases to check if "\n" is escaped properly. it "edge cases" $ do encodeParam "" `shouldBe` "|" encodeParam " " `shouldBe` "_"
test/Test/ViewActionSpec.hs view
@@ -7,7 +7,7 @@ import Web.Hyperbole (FromJSON, ToJSON) import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Data.Param-import Web.Hyperbole.HyperView (ViewAction (..))+import Web.Hyperbole.View import Web.Hyperbole.HyperView.Event (toActionInput)
test/Test/ViewIdSpec.hs view
@@ -8,7 +8,6 @@ import Skeletest import Web.Hyperbole import Web.Hyperbole.Data.Encoded-import Web.Hyperbole.HyperView data Thing = Thing