turbo_reflex 0.0.1 → 0.0.2
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/CODE_OF_CONDUCT.md +128 -0
- data/Gemfile +2 -5
- data/Gemfile.lock +128 -101
- data/README.md +469 -14
- data/Rakefile +15 -1
- data/app/assets/builds/turbo_reflex.min.js +2 -0
- data/app/assets/builds/turbo_reflex.min.js.map +7 -0
- data/app/assets/images/turbo-reflex-logo-dark.webp +0 -0
- data/app/assets/images/turbo-reflex-logo-light.webp +0 -0
- data/app/assets/images/turbo-reflex-mark.webp +0 -0
- data/app/controllers/concerns/turbo_reflex/controller.rb +132 -0
- data/app/javascript/elements.js +87 -0
- data/app/javascript/event_registry.js +34 -0
- data/app/javascript/frame_sources.js +28 -0
- data/app/javascript/lifecycle_events.js +24 -0
- data/app/javascript/security.js +7 -0
- data/app/javascript/turbo_reflex.js +111 -0
- data/bin/loc +1 -1
- data/lib/turbo_reflex/base.rb +32 -0
- data/lib/turbo_reflex/engine.rb +9 -0
- data/lib/turbo_reflex/sanitizer.rb +20 -0
- data/lib/turbo_reflex/version.rb +3 -1
- data/lib/turbo_reflex.rb +2 -5
- data/package.json +4 -4
- data/turbo_reflex.gemspec +30 -19
- data/yarn.lock +167 -157
- metadata +177 -10
- data/app/assets/builds/turbo_reflex.js +0 -1
- data/app/assets/builds/turbo_reflex.js.map +0 -7
- data/lib/turbo_reflex/railtie.rb +0 -4
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TurboReflex::Controller
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
before_action :perform_turbo_reflex, if: -> { turbo_reflex_requested? && turbo_reflex_valid? }
|
8
|
+
after_action :append_turbo_reflex_turbo_streams, if: :turbo_reflex_performed?
|
9
|
+
after_action :assign_turbo_reflex_token
|
10
|
+
helper_method :turbo_reflex_meta_tag, :turbo_reflex_performed?, :turbo_reflex_requested?
|
11
|
+
end
|
12
|
+
|
13
|
+
def turbo_reflex_meta_tag
|
14
|
+
masked_token = turbo_reflex_message_verifier.generate(new_turbo_reflex_token)
|
15
|
+
options = {id: "turbo-reflex-token", name: "turbo-reflex-token", content: masked_token}
|
16
|
+
view_context.tag("meta", options).html_safe
|
17
|
+
end
|
18
|
+
|
19
|
+
def turbo_reflex_params
|
20
|
+
return ActionController::Parameters.new if params[:turbo_reflex].nil?
|
21
|
+
@turbo_reflex_params ||= begin
|
22
|
+
payload = JSON.parse(params[:turbo_reflex]).deep_transform_keys(&:underscore)
|
23
|
+
ActionController::Parameters.new(payload).permit!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def turbo_reflex_requested?
|
28
|
+
return false unless client_turbo_reflex_token.present?
|
29
|
+
return false unless turbo_reflex_params.present?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def turbo_reflex_element
|
34
|
+
return nil if turbo_reflex_params.blank?
|
35
|
+
@turbo_reflex_element ||= Struct
|
36
|
+
.new(*turbo_reflex_params[:element].keys.map { |key| key.to_s.parameterize.underscore.to_sym })
|
37
|
+
.new(*turbo_reflex_params[:element].values)
|
38
|
+
end
|
39
|
+
|
40
|
+
def turbo_reflex_name
|
41
|
+
return nil unless turbo_reflex_requested?
|
42
|
+
turbo_reflex_element.data_turbo_reflex
|
43
|
+
end
|
44
|
+
|
45
|
+
def turbo_reflex_class_name
|
46
|
+
return nil unless turbo_reflex_requested?
|
47
|
+
turbo_reflex_name.split("#").first
|
48
|
+
end
|
49
|
+
|
50
|
+
def turbo_reflex_method_name
|
51
|
+
return nil unless turbo_reflex_requested?
|
52
|
+
turbo_reflex_name.split("#").last
|
53
|
+
end
|
54
|
+
|
55
|
+
def turbo_reflex_class
|
56
|
+
@turbo_reflex_class ||= turbo_reflex_class_name&.safe_constantize
|
57
|
+
end
|
58
|
+
|
59
|
+
def turbo_reflex_instance
|
60
|
+
@turbo_reflex_instance ||= turbo_reflex_class&.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def turbo_reflex_valid?
|
64
|
+
return false if request.get? && client_turbo_reflex_token.blank?
|
65
|
+
return false unless valid_turbo_reflex_token?
|
66
|
+
return false unless turbo_reflex_instance.is_a?(TurboReflex::Base)
|
67
|
+
turbo_reflex_instance.respond_to? turbo_reflex_method_name
|
68
|
+
end
|
69
|
+
|
70
|
+
def turbo_reflex_performed?
|
71
|
+
!!@turbo_reflex_performed
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def perform_turbo_reflex
|
77
|
+
turbo_reflex_instance.public_send turbo_reflex_method_name
|
78
|
+
@turbo_reflex_performed = true
|
79
|
+
end
|
80
|
+
|
81
|
+
def append_turbo_reflex_turbo_streams
|
82
|
+
return unless turbo_reflex_performed?
|
83
|
+
return unless turbo_reflex_instance&.turbo_streams.present?
|
84
|
+
append_turbo_reflex_content turbo_reflex_instance.turbo_streams.map(&:to_s).join
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def turbo_reflex_message_verifier
|
90
|
+
ActiveSupport::MessageVerifier.new session.id.to_s, digest: "SHA256"
|
91
|
+
end
|
92
|
+
|
93
|
+
def client_turbo_reflex_token
|
94
|
+
(request.headers["Turbo-Reflex"] || turbo_reflex_params[:token]).to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
def new_turbo_reflex_token
|
98
|
+
@new_turbo_reflex_token ||= SecureRandom.urlsafe_base64(32)
|
99
|
+
end
|
100
|
+
|
101
|
+
def current_turbo_reflex_token
|
102
|
+
session[:turbo_reflex_token]
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_turbo_reflex_token?
|
106
|
+
return false unless turbo_reflex_message_verifier.valid_message?(client_turbo_reflex_token)
|
107
|
+
unmasked_token = turbo_reflex_message_verifier.verify(client_turbo_reflex_token)
|
108
|
+
unmasked_token == current_turbo_reflex_token
|
109
|
+
end
|
110
|
+
|
111
|
+
def assign_turbo_reflex_token
|
112
|
+
return unless turbo_reflex_requested? || client_turbo_reflex_token.blank?
|
113
|
+
session[:turbo_reflex_token] = new_turbo_reflex_token
|
114
|
+
append_turbo_reflex_content turbo_stream.replace("turbo-reflex-token", turbo_reflex_meta_tag)
|
115
|
+
end
|
116
|
+
|
117
|
+
def turbo_reflex_response_type
|
118
|
+
body = response.body.to_s.strip
|
119
|
+
return :stream if body.ends_with?("</turbo-stream>")
|
120
|
+
return :frame if body.ends_with?("</turbo-frame>")
|
121
|
+
:default
|
122
|
+
end
|
123
|
+
|
124
|
+
def append_turbo_reflex_content(content)
|
125
|
+
sanitized_content = TurboReflex::Sanitizer.instance.sanitize(content).html_safe
|
126
|
+
case turbo_reflex_response_type
|
127
|
+
when :stream then response.body << sanitized_content
|
128
|
+
when :frame then response.body.sub!("</turbo-frame>", "#{sanitized_content}</turbo-frame>")
|
129
|
+
when :default then response.body.sub!("</body>", "#{sanitized_content}</body>")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import LifecycleEvents from './lifecycle_events'
|
2
|
+
|
3
|
+
function findClosestReflex (element) {
|
4
|
+
return element.closest('[data-turbo-reflex]')
|
5
|
+
}
|
6
|
+
|
7
|
+
function findClosestFrame (element) {
|
8
|
+
return element.closest('turbo-frame')
|
9
|
+
}
|
10
|
+
|
11
|
+
function findFrameId (element) {
|
12
|
+
let id = element.dataset.turboReflexFrame || element.dataset.turboFrame
|
13
|
+
if (!id) {
|
14
|
+
const frame = findClosestFrame(element)
|
15
|
+
if (frame) id = frame.id
|
16
|
+
}
|
17
|
+
if (!id) {
|
18
|
+
console.error(
|
19
|
+
`The reflex element does not specify a frame!`,
|
20
|
+
`Please move the reflex element inside a <turbo-frame> or set the 'data-turbo-reflex-frame' or 'data-turbo-frame' attribute.`,
|
21
|
+
element
|
22
|
+
)
|
23
|
+
LifecycleEvents.dispatch(LifecycleEvents.missingFrameId, element, {
|
24
|
+
element
|
25
|
+
})
|
26
|
+
}
|
27
|
+
return id
|
28
|
+
}
|
29
|
+
|
30
|
+
function findFrame (id) {
|
31
|
+
const frame = document.getElementById(id)
|
32
|
+
if (!frame) {
|
33
|
+
console.error(`The frame '${id}' does not exist!`)
|
34
|
+
LifecycleEvents.dispatch(LifecycleEvents.missingFrame, document, { id })
|
35
|
+
}
|
36
|
+
return frame
|
37
|
+
}
|
38
|
+
|
39
|
+
function findFrameSrc (frame) {
|
40
|
+
const frameSrc = frame.dataset.turboReflexSrc || frame.src
|
41
|
+
if (!frameSrc) {
|
42
|
+
console.error(
|
43
|
+
`The the 'src' for <turbo-frame id='${frame.id}'> is unknown!`,
|
44
|
+
`TurboReflex uses 'src' to (re)render frame content after the reflex is invoked.`,
|
45
|
+
`Please set the 'src' or 'data-turbo-reflex-src' attribute on the <turbo-frame> element.`,
|
46
|
+
frame
|
47
|
+
)
|
48
|
+
LifecycleEvents.dispatch(LifecycleEvents.missingFrameSrc, frame, { frame })
|
49
|
+
}
|
50
|
+
return frameSrc
|
51
|
+
}
|
52
|
+
|
53
|
+
function assignElementValueToPayload (element, payload = {}) {
|
54
|
+
if (element.tagName.toLowerCase() !== 'select')
|
55
|
+
return (payload.value = element.value)
|
56
|
+
|
57
|
+
if (!element.multiple)
|
58
|
+
return (payload.value = element.options[element.selectedIndex].value)
|
59
|
+
|
60
|
+
payload.values = Array.from(element.options).reduce((memo, option) => {
|
61
|
+
if (option.selected) memo.push(option.value)
|
62
|
+
return memo
|
63
|
+
}, [])
|
64
|
+
}
|
65
|
+
|
66
|
+
function buildAttributePayload (element) {
|
67
|
+
const payload = Array.from(element.attributes).reduce((memo, attr) => {
|
68
|
+
memo[attr.name] = attr.value
|
69
|
+
return memo
|
70
|
+
}, {})
|
71
|
+
|
72
|
+
payload.tag = element.tagName
|
73
|
+
payload.checked = element.checked
|
74
|
+
payload.disabled = element.disabled
|
75
|
+
assignElementValueToPayload(element, payload)
|
76
|
+
|
77
|
+
return payload
|
78
|
+
}
|
79
|
+
|
80
|
+
export {
|
81
|
+
findClosestReflex,
|
82
|
+
findClosestFrame,
|
83
|
+
findFrameId,
|
84
|
+
findFrame,
|
85
|
+
findFrameSrc,
|
86
|
+
buildAttributePayload
|
87
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
const registeredEvents = {}
|
2
|
+
let eventListener
|
3
|
+
|
4
|
+
function registerEventListener (fn) {
|
5
|
+
eventListener = fn
|
6
|
+
}
|
7
|
+
|
8
|
+
function registerEvent (eventName, tagNames) {
|
9
|
+
registeredEvents[eventName] = tagNames
|
10
|
+
document.addEventListener(eventName, eventListener, true)
|
11
|
+
}
|
12
|
+
|
13
|
+
function isRegisteredEvent (eventName, tagName) {
|
14
|
+
tagName = tagName.toLowerCase()
|
15
|
+
return (
|
16
|
+
registeredEvents[eventName].includes(tagName) ||
|
17
|
+
(!Object.values(registeredEvents)
|
18
|
+
.flat()
|
19
|
+
.includes(tagName) &&
|
20
|
+
registeredEvents[eventName].includes('*'))
|
21
|
+
)
|
22
|
+
}
|
23
|
+
|
24
|
+
function logRegisteredEvents () {
|
25
|
+
console.log(registeredEvents)
|
26
|
+
}
|
27
|
+
|
28
|
+
export {
|
29
|
+
registerEventListener,
|
30
|
+
registerEvent,
|
31
|
+
registeredEvents,
|
32
|
+
isRegisteredEvent,
|
33
|
+
logRegisteredEvents
|
34
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import LifecycleEvents from './lifecycle_events'
|
2
|
+
const frameSources = {}
|
3
|
+
|
4
|
+
// fires after receiving a turbo HTTP response
|
5
|
+
addEventListener('turbo:before-fetch-response', event => {
|
6
|
+
const frame = event.target
|
7
|
+
frameSources[frame.id] = frame.src
|
8
|
+
|
9
|
+
const { turboReflexActive, turboReflexElementId } = frame.dataset
|
10
|
+
if (!turboReflexActive) return
|
11
|
+
|
12
|
+
const element = document.getElementById(turboReflexElementId)
|
13
|
+
delete frame.dataset.turboReflexActive
|
14
|
+
delete frame.dataset.turboReflexElementId
|
15
|
+
|
16
|
+
LifecycleEvents.dispatch(LifecycleEvents.finish, element || document, {
|
17
|
+
frame,
|
18
|
+
element: element || 'Unknown! Missing id attribute.'
|
19
|
+
})
|
20
|
+
})
|
21
|
+
|
22
|
+
// fires when a frame element is navigated and finishes loading
|
23
|
+
addEventListener('turbo:frame-load', event => {
|
24
|
+
const frame = event.target
|
25
|
+
frame.dataset.turboReflexSrc =
|
26
|
+
frameSources[frame.id] || frame.src || frame.dataset.turboReflexSrc
|
27
|
+
delete frameSources[frame.id]
|
28
|
+
})
|
@@ -0,0 +1,24 @@
|
|
1
|
+
const events = {
|
2
|
+
beforeStart: 'turbo-reflex:before-start',
|
3
|
+
start: 'turbo-reflex:start',
|
4
|
+
finish: 'turbo-reflex:finish',
|
5
|
+
error: 'turbo-reflex:error',
|
6
|
+
missingFrameId: 'turbo-reflex:missing-frame-id',
|
7
|
+
missingFrame: 'turbo-reflex:missing-frame',
|
8
|
+
missingFrameSrc: 'turbo-reflex:missing-frame-src'
|
9
|
+
}
|
10
|
+
|
11
|
+
function dispatch (name, target = document, detail = {}) {
|
12
|
+
const event = new CustomEvent(name, {
|
13
|
+
detail,
|
14
|
+
cancelable: true,
|
15
|
+
bubbles: true
|
16
|
+
})
|
17
|
+
target.dispatchEvent(event)
|
18
|
+
}
|
19
|
+
|
20
|
+
function logEventNames () {
|
21
|
+
Object.values(events).forEach(name => console.log(name))
|
22
|
+
}
|
23
|
+
|
24
|
+
export default { ...events, dispatch, logEventNames }
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import './frame_sources'
|
2
|
+
import Security from './security'
|
3
|
+
import LifecycleEvents from './lifecycle_events'
|
4
|
+
import {
|
5
|
+
findClosestReflex,
|
6
|
+
findClosestFrame,
|
7
|
+
findFrameId,
|
8
|
+
findFrame,
|
9
|
+
findFrameSrc,
|
10
|
+
buildAttributePayload
|
11
|
+
} from './elements'
|
12
|
+
import {
|
13
|
+
registerEventListener,
|
14
|
+
registerEvent,
|
15
|
+
registeredEvents,
|
16
|
+
isRegisteredEvent,
|
17
|
+
logRegisteredEvents
|
18
|
+
} from './event_registry'
|
19
|
+
|
20
|
+
// fires before making a turbo HTTP request
|
21
|
+
addEventListener('turbo:before-fetch-request', event => {
|
22
|
+
const frame = event.target
|
23
|
+
const { turboReflexActive } = frame.dataset
|
24
|
+
if (!turboReflexActive) return
|
25
|
+
const { fetchOptions } = event.detail
|
26
|
+
fetchOptions.headers['Turbo-Reflex'] = Security.token
|
27
|
+
})
|
28
|
+
|
29
|
+
function buildURL (urlString) {
|
30
|
+
const a = document.createElement('a')
|
31
|
+
a.href = urlString
|
32
|
+
return new URL(a)
|
33
|
+
}
|
34
|
+
|
35
|
+
function invokeFormReflex (form, payload = {}) {
|
36
|
+
payload.token = Security.token
|
37
|
+
const input = document.createElement('input')
|
38
|
+
input.type = 'hidden'
|
39
|
+
input.name = 'turbo_reflex'
|
40
|
+
input.value = JSON.stringify(payload)
|
41
|
+
form.appendChild(input)
|
42
|
+
}
|
43
|
+
|
44
|
+
function invokeReflex (event) {
|
45
|
+
let element, frameId, frame, frameSrc
|
46
|
+
try {
|
47
|
+
element = findClosestReflex(event.target)
|
48
|
+
if (!element) return
|
49
|
+
|
50
|
+
if (!isRegisteredEvent(event.type, element.tagName)) return
|
51
|
+
|
52
|
+
LifecycleEvents.dispatch(LifecycleEvents.beforeStart, element, { element })
|
53
|
+
|
54
|
+
frameId = findFrameId(element)
|
55
|
+
if (!frameId) return
|
56
|
+
|
57
|
+
frame = findFrame(frameId)
|
58
|
+
if (!frame) return
|
59
|
+
|
60
|
+
frameSrc = findFrameSrc(frame)
|
61
|
+
if (!frameSrc) return
|
62
|
+
|
63
|
+
const payload = {
|
64
|
+
frameId: frameId,
|
65
|
+
element: buildAttributePayload(element)
|
66
|
+
}
|
67
|
+
|
68
|
+
LifecycleEvents.dispatch(LifecycleEvents.start, element, {
|
69
|
+
element,
|
70
|
+
frameId,
|
71
|
+
frame,
|
72
|
+
frameSrc,
|
73
|
+
payload
|
74
|
+
})
|
75
|
+
frame.dataset.turboReflexActive = true
|
76
|
+
frame.dataset.turboReflexElementId = element.id
|
77
|
+
|
78
|
+
if (element.tagName.toLowerCase() === 'form')
|
79
|
+
return invokeFormReflex(element, payload)
|
80
|
+
|
81
|
+
event.preventDefault()
|
82
|
+
const frameURL = buildURL(frameSrc)
|
83
|
+
frameURL.searchParams.set('turbo_reflex', JSON.stringify(payload))
|
84
|
+
frame.src = frameURL.toString()
|
85
|
+
} catch (error) {
|
86
|
+
console.error(
|
87
|
+
`TurboReflex encountered an unexpected error!`,
|
88
|
+
{ element, frameId, frame, frameSrc, target: event.target },
|
89
|
+
error
|
90
|
+
)
|
91
|
+
LifecycleEvents.dispatch(LifecycleEvents.error, element || document, {
|
92
|
+
element,
|
93
|
+
frameId,
|
94
|
+
frame,
|
95
|
+
frameSrc,
|
96
|
+
error
|
97
|
+
})
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
// wire things up and setup default events
|
102
|
+
registerEventListener(invokeReflex)
|
103
|
+
registerEvent('change', ['input', 'select', 'textarea'])
|
104
|
+
registerEvent('submit', ['form'])
|
105
|
+
registerEvent('click', ['*'])
|
106
|
+
|
107
|
+
export default {
|
108
|
+
registerEvent,
|
109
|
+
logRegisteredEvents,
|
110
|
+
logLifecycleEventNames: LifecycleEvents.logEventNames
|
111
|
+
}
|
data/bin/loc
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TurboReflex::Base
|
4
|
+
attr_reader :controller, :turbo_streams
|
5
|
+
|
6
|
+
def initialize(controller)
|
7
|
+
@controller = controller
|
8
|
+
@turbo_streams = Set.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def params
|
12
|
+
controller.turbo_reflex_params
|
13
|
+
end
|
14
|
+
|
15
|
+
def element
|
16
|
+
@element ||= begin
|
17
|
+
keys = params[:element].keys.map { |key| key.to_s.parameterize.underscore.to_sym }
|
18
|
+
values = params[:element].values
|
19
|
+
|
20
|
+
unless keys.include? :value
|
21
|
+
keys << :value
|
22
|
+
values << nil
|
23
|
+
end
|
24
|
+
|
25
|
+
Struct.new(*keys).new(*values)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def turbo_stream
|
30
|
+
@turbo_stream ||= Turbo::Streams::TagBuilder.new(controller.view_context)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TurboReflex::Sanitizer
|
4
|
+
include Singleton
|
5
|
+
include ActionView::Helpers::SanitizeHelper
|
6
|
+
|
7
|
+
attr_reader :scrubber
|
8
|
+
|
9
|
+
def sanitize(value)
|
10
|
+
super value, scrubber: scrubber
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@scrubber = Loofah::Scrubber.new do |node|
|
17
|
+
node.remove if node.name == "script"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/turbo_reflex/version.rb
CHANGED
data/lib/turbo_reflex.rb
CHANGED
data/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "turbo_reflex",
|
3
|
-
"version": "0.0.
|
4
|
-
"description": "
|
3
|
+
"version": "0.0.1",
|
4
|
+
"description": "Reflexes for Turbo Frames that help you build robust reactive applications",
|
5
5
|
"main": "app/assets/builds/turbo_reflex.js",
|
6
6
|
"repository": "https://github.com/hopsoft/turbo_reflex",
|
7
7
|
"author": "Nate Hopkins (hopsoft) <natehop@gmail.com>",
|
@@ -10,11 +10,11 @@
|
|
10
10
|
"@hotwired/turbo-rails": ">= 7.1"
|
11
11
|
},
|
12
12
|
"devDependencies": {
|
13
|
-
"esbuild": "^0.
|
13
|
+
"esbuild": "^0.15.7",
|
14
14
|
"eslint": "^8.19.0",
|
15
15
|
"prettier-standard": "^16.4.1"
|
16
16
|
},
|
17
17
|
"scripts": {
|
18
|
-
"build": "esbuild app/javascript
|
18
|
+
"build": "esbuild app/javascript/turbo_reflex.js --bundle --minify --sourcemap --format=esm --outfile=app/assets/builds/turbo_reflex.min.js"
|
19
19
|
}
|
20
20
|
}
|
data/turbo_reflex.gemspec
CHANGED
@@ -2,27 +2,38 @@
|
|
2
2
|
|
3
3
|
require_relative "lib/turbo_reflex/version"
|
4
4
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "turbo_reflex"
|
7
|
+
s.version = TurboReflex::VERSION
|
8
|
+
s.authors = ["Nate Hopkins (hopsoft)"]
|
9
|
+
s.email = ["natehop@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/hopsoft/turbo_reflex"
|
11
|
+
s.summary = "Reflexes for Turbo Frames that help you build robust reactive applications"
|
12
|
+
s.description = "TurboReflex extends Turbo Frames and adds support for client triggered reflexes (think RPC) which let you sprinkle in functionality and skip the REST boilerplate."
|
13
|
+
s.license = "MIT"
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
s.metadata["homepage_uri"] = s.homepage
|
16
|
+
s.metadata["source_code_uri"] = s.homepage
|
17
|
+
s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGELOG.md"
|
18
18
|
|
19
|
-
|
19
|
+
s.files = Dir["lib/**/*.rb", "app/**/*", "bin/*", "[A-Z]*"]
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
s.add_dependency "rails", ">= 6.1"
|
22
|
+
s.add_dependency "turbo-rails", ">= 1.1"
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
s.add_development_dependency "capybara"
|
25
|
+
s.add_development_dependency "cuprite"
|
26
|
+
s.add_development_dependency "importmap-rails"
|
27
|
+
s.add_development_dependency "magic_frozen_string_literal"
|
28
|
+
s.add_development_dependency "minitest-reporters"
|
29
|
+
s.add_development_dependency "net-smtp"
|
30
|
+
s.add_development_dependency "pry-byebug"
|
31
|
+
s.add_development_dependency "puma"
|
32
|
+
s.add_development_dependency "rake"
|
33
|
+
s.add_development_dependency "rexml"
|
34
|
+
s.add_development_dependency "sprockets-rails"
|
35
|
+
s.add_development_dependency "sqlite3"
|
36
|
+
s.add_development_dependency "standardrb"
|
37
|
+
s.add_development_dependency "turbo_ready"
|
38
|
+
s.add_development_dependency "webdrivers"
|
28
39
|
end
|