turbo_reflex 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/README.md +1 -6
- data/app/assets/builds/turbo_reflex.js +1 -1
- data/app/assets/builds/turbo_reflex.js.map +4 -4
- data/app/controllers/concerns/turbo_reflex/controller.rb +12 -108
- data/app/javascript/activity.js +20 -0
- data/app/javascript/delegates.js +27 -0
- data/app/javascript/drivers/form.js +12 -0
- data/app/javascript/drivers/frame.js +11 -0
- data/app/javascript/drivers/index.js +63 -0
- data/app/javascript/drivers/window.js +70 -0
- data/app/javascript/elements.js +29 -50
- data/app/javascript/index.js +72 -0
- data/app/javascript/lifecycle.js +40 -0
- data/app/javascript/logger.js +34 -0
- data/app/javascript/schema.js +6 -0
- data/app/javascript/turbo.js +24 -0
- data/app/javascript/urls.js +9 -0
- data/app/javascript/uuids.js +10 -0
- data/lib/turbo_reflex/base.rb +59 -9
- data/lib/turbo_reflex/engine.rb +1 -9
- data/lib/turbo_reflex/runner.rb +199 -0
- data/lib/turbo_reflex/version.rb +1 -1
- data/package.json +5 -4
- data/turbo_reflex.gemspec +1 -0
- data/yarn.lock +135 -135
- metadata +30 -11
- data/app/controllers/turbo_reflex/turbo_reflexes_controller.rb +0 -12
- data/app/helpers/turbo_reflex/turbo_reflex_helper.rb +0 -9
- data/app/javascript/event_registry.js +0 -34
- data/app/javascript/frame_sources.js +0 -28
- data/app/javascript/lifecycle_events.js +0 -24
- data/app/javascript/security.js +0 -7
- data/app/javascript/turbo_reflex.js +0 -111
- data/config/routes.rb +0 -5
- data/lib/turbo_reflex/errors.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d777dc777c4797542c2a285da1ba2ee9ff75e3a329b90f148abee3691da210f
|
4
|
+
data.tar.gz: cda1b302d539fe5f3081ce6722336eb663d2fa2c2881cc5b48ae65fe24e400b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584be627cda098a3aca5471e841c3c3da3b0b4eb51891e69570b6ab330b82f80977b604f288f4955895a6dbd21ae2c4661c814982e8f4a05168bbc9a7220625f
|
7
|
+
data.tar.gz: 42820cbb510fb61ef22c07a550831d69cae13f08867d15c626ed0f6dce045440d3ca10e0668d5e75671e8d5db04a58d75cecf091e9d11370d219f89d10b39fed
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
</h1>
|
9
9
|
<p align="center">
|
10
10
|
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
|
11
|
-
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-
|
11
|
+
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-672-47d299.svg" />
|
12
12
|
</a>
|
13
13
|
<a href="https://codeclimate.com/github/hopsoft/turbo_reflex/maintainability">
|
14
14
|
<img src="https://api.codeclimate.com/v1/badges/fe1162a742fe83a4fdfd/maintainability" />
|
@@ -145,7 +145,6 @@ TurboReflex is a lightweight Turbo Frame extension... which means that reactivit
|
|
145
145
|
+ <%= turbo_reflex_meta_tag %>
|
146
146
|
</head>
|
147
147
|
<body>
|
148
|
-
+ <%= turbo_reflex_frame_tag %>
|
149
148
|
</body>
|
150
149
|
</html>
|
151
150
|
```
|
@@ -207,12 +206,8 @@ TurboReflex.registerEvent('sl-change', ['sl-switch'])
|
|
207
206
|
|
208
207
|
TurboReflex supports the following lifecycle events.
|
209
208
|
|
210
|
-
- `turbo-reflex:before-start` - fires before reflex processing starts
|
211
209
|
- `turbo-reflex:start` - fires before the reflex is sent to the server
|
212
210
|
- `turbo-reflex:finish` - fires after the server has processed the reflex and responded
|
213
|
-
- `turbo-reflex:missing-frame-id` - fires if the reflex cannot determine the target frame id
|
214
|
-
- `turbo-reflex:missing-frame` - fires if the the reflex cannot locate the frame element
|
215
|
-
- `turbo-reflex:missing-frame-src` - fires if the reflex cannot determine the frame's `src`
|
216
211
|
- `turbo-reflex:error` - fires if an unexpected error occurs
|
217
212
|
|
218
213
|
### Targeting Frames
|
@@ -1,2 +1,2 @@
|
|
1
|
-
var
|
1
|
+
var S={frameAttribute:"data-turbo-frame",reflexAttribute:"data-turbo-reflex"},u={...S};var v={};function D(e){v[e.id]=e}function H(e){delete v[e]}var b={add:D,remove:H,get reflexes(){return[...Object.values(v)]},get length(){return Object.keys(v).length}};var f={start:"turbo-reflex:start",success:"turbo-reflex:success",finish:"turbo-reflex:finish",abort:"turbo-reflex:abort",clientError:"turbo-reflex:client-error",serverError:"turbo-reflex:server-error"};function p(e,t=document,r={},n=!1){try{t=t||document;let s=new CustomEvent(e,{detail:r,cancelable:!1,bubbles:!0});t.dispatchEvent(s)}catch(s){if(n)throw s;p(f.clientError,t,{error:s,...r},!0)}}function w(e){e.detail.endedAt=new Date().getTime(),e.detail.milliseconds=e.detail.endedAt-e.detail.startedAt,setTimeout(()=>p(f.finish,e.target,e.detail),10)}addEventListener(f.serverError,w);addEventListener(f.success,w);addEventListener(f.finish,e=>b.remove(e.detail.id),!0);var o={dispatch:p,events:f};function _(e){return e.closest(`[${u.reflexAttribute}]`)}function q(e){return e.closest("turbo-frame")}function P(e,t={}){if(e.tagName.toLowerCase()!=="select")return t.value=e.value;if(!e.multiple)return t.value=e.options[e.selectedIndex].value;t.values=Array.from(e.options).reduce((r,n)=>(n.selected&&r.push(n.value),r),[])}function V(e){let n=Array.from(e.attributes).reduce((s,c)=>{let i=c.value;return typeof i=="string"&&i.length>100&&(i=i.slice(0,100)+"..."),s[c.name]=i,s},{});return n.tag=e.tagName,n.checked=e.checked,n.disabled=e.disabled,P(e,n),typeof n.value=="string"&&n.value.length>500&&(n.value=n.value.slice(0,500)+"..."),delete n.class,delete n[u.reflexAttribute],delete n[u.frameAttribute],n}var a={buildAttributePayload:V,findClosestReflex:_,findClosestFrame:q,get metaElement(){return document.getElementById("turbo-reflex")},get metaElementToken(){return document.getElementById("turbo-reflex").getAttribute("content")}};var E={};addEventListener("turbo:before-fetch-request",e=>{let t=e.target.closest("turbo-frame"),{fetchOptions:r}=e.detail;r.headers["TurboReflex-Token"]=a.metaElementToken});addEventListener("turbo:before-fetch-response",e=>{let t=e.target.closest("turbo-frame");t&&(E[t.id]=t.src)});addEventListener("turbo:frame-load",e=>{let t=e.target.closest("turbo-frame");t.dataset.turboReflexSrc=E[t.id]||t.src||t.dataset.turboReflexSrc,delete E[t.id]});var m={},T;function $(e,t){m[e]=t,document.addEventListener(e,T,!0)}function N(e,t){return t=t.toLowerCase(),m[e].includes(t)||!Object.values(m).flat().includes(t)&&m[e].includes("*")}var l={events:m,register:$,isRegistered:N,set handler(e){T=e}};function B(e,t={}){t.token=a.metaElementToken;let r=document.createElement("input");r.type="hidden",r.name="turbo_reflex",r.value=JSON.stringify(t),e.appendChild(r)}var y={invokeReflex:B};function F(e,t={}){let r=document.createElement("a");r.href=e;let n=new URL(r);return n.searchParams.set("turbo_reflex",JSON.stringify(t)),n}var g={build:F};function M(e,t){let r=t.src;t={...t},delete t.src,e.src=g.build(r,t)}var k={invokeReflex:M};function U(e){let t=e.target;o.dispatch(o.events.abort,window,{xhr:t,...e.detail})}function R(e){let t=e.target;o.dispatch(o.events.clientError,window,{xhr:t,...e.detail,error:`Server returned a ${t.status} status code! TurboReflex requires 2XX status codes. Server message: ${t.statusText}`},!0)}function X(e){let t=e.target,r=t.responseText,n=t.getResponseHeader("TurboReflex-Hijacked")==="true";if((t.status<200||t.status>299)&&R(e),n){let s="<turbo-stream",c="</turbo-stream>",i=r.indexOf(s),d=r.lastIndexOf(c);if(i>=0&&d>=0){let h=r.slice(i,d+c.length);document.body.insertAdjacentHTML("beforeend",h)}}else{let s="<html",c="</html",i=r.indexOf(s),d=r.lastIndexOf(c);if(i>=0&&d>=0){let h=r.slice(r.indexOf(">",i)+1,d);document.documentElement.innerHTML=h}}}function J(e){let t=e.src;e={...e},delete e.src;try{let r=new XMLHttpRequest;r.open("GET",g.build(t,e),!0),r.setRequestHeader("TurboReflex-Token",a.metaElementToken),r.addEventListener("abort",U),r.addEventListener("error",R),r.addEventListener("load",X),r.send()}catch(r){let n=`Unexpected error sending HTTP request! ${r.message}`;R(r,{detail:{message:n}})}}var A={invokeReflex:J};function L(e,t){return t=t||{dataset:{}},e.href||t.src||t.dataset.turboReflexSrc||location.href}function G(e){let t=a.findClosestFrame(e),r=e.dataset.turboFrame;return e.tagName.toLowerCase()==="form"?{name:"form",reason:"Element is a form.",frame:t,src:e.action,invokeReflex:y.invokeReflex}:r&&r!=="_self"?(t=document.getElementById(r),{name:"frame",reason:"element targets a frame that is not _self",frame:t,src:L(e,t),invokeReflex:k.invokeReflex}):(!r||r==="_self")&&t?{name:"frame",reason:"element does NOT target a frame or targets _self and is contained by a frame",frame:t,src:L(e,t),invokeReflex:k.invokeReflex}:{name:"window",reason:"element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)",frame:null,src:L(e),invokeReflex:A.invokeReflex}}var O={find:G};var x="unknown",I={debug:Object.values(o.events),info:Object.values(o.events),warn:[o.events.abort,o.events.clientError,o.events.serverError],error:[o.events.clientError,o.events.serverError],unknown:[]};Object.values(o.events).forEach(e=>{addEventListener(e,t=>{I[x].includes(t.type)&&console[x==="debug"?"log":x](t.type,t.detail)})});var j={get level(){return x},set level(e){return Object.keys(I).includes(e)||(e="unknown"),x=e}};function z(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16))}var C={v4:z};function K(e){let t,r={};try{if(t=a.findClosestReflex(e.target),!t||!l.isRegistered(e.type,t.tagName))return;let n=O.find(t);switch(r={id:`reflex-${C.v4()}`,name:t.dataset.turboReflex,driver:n.name,src:n.src,frameId:n.frame?n.frame.id:null,elementId:t.id.length>0?t.id:null,elementAttributes:a.buildAttributePayload(t),startedAt:new Date().getTime()},b.add(r),o.dispatch(o.events.start,t,r),n.name!=="form"&&e.preventDefault(),n.name){case"form":return n.invokeReflex(t,r);case"frame":return n.invokeReflex(n.frame,r);case"window":return n.invokeReflex(r)}}catch(n){o.dispatch(o.events.clientError,t,{error:n,...r})}}l.handler=K;l.register("change",["input","select","textarea"]);l.register("submit",["form"]);l.register("click",["*"]);var qe={schema:u,logger:j,registerEventDelegate:l.register,get eventDelegates(){return{...l.events}},get lifecycleEvents(){return[...Object.values(o.events)]}};export{qe as default};
|
2
2
|
//# sourceMappingURL=turbo_reflex.js.map
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
|
-
"sources": ["../../javascript/
|
4
|
-
"sourcesContent": ["const events = {\n beforeStart: 'turbo-reflex:before-start',\n start: 'turbo-reflex:start',\n finish: 'turbo-reflex:finish',\n error: 'turbo-reflex:error',\n missingFrameId: 'turbo-reflex:missing-frame-id',\n missingFrame: 'turbo-reflex:missing-frame',\n missingFrameSrc: 'turbo-reflex:missing-frame-src'\n}\n\nfunction dispatch (name, target = document, detail = {}) {\n const event = new CustomEvent(name, {\n detail,\n cancelable: true,\n bubbles: true\n })\n target.dispatchEvent(event)\n}\n\nfunction logEventNames () {\n Object.values(events).forEach(name => console.log(name))\n}\n\nexport default { ...events, dispatch, logEventNames }\n", "import LifecycleEvents from './lifecycle_events'\nconst frameSources = {}\n\n// fires after receiving a turbo HTTP response\naddEventListener('turbo:before-fetch-response', event => {\n const frame = event.target\n frameSources[frame.id] = frame.src\n\n const { turboReflexActive, turboReflexElementId } = frame.dataset\n if (!turboReflexActive) return\n\n const element = document.getElementById(turboReflexElementId)\n delete frame.dataset.turboReflexActive\n delete frame.dataset.turboReflexElementId\n\n LifecycleEvents.dispatch(LifecycleEvents.finish, element || document, {\n frame,\n element: element || 'Unknown! Missing id attribute.'\n })\n})\n\n// fires when a frame element is navigated and finishes loading\naddEventListener('turbo:frame-load', event => {\n const frame = event.target\n frame.dataset.turboReflexSrc =\n frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc\n delete frameSources[frame.id]\n})\n", "const Security = {\n get token () {\n return document.getElementById('turbo-reflex-token').getAttribute('content')\n }\n}\n\nexport default Security\n", "import LifecycleEvents from './lifecycle_events'\n\nfunction findClosestReflex (element) {\n return element.closest('[data-turbo-reflex]')\n}\n\nfunction findClosestFrame (element) {\n return element.closest('turbo-frame')\n}\n\nfunction findFrameId (element) {\n let id = element.dataset.turboFrame\n if (!id) {\n const frame = findClosestFrame(element)\n if (frame) id = frame.id\n }\n if (!id) {\n console.error(\n `The reflex element does not specify a frame!`,\n `Please move the reflex element inside a <turbo-frame> or set the 'data-turbo-frame' attribute.`,\n element\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameId, element, {\n element\n })\n }\n return id\n}\n\nfunction findFrame (id) {\n const frame = document.getElementById(id)\n if (!frame) {\n console.error(`The frame '${id}' does not exist!`)\n LifecycleEvents.dispatch(LifecycleEvents.missingFrame, document, { id })\n }\n return frame\n}\n\nfunction findFrameSrc (frame) {\n const frameSrc = frame.dataset.turboReflexSrc || frame.src\n if (!frameSrc) {\n console.error(\n `The the 'src' for <turbo-frame id='${frame.id}'> is unknown!`,\n `TurboReflex uses 'src' to (re)render frame content after the reflex is invoked.`,\n `Please set the 'src' or 'data-turbo-reflex-src' attribute on the <turbo-frame> element.`,\n frame\n )\n LifecycleEvents.dispatch(LifecycleEvents.missingFrameSrc, frame, { frame })\n }\n return frameSrc\n}\n\nfunction assignElementValueToPayload (element, payload = {}) {\n if (element.tagName.toLowerCase() !== 'select')\n return (payload.value = element.value)\n\n if (!element.multiple)\n return (payload.value = element.options[element.selectedIndex].value)\n\n payload.values = Array.from(element.options).reduce((memo, option) => {\n if (option.selected) memo.push(option.value)\n return memo\n }, [])\n}\n\nfunction buildAttributePayload (element) {\n const payload = Array.from(element.attributes).reduce((memo, attr) => {\n memo[attr.name] = attr.value\n return memo\n }, {})\n\n payload.tag = element.tagName\n payload.checked = element.checked\n payload.disabled = element.disabled\n assignElementValueToPayload(element, payload)\n\n return payload\n}\n\nexport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n}\n", "const registeredEvents = {}\nlet eventListener\n\nfunction registerEventListener (fn) {\n eventListener = fn\n}\n\nfunction registerEvent (eventName, tagNames) {\n registeredEvents[eventName] = tagNames\n document.addEventListener(eventName, eventListener, true)\n}\n\nfunction isRegisteredEvent (eventName, tagName) {\n tagName = tagName.toLowerCase()\n return (\n registeredEvents[eventName].includes(tagName) ||\n (!Object.values(registeredEvents)\n .flat()\n .includes(tagName) &&\n registeredEvents[eventName].includes('*'))\n )\n}\n\nfunction logRegisteredEvents () {\n console.log(registeredEvents)\n}\n\nexport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n}\n", "import './frame_sources'\nimport Security from './security'\nimport LifecycleEvents from './lifecycle_events'\nimport {\n findClosestReflex,\n findClosestFrame,\n findFrameId,\n findFrame,\n findFrameSrc,\n buildAttributePayload\n} from './elements'\nimport {\n registerEventListener,\n registerEvent,\n registeredEvents,\n isRegisteredEvent,\n logRegisteredEvents\n} from './event_registry'\n\n// fires before making a turbo HTTP request\naddEventListener('turbo:before-fetch-request', event => {\n const frame = event.target\n const { turboReflexActive } = frame.dataset\n if (!turboReflexActive) return\n const { fetchOptions } = event.detail\n fetchOptions.headers['Turbo-Reflex'] = Security.token\n})\n\nfunction buildURL (urlString) {\n const a = document.createElement('a')\n a.href = urlString\n return new URL(a)\n}\n\nfunction invokeFormReflex (form, payload = {}) {\n payload.token = Security.token\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = 'turbo_reflex'\n input.value = JSON.stringify(payload)\n form.appendChild(input)\n}\n\nfunction invokeReflex (event) {\n let element, frameId, frame, frameSrc\n try {\n element = findClosestReflex(event.target)\n if (!element) return\n\n if (!isRegisteredEvent(event.type, element.tagName)) return\n\n LifecycleEvents.dispatch(LifecycleEvents.beforeStart, element, { element })\n\n frameId = findFrameId(element)\n if (!frameId) return\n\n frame = findFrame(frameId)\n if (!frame) return\n\n frameSrc = findFrameSrc(frame)\n if (!frameSrc) return\n\n const payload = {\n frameId: frameId,\n element: buildAttributePayload(element)\n }\n\n LifecycleEvents.dispatch(LifecycleEvents.start, element, {\n element,\n frameId,\n frame,\n frameSrc,\n payload\n })\n frame.dataset.turboReflexActive = true\n frame.dataset.turboReflexElementId = element.id\n\n if (element.tagName.toLowerCase() === 'form')\n return invokeFormReflex(element, payload)\n\n event.preventDefault()\n const frameURL = buildURL(frameSrc)\n frameURL.searchParams.set('turbo_reflex', JSON.stringify(payload))\n frame.src = frameURL.toString()\n } catch (error) {\n console.error(\n `TurboReflex encountered an unexpected error!`,\n { element, frameId, frame, frameSrc, target: event.target },\n error\n )\n LifecycleEvents.dispatch(LifecycleEvents.error, element || document, {\n element,\n frameId,\n frame,\n frameSrc,\n error\n })\n }\n}\n\n// wire things up and setup default events\nregisterEventListener(invokeReflex)\nregisterEvent('change', ['input', 'select', 'textarea'])\nregisterEvent('submit', ['form'])\nregisterEvent('click', ['*'])\n\nexport default {\n registerEvent,\n logRegisteredEvents,\n logLifecycleEventNames: LifecycleEvents.logEventNames\n}\n"],
|
5
|
-
"mappings": "AAAA,IAAMA,EAAS,CACb,
|
6
|
-
"names": ["events", "dispatch", "name", "target", "detail", "event", "
|
3
|
+
"sources": ["../../javascript/schema.js", "../../javascript/activity.js", "../../javascript/lifecycle.js", "../../javascript/elements.js", "../../javascript/turbo.js", "../../javascript/delegates.js", "../../javascript/drivers/form.js", "../../javascript/urls.js", "../../javascript/drivers/frame.js", "../../javascript/drivers/window.js", "../../javascript/drivers/index.js", "../../javascript/logger.js", "../../javascript/uuids.js", "../../javascript/index.js"],
|
4
|
+
"sourcesContent": ["const schema = {\n frameAttribute: 'data-turbo-frame',\n reflexAttribute: 'data-turbo-reflex'\n}\n\nexport default { ...schema }\n", "const active = {}\n\nfunction add (payload) {\n active[payload.id] = payload\n}\n\nfunction remove (id) {\n delete active[id]\n}\n\nexport default {\n add,\n remove,\n get reflexes () {\n return [...Object.values(active)]\n },\n get length () {\n return Object.keys(active).length\n }\n}\n", "import activity from './activity'\n\nconst events = {\n start: 'turbo-reflex:start',\n success: 'turbo-reflex:success',\n finish: 'turbo-reflex:finish',\n abort: 'turbo-reflex:abort',\n clientError: 'turbo-reflex:client-error',\n serverError: 'turbo-reflex:server-error'\n}\n\nfunction dispatch (name, target = document, detail = {}, raise = false) {\n try {\n target = target || document\n const event = new CustomEvent(name, {\n detail,\n cancelable: false,\n bubbles: true\n })\n target.dispatchEvent(event)\n } catch (error) {\n if (raise) throw error\n dispatch(events.clientError, target, { error, ...detail }, true)\n }\n}\n\nfunction finish (event) {\n event.detail.endedAt = new Date().getTime()\n event.detail.milliseconds = event.detail.endedAt - event.detail.startedAt\n setTimeout(() => dispatch(events.finish, event.target, event.detail), 10)\n}\n\naddEventListener(events.serverError, finish)\naddEventListener(events.success, finish)\naddEventListener(events.finish, event => activity.remove(event.detail.id), true)\n\nexport default {\n dispatch,\n events\n}\n", "import schema from './schema.js'\nimport lifecycle from './lifecycle'\n\nfunction findClosestReflex (element) {\n return element.closest(`[${schema.reflexAttribute}]`)\n}\n\nfunction findClosestFrame (element) {\n return element.closest('turbo-frame')\n}\n\nfunction assignElementValueToPayload (element, payload = {}) {\n if (element.tagName.toLowerCase() !== 'select')\n return (payload.value = element.value)\n\n if (!element.multiple)\n return (payload.value = element.options[element.selectedIndex].value)\n\n payload.values = Array.from(element.options).reduce((memo, option) => {\n if (option.selected) memo.push(option.value)\n return memo\n }, [])\n}\n\nfunction buildAttributePayload (element) {\n // truncate long values to optimize payload size\n // TODO: revisit this decision\n const maxAttributeLength = 100\n const maxValueLength = 500\n\n const payload = Array.from(element.attributes).reduce((memo, attr) => {\n let value = attr.value\n if (typeof value === 'string' && value.length > maxAttributeLength)\n value = value.slice(0, maxAttributeLength) + '...'\n memo[attr.name] = value\n return memo\n }, {})\n\n payload.tag = element.tagName\n payload.checked = element.checked\n payload.disabled = element.disabled\n assignElementValueToPayload(element, payload)\n\n if (\n typeof payload.value === 'string' &&\n payload.value.length > maxValueLength\n )\n payload.value = payload.value.slice(0, maxValueLength) + '...'\n\n delete payload.class\n delete payload[schema.reflexAttribute]\n delete payload[schema.frameAttribute]\n return payload\n}\n\nexport default {\n buildAttributePayload,\n findClosestReflex,\n findClosestFrame,\n get metaElement () {\n return document.getElementById('turbo-reflex')\n },\n get metaElementToken () {\n return document.getElementById('turbo-reflex').getAttribute('content')\n }\n}\n", "import elements from './elements'\n\nconst frameSources = {}\n\n// fires before making a turbo HTTP request\naddEventListener('turbo:before-fetch-request', event => {\n const frame = event.target.closest('turbo-frame')\n const { fetchOptions } = event.detail\n fetchOptions.headers['TurboReflex-Token'] = elements.metaElementToken\n})\n\n// fires after receiving a turbo HTTP response\naddEventListener('turbo:before-fetch-response', event => {\n const frame = event.target.closest('turbo-frame')\n if (frame) frameSources[frame.id] = frame.src\n})\n\n// fires when a frame element is navigated and finishes loading\naddEventListener('turbo:frame-load', event => {\n const frame = event.target.closest('turbo-frame')\n frame.dataset.turboReflexSrc =\n frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc\n delete frameSources[frame.id]\n})\n", "const events = {}\nlet eventListener\n\nfunction register (eventName, tagNames) {\n events[eventName] = tagNames\n document.addEventListener(eventName, eventListener, true)\n}\n\nfunction isRegistered (eventName, tagName) {\n tagName = tagName.toLowerCase()\n return (\n events[eventName].includes(tagName) ||\n (!Object.values(events)\n .flat()\n .includes(tagName) &&\n events[eventName].includes('*'))\n )\n}\n\nexport default {\n events,\n register,\n isRegistered,\n set handler (fn) {\n eventListener = fn\n }\n}\n", "import elements from '../elements'\n\nfunction invokeReflex (form, payload = {}) {\n payload.token = elements.metaElementToken\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = 'turbo_reflex'\n input.value = JSON.stringify(payload)\n form.appendChild(input)\n}\n\nexport default { invokeReflex }\n", "function build (urlString, payload = {}) {\n const a = document.createElement('a')\n a.href = urlString\n const url = new URL(a)\n url.searchParams.set('turbo_reflex', JSON.stringify(payload))\n return url\n}\n\nexport default { build }\n", "import urls from '../urls'\nimport elements from '../elements'\n\nfunction invokeReflex (frame, payload) {\n const src = payload.src\n payload = { ...payload }\n delete payload.src\n frame.src = urls.build(src, payload)\n}\n\nexport default { invokeReflex }\n", "import elements from '../elements'\nimport lifecycle from '../lifecycle'\nimport urls from '../urls'\n\nfunction aborted (event) {\n const xhr = event.target\n lifecycle.dispatch(lifecycle.events.abort, window, { xhr, ...event.detail })\n}\n\nfunction errored (event) {\n const xhr = event.target\n lifecycle.dispatch(\n lifecycle.events.clientError,\n window,\n {\n xhr,\n ...event.detail,\n error: `Server returned a ${xhr.status} status code! TurboReflex requires 2XX status codes. Server message: ${xhr.statusText}`\n },\n true\n )\n}\n\nfunction loaded (event) {\n const xhr = event.target\n const content = xhr.responseText\n const hijacked = xhr.getResponseHeader('TurboReflex-Hijacked') === 'true'\n if (xhr.status < 200 || xhr.status > 299) errored(event)\n\n if (hijacked) {\n const head = '<turbo-stream'\n const tail = '</turbo-stream>'\n const headIndex = content.indexOf(head)\n const tailIndex = content.lastIndexOf(tail)\n if (headIndex >= 0 && tailIndex >= 0) {\n const streams = content.slice(headIndex, tailIndex + tail.length)\n document.body.insertAdjacentHTML('beforeend', streams)\n }\n } else {\n const head = '<html'\n const tail = '</html'\n const headIndex = content.indexOf(head)\n const tailIndex = content.lastIndexOf(tail)\n if (headIndex >= 0 && tailIndex >= 0) {\n const html = content.slice(content.indexOf('>', headIndex) + 1, tailIndex)\n document.documentElement.innerHTML = html\n }\n }\n}\n\nfunction invokeReflex (payload) {\n const src = payload.src\n payload = { ...payload }\n delete payload.src\n\n try {\n const xhr = new XMLHttpRequest()\n xhr.open('GET', urls.build(src, payload), true)\n xhr.setRequestHeader('TurboReflex-Token', elements.metaElementToken)\n xhr.addEventListener('abort', aborted)\n xhr.addEventListener('error', errored)\n xhr.addEventListener('load', loaded)\n xhr.send()\n } catch (ex) {\n const message = `Unexpected error sending HTTP request! ${ex.message}`\n errored(ex, { detail: { message } })\n }\n}\n\nexport default { invokeReflex }\n", "import elements from '../elements'\nimport formDriver from './form'\nimport frameDriver from './frame'\nimport windowDriver from './window'\n\nfunction src (element, frame) {\n frame = frame || { dataset: {} }\n return (\n element.href || frame.src || frame.dataset.turboReflexSrc || location.href\n )\n}\n\nfunction find (element) {\n let frame = elements.findClosestFrame(element)\n const targetId = element.dataset.turboFrame\n\n if (element.tagName.toLowerCase() === 'form')\n return {\n name: 'form',\n reason: 'Element is a form.',\n frame,\n src: element.action,\n invokeReflex: formDriver.invokeReflex\n }\n\n // element targets a frame that is not _self\n if (targetId && targetId !== '_self') {\n frame = document.getElementById(targetId)\n return {\n name: 'frame',\n reason: 'element targets a frame that is not _self',\n frame,\n src: src(element, frame),\n invokeReflex: frameDriver.invokeReflex\n }\n }\n\n // element does NOT target a frame or targets _self and is contained by a frame\n if ((!targetId || targetId === '_self') && frame)\n return {\n name: 'frame',\n reason:\n 'element does NOT target a frame or targets _self and is contained by a frame',\n frame,\n src: src(element, frame),\n invokeReflex: frameDriver.invokeReflex\n }\n\n // element matches one or more of the following conditions\n // - targets _top\n // - does NOT target a frame\n // - is NOT contained by a frame\n return {\n name: 'window',\n reason:\n 'element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)',\n frame: null,\n src: src(element),\n invokeReflex: windowDriver.invokeReflex\n }\n}\n\nexport default { find }\n", "import lifecycle from './lifecycle'\n\nlet currentLevel = 'unknown'\n\nconst logLevels = {\n debug: Object.values(lifecycle.events),\n info: Object.values(lifecycle.events),\n warn: [\n lifecycle.events.abort,\n lifecycle.events.clientError,\n lifecycle.events.serverError\n ],\n error: [lifecycle.events.clientError, lifecycle.events.serverError],\n unknown: []\n}\n\nObject.values(lifecycle.events).forEach(name => {\n addEventListener(name, event => {\n if (logLevels[currentLevel].includes(event.type)) {\n const level = currentLevel === 'debug' ? 'log' : currentLevel\n console[level](event.type, event.detail)\n }\n })\n})\n\nexport default {\n get level () {\n return currentLevel\n },\n set level (value) {\n if (!Object.keys(logLevels).includes(value)) value = 'unknown'\n return (currentLevel = value)\n }\n}\n", "function v4 () {\n return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>\n (\n c ^\n (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))\n ).toString(16)\n )\n}\n\nexport default { v4 }\n", "import './turbo'\nimport schema from './schema.js'\nimport activity from './activity'\nimport delegates from './delegates'\nimport drivers from './drivers'\nimport elements from './elements'\nimport lifecycle from './lifecycle'\nimport logger from './logger'\nimport urls from './urls'\nimport uuids from './uuids'\n\nfunction invokeReflex (event) {\n let element\n let payload = {}\n\n try {\n element = elements.findClosestReflex(event.target)\n if (!element) return\n if (!delegates.isRegistered(event.type, element.tagName)) return\n\n const driver = drivers.find(element)\n\n // payload sent to server (also used for lifecycle event.detail)\n payload = {\n id: `reflex-${uuids.v4()}`,\n name: element.dataset.turboReflex,\n driver: driver.name,\n src: driver.src,\n frameId: driver.frame ? driver.frame.id : null,\n elementId: element.id.length > 0 ? element.id : null,\n elementAttributes: elements.buildAttributePayload(element),\n startedAt: new Date().getTime()\n }\n\n activity.add(payload)\n lifecycle.dispatch(lifecycle.events.start, element, payload)\n\n if (driver.name !== 'form') event.preventDefault()\n\n switch (driver.name) {\n case 'form':\n return driver.invokeReflex(element, payload)\n case 'frame':\n return driver.invokeReflex(driver.frame, payload)\n case 'window':\n return driver.invokeReflex(payload)\n }\n } catch (error) {\n lifecycle.dispatch(lifecycle.events.clientError, element, {\n error,\n ...payload\n })\n }\n}\n\n// wire things up and setup defaults for event delegation\ndelegates.handler = invokeReflex\ndelegates.register('change', ['input', 'select', 'textarea'])\ndelegates.register('submit', ['form'])\ndelegates.register('click', ['*'])\n\nexport default {\n schema,\n logger,\n registerEventDelegate: delegates.register,\n get eventDelegates () {\n return { ...delegates.events }\n },\n get lifecycleEvents () {\n return [...Object.values(lifecycle.events)]\n }\n}\n"],
|
5
|
+
"mappings": "AAAA,IAAMA,EAAS,CACb,eAAgB,mBAChB,gBAAiB,mBACnB,EAEOC,EAAQ,CAAE,GAAGD,CAAO,ECL3B,IAAME,EAAS,CAAC,EAEhB,SAASC,EAAKC,EAAS,CACrBF,EAAOE,EAAQ,IAAMA,CACvB,CAEA,SAASC,EAAQC,EAAI,CACnB,OAAOJ,EAAOI,EAChB,CAEA,IAAOC,EAAQ,CACb,IAAAJ,EACA,OAAAE,EACA,IAAI,UAAY,CACd,MAAO,CAAC,GAAG,OAAO,OAAOH,CAAM,CAAC,CAClC,EACA,IAAI,QAAU,CACZ,OAAO,OAAO,KAAKA,CAAM,EAAE,MAC7B,CACF,ECjBA,IAAMM,EAAS,CACb,MAAO,qBACP,QAAS,uBACT,OAAQ,sBACR,MAAO,qBACP,YAAa,4BACb,YAAa,2BACf,EAEA,SAASC,EAAUC,EAAMC,EAAS,SAAUC,EAAS,CAAC,EAAGC,EAAQ,GAAO,CACtE,GAAI,CACFF,EAASA,GAAU,SACnB,IAAMG,EAAQ,IAAI,YAAYJ,EAAM,CAClC,OAAAE,EACA,WAAY,GACZ,QAAS,EACX,CAAC,EACDD,EAAO,cAAcG,CAAK,CAC5B,OAASC,EAAP,CACA,GAAIF,EAAO,MAAME,EACjBN,EAASD,EAAO,YAAaG,EAAQ,CAAE,MAAAI,EAAO,GAAGH,CAAO,EAAG,EAAI,CACjE,CACF,CAEA,SAASI,EAAQF,EAAO,CACtBA,EAAM,OAAO,QAAU,IAAI,KAAK,EAAE,QAAQ,EAC1CA,EAAM,OAAO,aAAeA,EAAM,OAAO,QAAUA,EAAM,OAAO,UAChE,WAAW,IAAML,EAASD,EAAO,OAAQM,EAAM,OAAQA,EAAM,MAAM,EAAG,EAAE,CAC1E,CAEA,iBAAiBN,EAAO,YAAaQ,CAAM,EAC3C,iBAAiBR,EAAO,QAASQ,CAAM,EACvC,iBAAiBR,EAAO,OAAQM,GAASG,EAAS,OAAOH,EAAM,OAAO,EAAE,EAAG,EAAI,EAE/E,IAAOI,EAAQ,CACb,SAAAT,EACA,OAAAD,CACF,ECpCA,SAASW,EAAmBC,EAAS,CACnC,OAAOA,EAAQ,QAAQ,IAAIC,EAAO,kBAAkB,CACtD,CAEA,SAASC,EAAkBF,EAAS,CAClC,OAAOA,EAAQ,QAAQ,aAAa,CACtC,CAEA,SAASG,EAA6BH,EAASI,EAAU,CAAC,EAAG,CAC3D,GAAIJ,EAAQ,QAAQ,YAAY,IAAM,SACpC,OAAQI,EAAQ,MAAQJ,EAAQ,MAElC,GAAI,CAACA,EAAQ,SACX,OAAQI,EAAQ,MAAQJ,EAAQ,QAAQA,EAAQ,eAAe,MAEjEI,EAAQ,OAAS,MAAM,KAAKJ,EAAQ,OAAO,EAAE,OAAO,CAACK,EAAMC,KACrDA,EAAO,UAAUD,EAAK,KAAKC,EAAO,KAAK,EACpCD,GACN,CAAC,CAAC,CACP,CAEA,SAASE,EAAuBP,EAAS,CAMvC,IAAMI,EAAU,MAAM,KAAKJ,EAAQ,UAAU,EAAE,OAAO,CAACK,EAAMG,IAAS,CACpE,IAAIC,EAAQD,EAAK,MACjB,OAAI,OAAOC,GAAU,UAAYA,EAAM,OAAS,MAC9CA,EAAQA,EAAM,MAAM,EAAG,GAAkB,EAAI,OAC/CJ,EAAKG,EAAK,MAAQC,EACXJ,CACT,EAAG,CAAC,CAAC,EAEL,OAAAD,EAAQ,IAAMJ,EAAQ,QACtBI,EAAQ,QAAUJ,EAAQ,QAC1BI,EAAQ,SAAWJ,EAAQ,SAC3BG,EAA4BH,EAASI,CAAO,EAG1C,OAAOA,EAAQ,OAAU,UACzBA,EAAQ,MAAM,OAAS,MAEvBA,EAAQ,MAAQA,EAAQ,MAAM,MAAM,EAAG,GAAc,EAAI,OAE3D,OAAOA,EAAQ,MACf,OAAOA,EAAQH,EAAO,iBACtB,OAAOG,EAAQH,EAAO,gBACfG,CACT,CAEA,IAAOM,EAAQ,CACb,sBAAAH,EACA,kBAAAR,EACA,iBAAAG,EACA,IAAI,aAAe,CACjB,OAAO,SAAS,eAAe,cAAc,CAC/C,EACA,IAAI,kBAAoB,CACtB,OAAO,SAAS,eAAe,cAAc,EAAE,aAAa,SAAS,CACvE,CACF,EC/DA,IAAMS,EAAe,CAAC,EAGtB,iBAAiB,6BAA8BC,GAAS,CACtD,IAAMC,EAAQD,EAAM,OAAO,QAAQ,aAAa,EAC1C,CAAE,aAAAE,CAAa,EAAIF,EAAM,OAC/BE,EAAa,QAAQ,qBAAuBC,EAAS,gBACvD,CAAC,EAGD,iBAAiB,8BAA+BH,GAAS,CACvD,IAAMC,EAAQD,EAAM,OAAO,QAAQ,aAAa,EAC5CC,IAAOF,EAAaE,EAAM,IAAMA,EAAM,IAC5C,CAAC,EAGD,iBAAiB,mBAAoBD,GAAS,CAC5C,IAAMC,EAAQD,EAAM,OAAO,QAAQ,aAAa,EAChDC,EAAM,QAAQ,eACZF,EAAaE,EAAM,KAAOA,EAAM,KAAOA,EAAM,QAAQ,eACvD,OAAOF,EAAaE,EAAM,GAC5B,CAAC,ECvBD,IAAMG,EAAS,CAAC,EACZC,EAEJ,SAASC,EAAUC,EAAWC,EAAU,CACtCJ,EAAOG,GAAaC,EACpB,SAAS,iBAAiBD,EAAWF,EAAe,EAAI,CAC1D,CAEA,SAASI,EAAcF,EAAWG,EAAS,CACzC,OAAAA,EAAUA,EAAQ,YAAY,EAE5BN,EAAOG,GAAW,SAASG,CAAO,GACjC,CAAC,OAAO,OAAON,CAAM,EACnB,KAAK,EACL,SAASM,CAAO,GACjBN,EAAOG,GAAW,SAAS,GAAG,CAEpC,CAEA,IAAOI,EAAQ,CACb,OAAAP,EACA,SAAAE,EACA,aAAAG,EACA,IAAI,QAASG,EAAI,CACfP,EAAgBO,CAClB,CACF,ECxBA,SAASC,EAAcC,EAAMC,EAAU,CAAC,EAAG,CACzCA,EAAQ,MAAQC,EAAS,iBACzB,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,KAAO,SACbA,EAAM,KAAO,eACbA,EAAM,MAAQ,KAAK,UAAUF,CAAO,EACpCD,EAAK,YAAYG,CAAK,CACxB,CAEA,IAAOC,EAAQ,CAAE,aAAAL,CAAa,ECX9B,SAASM,EAAOC,EAAWC,EAAU,CAAC,EAAG,CACvC,IAAMC,EAAI,SAAS,cAAc,GAAG,EACpCA,EAAE,KAAOF,EACT,IAAMG,EAAM,IAAI,IAAID,CAAC,EACrB,OAAAC,EAAI,aAAa,IAAI,eAAgB,KAAK,UAAUF,CAAO,CAAC,EACrDE,CACT,CAEA,IAAOC,EAAQ,CAAE,MAAAL,CAAM,ECLvB,SAASM,EAAcC,EAAOC,EAAS,CACrC,IAAMC,EAAMD,EAAQ,IACpBA,EAAU,CAAE,GAAGA,CAAQ,EACvB,OAAOA,EAAQ,IACfD,EAAM,IAAMG,EAAK,MAAMD,EAAKD,CAAO,CACrC,CAEA,IAAOG,EAAQ,CAAE,aAAAL,CAAa,ECN9B,SAASM,EAASC,EAAO,CACvB,IAAMC,EAAMD,EAAM,OAClBE,EAAU,SAASA,EAAU,OAAO,MAAO,OAAQ,CAAE,IAAAD,EAAK,GAAGD,EAAM,MAAO,CAAC,CAC7E,CAEA,SAASG,EAASH,EAAO,CACvB,IAAMC,EAAMD,EAAM,OAClBE,EAAU,SACRA,EAAU,OAAO,YACjB,OACA,CACE,IAAAD,EACA,GAAGD,EAAM,OACT,MAAO,qBAAqBC,EAAI,8EAA8EA,EAAI,YACpH,EACA,EACF,CACF,CAEA,SAASG,EAAQJ,EAAO,CACtB,IAAMC,EAAMD,EAAM,OACZK,EAAUJ,EAAI,aACdK,EAAWL,EAAI,kBAAkB,sBAAsB,IAAM,OAGnE,IAFIA,EAAI,OAAS,KAAOA,EAAI,OAAS,MAAKE,EAAQH,CAAK,EAEnDM,EAAU,CACZ,IAAMC,EAAO,gBACPC,EAAO,kBACPC,EAAYJ,EAAQ,QAAQE,CAAI,EAChCG,EAAYL,EAAQ,YAAYG,CAAI,EAC1C,GAAIC,GAAa,GAAKC,GAAa,EAAG,CACpC,IAAMC,EAAUN,EAAQ,MAAMI,EAAWC,EAAYF,EAAK,MAAM,EAChE,SAAS,KAAK,mBAAmB,YAAaG,CAAO,CACvD,CACF,KAAO,CACL,IAAMJ,EAAO,QACPC,EAAO,SACPC,EAAYJ,EAAQ,QAAQE,CAAI,EAChCG,EAAYL,EAAQ,YAAYG,CAAI,EAC1C,GAAIC,GAAa,GAAKC,GAAa,EAAG,CACpC,IAAME,EAAOP,EAAQ,MAAMA,EAAQ,QAAQ,IAAKI,CAAS,EAAI,EAAGC,CAAS,EACzE,SAAS,gBAAgB,UAAYE,CACvC,CACF,CACF,CAEA,SAASC,EAAcC,EAAS,CAC9B,IAAMC,EAAMD,EAAQ,IACpBA,EAAU,CAAE,GAAGA,CAAQ,EACvB,OAAOA,EAAQ,IAEf,GAAI,CACF,IAAMb,EAAM,IAAI,eAChBA,EAAI,KAAK,MAAOe,EAAK,MAAMD,EAAKD,CAAO,EAAG,EAAI,EAC9Cb,EAAI,iBAAiB,oBAAqBgB,EAAS,gBAAgB,EACnEhB,EAAI,iBAAiB,QAASF,CAAO,EACrCE,EAAI,iBAAiB,QAASE,CAAO,EACrCF,EAAI,iBAAiB,OAAQG,CAAM,EACnCH,EAAI,KAAK,CACX,OAASiB,EAAP,CACA,IAAMC,EAAU,0CAA0CD,EAAG,UAC7Df,EAAQe,EAAI,CAAE,OAAQ,CAAE,QAAAC,CAAQ,CAAE,CAAC,CACrC,CACF,CAEA,IAAOC,EAAQ,CAAE,aAAAP,CAAa,EChE9B,SAASQ,EAAKC,EAASC,EAAO,CAC5B,OAAAA,EAAQA,GAAS,CAAE,QAAS,CAAC,CAAE,EAE7BD,EAAQ,MAAQC,EAAM,KAAOA,EAAM,QAAQ,gBAAkB,SAAS,IAE1E,CAEA,SAASC,EAAMF,EAAS,CACtB,IAAIC,EAAQE,EAAS,iBAAiBH,CAAO,EACvCI,EAAWJ,EAAQ,QAAQ,WAEjC,OAAIA,EAAQ,QAAQ,YAAY,IAAM,OAC7B,CACL,KAAM,OACN,OAAQ,qBACR,MAAAC,EACA,IAAKD,EAAQ,OACb,aAAcK,EAAW,YAC3B,EAGED,GAAYA,IAAa,SAC3BH,EAAQ,SAAS,eAAeG,CAAQ,EACjC,CACL,KAAM,QACN,OAAQ,4CACR,MAAAH,EACA,IAAKF,EAAIC,EAASC,CAAK,EACvB,aAAcK,EAAY,YAC5B,IAIG,CAACF,GAAYA,IAAa,UAAYH,EAClC,CACL,KAAM,QACN,OACE,+EACF,MAAAA,EACA,IAAKF,EAAIC,EAASC,CAAK,EACvB,aAAcK,EAAY,YAC5B,EAMK,CACL,KAAM,SACN,OACE,+HACF,MAAO,KACP,IAAKP,EAAIC,CAAO,EAChB,aAAcO,EAAa,YAC7B,CACF,CAEA,IAAOC,EAAQ,CAAE,KAAAN,CAAK,EC5DtB,IAAIO,EAAe,UAEbC,EAAY,CAChB,MAAO,OAAO,OAAOC,EAAU,MAAM,EACrC,KAAM,OAAO,OAAOA,EAAU,MAAM,EACpC,KAAM,CACJA,EAAU,OAAO,MACjBA,EAAU,OAAO,YACjBA,EAAU,OAAO,WACnB,EACA,MAAO,CAACA,EAAU,OAAO,YAAaA,EAAU,OAAO,WAAW,EAClE,QAAS,CAAC,CACZ,EAEA,OAAO,OAAOA,EAAU,MAAM,EAAE,QAAQC,GAAQ,CAC9C,iBAAiBA,EAAMC,GAAS,CAC1BH,EAAUD,GAAc,SAASI,EAAM,IAAI,GAE7C,QADcJ,IAAiB,QAAU,MAAQA,GAClCI,EAAM,KAAMA,EAAM,MAAM,CAE3C,CAAC,CACH,CAAC,EAED,IAAOC,EAAQ,CACb,IAAI,OAAS,CACX,OAAOL,CACT,EACA,IAAI,MAAOM,EAAO,CAChB,OAAK,OAAO,KAAKL,CAAS,EAAE,SAASK,CAAK,IAAGA,EAAQ,WAC7CN,EAAeM,CACzB,CACF,ECjCA,SAASC,GAAM,CACb,OAAQ,CAAC,GAAG,EAAI,KAAO,KAAO,KAAO,OAAO,QAAQ,SAAUC,IAE1DA,EACC,OAAO,gBAAgB,IAAI,WAAW,CAAC,CAAC,EAAE,GAAM,IAAOA,EAAI,GAC5D,SAAS,EAAE,CACf,CACF,CAEA,IAAOC,EAAQ,CAAE,GAAAF,CAAG,ECEpB,SAASG,EAAcC,EAAO,CAC5B,IAAIC,EACAC,EAAU,CAAC,EAEf,GAAI,CAGF,GAFAD,EAAUE,EAAS,kBAAkBH,EAAM,MAAM,EAC7C,CAACC,GACD,CAACG,EAAU,aAAaJ,EAAM,KAAMC,EAAQ,OAAO,EAAG,OAE1D,IAAMI,EAASC,EAAQ,KAAKL,CAAO,EAmBnC,OAhBAC,EAAU,CACR,GAAI,UAAUK,EAAM,GAAG,IACvB,KAAMN,EAAQ,QAAQ,YACtB,OAAQI,EAAO,KACf,IAAKA,EAAO,IACZ,QAASA,EAAO,MAAQA,EAAO,MAAM,GAAK,KAC1C,UAAWJ,EAAQ,GAAG,OAAS,EAAIA,EAAQ,GAAK,KAChD,kBAAmBE,EAAS,sBAAsBF,CAAO,EACzD,UAAW,IAAI,KAAK,EAAE,QAAQ,CAChC,EAEAO,EAAS,IAAIN,CAAO,EACpBO,EAAU,SAASA,EAAU,OAAO,MAAOR,EAASC,CAAO,EAEvDG,EAAO,OAAS,QAAQL,EAAM,eAAe,EAEzCK,EAAO,KAAM,CACnB,IAAK,OACH,OAAOA,EAAO,aAAaJ,EAASC,CAAO,EAC7C,IAAK,QACH,OAAOG,EAAO,aAAaA,EAAO,MAAOH,CAAO,EAClD,IAAK,SACH,OAAOG,EAAO,aAAaH,CAAO,CACtC,CACF,OAASQ,EAAP,CACAD,EAAU,SAASA,EAAU,OAAO,YAAaR,EAAS,CACxD,MAAAS,EACA,GAAGR,CACL,CAAC,CACH,CACF,CAGAE,EAAU,QAAUL,EACpBK,EAAU,SAAS,SAAU,CAAC,QAAS,SAAU,UAAU,CAAC,EAC5DA,EAAU,SAAS,SAAU,CAAC,MAAM,CAAC,EACrCA,EAAU,SAAS,QAAS,CAAC,GAAG,CAAC,EAEjC,IAAOO,GAAQ,CACb,OAAAC,EACA,OAAAC,EACA,sBAAuBT,EAAU,SACjC,IAAI,gBAAkB,CACpB,MAAO,CAAE,GAAGA,EAAU,MAAO,CAC/B,EACA,IAAI,iBAAmB,CACrB,MAAO,CAAC,GAAG,OAAO,OAAOK,EAAU,MAAM,CAAC,CAC5C,CACF",
|
6
|
+
"names": ["schema", "schema_default", "active", "add", "payload", "remove", "id", "activity_default", "events", "dispatch", "name", "target", "detail", "raise", "event", "error", "finish", "activity_default", "lifecycle_default", "findClosestReflex", "element", "schema_default", "findClosestFrame", "assignElementValueToPayload", "payload", "memo", "option", "buildAttributePayload", "attr", "value", "elements_default", "frameSources", "event", "frame", "fetchOptions", "elements_default", "events", "eventListener", "register", "eventName", "tagNames", "isRegistered", "tagName", "delegates_default", "fn", "invokeReflex", "form", "payload", "elements_default", "input", "form_default", "build", "urlString", "payload", "a", "url", "urls_default", "invokeReflex", "frame", "payload", "src", "urls_default", "frame_default", "aborted", "event", "xhr", "lifecycle_default", "errored", "loaded", "content", "hijacked", "head", "tail", "headIndex", "tailIndex", "streams", "html", "invokeReflex", "payload", "src", "urls_default", "elements_default", "ex", "message", "window_default", "src", "element", "frame", "find", "elements_default", "targetId", "form_default", "frame_default", "window_default", "drivers_default", "currentLevel", "logLevels", "lifecycle_default", "name", "event", "logger_default", "value", "v4", "c", "uuids_default", "invokeReflex", "event", "element", "payload", "elements_default", "delegates_default", "driver", "drivers_default", "uuids_default", "activity_default", "lifecycle_default", "error", "javascript_default", "schema_default", "logger_default"]
|
7
7
|
}
|
@@ -4,130 +4,34 @@ module TurboReflex::Controller
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
before_action :
|
8
|
-
after_action :
|
9
|
-
after_action :assign_turbo_reflex_token
|
7
|
+
before_action :run_turbo_reflex
|
8
|
+
after_action :append_turbo_reflex_to_response
|
10
9
|
helper_method :turbo_reflex_meta_tag, :turbo_reflex_performed?, :turbo_reflex_requested?
|
11
|
-
# helper TurboReflex::TurboReflexHelper # only required if we isolate_namespace
|
12
10
|
end
|
13
11
|
|
14
|
-
def
|
15
|
-
|
16
|
-
options = {id: "turbo-reflex-token", name: "turbo-reflex-token", content: masked_token}
|
17
|
-
view_context.tag("meta", options).html_safe
|
12
|
+
def turbo_reflex_runner
|
13
|
+
@turbo_reflex_runner ||= TurboReflex::Runner.new(self)
|
18
14
|
end
|
19
15
|
|
20
|
-
def
|
21
|
-
|
22
|
-
@turbo_reflex_params ||= begin
|
23
|
-
payload = JSON.parse(params[:turbo_reflex]).deep_transform_keys(&:underscore)
|
24
|
-
ActionController::Parameters.new(payload).permit!
|
25
|
-
end
|
16
|
+
def turbo_reflex_meta_tag
|
17
|
+
turbo_reflex_runner.meta_tag
|
26
18
|
end
|
27
19
|
|
28
20
|
def turbo_reflex_requested?
|
29
|
-
|
30
|
-
return false unless turbo_reflex_params.present?
|
31
|
-
true
|
32
|
-
end
|
33
|
-
|
34
|
-
def turbo_reflex_element
|
35
|
-
return nil if turbo_reflex_params.blank?
|
36
|
-
@turbo_reflex_element ||= Struct
|
37
|
-
.new(*turbo_reflex_params[:element].keys.map { |key| key.to_s.parameterize.underscore.to_sym })
|
38
|
-
.new(*turbo_reflex_params[:element].values)
|
39
|
-
end
|
40
|
-
|
41
|
-
def turbo_reflex_name
|
42
|
-
return nil unless turbo_reflex_requested?
|
43
|
-
turbo_reflex_element.data_turbo_reflex
|
44
|
-
end
|
45
|
-
|
46
|
-
def turbo_reflex_class_name
|
47
|
-
return nil unless turbo_reflex_requested?
|
48
|
-
turbo_reflex_name.split("#").first
|
49
|
-
end
|
50
|
-
|
51
|
-
def turbo_reflex_method_name
|
52
|
-
return nil unless turbo_reflex_requested?
|
53
|
-
turbo_reflex_name.split("#").last
|
54
|
-
end
|
55
|
-
|
56
|
-
def turbo_reflex_class
|
57
|
-
@turbo_reflex_class ||= turbo_reflex_class_name&.safe_constantize
|
58
|
-
end
|
59
|
-
|
60
|
-
def turbo_reflex_instance
|
61
|
-
@turbo_reflex_instance ||= turbo_reflex_class&.new(self)
|
62
|
-
end
|
63
|
-
|
64
|
-
def turbo_reflex_valid?
|
65
|
-
return false if request.get? && client_turbo_reflex_token.blank?
|
66
|
-
return false unless valid_turbo_reflex_token?
|
67
|
-
return false unless turbo_reflex_instance.is_a?(TurboReflex::Base)
|
68
|
-
turbo_reflex_instance.respond_to? turbo_reflex_method_name
|
21
|
+
turbo_reflex_runner.reflex_requested?
|
69
22
|
end
|
70
23
|
|
71
24
|
def turbo_reflex_performed?
|
72
|
-
|
25
|
+
turbo_reflex_runner.reflex_performed?
|
73
26
|
end
|
74
27
|
|
75
28
|
protected
|
76
29
|
|
77
|
-
def
|
78
|
-
|
79
|
-
turbo_reflex_instance.public_send turbo_reflex_method_name
|
80
|
-
end
|
81
|
-
|
82
|
-
def append_turbo_reflex_turbo_streams
|
83
|
-
return unless turbo_reflex_performed?
|
84
|
-
return unless turbo_reflex_instance&.turbo_streams.present?
|
85
|
-
append_turbo_reflex_content turbo_reflex_instance.turbo_streams.map(&:to_s).join
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def turbo_reflex_message_verifier
|
91
|
-
ActiveSupport::MessageVerifier.new session.id.to_s, digest: "SHA256"
|
92
|
-
end
|
93
|
-
|
94
|
-
def client_turbo_reflex_token
|
95
|
-
(request.headers["Turbo-Reflex"] || turbo_reflex_params[:token]).to_s
|
96
|
-
end
|
97
|
-
|
98
|
-
def new_turbo_reflex_token
|
99
|
-
@new_turbo_reflex_token ||= SecureRandom.urlsafe_base64(32)
|
100
|
-
end
|
101
|
-
|
102
|
-
def current_turbo_reflex_token
|
103
|
-
session[:turbo_reflex_token]
|
104
|
-
end
|
105
|
-
|
106
|
-
def valid_turbo_reflex_token?
|
107
|
-
return false unless turbo_reflex_message_verifier.valid_message?(client_turbo_reflex_token)
|
108
|
-
unmasked_token = turbo_reflex_message_verifier.verify(client_turbo_reflex_token)
|
109
|
-
unmasked_token == current_turbo_reflex_token
|
110
|
-
end
|
111
|
-
|
112
|
-
def assign_turbo_reflex_token
|
113
|
-
return unless turbo_reflex_requested? || client_turbo_reflex_token.blank?
|
114
|
-
session[:turbo_reflex_token] = new_turbo_reflex_token
|
115
|
-
append_turbo_reflex_content turbo_stream.replace("turbo-reflex-token", turbo_reflex_meta_tag)
|
116
|
-
end
|
117
|
-
|
118
|
-
def turbo_reflex_response_type
|
119
|
-
body = response.body.to_s.strip
|
120
|
-
return :stream if body.ends_with?("</turbo-stream>")
|
121
|
-
return :frame if body.ends_with?("</turbo-frame>")
|
122
|
-
:default
|
30
|
+
def run_turbo_reflex
|
31
|
+
turbo_reflex_runner.run
|
123
32
|
end
|
124
33
|
|
125
|
-
def
|
126
|
-
|
127
|
-
case turbo_reflex_response_type
|
128
|
-
when :stream then response.body << sanitized_content
|
129
|
-
when :frame then response.body.sub!("</turbo-frame>", "#{sanitized_content}</turbo-frame>")
|
130
|
-
when :default then response.body.sub!("</body>", "#{sanitized_content}</body>")
|
131
|
-
end
|
34
|
+
def append_turbo_reflex_to_response
|
35
|
+
turbo_reflex_runner.append_to_response
|
132
36
|
end
|
133
37
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
const active = {}
|
2
|
+
|
3
|
+
function add (payload) {
|
4
|
+
active[payload.id] = payload
|
5
|
+
}
|
6
|
+
|
7
|
+
function remove (id) {
|
8
|
+
delete active[id]
|
9
|
+
}
|
10
|
+
|
11
|
+
export default {
|
12
|
+
add,
|
13
|
+
remove,
|
14
|
+
get reflexes () {
|
15
|
+
return [...Object.values(active)]
|
16
|
+
},
|
17
|
+
get length () {
|
18
|
+
return Object.keys(active).length
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
const events = {}
|
2
|
+
let eventListener
|
3
|
+
|
4
|
+
function register (eventName, tagNames) {
|
5
|
+
events[eventName] = tagNames
|
6
|
+
document.addEventListener(eventName, eventListener, true)
|
7
|
+
}
|
8
|
+
|
9
|
+
function isRegistered (eventName, tagName) {
|
10
|
+
tagName = tagName.toLowerCase()
|
11
|
+
return (
|
12
|
+
events[eventName].includes(tagName) ||
|
13
|
+
(!Object.values(events)
|
14
|
+
.flat()
|
15
|
+
.includes(tagName) &&
|
16
|
+
events[eventName].includes('*'))
|
17
|
+
)
|
18
|
+
}
|
19
|
+
|
20
|
+
export default {
|
21
|
+
events,
|
22
|
+
register,
|
23
|
+
isRegistered,
|
24
|
+
set handler (fn) {
|
25
|
+
eventListener = fn
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
|
3
|
+
function invokeReflex (form, payload = {}) {
|
4
|
+
payload.token = elements.metaElementToken
|
5
|
+
const input = document.createElement('input')
|
6
|
+
input.type = 'hidden'
|
7
|
+
input.name = 'turbo_reflex'
|
8
|
+
input.value = JSON.stringify(payload)
|
9
|
+
form.appendChild(input)
|
10
|
+
}
|
11
|
+
|
12
|
+
export default { invokeReflex }
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import urls from '../urls'
|
2
|
+
import elements from '../elements'
|
3
|
+
|
4
|
+
function invokeReflex (frame, payload) {
|
5
|
+
const src = payload.src
|
6
|
+
payload = { ...payload }
|
7
|
+
delete payload.src
|
8
|
+
frame.src = urls.build(src, payload)
|
9
|
+
}
|
10
|
+
|
11
|
+
export default { invokeReflex }
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
import formDriver from './form'
|
3
|
+
import frameDriver from './frame'
|
4
|
+
import windowDriver from './window'
|
5
|
+
|
6
|
+
function src (element, frame) {
|
7
|
+
frame = frame || { dataset: {} }
|
8
|
+
return (
|
9
|
+
element.href || frame.src || frame.dataset.turboReflexSrc || location.href
|
10
|
+
)
|
11
|
+
}
|
12
|
+
|
13
|
+
function find (element) {
|
14
|
+
let frame = elements.findClosestFrame(element)
|
15
|
+
const targetId = element.dataset.turboFrame
|
16
|
+
|
17
|
+
if (element.tagName.toLowerCase() === 'form')
|
18
|
+
return {
|
19
|
+
name: 'form',
|
20
|
+
reason: 'Element is a form.',
|
21
|
+
frame,
|
22
|
+
src: element.action,
|
23
|
+
invokeReflex: formDriver.invokeReflex
|
24
|
+
}
|
25
|
+
|
26
|
+
// element targets a frame that is not _self
|
27
|
+
if (targetId && targetId !== '_self') {
|
28
|
+
frame = document.getElementById(targetId)
|
29
|
+
return {
|
30
|
+
name: 'frame',
|
31
|
+
reason: 'element targets a frame that is not _self',
|
32
|
+
frame,
|
33
|
+
src: src(element, frame),
|
34
|
+
invokeReflex: frameDriver.invokeReflex
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
// element does NOT target a frame or targets _self and is contained by a frame
|
39
|
+
if ((!targetId || targetId === '_self') && frame)
|
40
|
+
return {
|
41
|
+
name: 'frame',
|
42
|
+
reason:
|
43
|
+
'element does NOT target a frame or targets _self and is contained by a frame',
|
44
|
+
frame,
|
45
|
+
src: src(element, frame),
|
46
|
+
invokeReflex: frameDriver.invokeReflex
|
47
|
+
}
|
48
|
+
|
49
|
+
// element matches one or more of the following conditions
|
50
|
+
// - targets _top
|
51
|
+
// - does NOT target a frame
|
52
|
+
// - is NOT contained by a frame
|
53
|
+
return {
|
54
|
+
name: 'window',
|
55
|
+
reason:
|
56
|
+
'element matches one or more of the following conditions (targets _top, does NOT target a frame, is NOT contained by a frame)',
|
57
|
+
frame: null,
|
58
|
+
src: src(element),
|
59
|
+
invokeReflex: windowDriver.invokeReflex
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
export default { find }
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import elements from '../elements'
|
2
|
+
import lifecycle from '../lifecycle'
|
3
|
+
import urls from '../urls'
|
4
|
+
|
5
|
+
function aborted (event) {
|
6
|
+
const xhr = event.target
|
7
|
+
lifecycle.dispatch(lifecycle.events.abort, window, { xhr, ...event.detail })
|
8
|
+
}
|
9
|
+
|
10
|
+
function errored (event) {
|
11
|
+
const xhr = event.target
|
12
|
+
lifecycle.dispatch(
|
13
|
+
lifecycle.events.clientError,
|
14
|
+
window,
|
15
|
+
{
|
16
|
+
xhr,
|
17
|
+
...event.detail,
|
18
|
+
error: `Server returned a ${xhr.status} status code! TurboReflex requires 2XX status codes. Server message: ${xhr.statusText}`
|
19
|
+
},
|
20
|
+
true
|
21
|
+
)
|
22
|
+
}
|
23
|
+
|
24
|
+
function loaded (event) {
|
25
|
+
const xhr = event.target
|
26
|
+
const content = xhr.responseText
|
27
|
+
const hijacked = xhr.getResponseHeader('TurboReflex-Hijacked') === 'true'
|
28
|
+
if (xhr.status < 200 || xhr.status > 299) errored(event)
|
29
|
+
|
30
|
+
if (hijacked) {
|
31
|
+
const head = '<turbo-stream'
|
32
|
+
const tail = '</turbo-stream>'
|
33
|
+
const headIndex = content.indexOf(head)
|
34
|
+
const tailIndex = content.lastIndexOf(tail)
|
35
|
+
if (headIndex >= 0 && tailIndex >= 0) {
|
36
|
+
const streams = content.slice(headIndex, tailIndex + tail.length)
|
37
|
+
document.body.insertAdjacentHTML('beforeend', streams)
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
const head = '<html'
|
41
|
+
const tail = '</html'
|
42
|
+
const headIndex = content.indexOf(head)
|
43
|
+
const tailIndex = content.lastIndexOf(tail)
|
44
|
+
if (headIndex >= 0 && tailIndex >= 0) {
|
45
|
+
const html = content.slice(content.indexOf('>', headIndex) + 1, tailIndex)
|
46
|
+
document.documentElement.innerHTML = html
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function invokeReflex (payload) {
|
52
|
+
const src = payload.src
|
53
|
+
payload = { ...payload }
|
54
|
+
delete payload.src
|
55
|
+
|
56
|
+
try {
|
57
|
+
const xhr = new XMLHttpRequest()
|
58
|
+
xhr.open('GET', urls.build(src, payload), true)
|
59
|
+
xhr.setRequestHeader('TurboReflex-Token', elements.metaElementToken)
|
60
|
+
xhr.addEventListener('abort', aborted)
|
61
|
+
xhr.addEventListener('error', errored)
|
62
|
+
xhr.addEventListener('load', loaded)
|
63
|
+
xhr.send()
|
64
|
+
} catch (ex) {
|
65
|
+
const message = `Unexpected error sending HTTP request! ${ex.message}`
|
66
|
+
errored(ex, { detail: { message } })
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
export default { invokeReflex }
|