rest_framework 0.9.14 → 0.9.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,13 +2,17 @@
2
2
  module RESTFramework::Mixins::BaseModelControllerMixin
3
3
  BASE64_REGEX = /data:(.*);base64,(.*)/
4
4
  BASE64_TRANSLATE = ->(field, value) {
5
+ return value unless BASE64_REGEX.match?(value)
6
+
5
7
  _, content_type, payload = value.match(BASE64_REGEX).to_a
6
8
  return {
7
9
  io: StringIO.new(Base64.decode64(payload)),
8
10
  content_type: content_type,
9
- filename: "image_#{field}#{Rack::Mime::MIME_TYPES.invert[content_type]}",
11
+ filename: "file_#{field}#{Rack::Mime::MIME_TYPES.invert[content_type]}",
10
12
  }
11
13
  }
14
+ ACTIVESTORAGE_KEYS = [:io, :content_type, :filename, :identify, :key]
15
+
12
16
  include RESTFramework::BaseControllerMixin
13
17
 
14
18
  RRF_BASE_MODEL_CONFIG = {
@@ -43,14 +47,24 @@ module RESTFramework::Mixins::BaseModelControllerMixin
43
47
  native_serializer_associations_limit_query_param: "associations_limit",
44
48
  native_serializer_include_associations_count: false,
45
49
 
46
- # Attributes for default model filtering, ordering, and searching.
47
- filterset_fields: nil,
50
+ # Attributes for filtering, ordering, and searching.
51
+ filter_backends: [
52
+ RESTFramework::QueryFilter,
53
+ RESTFramework::OrderingFilter,
54
+ RESTFramework::SearchFilter,
55
+ ],
56
+ filter_recordset_before_find: true,
57
+ filter_fields: nil,
48
58
  ordering_fields: nil,
49
59
  ordering_query_param: "ordering",
50
60
  ordering_no_reorder: false,
51
61
  search_fields: nil,
52
62
  search_query_param: "search",
53
63
  search_ilike: false,
64
+ ransack_options: nil,
65
+ ransack_query_param: "q",
66
+ ransack_distinct: true,
67
+ ransack_distinct_query_param: "distinct",
54
68
 
55
69
  # Options for association assignment.
56
70
  permit_id_assignment: true,
@@ -58,21 +72,11 @@ module RESTFramework::Mixins::BaseModelControllerMixin
58
72
 
59
73
  # Option for `recordset.create` vs `Model.create` behavior.
60
74
  create_from_recordset: true,
61
-
62
- # Control if filtering is done before find.
63
- filter_recordset_before_find: true,
64
-
65
- # Options for `ransack` filtering.
66
- ransack_options: nil,
67
- ransack_query_param: "q",
68
- ransack_distinct: true,
69
- ransack_distinct_query_param: "distinct",
70
75
  }
71
76
 
72
77
  module ClassMethods
73
78
  IGNORE_VALIDATORS_WITH_KEYS = [:if, :unless].freeze
74
79
 
75
- # Get the model for this controller.
76
80
  def get_model
77
81
  return @model if @model
78
82
  return (@model = self.model) if self.model
@@ -86,8 +90,8 @@ module RESTFramework::Mixins::BaseModelControllerMixin
86
90
  raise RESTFramework::UnknownModelError, self
87
91
  end
88
92
 
89
- # Override `get_label` to include ActiveRecord i18n-translated column names.
90
- def get_label(s)
93
+ # Override to include ActiveRecord i18n-translated column names.
94
+ def label_for(s)
91
95
  return self.get_model.human_attribute_name(s, default: super)
92
96
  end
93
97
 
@@ -115,10 +119,10 @@ module RESTFramework::Mixins::BaseModelControllerMixin
115
119
  end
116
120
 
117
121
  # Get a field's config, including defaults.
118
- def get_field_config(f)
122
+ def field_config_for(f)
119
123
  f = f.to_sym
120
- @_get_field_config ||= {}
121
- return @_get_field_config[f] if @_get_field_config[f]
124
+ @_field_config_for ||= {}
125
+ return @_field_config_for[f] if @_field_config_for[f]
122
126
 
123
127
  config = self.field_config&.dig(f) || {}
124
128
 
@@ -145,7 +149,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
145
149
  }.to_h.compact.presence
146
150
  end
147
151
 
148
- return @_get_field_config[f] = config.compact
152
+ return @_field_config_for[f] = config.compact
149
153
  end
150
154
 
151
155
  # Get metadata about the resource's fields.
@@ -171,7 +175,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
171
175
  metadata = {
172
176
  type: nil,
173
177
  kind: nil,
174
- label: self.get_label(f),
178
+ label: self.label_for(f),
175
179
  primary_key: nil,
176
180
  required: nil,
177
181
  read_only: nil,
@@ -303,14 +307,14 @@ module RESTFramework::Mixins::BaseModelControllerMixin
303
307
  end
304
308
 
305
309
  # Serialize any field config.
306
- metadata[:config] = self.get_field_config(f).presence
310
+ metadata[:config] = self.field_config_for(f).presence
307
311
 
308
312
  next [f, metadata.compact]
309
313
  }.to_h
310
314
  end
311
315
 
312
316
  # Get a hash of metadata to be rendered in the `OPTIONS` response.
313
- def get_options_metadata
317
+ def options_metadata
314
318
  return super.merge(
315
319
  {
316
320
  primary_key: self.get_model.primary_key,
@@ -396,9 +400,8 @@ module RESTFramework::Mixins::BaseModelControllerMixin
396
400
  return self.class.get_fields(input_fields: self.class.fields)
397
401
  end
398
402
 
399
- # Pass fields to get dynamic metadata based on which fields are available.
400
- def get_options_metadata
401
- return self.class.get_options_metadata
403
+ def options_metadata
404
+ return self.class.options_metadata
402
405
  end
403
406
 
404
407
  # Get a list of parameters allowed for the current action.
@@ -408,7 +411,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
408
411
  @_get_allowed_parameters = self.allowed_parameters
409
412
  return @_get_allowed_parameters if @_get_allowed_parameters
410
413
 
411
- # For fields, automatically add `_id`/`_ids` and `_attributes` variations for associations.
414
+ # Assemble strong parameters.
412
415
  variations = []
413
416
  hash_variations = {}
414
417
  reflections = self.class.get_model.reflections
@@ -417,12 +420,13 @@ module RESTFramework::Mixins::BaseModelControllerMixin
417
420
 
418
421
  # ActiveStorage Integration: `has_one_attached`.
419
422
  if reflections.key?("#{f}_attachment")
423
+ hash_variations[f] = ACTIVESTORAGE_KEYS
420
424
  next f
421
425
  end
422
426
 
423
427
  # ActiveStorage Integration: `has_many_attached`.
424
428
  if reflections.key?("#{f}_attachments")
425
- hash_variations[f] = []
429
+ hash_variations[f] = ACTIVESTORAGE_KEYS
426
430
  next nil
427
431
  end
428
432
 
@@ -434,6 +438,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
434
438
  # Return field if it's not an association.
435
439
  next f unless ref = reflections[f]
436
440
 
441
+ # Add `_id`/`_ids` variations for associations.
437
442
  if self.permit_id_assignment && id_field = RESTFramework::Utils.get_id_field(f, ref)
438
443
  if id_field.ends_with?("_ids")
439
444
  hash_variations[id_field] = []
@@ -442,31 +447,36 @@ module RESTFramework::Mixins::BaseModelControllerMixin
442
447
  end
443
448
  end
444
449
 
450
+ # Add `_attributes` variations for associations.
445
451
  if self.permit_nested_attributes_assignment
446
- hash_variations["#{f}_attributes"] = self.class.get_field_config(f)[:sub_fields]
452
+ hash_variations["#{f}_attributes"] = (
453
+ self.class.field_config_for(f)[:sub_fields] + ["_destroy"]
454
+ )
447
455
  end
448
456
 
449
- # Associations are not allowed to be submitted in their bare form.
457
+ # Associations are not allowed to be submitted in their bare form (if they are submitted that
458
+ # way, they will be translated to either ID assignment or nested attributes assignment).
450
459
  next nil
451
460
  }.compact
452
461
  @_get_allowed_parameters += variations
453
462
  @_get_allowed_parameters << hash_variations
463
+
454
464
  return @_get_allowed_parameters
455
465
  end
456
466
 
457
- # Get the configured serializer class, or `NativeSerializer` as a default.
458
- def get_serializer_class
467
+ def serializer_class
459
468
  return super || RESTFramework::NativeSerializer
460
469
  end
461
470
 
462
- # Get filtering backends, defaulting to using `ModelQueryFilter`, `ModelOrderingFilter`, and
463
- # `ModelSearchFilter`.
464
- def get_filter_backends
465
- return self.filter_backends || [
466
- RESTFramework::ModelQueryFilter,
467
- RESTFramework::ModelOrderingFilter,
468
- RESTFramework::ModelSearchFilter,
469
- ]
471
+ def apply_filters(data)
472
+ # TODO: Compatibility; remove in 1.0.
473
+ if filtered_data = self.try(:get_filtered_data, data)
474
+ return filtered_data
475
+ end
476
+
477
+ return self.filter_backends&.reduce(data) { |d, filter|
478
+ filter.new(controller: self).filter_data(d)
479
+ } || data
470
480
  end
471
481
 
472
482
  # Use strong parameters to filter the request body using the configured allowed parameters.
@@ -492,6 +502,45 @@ module RESTFramework::Mixins::BaseModelControllerMixin
492
502
  end
493
503
  end
494
504
 
505
+ # ActiveStorage Integration: Translate base64 encoded attachments to upload objects.
506
+ #
507
+ # rubocop:disable Layout/LineLength
508
+ #
509
+ # Example base64 images (red, green, and blue squares):
510
+ # data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC
511
+ # data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNk+M9Qz0AEYBxVSF+FAAhKDveksOjmAAAAAElFTkSuQmCC
512
+ # data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNkYPhfz0AEYBxVSF+FAP5FDvcfRYWgAAAAAElFTkSuQmCC
513
+ #
514
+ # rubocop:enable Layout/LineLength
515
+ has_many_attached_scalar_data = {}
516
+ self.class.get_model.attachment_reflections.keys.each do |k|
517
+ if data[k].is_a?(Array)
518
+ data[k] = data[k].map { |v|
519
+ if v.is_a?(String)
520
+ v = BASE64_TRANSLATE.call(k, v)
521
+
522
+ # Remember scalars because Rails strong params will remove it.
523
+ if v.is_a?(String)
524
+ has_many_attached_scalar_data[k] ||= []
525
+ has_many_attached_scalar_data[k] << v
526
+ end
527
+ elsif v.is_a?(Hash)
528
+ if v[:io].is_a?(String)
529
+ v[:io] = StringIO.new(Base64.decode64(v[:io]))
530
+ end
531
+ end
532
+
533
+ next v
534
+ }
535
+ elsif data[k].is_a?(Hash)
536
+ if data[k][:io].is_a?(String)
537
+ data[k][:io] = StringIO.new(Base64.decode64(data[k][:io]))
538
+ end
539
+ elsif data[k].is_a?(String)
540
+ data[k] = BASE64_TRANSLATE.call(k, data[k])
541
+ end
542
+ end
543
+
495
544
  # Filter the request body with strong params. If `bulk` is true, then we apply allowed
496
545
  # parameters to the `_json` key of the request body.
497
546
  body_params = if allowed_params == true
@@ -503,6 +552,15 @@ module RESTFramework::Mixins::BaseModelControllerMixin
503
552
  ActionController::Parameters.new(data).permit(*allowed_params)
504
553
  end
505
554
 
555
+ # ActiveStorage Integration: Workaround for Rails strong params not allowing you to permit an
556
+ # array containing a mix of scalars and hashes. This is needed for `has_many_attached`, because
557
+ # API consumers must be able to provide scalar `signed_id` values for existing attachments along
558
+ # with hashes for new attachments. It's worth mentioning that base64 scalars are converted to
559
+ # hashes that conform to the ActiveStorage API.
560
+ has_many_attached_scalar_data.each do |k, v|
561
+ body_params[k].unshift(*v)
562
+ end
563
+
506
564
  # Filter primary key, if configured.
507
565
  if self.filter_pk_from_request_body && bulk_mode != :update
508
566
  body_params.delete(pk)
@@ -511,27 +569,6 @@ module RESTFramework::Mixins::BaseModelControllerMixin
511
569
  # Filter fields in `exclude_body_fields`.
512
570
  (self.exclude_body_fields || []).each { |f| body_params.delete(f) }
513
571
 
514
- # ActiveStorage Integration: Translate base64 encoded attachments to upload objects.
515
- #
516
- # rubocop:disable Layout/LineLength
517
- #
518
- # Example base64 image:
519
- # data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=
520
- #
521
- # rubocop:enable Layout/LineLength
522
- self.class.get_model.attachment_reflections.keys.each do |k|
523
- next unless (body_params[k].is_a?(String) && body_params[k].match?(BASE64_REGEX)) ||
524
- (body_params[k].is_a?(Array) && body_params[k].all? { |v|
525
- v.is_a?(String) && v.match?(BASE64_REGEX)
526
- })
527
-
528
- if body_params[k].is_a?(Array)
529
- body_params[k] = body_params[k].map { |v| BASE64_TRANSLATE.call(k, v) }
530
- else
531
- body_params[k] = BASE64_TRANSLATE.call(k, body_params[k])
532
- end
533
- end
534
-
535
572
  return body_params
536
573
  end
537
574
  alias_method :get_create_params, :get_body_params
@@ -551,13 +588,12 @@ module RESTFramework::Mixins::BaseModelControllerMixin
551
588
 
552
589
  # Get the records this controller has access to *after* any filtering is applied.
553
590
  def get_records
554
- return @records ||= self.get_filtered_data(self.get_recordset)
591
+ return @records ||= self.apply_filters(self.get_recordset)
555
592
  end
556
593
 
557
- # Get a single record by primary key or another column, if allowed. The return value is cached and
558
- # exposed to the view as the `@record` instance variable.
594
+ # Get a single record by primary key or another column, if allowed. The return value is memoized
595
+ # and exposed to the view as the `@record` instance variable.
559
596
  def get_record
560
- # Cache the result.
561
597
  return @record if @record
562
598
 
563
599
  find_by_key = self.class.get_model.primary_key
@@ -582,7 +618,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
582
618
  self.get_recordset
583
619
  end
584
620
 
585
- # Return the record. Route key is always `:id` by Rails convention.
621
+ # Return the record. Route key is always `:id` by Rails' convention.
586
622
  if is_pk
587
623
  return @record = collection.find(request.path_parameters[:id])
588
624
  else
@@ -609,11 +645,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
609
645
  # This is kinda slow, so perhaps we should eventually integrate `errors` serialization into
610
646
  # the serializer directly. This would fail for active model serializers, but maybe we don't
611
647
  # care?
612
- serializer_class = self.get_serializer_class
648
+ s = RESTFramework::Utils.wrap_ams(self.serializer_class)
613
649
  serialized_records = records.map do |record|
614
- serializer_class.new(record, controller: self).serialize.merge!(
615
- {errors: record.errors.presence}.compact,
616
- )
650
+ s.new(record, controller: self).serialize.merge!({errors: record.errors.presence}.compact)
617
651
  end
618
652
 
619
653
  return serialized_records
@@ -195,7 +195,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
195
195
  attachment_reflections = @model.attachment_reflections
196
196
 
197
197
  fields.each do |f|
198
- field_config = @controller.class.get_field_config(f)
198
+ field_config = @controller.class.field_config_for(f)
199
199
  next if field_config[:write_only]
200
200
 
201
201
  if f.in?(column_names)
@@ -259,18 +259,22 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
259
259
  serializer_methods[f] = f
260
260
  includes_map[f] = {"#{f}_attachment": :blob}
261
261
  self.define_singleton_method(f) do |record|
262
- next record.send(f).attachment&.url
262
+ attached = record.send(f)
263
+ next attached.attachment ? {signed_id: attached.signed_id, url: attached.url} : nil
263
264
  end
264
265
  elsif ref.macro == :has_many_attached
265
266
  serializer_methods[f] = f
266
267
  includes_map[f] = {"#{f}_attachments": :blob}
267
268
  self.define_singleton_method(f) do |record|
268
269
  # Iterating the collection yields attachment objects.
269
- next record.send(f).map(&:url)
270
+ next record.send(f).map { |a| {signed_id: a.signed_id, url: a.url} }
270
271
  end
271
272
  end
272
273
  elsif @model.method_defined?(f)
273
274
  methods << f
275
+ else
276
+ # Assume anything else is a virtual column.
277
+ columns << f
274
278
  end
275
279
  end
276
280
 
@@ -28,7 +28,7 @@ module RESTFramework::Utils
28
28
  [f, {}]
29
29
  }.to_h if metadata[:fields].is_a?(Array)
30
30
  metadata[:fields]&.each do |field, cfg|
31
- cfg[:label] = controller.get_label(field) unless cfg[:label]
31
+ cfg[:label] = controller.label_for(field) unless cfg[:label]
32
32
  end
33
33
  end
34
34
 
@@ -47,7 +47,7 @@ module RESTFramework::Utils
47
47
 
48
48
  # Insert action label if it's not provided.
49
49
  if controller
50
- metadata[:label] ||= controller.get_label(k)
50
+ metadata[:label] ||= controller.label_for(k)
51
51
  end
52
52
 
53
53
  next [
@@ -159,9 +159,10 @@ module RESTFramework::Utils
159
159
  )
160
160
  parsed_fields += fields_hash[:include].map(&:to_s) if fields_hash[:include]
161
161
  parsed_fields -= fields_hash[:exclude].map(&:to_s) if fields_hash[:exclude]
162
+ parsed_fields -= fields_hash[:except].map(&:to_s) if fields_hash[:except]
162
163
 
163
164
  # Warn for any unknown keys.
164
- (fields_hash.keys - [:only, :include, :exclude]).each do |k|
165
+ (fields_hash.keys - [:only, :except, :include, :exclude]).each do |k|
165
166
  Rails.logger.warn("RRF: Unknown key in fields hash: #{k}")
166
167
  end
167
168
 
@@ -233,4 +234,13 @@ module RESTFramework::Utils
233
234
 
234
235
  return nil
235
236
  end
237
+
238
+ # Wrap a serializer with an adapter if it is an ActiveModel::Serializer.
239
+ def self.wrap_ams(s)
240
+ if defined?(ActiveModel::Serializer) && (s < ActiveModel::Serializer)
241
+ return RESTFramework::ActiveModelSerializerAdapterFactory.for(s)
242
+ end
243
+
244
+ return s
245
+ end
236
246
  end
@@ -31,57 +31,57 @@ module RESTFramework
31
31
  EXTERNAL_ASSETS = {
32
32
  # Bootstrap
33
33
  "bootstrap.min.css" => {
34
- url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css",
35
- sri: "sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp",
34
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
35
+ sri: "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
36
36
  },
37
37
  "bootstrap.min.js" => {
38
- url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js",
39
- sri: "sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N",
38
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
39
+ sri: "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz",
40
40
  },
41
41
 
42
42
  # Bootstrap Icons
43
43
  "bootstrap-icons.min.css" => {
44
- url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.min.css",
44
+ url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css",
45
45
  inline_fonts: true,
46
46
  },
47
47
 
48
48
  # Highlight.js
49
49
  "highlight.min.js" => {
50
- url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js",
51
- sri: "sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA==",
50
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js",
51
+ sri: "sha512-6QBAC6Sxc4IF04SvIg0k78l5rP5YgVjmHX2NeArelbxM3JGj4imMqfNzEta3n+mi7iG3nupdLnl3QrbfjdXyTg==",
52
52
  },
53
53
  "highlight-json.min.js" => {
54
- url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/json.min.js",
55
- sri: "sha512-0xYvyncS9OLE7GOpNBZFnwyh9+bq4HVgk4yVVYI678xRvE22ASicF1v6fZ1UiST+M6pn17MzFZdvVCI3jTHSyw==",
54
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/languages/json.min.js",
55
+ sri: "sha512-8JO7/pRnd1Ce8OBXWQg85e5wNPJdBaQdN8w4oDa+HelMXaLwCxTdbzdWHmJtWR9AmcI6dOln4FS5/KrzpxqqfQ==",
56
56
  },
57
57
  "highlight-xml.min.js" => {
58
- url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/xml.min.js",
59
- sri: "sha512-5zBcw+OKRkaNyvUEPlTSfYylVzgpi7KpncY36b0gRudfxIYIH0q0kl2j26uCUB3YBRM6ytQQEZSgRg+ZlBTmdA==",
58
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/languages/xml.min.js",
59
+ sri: "sha512-/vq6wbS2Qkv8Hj4mP3Jd/m6MbnIrquzZiUt9tIluQfe332IQeFDrSIK7j2cjAyn6/9Ntb2WMPbo1CAxu26NViA==",
60
60
  },
61
61
  "highlight-a11y-dark.min.css" => {
62
- url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-dark.min.css",
62
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-dark.min.css",
63
63
  sri: "sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==",
64
64
  extra_tag_attrs: {class: "rrf-dark-mode"},
65
65
  },
66
66
  "highlight-a11y-light.min.css" => {
67
- url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-light.min.css",
67
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-light.min.css",
68
68
  sri: "sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==",
69
69
  extra_tag_attrs: {class: "rrf-light-mode"},
70
70
  },
71
71
 
72
72
  # NeatJSON
73
73
  "neatjson.min.js" => {
74
- url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js",
74
+ url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.6/javascript/neatjson.min.js",
75
75
  exclude_from_docs: true,
76
76
  },
77
77
 
78
78
  # Trix
79
79
  "trix.min.css" => {
80
- url: "https://unpkg.com/trix@2.0.0/dist/trix.css",
80
+ url: "https://unpkg.com/trix@2.0.8/dist/trix.css",
81
81
  exclude_from_docs: true,
82
82
  },
83
83
  "trix.min.js" => {
84
- url: "https://unpkg.com/trix@2.0.0/dist/trix.umd.min.js",
84
+ url: "https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js",
85
85
  exclude_from_docs: true,
86
86
  },
87
87
  }.map { |name, cfg|