turbo_reflex 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|