svelte-on-rails 5.2.0 → 5.3.0

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -11
  3. data/lib/generators/svelte_on_rails/install/install_generator.rb +3 -3
  4. data/lib/svelte-on-rails.rb +2 -0
  5. data/lib/svelte_on_rails/action_cable.rb +43 -8
  6. data/lib/svelte_on_rails/active_record_extensions.rb +19 -0
  7. data/lib/svelte_on_rails/installer/hello_world.rb +1 -1
  8. data/lib/svelte_on_rails/lib/utils.rb +147 -0
  9. data/lib/svelte_on_rails/lib/view_helper_support.rb +3 -30
  10. data/lib/svelte_on_rails/railtie.rb +6 -0
  11. data/lib/svelte_on_rails/turbo_stream.rb +42 -2
  12. data/lib/tasks/svelte_on_rails_tasks.rake +2 -2
  13. data/templates/{rails_vite_hello_world → all_features_test}/app/controllers/svelte_on_rails_hello_world_controller.rb +27 -6
  14. data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/ReceiveFromChannel.svelte +25 -8
  15. data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_nav.html.erb +1 -1
  16. metadata +24 -23
  17. /data/templates/{rails_vite_hello_world → all_features_test}/app/channels/svelte_on_rails_channel.rb +0 -0
  18. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-england.png +0 -0
  19. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg +0 -0
  20. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg +0 -0
  21. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/initializers/actionCable.js +0 -0
  22. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JavascriptImport.svelte +0 -0
  23. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/JpgImport.svelte +0 -0
  24. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/ParentWithChild.svelte +0 -0
  25. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/PngImport.svelte +0 -0
  26. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte +0 -0
  27. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/SvgRawImport.svelte +0 -0
  28. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/components/sub/NestedComponent.svelte +0 -0
  29. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascript.js +0 -0
  30. /data/templates/{rails_vite_hello_world → all_features_test}/app/frontend/javascript/nestedJavascriptToggled.js +0 -0
  31. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/_styles.html.erb +0 -0
  32. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb +0 -0
  33. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/index.html.erb +0 -0
  34. /data/templates/{rails_vite_hello_world → all_features_test}/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb +0 -0
  35. /data/templates/{rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb → all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f7ba72715095d212a289e5fba57a8ddd78b358474e4219a5b3c47f3b7d66c88
4
- data.tar.gz: e690deefa5507a42cf31176efc368c3ff2f304ce1ff057dc79f431e33b5c4232
3
+ metadata.gz: 8f91cbdd2375be3978749859e8bb36b16fbba13fa1e9ac494b9b5e731bc06f91
4
+ data.tar.gz: f0164b5a93bfb62b2abeedbaebc85064253ad6e4500d9d94a0cfda570416e794
5
5
  SHA512:
6
- metadata.gz: c1b2150fa517ce388feaf13c9816b8d71cb339c9ac9670d7552096430a8cddff8688629ca96c58dbcde715e4f9e9b3953e6760ab6e67c37f3a0ad10797b8fcc9
7
- data.tar.gz: 275691b15976c8dbcd551b3c07ec7495da686c7fa23192b5aca471527b4fe63c20fa8048851a3d9b0085756de52bc7ae1d1e4e5a663114a9fbed9608873ba68e
6
+ metadata.gz: f490b501d07cfe64636b73e45ba2b42d30cfc2fc33168488635565fee4a1d3def9e31b5097cbba82aa7ebfad92e08b6a6be30afa53385dbb206e616d6a72352e
7
+ data.tar.gz: 94e88d6876d9d434e504899f47f5d71ba2d3129a3a55a94992ce6a573d9e78ab305e306f7c3c2576c0135b3aeb03c5578fec74175eb2fc18d89151bac059d664
data/README.md CHANGED
@@ -34,11 +34,14 @@ vision while aligning seamlessly with Rails’ full-stack philosophy.
34
34
  - Svelte eliminates redundant HTML state logic
35
35
  - Consolidates component logic into a single file
36
36
  - Offloads rendering of certain HTML components to frontend JavaScript, reducing server load
37
- - **Compared to React**
38
- - Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison
39
- - No shadow DOM or performance-heavy packages (e.g., useCallback)
40
- - Leaner, slimmer packages
41
- - Easier to learn and faster performance
37
+ - **Compared to React/Vue**
38
+ - Svelte eliminates the virtual DOM, resulting in leaner, more compact packages and faster performance
39
+ - Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison with react
40
+ - Easier to learn
41
+ - React and Vue having a vaster Community, bigger Ecosystem
42
+ - But, for integration, like here, this is not importand
43
+ - Svelte is sufficiently established
44
+ - Svelte is younger and growing
42
45
 
43
46
  Svelte empowers Rails’ full-stack vision with modern, streamlined frontend integration.
44
47
 
@@ -307,6 +310,46 @@ Consider setting `ssr: false` for testing only for performance reasons.
307
310
  Yo can override this on a attribute on the view-helper for specific components.
308
311
  But, in normal cases it should not be neccessary testing ssr explicitly.
309
312
 
313
+ ## ActiveRecord helpers
314
+
315
+ Adds the `#svelte_attributes` helper to your models, example:
316
+
317
+ ```ruby
318
+ @book.svelte_attributes(
319
+ :name,
320
+ :calculation_method,
321
+ author: [:name]
322
+ )
323
+ ```
324
+
325
+ would result in something like this:
326
+
327
+ ```ruby
328
+ {
329
+ "values" => {
330
+ "name" => "Learning Ruby",
331
+ "calculation_method": "any-result",
332
+ "author" => {
333
+ "name" => "Michael Hartl"
334
+ },
335
+ },
336
+ "book_labels" => {
337
+ "name" => "Name", # translated by human_attribute_name..
338
+ "calculation_method" => "Calculation method",
339
+ "author" => "Author"
340
+ },
341
+ "author_labels": {
342
+ "name" => "Name"
343
+ }
344
+ }
345
+ ```
346
+
347
+ This should ease transferring data you need within the component mostly.
348
+
349
+ If an optional `belongs_to` association is empty, the labels are still calculated.
350
+
351
+ **Caching:** The component's attributes are used to generate a checksum, which serves as the cache key for efficient storage and retrieval.
352
+
310
353
  ## Caching
311
354
 
312
355
  Caching only is relevant for `ssr`
@@ -375,7 +418,7 @@ turbo_stream:
375
418
  over streams or have different channels for each user privileges
376
419
  - Has [Compatibility issue with UJS](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#compatibility-with-rails-ujs)
377
420
 
378
- ## SvelteOnRails::ActionCable.send
421
+ ## SvelteOnRails::ActionCable
379
422
 
380
423
  ActionCable is the more basic library behind `TurboStream` and it is a second Option to call Javascript Actions from the server.
381
424
 
@@ -453,7 +496,7 @@ The «wrapping Element» is the Element from the view helper `svelte_component`
453
496
  Now you can dispatch events on the component by:
454
497
 
455
498
  ```ruby
456
- SvelteOnRails::ActionCable.send(
499
+ SvelteOnRails::ActionCable.dispatch(
457
500
  'folder/MyComponent',
458
501
  { message: "greetings from Server: äöü🤣🌴🌍漢字" }
459
502
  )
@@ -461,9 +504,12 @@ SvelteOnRails::ActionCable.send(
461
504
 
462
505
  And you will find the object, with the message key on the browser logs.
463
506
 
464
- Without any arguments, just by `SvelteOnRails::ActionCable.send` it would fire the `stream-action` event on all components.
507
+ Without any arguments, just by `SvelteOnRails::ActionCable.dispatch` it would fire the `stream-action` event on all components.
465
508
 
466
- # SvelteOnRails::TurboStream.send
509
+ The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
510
+ on the whole `document` and fires the given event there.
511
+
512
+ # SvelteOnRails::TurboStream
467
513
 
468
514
  Turbo Stream makes more sense when you think of sending confidential data to the components
469
515
  or you want to separate to channels based on user groups, for example.
@@ -501,7 +547,7 @@ When this works you are good to go.
501
547
  And call this by:
502
548
 
503
549
  ```ruby
504
- SvelteOnRails::TurboStream.send
550
+ SvelteOnRails::TurboStream.dispatch
505
551
  ```
506
552
  **What it does**
507
553
 
@@ -527,7 +573,7 @@ within the svelte component.
527
573
  Then, call it by:
528
574
 
529
575
  ```ruby
530
- SvelteOnRails::TurboStream.new.dispatch_event(
576
+ SvelteOnRails::TurboStream.dispatch(
531
577
  channel: 'my-custom-stream',
532
578
  component: '/javascript/components/TurboStreamsChannel',
533
579
  selector: '.counter-button',
@@ -536,6 +582,9 @@ Then, call it by:
536
582
  )
537
583
  ```
538
584
 
585
+ The **#dispatch_by_selector** does not go over the component, it searches for any matching selector just
586
+ on the whole `document` and fires the given event there.
587
+
539
588
  ## More rake tasks
540
589
 
541
590
  This tasks are more for testing/playground purposes
@@ -129,15 +129,15 @@ module SvelteOnRails
129
129
  hw_i = SvelteOnRails::Installer::HelloWorld
130
130
 
131
131
  utils_i = SvelteOnRails::Installer::Utils
132
- utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_streams_channel\"", app_root: Rails.root)
133
- utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_stream_action\"", app_root: Rails.root)
132
+ utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket\"", app_root: Rails.root)
133
+ utils_i.add_route(" get \"svelte_on_rails_hello_world/web_socket_action\"", app_root: Rails.root)
134
134
  npm_i = SvelteOnRails::Installer::Npm
135
135
  npm_i.install_or_update_package('axios')
136
136
  npm_i.install_or_update_package('@rails/actioncable')
137
137
  js_i = SvelteOnRails::Installer::Javascript
138
138
  init_stat = '../initializers/actionCable.js'
139
139
  js_i.append_import_statement(application_js_path, init_stat, "import '#{init_stat}';")
140
- @hello_world_path = hw_i.install_hello_world(['rails_vite_hello_world'], app_root: nil, force: true, silent: true)
140
+ @hello_world_path = hw_i.install_hello_world(['all_features_test'], app_root: nil, force: true, silent: true)
141
141
  end
142
142
 
143
143
  def haml_and_convert
@@ -2,6 +2,8 @@ require "svelte_on_rails/configuration"
2
2
  require "svelte_on_rails/view_helpers"
3
3
  require "svelte_on_rails/turbo_stream"
4
4
  require "svelte_on_rails/action_cable"
5
+ require "svelte_on_rails/active_record_extensions"
6
+
5
7
  require "svelte_on_rails/railtie" if defined?(Rails)
6
8
 
7
9
  require "svelte_on_rails/renderer/renderer"
@@ -1,11 +1,16 @@
1
1
  module SvelteOnRails
2
2
  class ActionCable
3
- def self.send(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
3
+ def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
4
4
 
5
- _channel = configs['channel'] || channel
6
- unless _channel.present?
7
- raise 'Missing attribute or configuration: action_cable/channel'
8
- end
5
+ utils = SvelteOnRails::Lib::Utils
6
+ conf = SvelteOnRails::Configuration.instance
7
+
8
+ _comp = if component
9
+ utils.validate_filename(component)
10
+ "/#{conf.components_folder + component}"
11
+ end
12
+
13
+ _channel = require_channel(svelte_on_rails_configs['channel'] || channel)
9
14
 
10
15
  if event != 'stream-action' && !selector
11
16
  raise "Another event name than the default one is only possible together with a selector"
@@ -13,7 +18,7 @@ module SvelteOnRails
13
18
 
14
19
  args = {
15
20
  eventDetail: event_detail,
16
- component: component,
21
+ component: _comp,
17
22
  event: event,
18
23
  selector: selector
19
24
  }
@@ -23,13 +28,43 @@ module SvelteOnRails
23
28
  args
24
29
  )
25
30
 
31
+ end
32
+
33
+ def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
34
+
35
+ _channel = require_channel(svelte_on_rails_configs['channel'] || channel)
36
+
37
+ if event != 'stream-action' && !selector
38
+ raise "Another event name than the default one is only possible together with a selector"
39
+ end
40
+
41
+ args = {
42
+ eventDetail: event_detail,
43
+ component: '/false/',
44
+ event: event,
45
+ selector: selector
46
+ }
47
+
48
+ ::ActionCable.server.broadcast(
49
+ _channel,
50
+ args
51
+ )
26
52
 
27
53
  end
28
54
 
29
55
  private
30
56
 
31
- def self.configs
32
- SvelteOnRails::Configuration.instance.configs[:action_cable]
57
+ def self.svelte_on_rails_configs
58
+ cnf = SvelteOnRails::Configuration.instance.configs[:action_cable]
59
+ raise 'ActionCable not configured for SvelteOnRails' if cnf.nil?
60
+ cnf
61
+ end
62
+
63
+ def self.require_channel(channel)
64
+ unless channel.present?
65
+ raise 'Missing attribute or configuration: action_cable/channel'
66
+ end
67
+ channel
33
68
  end
34
69
 
35
70
  end
@@ -0,0 +1,19 @@
1
+ # lib/svelte_on_rails/active_record_extensions.rb
2
+ module SvelteOnRails
3
+ module ActiveRecordExtensions
4
+ def self.included(base)
5
+ unless defined?(ActiveRecord::Base) && base.ancestors.include?(ActiveRecord::Base)
6
+ raise 'SvelteOnRails::ActiveRecordExtensions can only be included in ActiveRecord models'
7
+ end
8
+ end
9
+
10
+ # Returns a hash of attributes, methods, and associations formatted for Svelte components
11
+ def svelte_attributes(*attributes)
12
+ @svelte_attributes ||= begin
13
+ utils = SvelteOnRails::Lib::Utils
14
+ utils.svelte_attributes(self, attributes)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -38,7 +38,7 @@ module SvelteOnRails
38
38
  end
39
39
 
40
40
 
41
- def self.remove_hello_world(templates = ['rails_vite_hello_world'], app_root: nil, ask: true)
41
+ def self.remove_hello_world(templates = ['all_features_test'], app_root: nil, ask: true)
42
42
 
43
43
  utils = SvelteOnRails::Installer::Utils
44
44
 
@@ -137,6 +137,153 @@ module SvelteOnRails
137
137
  Rails.logger.warn(" => #{line}")
138
138
  end
139
139
  end
140
+
141
+ def self.validate_filename(filename)
142
+
143
+ conf = SvelteOnRails::Configuration.instance
144
+
145
+ file_path = conf.components_folder_full + "#{filename}.svelte"
146
+
147
+ unless File.exist?(file_path)
148
+
149
+ raise "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
150
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
151
+ "Your configurations are:\n\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
152
+
153
+ end
154
+
155
+ if conf.watch_changes? && !file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
156
+
157
+ raise "svelte-on-rails gem, view helper #svelte_component\n\n" +
158
+ "File found but Upper and lower case letters are incorrect:\n" +
159
+ "(This check is only on development environments when watch_changes is true)\n\n" +
160
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
161
+ "Your configurations are:\nfrontend_folder: «#{conf.frontend_folder}»\ncomponents_folder: «#{conf.components_folder}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
162
+
163
+ end
164
+ end
165
+
166
+ def self.svelte_attributes(record, attributes, labels = {}, call_stack: 0, offset: nil, limit: nil)
167
+
168
+ if record.respond_to?(:each)
169
+ unless record.is_a?(ActiveRecord::Relation) || record.is_a?(ActiveRecord::Associations::CollectionProxy)
170
+ raise 'record set must be a Article::ActiveRecord_Associations_CollectionProxy'
171
+ end
172
+
173
+ recs = (limit ? record.limit(limit) : record)
174
+ recs2 = (offset ? recs.offset(limit) : recs)
175
+
176
+ values = recs2.map do |rec|
177
+ svelte_attributes(rec, attributes, labels, call_stack: call_stack + 1)
178
+ end
179
+
180
+ elsif record.is_a?(Class)
181
+
182
+ # this is the case if a belongs_to association is empty; In that case we pass the class itself and extract only the labels
183
+ raise 'limit and offset are supported only for iterable objects' if limit || offset
184
+ attributes.each do |attr|
185
+ unless attr.respond_to?(:each)
186
+ labels["#{record.to_s.underscore}_labels"] ||= {}
187
+ labels["#{record.to_s.underscore}_labels"][attr.to_s] ||= record.human_attribute_name(attr)
188
+ end
189
+ end
190
+ values = {}
191
+
192
+ else
193
+
194
+ # we have a single record
195
+
196
+ raise 'limit and offset are supported only for iterable objects' if limit || offset
197
+ values = {}
198
+
199
+ table_name = record.class.to_s.underscore
200
+
201
+ attributes.each do |attr|
202
+
203
+ if attr.is_a? Hash
204
+
205
+ # we have associations
206
+ attr.each do |key, value|
207
+ _key = key.to_s
208
+
209
+ # skip and fetch offset and limit
210
+ if key.to_s.match(/_limit$/) && !record.respond_to?(key)
211
+ raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-7])
212
+ next
213
+ end
214
+ if key.to_s.match(/_offset$/) && !record.respond_to?(key)
215
+ raise 'Invalid attribute' unless record.respond_to?(key.to_s[0..-8])
216
+ next
217
+ end
218
+ offs, lim = svelte_attribute_extract_limit(record, attributes, _key)
219
+
220
+ labels["#{table_name}_labels"] ||= {}
221
+ labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
222
+
223
+ # values
224
+
225
+ content = record.send(_key)
226
+ reflect = record.class.reflect_on_association(_key)
227
+ if reflect
228
+ if content.respond_to?(:each)
229
+ values[_key] = svelte_attributes(
230
+ content, value, labels,
231
+ call_stack: call_stack + 1,
232
+ offset: offs,
233
+ limit: lim
234
+ )
235
+ else
236
+ values[_key] = svelte_attributes(
237
+ content || reflect.active_record, # if no record, we extract only the labels
238
+ value, labels,
239
+ call_stack: call_stack + 1,
240
+ offset: offs,
241
+ limit: lim
242
+ )
243
+ end
244
+ else
245
+ raise "invalid association: #{_key}"
246
+ end
247
+ end
248
+
249
+ else
250
+ # we have attributes
251
+ raise 'Invalid attribute' unless [Symbol, String].include?(attr.class)
252
+ _key = attr.to_s
253
+
254
+ labels["#{table_name}_labels"] ||= {}
255
+ labels["#{table_name}_labels"][_key] ||= record.class.human_attribute_name(_key)
256
+
257
+ values[_key] = record.send(_key)
258
+ end
259
+
260
+ end
261
+ end
262
+
263
+ if call_stack >= 1
264
+ values
265
+ else
266
+ { 'values' => values }.merge(labels)
267
+ end
268
+
269
+ end
270
+
271
+ private
272
+
273
+ def self.svelte_attribute_extract_limit(record, attributes, key)
274
+
275
+ hash_args = attributes.grep(Hash).first.with_indifferent_access # multiple arrays is not possible
276
+
277
+ offset = if hash_args["#{key}_offset"] && !record.respond_to?("#{key}_offset")
278
+ hash_args["#{key}_offset"]
279
+ end
280
+
281
+ limit = if hash_args["#{key}_limit"] && !record.respond_to?("#{key}_limit")
282
+ hash_args["#{key}_limit"]
283
+ end
284
+
285
+ [offset, limit]
286
+ end
140
287
  end
141
288
  end
142
289
  end
@@ -8,9 +8,11 @@ module SvelteOnRails
8
8
  def initialize(file, args, request, caching = false)
9
9
 
10
10
  @start_time = Time.now
11
+ @conf = SvelteOnRails::Configuration.instance
12
+ utils = SvelteOnRails::Lib::Utils
13
+ utils.validate_filename(file) if @conf.watch_changes?
11
14
  @filename = (file.match(/\.svelte$/) ? file[0..-8] : file)
12
15
  @args_checksum = Zlib.crc32(args.to_json).to_s(36)
13
- @conf = SvelteOnRails::Configuration.instance
14
16
  @helper_options, @html_options, @svelte_props = split_props(
15
17
  args,
16
18
  %i[class id style],
@@ -18,7 +20,6 @@ module SvelteOnRails
18
20
  )
19
21
  @request = request
20
22
  @ssr = determine_ssr
21
- validate_file if @conf.watch_changes?
22
23
 
23
24
  # precompile
24
25
 
@@ -64,24 +65,6 @@ module SvelteOnRails
64
65
  @ssr
65
66
  end
66
67
 
67
- def file_not_found_message
68
- ff = conf.frontend_folder
69
- cf = conf.components_folder
70
- "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
71
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
72
- "Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
73
- end
74
-
75
- def file_case_sensitive_message
76
- ff = conf.frontend_folder
77
- cf = conf.components_folder
78
- "svelte-on-rails gem, view helper #svelte_component\n\n" +
79
- "File found but Upper and lower case letters are incorrect:\n" +
80
- "(This check is only on development environments when watch_changes is true)\n\n" +
81
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
82
- "Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
83
- end
84
-
85
68
  def log_rendering(message)
86
69
 
87
70
  Rails.logger.info " #{message} (#{elapsed_milliseconds}ms)"
@@ -119,16 +102,6 @@ module SvelteOnRails
119
102
 
120
103
  private
121
104
 
122
- def validate_file
123
- file_path = conf.components_folder_full + "#{filename}.svelte"
124
- unless File.exist?(file_path)
125
- raise file_not_found_message
126
- end
127
- if conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
128
- raise file_case_sensitive_message
129
- end
130
- end
131
-
132
105
  def determine_ssr
133
106
  _ssr = if @helper_options.key?(:ssr)
134
107
  if @conf.watch_changes?
@@ -13,6 +13,12 @@ module SvelteOnRails
13
13
  SvelteOnRails::Configuration.instance
14
14
  end
15
15
 
16
+ initializer 'svelte_on_rails.active_record_extensions' do
17
+ ActiveSupport.on_load(:active_record) do
18
+ include SvelteOnRails::ActiveRecordExtensions
19
+ end
20
+ end
21
+
16
22
  rake_tasks do
17
23
  load File.expand_path("../../tasks/svelte_on_rails_tasks.rake", __FILE__)
18
24
  end
@@ -1,6 +1,14 @@
1
1
  module SvelteOnRails
2
2
  class TurboStream
3
- def self.send(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
3
+ def self.dispatch(component = nil, event_detail = nil, event: 'stream-action', selector: nil, channel: nil)
4
+
5
+ utils = SvelteOnRails::Lib::Utils
6
+ conf = SvelteOnRails::Configuration.instance
7
+
8
+ _comp = if component
9
+ utils.validate_filename(component)
10
+ "/#{conf.components_folder + component}"
11
+ end
4
12
 
5
13
  if event != 'stream-action' && !selector
6
14
  raise "Another event name than the default one is only possible together with a selector"
@@ -8,7 +16,7 @@ module SvelteOnRails
8
16
 
9
17
  args = {
10
18
  eventDetail: event_detail,
11
- component: component,
19
+ component: _comp,
12
20
  event: event,
13
21
  selector: selector
14
22
  }
@@ -24,6 +32,30 @@ module SvelteOnRails
24
32
 
25
33
  end
26
34
 
35
+ def self.dispatch_by_selector(selector, event_detail = nil, event: 'stream-action', channel: nil)
36
+
37
+ if event != 'stream-action' && !selector
38
+ raise "Another event name than the default one is only possible together with a selector"
39
+ end
40
+
41
+ args = {
42
+ eventDetail: event_detail,
43
+ component: '/false/',
44
+ event: event,
45
+ selector: selector
46
+ }
47
+
48
+ args_enc = Base64.strict_encode64(args.to_json)
49
+
50
+ Turbo::StreamsChannel.send(
51
+ "broadcast_append_to",
52
+ require_channel(channel || configs['channel']),
53
+ target: configs['target_html_id'],
54
+ content: "<div style=\"display: none;\" data-controller=\"svelte-on-rails-turbo-stream\" data-args=\"#{args_enc}\"></div>"
55
+ )
56
+
57
+ end
58
+
27
59
  private
28
60
 
29
61
  def self.configs
@@ -43,5 +75,13 @@ module SvelteOnRails
43
75
  conf.configs[:turbo_stream]
44
76
  end
45
77
  end
78
+
79
+ def self.require_channel(channel)
80
+ unless channel.present?
81
+ raise 'Missing attribute or configuration: turbo_stream/channel'
82
+ end
83
+ channel
84
+ end
85
+
46
86
  end
47
87
  end
@@ -18,7 +18,7 @@ namespace :svelte_on_rails do
18
18
  task :remove_hello_world do
19
19
 
20
20
  hw_i = SvelteOnRails::Installer::HelloWorld
21
- hw_i.remove_hello_world(['rails_vite_hello_world'])
21
+ hw_i.remove_hello_world(['all_features_test'])
22
22
 
23
23
  end
24
24
 
@@ -27,7 +27,7 @@ namespace :svelte_on_rails do
27
27
 
28
28
  puts '-' * 80
29
29
  hw_i = SvelteOnRails::Installer::HelloWorld
30
- hello_world_path = hw_i.install_hello_world(['rails_vite_hello_world'])
30
+ hello_world_path = hw_i.install_hello_world(['all_features_test'])
31
31
  puts "You can now see the Hello World component on: #{hello_world_path}."
32
32
 
33
33
  end
@@ -3,43 +3,64 @@ class SvelteOnRailsHelloWorldController < ApplicationController
3
3
  def index
4
4
  end
5
5
 
6
- def turbo_stream_action
6
+ def web_socket_action
7
7
 
8
- comp = '/javascript/components/ReceiveFromChannel'
8
+ # render plain: Article.first.svelte_attributes(:name) #(:name, :calc_method, children: [:name], children_limit: 2, parent: [:name]).to_json
9
+ #
10
+ # return
11
+
12
+ comp = 'ReceiveFromChannel'
9
13
 
10
14
  case params['stream']
11
15
 
12
16
  when 'action-cable-to-component'
13
- SvelteOnRails::ActionCable.send(
17
+ SvelteOnRails::ActionCable.dispatch(
14
18
  comp,
15
19
  { message: "#{SecureRandom.hex(2)} Sent by <span class='transfer'>ActionCable</span>: äöü🤣🌴🌍漢字", class: 'action-cable-to-component' },
16
20
  event: 'stream-action'
17
21
  )
18
22
 
19
23
  when 'action-cable-to-element'
20
- SvelteOnRails::ActionCable.send(
24
+ SvelteOnRails::ActionCable.dispatch(
21
25
  comp,
22
26
  { message: "#{SecureRandom.hex(2)} <span class='transfer'>ActionCable to .my-custom-class / my-custom-event</span>", class: 'action-cable-to-element' },
23
27
  selector: '.my-custom-class',
24
28
  event: 'my-custom-event'
25
29
  )
26
30
 
31
+ when 'action-cable-to-selector'
32
+ SvelteOnRails::ActionCable.dispatch_by_selector(
33
+ '.receive-by-selector',
34
+ { message: "Sent by ActionCable/Selector: äöü🤣🌴🌍漢字", class: 'action-cable-to-selector' }
35
+ )
36
+
27
37
  when 'turbo-stream-to-all-components'
28
- SvelteOnRails::TurboStream.send(
38
+ SvelteOnRails::TurboStream.dispatch(
29
39
  nil,
30
40
  { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-all-components' },
31
41
  )
32
42
 
33
43
  when 'turbo-stream-to-element'
34
- SvelteOnRails::TurboStream.send(
44
+ SvelteOnRails::TurboStream.dispatch(
35
45
  nil,
36
46
  { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-element' },
37
47
  selector: '.my-custom-class',
38
48
  event: 'my-custom-event'
39
49
  )
40
50
 
51
+ when 'turbo-stream-to-selector'
52
+ SvelteOnRails::TurboStream.dispatch_by_selector(
53
+ '.receive-by-selector',
54
+ { message: "Sent by TurboStream/Selector: äöü🤣🌴🌍漢字", class: 'turbo-stream-to-selector' }
55
+ )
56
+
57
+ else
58
+ raise 'Unknown stream'
59
+
41
60
  end
42
61
 
62
+ render plain: "dispatched: #{params['stream']}"
63
+
43
64
  end
44
65
 
45
66
  end
@@ -6,7 +6,7 @@
6
6
 
7
7
  function callChannelAction(action) {
8
8
  results = []
9
- axios.get(`/svelte_on_rails_hello_world/turbo_stream_action?stream=${action}`)
9
+ axios.get(`/svelte_on_rails_hello_world/web_socket_action?stream=${action}`)
10
10
  .then(function (response) {
11
11
  console.log(`server action called, status: ${response.status}`)
12
12
  })
@@ -30,21 +30,38 @@
30
30
  )
31
31
  };
32
32
 
33
+ function handleStreamSelectorEvent(ev) {
34
+ results.push(
35
+ {
36
+ text: `Element triggered from Server: ${JSON.stringify(ev.detail.message)}`,
37
+ class: ev.detail.class
38
+ }
39
+ )
40
+ }
41
+
33
42
  </script>
34
43
 
35
- <h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
44
+ <h1 use:addComponentStreamListener={handleCableEvent}>Dispatch Actions from Server</h1>
45
+
46
+ <p>Actions that are triggered from the server, by TurboStream or ActionCable</p>
36
47
 
37
- <p>Actions that are triggered by SvelteOnRails::TurboStream channel from the server</p>
48
+ <h3>ActionCable</h3>
49
+ <p>Easier Technic, more basic</p>
50
+ <button class="action-cable-to-component" onclick="{() => callChannelAction('action-cable-to-component')}">to component</button>
51
+ <button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-element')}">to element</button>
52
+ <button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-selector')}">to selector</button>
38
53
 
39
- <button class="action-cable-to-component" onclick="{() => callChannelAction('action-cable-to-component')}">ActionCable to component</button>
40
- <button class="action-cable-to-element" onclick="{() => callChannelAction('action-cable-to-element')}">ActionCable to element</button>
41
- <button class="turbo-stream-to-all-components" onclick="{() => callChannelAction('turbo-stream-to-all-components')}">TurboStream to all components</button>
42
- <button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-element')}">TurboStream to element</button>
54
+ <h3>TurboStream</h3>
55
+ <p>Securer Channels, for example if sending confidential data from the server to privileged user groups</p>
56
+ <button class="turbo-stream-to-all-components" onclick="{() => callChannelAction('turbo-stream-to-all-components')}">to all components</button>
57
+ <button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-element')}">to element</button>
58
+ <button class="turbo-stream-to-element" onclick="{() => callChannelAction('turbo-stream-to-selector')}">to selector</button>
43
59
 
44
60
  <span class="my-custom-class" onmy-custom-event="{handleElementEvent}"></span>
61
+ <span class="receive-by-selector" onstream-action="{handleStreamSelectorEvent}"></span>
45
62
 
63
+ <h3>Results</h3>
46
64
  <div class="results-box">
47
- <p>Result:</p>
48
65
  <ul class="results">
49
66
  {#each results as result}
50
67
  <li class="{result.class}">{@html result.text}</li>
@@ -5,7 +5,7 @@
5
5
  |
6
6
  <%= link_to 'SSR-Auto rendered (default)', '/svelte_on_rails_hello_world/ssr_auto_rendered' %>
7
7
  |
8
- <%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/turbo_streams_channel' %>
8
+ <%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/web_socket' %>
9
9
  </div>
10
10
 
11
11
  <% turbo_id = request.headers['X-Turbo-Request-ID'] %>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
@@ -32,6 +32,7 @@ files:
32
32
  - lib/generators/svelte_on_rails/install/install_generator.rb
33
33
  - lib/svelte-on-rails.rb
34
34
  - lib/svelte_on_rails/action_cable.rb
35
+ - lib/svelte_on_rails/active_record_extensions.rb
35
36
  - lib/svelte_on_rails/configuration.rb
36
37
  - lib/svelte_on_rails/installer/gem_utils.rb
37
38
  - lib/svelte_on_rails/installer/haml.rb
@@ -51,31 +52,31 @@ files:
51
52
  - lib/svelte_on_rails/turbo_stream.rb
52
53
  - lib/svelte_on_rails/view_helpers.rb
53
54
  - lib/tasks/svelte_on_rails_tasks.rake
55
+ - templates/all_features_test/app/channels/svelte_on_rails_channel.rb
56
+ - templates/all_features_test/app/controllers/svelte_on_rails_hello_world_controller.rb
57
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-england.png
58
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
59
+ - templates/all_features_test/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
60
+ - templates/all_features_test/app/frontend/initializers/actionCable.js
61
+ - templates/all_features_test/app/frontend/javascript/components/JavascriptImport.svelte
62
+ - templates/all_features_test/app/frontend/javascript/components/JpgImport.svelte
63
+ - templates/all_features_test/app/frontend/javascript/components/ParentWithChild.svelte
64
+ - templates/all_features_test/app/frontend/javascript/components/PngImport.svelte
65
+ - templates/all_features_test/app/frontend/javascript/components/ReceiveFromChannel.svelte
66
+ - templates/all_features_test/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
67
+ - templates/all_features_test/app/frontend/javascript/components/SvgRawImport.svelte
68
+ - templates/all_features_test/app/frontend/javascript/components/sub/NestedComponent.svelte
69
+ - templates/all_features_test/app/frontend/javascript/nestedJavascript.js
70
+ - templates/all_features_test/app/frontend/javascript/nestedJavascriptToggled.js
71
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/_nav.html.erb
72
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/_styles.html.erb
73
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
74
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/index.html.erb
75
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
76
+ - templates/all_features_test/app/views/svelte_on_rails_hello_world/web_socket.html.erb
54
77
  - templates/config_base/app/frontend/ssr/ssr.js
55
78
  - templates/config_base/config/svelte_on_rails.yml
56
79
  - templates/config_base/vite-ssr.config.ts
57
- - templates/rails_vite_hello_world/app/channels/svelte_on_rails_channel.rb
58
- - templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
59
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-england.png
60
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
61
- - templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-switzerland.jpg
62
- - templates/rails_vite_hello_world/app/frontend/initializers/actionCable.js
63
- - templates/rails_vite_hello_world/app/frontend/javascript/components/JavascriptImport.svelte
64
- - templates/rails_vite_hello_world/app/frontend/javascript/components/JpgImport.svelte
65
- - templates/rails_vite_hello_world/app/frontend/javascript/components/ParentWithChild.svelte
66
- - templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
67
- - templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
68
- - templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
69
- - templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
70
- - templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
71
- - templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascript.js
72
- - templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascriptToggled.js
73
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_nav.html.erb
74
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_styles.html.erb
75
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
76
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
77
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
78
- - templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
79
80
  homepage: https://gitlab.com/sedl/svelte-on-rails
80
81
  licenses:
81
82
  - MIT