turbo_boost-commands 0.0.10 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of turbo_boost-commands might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +167 -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 +6 -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 +10 -9
- data/app/javascript/lifecycle.js +4 -12
- 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 +8 -20
- 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":858,"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":3478,"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":37288},"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":436},"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":1656}},"bytes":11086}}}
|
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,7 +1,7 @@
|
|
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]
|
@@ -9,15 +9,13 @@ function register (eventName, selectors) {
|
|
9
9
|
return { ...events.find(evt => evt.name === eventName) }
|
10
10
|
}
|
11
11
|
|
12
|
-
function getRegisteredEventForElement
|
12
|
+
function getRegisteredEventForElement(element) {
|
13
13
|
return events.find(evt =>
|
14
|
-
evt.selectors.find(selector =>
|
15
|
-
Array.from(document.querySelectorAll(selector)).find(el => el === element)
|
16
|
-
)
|
14
|
+
evt.selectors.find(selector => Array.from(document.querySelectorAll(selector)).find(el => el === element))
|
17
15
|
)
|
18
16
|
}
|
19
17
|
|
20
|
-
function isRegisteredForElement
|
18
|
+
function isRegisteredForElement(eventName, element) {
|
21
19
|
const evt = getRegisteredEventForElement(element)
|
22
20
|
return evt && evt.name === eventName
|
23
21
|
}
|
@@ -25,10 +23,10 @@ function isRegisteredForElement (eventName, element) {
|
|
25
23
|
export default {
|
26
24
|
register,
|
27
25
|
isRegisteredForElement,
|
28
|
-
get events
|
26
|
+
get events() {
|
29
27
|
return [...events]
|
30
28
|
},
|
31
|
-
set handler
|
29
|
+
set handler(fn) {
|
32
30
|
eventListener = fn
|
33
31
|
}
|
34
32
|
}
|
@@ -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,17 +13,17 @@ 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),
|
20
20
|
elementId: element.id.length > 0 ? element.id : null,
|
21
21
|
elementAttributes: elements.buildAttributePayload(element),
|
22
|
-
startedAt:
|
22
|
+
startedAt: Date.now()
|
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!`,
|
@@ -104,21 +104,22 @@ self.TurboBoost = {
|
|
104
104
|
|
105
105
|
stateEvents,
|
106
106
|
|
107
|
-
get state
|
107
|
+
get state() {
|
108
108
|
return state.current
|
109
109
|
},
|
110
110
|
|
111
|
-
get stateDelta
|
111
|
+
get stateDelta() {
|
112
112
|
return state.delta
|
113
113
|
}
|
114
114
|
}
|
115
115
|
|
116
116
|
self.TurboBoost.Commands = {
|
117
|
+
confirmation,
|
117
118
|
logger,
|
118
119
|
schema,
|
119
120
|
events: commandEvents,
|
120
121
|
registerEventDelegate: delegates.register,
|
121
|
-
get eventDelegates
|
122
|
+
get eventDelegates() {
|
122
123
|
return delegates.events
|
123
124
|
}
|
124
125
|
}
|
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
|
5
|
-
event.detail.endedAt =
|
4
|
+
function finish(event) {
|
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
data/app/javascript/uuids.js
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
function v4
|
1
|
+
function v4() {
|
2
2
|
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
|
3
|
-
(
|
4
|
-
c ^
|
5
|
-
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
6
|
-
).toString(16)
|
3
|
+
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
7
4
|
)
|
8
5
|
}
|
9
6
|
|
@@ -21,7 +21,7 @@ class TurboBoost::Commands::AttributeSet
|
|
21
21
|
name.delete_prefix!("#{prefix}_") unless prefix.blank?
|
22
22
|
|
23
23
|
# type casting
|
24
|
-
value = value.to_i if value.is_a?(String) && value.match?(/\A
|
24
|
+
value = value.to_i if value.is_a?(String) && value.match?(/\A-?\d+\z/)
|
25
25
|
value = value == "true" if value.is_a?(String) && value.match?(/\A(true|false)\z/i)
|
26
26
|
|
27
27
|
instance_variable_set "@#{name}", value
|
@@ -19,7 +19,7 @@ module TurboBoost::Commands
|
|
19
19
|
class Engine < ::Rails::Engine
|
20
20
|
config.turbo_boost_commands = ActiveSupport::OrderedOptions.new
|
21
21
|
config.turbo_boost_commands[:max_cookie_size] = ActionDispatch::Cookies::MAX_COOKIE_SIZE / 3
|
22
|
-
config.turbo_boost_commands[:validate_client_token] =
|
22
|
+
config.turbo_boost_commands[:validate_client_token] = false
|
23
23
|
|
24
24
|
# must opt-in to state overrides
|
25
25
|
config.turbo_boost_commands[:apply_client_state_overrides] = false
|