turbo_boost-commands 0.0.11 → 0.0.13
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.
Potentially problematic release.
This version of turbo_boost-commands might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +166 -133
- data/app/assets/builds/@turbo-boost/commands.js +1 -5
- data/app/assets/builds/@turbo-boost/commands.js.map +4 -4
- data/app/assets/builds/@turbo-boost/commands.metafile.json +1 -0
- data/app/assets/images/hopsoft.webp +0 -0
- data/app/javascript/activity.js +4 -4
- data/app/javascript/confirmation.js +33 -0
- data/app/javascript/delegates.js +9 -8
- data/app/javascript/drivers/form.js +1 -1
- data/app/javascript/drivers/frame.js +1 -1
- data/app/javascript/drivers/index.js +4 -7
- data/app/javascript/drivers/method.js +1 -1
- data/app/javascript/drivers/window.js +15 -24
- data/app/javascript/elements.js +6 -8
- data/app/javascript/events.js +9 -7
- data/app/javascript/index.js +27 -24
- data/app/javascript/lifecycle.js +3 -11
- data/app/javascript/logger.js +4 -4
- data/app/javascript/meta.js +4 -4
- data/app/javascript/renderer.js +2 -2
- data/app/javascript/schema.js +2 -1
- data/app/javascript/state/index.js +5 -5
- data/app/javascript/state/observable.js +4 -5
- data/app/javascript/turbo.js +5 -18
- data/app/javascript/urls.js +1 -1
- data/app/javascript/uuids.js +2 -5
- data/lib/turbo_boost/commands/attribute_set.rb +1 -1
- data/lib/turbo_boost/commands/engine.rb +1 -1
- data/lib/turbo_boost/commands/runner.rb +27 -4
- data/lib/turbo_boost/commands/version.rb +1 -1
- metadata +6 -18
- data/app/javascript/drivers/turbo_form.js +0 -17
@@ -0,0 +1 @@
|
|
1
|
+
{"inputs":{"app/javascript/meta.js":{"bytes":337,"imports":[],"format":"esm"},"app/javascript/events.js":{"bytes":783,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/state/observable.js":{"bytes":920,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"}],"format":"esm"},"app/javascript/state/index.js":{"bytes":1835,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/observable.js","kind":"import-statement","original":"./observable"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/renderer.js":{"bytes":475,"imports":[],"format":"esm"},"app/javascript/activity.js":{"bytes":279,"imports":[],"format":"esm"},"app/javascript/lifecycle.js":{"bytes":610,"imports":[{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/turbo.js":{"bytes":1952,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"./renderer"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/schema.js":{"bytes":210,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/confirmation.js":{"bytes":947,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"}],"format":"esm"},"app/javascript/delegates.js":{"bytes":923,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/elements.js":{"bytes":1451,"imports":[{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"}],"format":"esm"},"app/javascript/drivers/form.js":{"bytes":313,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"}],"format":"esm"},"app/javascript/urls.js":{"bytes":240,"imports":[],"format":"esm"},"app/javascript/drivers/frame.js":{"bytes":218,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/method.js":{"bytes":265,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/window.js":{"bytes":2074,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"../state"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"../lifecycle"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"../renderer"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/index.js":{"bytes":2044,"imports":[{"path":"app/javascript/elements.js","kind":"import-statement","original":"../elements"},{"path":"app/javascript/drivers/form.js","kind":"import-statement","original":"./form"},{"path":"app/javascript/drivers/frame.js","kind":"import-statement","original":"./frame"},{"path":"app/javascript/drivers/method.js","kind":"import-statement","original":"./method"},{"path":"app/javascript/drivers/window.js","kind":"import-statement","original":"./window"}],"format":"esm"},"app/javascript/logger.js":{"bytes":729,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/uuids.js":{"bytes":202,"imports":[],"format":"esm"},"app/javascript/index.js":{"bytes":3551,"imports":[{"path":"app/javascript/turbo.js","kind":"import-statement","original":"./turbo"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/confirmation.js","kind":"import-statement","original":"./confirmation"},{"path":"app/javascript/delegates.js","kind":"import-statement","original":"./delegates"},{"path":"app/javascript/drivers/index.js","kind":"import-statement","original":"./drivers"},{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/elements.js","kind":"import-statement","original":"./elements"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"app/javascript/logger.js","kind":"import-statement","original":"./logger"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"./urls"},{"path":"app/javascript/uuids.js","kind":"import-statement","original":"./uuids"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"app/assets/builds/@turbo-boost/commands.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":37483},"app/assets/builds/@turbo-boost/commands.js":{"imports":[],"exports":["default"],"entryPoint":"app/javascript/index.js","inputs":{"app/javascript/meta.js":{"bytesInOutput":252},"app/javascript/events.js":{"bytesInOutput":500},"app/javascript/state/observable.js":{"bytesInOutput":405},"app/javascript/state/index.js":{"bytesInOutput":762},"app/javascript/renderer.js":{"bytesInOutput":263},"app/javascript/activity.js":{"bytesInOutput":173},"app/javascript/lifecycle.js":{"bytesInOutput":294},"app/javascript/turbo.js":{"bytesInOutput":1024},"app/javascript/schema.js":{"bytesInOutput":166},"app/javascript/confirmation.js":{"bytesInOutput":424},"app/javascript/delegates.js":{"bytesInOutput":473},"app/javascript/elements.js":{"bytesInOutput":782},"app/javascript/drivers/form.js":{"bytesInOutput":187},"app/javascript/urls.js":{"bytesInOutput":166},"app/javascript/drivers/frame.js":{"bytesInOutput":96},"app/javascript/drivers/method.js":{"bytesInOutput":130},"app/javascript/drivers/window.js":{"bytesInOutput":1329},"app/javascript/drivers/index.js":{"bytesInOutput":991},"app/javascript/logger.js":{"bytesInOutput":399},"app/javascript/uuids.js":{"bytesInOutput":154},"app/javascript/index.js":{"bytesInOutput":1684}},"bytes":11151}}}
|
Binary file
|
data/app/javascript/activity.js
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
const active = {}
|
2
2
|
|
3
|
-
function add
|
3
|
+
function add(payload) {
|
4
4
|
active[payload.id] = payload
|
5
5
|
}
|
6
6
|
|
7
|
-
function remove
|
7
|
+
function remove(id) {
|
8
8
|
delete active[id]
|
9
9
|
}
|
10
10
|
|
11
11
|
export default {
|
12
12
|
add,
|
13
13
|
remove,
|
14
|
-
get commands
|
14
|
+
get commands() {
|
15
15
|
return [...Object.values(active)]
|
16
16
|
},
|
17
|
-
get length
|
17
|
+
get length() {
|
18
18
|
return Object.keys(active).length
|
19
19
|
}
|
20
20
|
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { commandEvents } from './events'
|
2
|
+
import schema from './schema'
|
3
|
+
|
4
|
+
const confirmation = {
|
5
|
+
method: message => Promise.resolve(confirm(message))
|
6
|
+
}
|
7
|
+
|
8
|
+
const isTurboMethod = event => event.detail.driver === 'method'
|
9
|
+
|
10
|
+
const isTurboForm = event => {
|
11
|
+
if (event.detail.driver !== 'form') return false
|
12
|
+
|
13
|
+
const element = event.target
|
14
|
+
const frame = element.closest('turbo-frame')
|
15
|
+
const target = element.closest(`[${schema.frameAttribute}]`)
|
16
|
+
return !!(frame || target)
|
17
|
+
}
|
18
|
+
|
19
|
+
const shouldDelegate = event => isTurboMethod(event) || isTurboForm(event)
|
20
|
+
|
21
|
+
document.addEventListener(commandEvents.start, async event => {
|
22
|
+
const message = event.target.getAttribute(schema.confirmAttribute)
|
23
|
+
if (!message) return
|
24
|
+
|
25
|
+
event.detail.confirmation = true
|
26
|
+
|
27
|
+
if (shouldDelegate(event)) return // delegate confirmation handling to Turbo
|
28
|
+
|
29
|
+
const proceed = await confirmation.method(message)
|
30
|
+
if (!proceed) event.preventDefault()
|
31
|
+
})
|
32
|
+
|
33
|
+
export default confirmation
|
data/app/javascript/delegates.js
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
let events = []
|
2
2
|
let eventListener
|
3
3
|
|
4
|
-
function register
|
4
|
+
function register(eventName, selectors) {
|
5
5
|
const match = events.find(evt => evt.name === eventName)
|
6
6
|
if (match) events.splice(events.indexOf(match), 1)
|
7
7
|
events = [{ name: eventName, selectors }, ...events]
|
8
|
+
|
9
|
+
document.removeEventListener(eventName, eventListener, true)
|
8
10
|
document.addEventListener(eventName, eventListener, true)
|
11
|
+
|
9
12
|
return { ...events.find(evt => evt.name === eventName) }
|
10
13
|
}
|
11
14
|
|
12
|
-
function getRegisteredEventForElement
|
15
|
+
function getRegisteredEventForElement(element) {
|
13
16
|
return events.find(evt =>
|
14
|
-
evt.selectors.find(selector =>
|
15
|
-
Array.from(document.querySelectorAll(selector)).find(el => el === element)
|
16
|
-
)
|
17
|
+
evt.selectors.find(selector => Array.from(document.querySelectorAll(selector)).find(el => el === element))
|
17
18
|
)
|
18
19
|
}
|
19
20
|
|
20
|
-
function isRegisteredForElement
|
21
|
+
function isRegisteredForElement(eventName, element) {
|
21
22
|
const evt = getRegisteredEventForElement(element)
|
22
23
|
return evt && evt.name === eventName
|
23
24
|
}
|
@@ -25,10 +26,10 @@ function isRegisteredForElement (eventName, element) {
|
|
25
26
|
export default {
|
26
27
|
register,
|
27
28
|
isRegisteredForElement,
|
28
|
-
get events
|
29
|
+
get events() {
|
29
30
|
return [...events]
|
30
31
|
},
|
31
|
-
set handler
|
32
|
+
set handler(fn) {
|
32
33
|
eventListener = fn
|
33
34
|
}
|
34
35
|
}
|
@@ -4,14 +4,12 @@ import frameDriver from './frame'
|
|
4
4
|
import methodDriver from './method'
|
5
5
|
import windowDriver from './window'
|
6
6
|
|
7
|
-
function src
|
7
|
+
function src(element, frame) {
|
8
8
|
frame = frame || { dataset: {} }
|
9
|
-
return
|
10
|
-
element.href || frame.src || frame.dataset.turboBoostSrc || location.href
|
11
|
-
)
|
9
|
+
return element.href || frame.src || frame.dataset.turboBoostSrc || location.href
|
12
10
|
}
|
13
11
|
|
14
|
-
function find
|
12
|
+
function find(element) {
|
15
13
|
let frame = elements.findClosestFrameWithSource(element)
|
16
14
|
|
17
15
|
const { turboFrame, turboMethod } = element.dataset
|
@@ -50,8 +48,7 @@ function find (element) {
|
|
50
48
|
if ((!turboFrame || turboFrame === '_self') && frame)
|
51
49
|
return {
|
52
50
|
name: 'frame',
|
53
|
-
reason:
|
54
|
-
'element does NOT target a frame or targets _self and is contained by a frame',
|
51
|
+
reason: 'element does NOT target a frame or targets _self and is contained by a frame',
|
55
52
|
frame,
|
56
53
|
src: src(element, frame),
|
57
54
|
invokeCommand: frameDriver.invokeCommand
|
@@ -5,40 +5,37 @@ import lifecycle from '../lifecycle'
|
|
5
5
|
import urls from '../urls'
|
6
6
|
import renderer from '../renderer'
|
7
7
|
|
8
|
-
function aborted
|
8
|
+
function aborted(event) {
|
9
9
|
const xhr = event.target
|
10
10
|
dispatch(lifecycle.events.abort, document, {
|
11
11
|
detail: { ...event.detail, xhr }
|
12
12
|
})
|
13
13
|
}
|
14
14
|
|
15
|
-
function errored
|
15
|
+
function errored(event) {
|
16
16
|
const xhr = event.target
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
const append =
|
19
|
+
xhr.getResponseHeader('TurboBoost') === 'Append' ||
|
20
|
+
xhr.getResponseHeader('Content-Type').startsWith('text/vnd.turbo-boost.html')
|
21
|
+
append ? renderer.append(xhr.responseText) : renderer.replaceDocument(xhr.responseText)
|
21
22
|
|
22
23
|
const error = `Server returned a ${xhr.status} status code! TurboBoost Commands require 2XX-3XX status codes.`
|
23
24
|
|
24
|
-
dispatch(
|
25
|
-
lifecycle.events.clientError,
|
26
|
-
document,
|
27
|
-
{ detail: { ...event.detail, error, xhr } },
|
28
|
-
true
|
29
|
-
)
|
25
|
+
dispatch(lifecycle.events.clientError, document, { detail: { ...event.detail, error, xhr } }, true)
|
30
26
|
}
|
31
27
|
|
32
|
-
function loaded
|
28
|
+
function loaded(event) {
|
33
29
|
const xhr = event.target
|
34
30
|
if (xhr.status < 200 || xhr.status > 399) return errored(event)
|
35
31
|
const content = xhr.responseText
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
const append =
|
33
|
+
xhr.getResponseHeader('TurboBoost') === 'Append' ||
|
34
|
+
xhr.getResponseHeader('Content-Type').startsWith('text/vnd.turbo-boost.html')
|
35
|
+
append ? renderer.append(xhr.responseText) : renderer.replaceDocument(xhr.responseText)
|
39
36
|
}
|
40
37
|
|
41
|
-
function invokeCommand
|
38
|
+
function invokeCommand(payload) {
|
42
39
|
const src = payload.src
|
43
40
|
payload = { ...payload }
|
44
41
|
delete payload.src
|
@@ -46,16 +43,10 @@ function invokeCommand (payload) {
|
|
46
43
|
try {
|
47
44
|
const xhr = new XMLHttpRequest()
|
48
45
|
xhr.open('GET', urls.build(src, payload), true)
|
49
|
-
xhr.setRequestHeader(
|
50
|
-
'Accept',
|
51
|
-
'text/vnd.turbo-boost.html, text/html, application/xhtml+xml'
|
52
|
-
)
|
46
|
+
xhr.setRequestHeader('Accept', 'text/vnd.turbo-boost.html, text/html, application/xhtml+xml')
|
53
47
|
xhr.setRequestHeader('TurboBoost-Token', meta.token)
|
54
48
|
state.payloadChunks.forEach((chunk, i) =>
|
55
|
-
xhr.setRequestHeader(
|
56
|
-
`TurboBoost-State-${i.toString().padStart(4, '0')}`,
|
57
|
-
chunk
|
58
|
-
)
|
49
|
+
xhr.setRequestHeader(`TurboBoost-State-${i.toString().padStart(4, '0')}`, chunk)
|
59
50
|
)
|
60
51
|
|
61
52
|
xhr.addEventListener('abort', aborted)
|
data/app/javascript/elements.js
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
import schema from './schema'
|
2
2
|
import lifecycle from './lifecycle'
|
3
3
|
|
4
|
-
function findClosestCommand
|
4
|
+
function findClosestCommand(element) {
|
5
5
|
return element.closest(`[${schema.commandAttribute}]`)
|
6
6
|
}
|
7
7
|
|
8
|
-
function findClosestFrameWithSource
|
8
|
+
function findClosestFrameWithSource(element) {
|
9
9
|
return (
|
10
10
|
element.closest('turbo-frame[src]') ||
|
11
11
|
element.closest('turbo-frame[data-turbo-frame-src]') ||
|
@@ -13,12 +13,10 @@ function findClosestFrameWithSource (element) {
|
|
13
13
|
)
|
14
14
|
}
|
15
15
|
|
16
|
-
function assignElementValueToPayload
|
17
|
-
if (element.tagName.toLowerCase() !== 'select')
|
18
|
-
return (payload.value = element.value || null)
|
16
|
+
function assignElementValueToPayload(element, payload = {}) {
|
17
|
+
if (element.tagName.toLowerCase() !== 'select') return (payload.value = element.value || null)
|
19
18
|
|
20
|
-
if (!element.multiple)
|
21
|
-
return (payload.value = element.options[element.selectedIndex].value)
|
19
|
+
if (!element.multiple) return (payload.value = element.options[element.selectedIndex].value)
|
22
20
|
|
23
21
|
payload.values = Array.from(element.options).reduce((memo, option) => {
|
24
22
|
if (option.selected) memo.push(option.value)
|
@@ -26,7 +24,7 @@ function assignElementValueToPayload (element, payload = {}) {
|
|
26
24
|
}, [])
|
27
25
|
}
|
28
26
|
|
29
|
-
function buildAttributePayload
|
27
|
+
function buildAttributePayload(element) {
|
30
28
|
const payload = Array.from(element.attributes).reduce((memo, attr) => {
|
31
29
|
let value = attr.value
|
32
30
|
memo[attr.name] = value
|
data/app/javascript/events.js
CHANGED
@@ -14,11 +14,13 @@ export const stateEvents = {
|
|
14
14
|
|
15
15
|
export const allEvents = { ...commandEvents, ...stateEvents }
|
16
16
|
|
17
|
-
export function dispatch
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
export function dispatch(name, target, options = {}) {
|
18
|
+
return new Promise(resolve => {
|
19
|
+
options = options || {}
|
20
|
+
options.detail = options.detail || {}
|
21
|
+
target = target || document
|
22
|
+
const evt = new CustomEvent(name, { ...options, bubbles: true })
|
23
|
+
target.dispatchEvent(evt)
|
24
|
+
resolve(evt)
|
25
|
+
})
|
24
26
|
}
|
data/app/javascript/index.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
import '@turbo-boost/streams'
|
2
1
|
import './turbo'
|
3
2
|
import schema from './schema'
|
4
3
|
import { dispatch, commandEvents, stateEvents } from './events'
|
5
4
|
import activity from './activity'
|
5
|
+
import confirmation from './confirmation'
|
6
6
|
import delegates from './delegates'
|
7
7
|
import drivers from './drivers'
|
8
8
|
import meta from './meta'
|
@@ -13,7 +13,7 @@ import state from './state'
|
|
13
13
|
import urls from './urls'
|
14
14
|
import uuids from './uuids'
|
15
15
|
|
16
|
-
function buildCommandPayload
|
16
|
+
function buildCommandPayload(id, element) {
|
17
17
|
return {
|
18
18
|
id, // uniquely identifies the command
|
19
19
|
name: element.getAttribute(schema.commandAttribute),
|
@@ -23,7 +23,7 @@ function buildCommandPayload (id, element) {
|
|
23
23
|
}
|
24
24
|
}
|
25
25
|
|
26
|
-
function invokeCommand
|
26
|
+
async function invokeCommand(event) {
|
27
27
|
let element
|
28
28
|
let payload = {}
|
29
29
|
|
@@ -41,12 +41,12 @@ function invokeCommand (event) {
|
|
41
41
|
src: driver.src
|
42
42
|
}
|
43
43
|
|
44
|
-
const startEvent = dispatch(commandEvents.start, element, {
|
44
|
+
const startEvent = await dispatch(commandEvents.start, element, {
|
45
45
|
cancelable: true,
|
46
46
|
detail: payload
|
47
47
|
})
|
48
48
|
|
49
|
-
if (startEvent.defaultPrevented)
|
49
|
+
if (startEvent.defaultPrevented || (startEvent.detail.confirmation && event.defaultPrevented))
|
50
50
|
return dispatch(commandEvents.abort, element, {
|
51
51
|
detail: {
|
52
52
|
message: `An event handler for '${commandEvents.start}' prevented default behavior and blocked command invocation!`,
|
@@ -87,16 +87,6 @@ function invokeCommand (event) {
|
|
87
87
|
}
|
88
88
|
}
|
89
89
|
|
90
|
-
// wire things up and setup defaults for event delegation
|
91
|
-
delegates.handler = invokeCommand
|
92
|
-
delegates.register('click', [`[${schema.commandAttribute}]`])
|
93
|
-
delegates.register('submit', [`form[${schema.commandAttribute}]`])
|
94
|
-
delegates.register('change', [
|
95
|
-
`input[${schema.commandAttribute}]`,
|
96
|
-
`select[${schema.commandAttribute}]`,
|
97
|
-
`textarea[${schema.commandAttribute}]`
|
98
|
-
])
|
99
|
-
|
100
90
|
self.TurboBoost = self.TurboBoost || {}
|
101
91
|
|
102
92
|
self.TurboBoost = {
|
@@ -104,22 +94,35 @@ self.TurboBoost = {
|
|
104
94
|
|
105
95
|
stateEvents,
|
106
96
|
|
107
|
-
get state
|
97
|
+
get state() {
|
108
98
|
return state.current
|
109
99
|
},
|
110
100
|
|
111
|
-
get stateDelta
|
101
|
+
get stateDelta() {
|
112
102
|
return state.delta
|
113
103
|
}
|
114
104
|
}
|
115
105
|
|
116
|
-
self.TurboBoost.Commands
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
106
|
+
if (!self.TurboBoost.Commands) {
|
107
|
+
// wire things up and setup defaults for event delegation
|
108
|
+
delegates.handler = invokeCommand
|
109
|
+
delegates.register('click', [`[${schema.commandAttribute}]`])
|
110
|
+
delegates.register('submit', [`form[${schema.commandAttribute}]`])
|
111
|
+
delegates.register('change', [
|
112
|
+
`input[${schema.commandAttribute}]`,
|
113
|
+
`select[${schema.commandAttribute}]`,
|
114
|
+
`textarea[${schema.commandAttribute}]`
|
115
|
+
])
|
116
|
+
|
117
|
+
self.TurboBoost.Commands = {
|
118
|
+
confirmation,
|
119
|
+
logger,
|
120
|
+
schema,
|
121
|
+
events: commandEvents,
|
122
|
+
registerEventDelegate: delegates.register,
|
123
|
+
get eventDelegates() {
|
124
|
+
return delegates.events
|
125
|
+
}
|
123
126
|
}
|
124
127
|
}
|
125
128
|
|
data/app/javascript/lifecycle.js
CHANGED
@@ -1,23 +1,15 @@
|
|
1
1
|
import activity from './activity'
|
2
2
|
import { dispatch, commandEvents } from './events'
|
3
3
|
|
4
|
-
function finish
|
4
|
+
function finish(event) {
|
5
5
|
event.detail.endedAt = Date.now()
|
6
6
|
event.detail.milliseconds = event.detail.endedAt - event.detail.startedAt
|
7
|
-
setTimeout(
|
8
|
-
() =>
|
9
|
-
dispatch(commandEvents.finish, event.target, { detail: event.detail }),
|
10
|
-
25
|
11
|
-
)
|
7
|
+
setTimeout(() => dispatch(commandEvents.finish, event.target, { detail: event.detail }), 25)
|
12
8
|
}
|
13
9
|
|
14
10
|
// TODO: forward source event to finish (error or success)
|
15
11
|
addEventListener(commandEvents.serverError, finish)
|
16
12
|
addEventListener(commandEvents.success, finish)
|
17
|
-
addEventListener(
|
18
|
-
commandEvents.finish,
|
19
|
-
event => activity.remove(event.detail.id),
|
20
|
-
true
|
21
|
-
)
|
13
|
+
addEventListener(commandEvents.finish, event => activity.remove(event.detail.id), true)
|
22
14
|
|
23
15
|
export default { events: commandEvents }
|
data/app/javascript/logger.js
CHANGED
@@ -13,17 +13,17 @@ const logLevels = {
|
|
13
13
|
Object.values(events).forEach(name => {
|
14
14
|
addEventListener(name, event => {
|
15
15
|
if (logLevels[currentLevel].includes(event.type)) {
|
16
|
-
const
|
17
|
-
console[
|
16
|
+
const { target, detail } = event
|
17
|
+
console[currentLevel](event.type, { target, detail })
|
18
18
|
}
|
19
19
|
})
|
20
20
|
})
|
21
21
|
|
22
22
|
export default {
|
23
|
-
get level
|
23
|
+
get level() {
|
24
24
|
return currentLevel
|
25
25
|
},
|
26
|
-
set level
|
26
|
+
set level(value) {
|
27
27
|
if (!Object.keys(logLevels).includes(value)) value = 'unknown'
|
28
28
|
return (currentLevel = value)
|
29
29
|
}
|
data/app/javascript/meta.js
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
class Meta {
|
2
|
-
get element
|
2
|
+
get element() {
|
3
3
|
return document.querySelector('meta[name="turbo-boost"]')
|
4
4
|
}
|
5
5
|
|
6
|
-
get token
|
6
|
+
get token() {
|
7
7
|
return this.element.getAttribute('content')
|
8
8
|
}
|
9
9
|
|
10
|
-
get busy
|
10
|
+
get busy() {
|
11
11
|
return this.element.dataset.busy === 'true'
|
12
12
|
}
|
13
13
|
|
14
|
-
set busy
|
14
|
+
set busy(value) {
|
15
15
|
return (this.element.dataset.busy = !!value)
|
16
16
|
}
|
17
17
|
}
|
data/app/javascript/renderer.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
function replaceDocument
|
1
|
+
function replaceDocument(content) {
|
2
2
|
const head = '<html'
|
3
3
|
const tail = '</html'
|
4
4
|
const headIndex = content.indexOf(head)
|
@@ -9,7 +9,7 @@ function replaceDocument (content) {
|
|
9
9
|
}
|
10
10
|
}
|
11
11
|
|
12
|
-
function append
|
12
|
+
function append(content) {
|
13
13
|
document.body.insertAdjacentHTML('beforeend', content)
|
14
14
|
}
|
15
15
|
|
data/app/javascript/schema.js
CHANGED
@@ -5,7 +5,7 @@ import { dispatch, commandEvents, stateEvents } from '../events'
|
|
5
5
|
let loadedState, currentState, changedState
|
6
6
|
let loadStateTimeout
|
7
7
|
|
8
|
-
function loadState
|
8
|
+
function loadState() {
|
9
9
|
if (!meta.element) return loadStateLater()
|
10
10
|
const json = atob(meta.element.dataset.state)
|
11
11
|
changedState = {}
|
@@ -19,7 +19,7 @@ function loadState () {
|
|
19
19
|
)
|
20
20
|
}
|
21
21
|
|
22
|
-
function loadStateLater
|
22
|
+
function loadStateLater() {
|
23
23
|
clearTimeout(loadStateTimeout)
|
24
24
|
loadStateTimeout = setTimeout(loadState, 10)
|
25
25
|
}
|
@@ -43,11 +43,11 @@ addEventListener(stateEvents.stateChange, event => {
|
|
43
43
|
export default {
|
44
44
|
events: stateEvents,
|
45
45
|
|
46
|
-
get current
|
46
|
+
get current() {
|
47
47
|
return currentState
|
48
48
|
},
|
49
49
|
|
50
|
-
get delta
|
50
|
+
get delta() {
|
51
51
|
return changedState
|
52
52
|
},
|
53
53
|
|
@@ -56,7 +56,7 @@ export default {
|
|
56
56
|
// A Base64 character is an 8-bit-padded ASCII character... or 1 byte
|
57
57
|
//
|
58
58
|
// SEE: lib/state.rb - for info on how `state` is serialized/deserialized
|
59
|
-
get payloadChunks
|
59
|
+
get payloadChunks() {
|
60
60
|
return btoa(JSON.stringify(changedState)).match(/.{1,2000}/g)
|
61
61
|
}
|
62
62
|
}
|
@@ -3,17 +3,17 @@ import { dispatch, stateEvents as events } from '../events'
|
|
3
3
|
|
4
4
|
let head
|
5
5
|
|
6
|
-
function observable
|
6
|
+
function observable(object, parent = null) {
|
7
7
|
if (!object || typeof object !== 'object') return object
|
8
8
|
|
9
9
|
const proxy = new Proxy(object, {
|
10
|
-
deleteProperty
|
10
|
+
deleteProperty(target, key) {
|
11
11
|
delete target[key]
|
12
12
|
dispatch(events.stateChange, meta.element, { detail: { state: head } })
|
13
13
|
return true
|
14
14
|
},
|
15
15
|
|
16
|
-
set
|
16
|
+
set(target, key, value, receiver) {
|
17
17
|
target[key] = observable(value, this)
|
18
18
|
dispatch(events.stateChange, meta.element, { detail: { state: head } })
|
19
19
|
return true
|
@@ -23,8 +23,7 @@ function observable (object, parent = null) {
|
|
23
23
|
if (Array.isArray(object)) {
|
24
24
|
object.forEach((value, index) => (object[index] = observable(value, proxy)))
|
25
25
|
} else if (typeof object === 'object') {
|
26
|
-
for (const [key, value] of Object.entries(object))
|
27
|
-
object[key] = observable(value, proxy)
|
26
|
+
for (const [key, value] of Object.entries(object)) object[key] = observable(value, proxy)
|
28
27
|
}
|
29
28
|
|
30
29
|
if (!parent) head = proxy
|
data/app/javascript/turbo.js
CHANGED
@@ -13,22 +13,15 @@ addEventListener('turbo:before-fetch-request', event => {
|
|
13
13
|
|
14
14
|
// command invoked and busy
|
15
15
|
if (meta.busy) {
|
16
|
-
let acceptHeaders = [
|
17
|
-
|
18
|
-
fetchOptions.headers['Accept']
|
19
|
-
]
|
20
|
-
acceptHeaders = acceptHeaders
|
21
|
-
.filter(entry => entry && entry.trim().length > 0)
|
22
|
-
.join(', ')
|
16
|
+
let acceptHeaders = ['text/vnd.turbo-boost.html', fetchOptions.headers['Accept']]
|
17
|
+
acceptHeaders = acceptHeaders.filter(entry => entry && entry.trim().length > 0).join(', ')
|
23
18
|
fetchOptions.headers['Accept'] = acceptHeaders
|
24
19
|
fetchOptions.headers['TurboBoost-Token'] = meta.token
|
25
20
|
}
|
26
21
|
|
27
22
|
// always send state
|
28
23
|
state.payloadChunks.forEach((chunk, i) => {
|
29
|
-
fetchOptions.headers[
|
30
|
-
`TurboBoost-State-${i.toString().padStart(4, '0')}`
|
31
|
-
] = chunk
|
24
|
+
fetchOptions.headers[`TurboBoost-State-${i.toString().padStart(4, '0')}`] = chunk
|
32
25
|
})
|
33
26
|
})
|
34
27
|
|
@@ -42,12 +35,7 @@ addEventListener('turbo:before-fetch-response', event => {
|
|
42
35
|
if (response.header('TurboBoost')) {
|
43
36
|
if (response.statusCode < 200 || response.statusCode > 399) {
|
44
37
|
const error = `Server returned a ${response.statusCode} status code! TurboBoost Commands require 2XX-3XX status codes.`
|
45
|
-
dispatch(
|
46
|
-
lifecycle.events.clientError,
|
47
|
-
document,
|
48
|
-
{ detail: { ...event.detail, error } },
|
49
|
-
true
|
50
|
-
)
|
38
|
+
dispatch(lifecycle.events.clientError, document, { detail: { ...event.detail, error } }, true)
|
51
39
|
}
|
52
40
|
|
53
41
|
if (response.header('TurboBoost') === 'Append') {
|
@@ -60,7 +48,6 @@ addEventListener('turbo:before-fetch-response', event => {
|
|
60
48
|
// fires when a frame element is navigated and finishes loading
|
61
49
|
addEventListener('turbo:frame-load', event => {
|
62
50
|
const frame = event.target.closest('turbo-frame')
|
63
|
-
frame.dataset.turboBoostSrc =
|
64
|
-
frameSources[frame.id] || frame.src || frame.dataset.turboBoostSrc
|
51
|
+
frame.dataset.turboBoostSrc = frameSources[frame.id] || frame.src || frame.dataset.turboBoostSrc
|
65
52
|
delete frameSources[frame.id]
|
66
53
|
})
|
data/app/javascript/urls.js
CHANGED