scrivito_sdk 0.70.2 → 0.71.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/binary_redirect_controller.rb +12 -9
  3. data/app/controllers/scrivito/blobs_controller.rb +1 -2
  4. data/app/controllers/scrivito/objs_controller.rb +16 -0
  5. data/app/helpers/scrivito_helper.rb +6 -2
  6. data/app/views/cms/index.html.erb +1 -1
  7. data/app/views/scrivito/blobs/activate_upload.json.jbuilder +1 -0
  8. data/app/views/scrivito/objs/obj.json.jbuilder +1 -1
  9. data/app/views/scrivito/objs/transfer_modifications.json.jbuilder +6 -0
  10. data/config/ca-bundle.crt +128 -81
  11. data/config/precedence_routes.rb +1 -0
  12. data/lib/assets/images/scrivito/source_invalid.png +0 -0
  13. data/lib/assets/images/scrivito/source_too_large.png +0 -0
  14. data/lib/assets/images/scrivito/source_type_invalid.png +0 -0
  15. data/lib/assets/javascripts/scrivito_ui.js +1043 -645
  16. data/lib/assets/stylesheets/scrivito.css +1 -1
  17. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  18. data/lib/generators/scrivito/page/templates/thumbnail.html.erb +1 -1
  19. data/lib/scrivito/attribute_content.rb +54 -17
  20. data/lib/scrivito/attribute_deserializer.rb +1 -1
  21. data/lib/scrivito/attribute_serializer.rb +6 -4
  22. data/lib/scrivito/backend/obj_query.rb +11 -2
  23. data/lib/scrivito/base_widget_tag.rb +77 -0
  24. data/lib/scrivito/basic_obj.rb +67 -67
  25. data/lib/scrivito/basic_widget.rb +11 -6
  26. data/lib/scrivito/binary.rb +50 -5
  27. data/lib/scrivito/binary_param_verifier.rb +4 -5
  28. data/lib/scrivito/client_attribute_serializer.rb +3 -3
  29. data/lib/scrivito/cms_backend.rb +12 -0
  30. data/lib/scrivito/cms_field_tag.rb +8 -6
  31. data/lib/scrivito/cms_rest_api.rb +28 -8
  32. data/lib/scrivito/cms_rest_api/rate_limit.rb +1 -0
  33. data/lib/scrivito/cms_routing.rb +7 -1
  34. data/lib/scrivito/configuration.rb +1 -1
  35. data/lib/scrivito/controller_actions.rb +38 -35
  36. data/lib/scrivito/date_attribute.rb +3 -7
  37. data/lib/scrivito/editing_context.rb +3 -3
  38. data/lib/scrivito/editing_context_middleware.rb +3 -3
  39. data/lib/scrivito/errored_widget_tag.rb +34 -0
  40. data/lib/scrivito/errors.rb +6 -0
  41. data/lib/scrivito/future_binary.rb +23 -0
  42. data/lib/scrivito/link_parser.rb +12 -1
  43. data/lib/scrivito/membership_collection.rb +8 -8
  44. data/lib/scrivito/obj_collection.rb +2 -2
  45. data/lib/scrivito/obj_create_params_parser.rb +2 -2
  46. data/lib/scrivito/obj_params_parser.rb +5 -1
  47. data/lib/scrivito/obj_search_builder.rb +2 -12
  48. data/lib/scrivito/obj_search_enumerator.rb +48 -43
  49. data/lib/scrivito/page_config.rb +2 -1
  50. data/lib/scrivito/type_computer.rb +6 -6
  51. data/lib/scrivito/user.rb +9 -10
  52. data/lib/scrivito/user_definition.rb +2 -2
  53. data/lib/scrivito/warning.rb +17 -0
  54. data/lib/scrivito/widget_garbage_collection.rb +1 -1
  55. data/lib/scrivito/widget_tag.rb +28 -53
  56. data/lib/scrivito/workspace.rb +32 -23
  57. metadata +10 -6
  58. data/app/views/scrivito/objs/copy_widget.html.erb +0 -1
  59. data/app/views/scrivito/objs/create_widget.html.erb +0 -1
@@ -228,11 +228,14 @@ class BasicWidget
228
228
  previous_obj_content =
229
229
  CmsRestApi.get("revisions/#{workspace.base_revision_id}/objs/#{obj.id}")
230
230
  previous_widget_content = previous_obj_content["_widget_pool"]["#{id}"]
231
+ previous_widget_content = self.class.reset_blank_attributes(previous_widget_content)
232
+
231
233
  previous_widget_content.delete_if do |attribute_name, _|
232
234
  type_of_attribute(attribute_name) == 'widgetlist'
233
235
  end
234
236
  CmsRestApi.put("workspaces/#{workspace.id}/objs/#{obj.id}",
235
237
  { obj: {_widget_pool: {id => previous_widget_content}} })
238
+ reload
236
239
  else
237
240
  raise ScrivitoError, "cannot revert changes, since widget is #{modification}."
238
241
  end
@@ -243,17 +246,17 @@ class BasicWidget
243
246
  obj_in_revision && obj_in_revision.widget_from_pool(id)
244
247
  end
245
248
 
246
- def new?(revision=Workspace.current.base_revision)
249
+ def new?(revision=workspace.base_revision)
247
250
  return false unless revision
248
251
  obj.new?(revision) || cms_data_for_revision(revision).nil?
249
252
  end
250
253
 
251
- def deleted?(revision=Workspace.current.base_revision)
254
+ def deleted?(revision=workspace.base_revision)
252
255
  return false unless revision
253
256
  obj.deleted?(revision)
254
257
  end
255
258
 
256
- def modification(revision=Workspace.current.base_revision)
259
+ def modification(revision=workspace.base_revision)
257
260
  return Modification::UNMODIFIED unless revision
258
261
 
259
262
  if deleted?(revision)
@@ -329,14 +332,16 @@ class BasicWidget
329
332
 
330
333
  def forget_unsaved_attributes
331
334
  @attributes_to_be_saved = nil
332
- reload
335
+ reload_data
333
336
  end
334
337
 
335
338
  def reload
336
339
  obj.reload
340
+ reload_data
341
+ end
337
342
 
338
- update_proc = -> { obj.widget_data_from_pool(id) }
339
- update_data(update_proc)
343
+ def reload_data
344
+ update_data -> { obj.widget_data_from_pool(id) }
340
345
  end
341
346
 
342
347
  # @api public
@@ -6,13 +6,56 @@ module Scrivito
6
6
  class Binary
7
7
  attr_reader :id, :transformation_definition
8
8
 
9
- def initialize(id, is_public, transformation_definition = nil, original = nil)
9
+ def initialize(id, is_public, transformation_definition: nil, original: nil)
10
10
  @id = id
11
11
  @is_public = !!is_public
12
12
  @transformation_definition = transformation_definition
13
13
  @original = original
14
14
  end
15
15
 
16
+ # @api public
17
+ # Uploads a local file to the CMS.
18
+ # @example Upload a single file
19
+ # @obj.update(blob: Scrivito::Binary.upload("/Desktop/kitten.jpg"))
20
+ #
21
+ # # equivalent to
22
+ # @obj.update(blob: File.new("/Desktop/kitten.jpg"))
23
+ # @example Upload a file with a different filename and content type
24
+ # @obj.update(blob: Scrivito::Binary.upload(
25
+ # "/Desktop/rick_astley", content_type: "Video/MP4", filename: "ufo_landing.m4v"))
26
+ # @param file_or_path [File, String] the file to be uploaded or
27
+ # the path of the file to be uploaded.
28
+ # @param filename [String, Nil] the desired filename. If +nil+,
29
+ # the name of the given file is used.
30
+ # @param content_type [String, Nil] the desired content type. If +nil+,
31
+ # the content type is calculated based on the extension of the filename.
32
+ # @return [Scrivito::FutureBinary] the returned object should be inserted into the binary field
33
+ # of an {Scrivito::BasicObj Obj} or {Scrivito::BasicWidget Widget}.
34
+ def self.upload(file_or_path, filename: nil, content_type: nil)
35
+ if file_or_path.is_a?(File)
36
+ file = file_or_path
37
+ else
38
+ file = File.new(file_or_path)
39
+ end
40
+ new_filename = filename || File.basename(file_or_path)
41
+ FutureBinary.new(content_type: content_type, filename: new_filename, file_to_be_uploaded: file)
42
+ end
43
+
44
+ # @api public
45
+ # Create a copy of this Binary with a different filename and/or content type.
46
+ # @example Change filename and content_type of an existing binary. Binary content remains the same.
47
+ # @obj.update(blob: @obj.blob.copy(filename: "cute_kitten.jpg", content_type: "video/mp4"))
48
+ # @param filename [String, Nil] the desired filename. If +nil+,
49
+ # the filename of the original binary is used.
50
+ # @param content_type [String, Nil] the desired content type. If +nil+,
51
+ # the content type is calculated based on the extension of the filename.
52
+ # @return [Scrivito::FutureBinary] the returned object should be inserted into the binary field
53
+ # of an Obj or Widget.
54
+ def copy(content_type: nil, filename: nil)
55
+ new_filename = filename || self.filename
56
+ FutureBinary.new(content_type: content_type, filename: new_filename, id_to_be_copied: id)
57
+ end
58
+
16
59
  # @api public
17
60
  # Some Scrivito data is considered private, i.e. it is not currently intended
18
61
  # for the general public, for example content in a workspace that has not
@@ -131,8 +174,8 @@ class Binary
131
174
  #
132
175
  # If the given width and height are adjusted, the aspect ratio is preserved.
133
176
  #
134
- # @option definition [Symbol,String] :fit Controls how the tranformed image is fitted to the
135
- # given width and heigth. Valid values are +:clip+ and +:crop+. The default value is +:clip+.
177
+ # @option definition [Symbol,String] :fit Controls how the transformed image is fitted to the
178
+ # given width and height. Valid values are +:clip+ and +:crop+. The default value is +:clip+.
136
179
  #
137
180
  # If set to +:clip+, the image is resized so as to fit within the width and height boundaries
138
181
  # without cropping or distorting the image. The resulting image is assured to match one of the
@@ -163,7 +206,9 @@ class Binary
163
206
  # @see ScrivitoHelper#scrivito_image_tag
164
207
  #
165
208
  def transform(definition)
166
- self.class.new(id, public?, (transformation_definition || {}).merge(definition), original)
209
+ self.class.new(id, public?,
210
+ transformation_definition: (transformation_definition || {}).merge(definition),
211
+ original: original)
167
212
  end
168
213
 
169
214
  #
@@ -232,7 +277,7 @@ class Binary
232
277
  deserialized_meta_data = {}
233
278
  raw_meta_data.each_pair do |key, (type, value)|
234
279
  deserialized_meta_data[key] = case type
235
- when 'date' then DateAttribute.deserialize_from_backend(value)
280
+ when 'date' then DateAttribute.parse(value)
236
281
  when 'number' then value.to_i
237
282
  else value
238
283
  end
@@ -6,18 +6,17 @@ module BinaryParamVerifier
6
6
  class << self
7
7
  def verify(params)
8
8
  params = message_verifier.verify(params)
9
- expires = DateAttribute.deserialize_from_backend(params['expires'])
9
+ expires = DateAttribute.parse(params['expires'])
10
10
  raise InvalidSignature if expires && expires < Time.zone.now
11
- Binary.new(params['binary_id'], expires.nil?, params['transformation_definition'])
11
+ Binary.new(params['binary_id'], expires.nil?,
12
+ transformation_definition: params['transformation_definition'])
12
13
  rescue ActiveSupport::MessageVerifier::InvalidSignature
13
14
  raise InvalidSignature
14
15
  end
15
16
 
16
17
  def generate(binary)
17
18
  params = {binary_id: binary.id, transformation_definition: binary.transformation_definition}
18
- if binary.private?
19
- params[:expires] = DateAttribute.serialize_for_backend(Time.zone.now + 1.hour)
20
- end
19
+ params[:expires] = DateAttribute.serialize(Time.zone.now + 1.hour) if binary.private?
21
20
  message_verifier.generate(params)
22
21
  end
23
22
 
@@ -19,7 +19,7 @@ module ClientAttributeSerializer
19
19
  def self.serialize_system_attrs(obj)
20
20
  {
21
21
  '_id' => obj.id,
22
- '_obj_class' => obj.obj_class_name,
22
+ '_obj_class' => obj.obj_class,
23
23
  '_path' => obj.path,
24
24
  '_permalink' => obj.permalink,
25
25
  }
@@ -71,7 +71,7 @@ module ClientAttributeSerializer
71
71
  end
72
72
 
73
73
  def self.serialize_date_value(value)
74
- DateAttribute.serialize_for_client(value)
74
+ value.utc.iso8601
75
75
  end
76
76
 
77
77
  def self.serialize_enum_value(value)
@@ -126,7 +126,7 @@ module ClientAttributeSerializer
126
126
 
127
127
  def self.serialize_widget(widget)
128
128
  {
129
- '_obj_class' => widget.obj_class_name
129
+ '_obj_class' => widget.obj_class
130
130
  }.merge(serialize_custom_attrs(widget))
131
131
  end
132
132
  end
@@ -250,8 +250,20 @@ module Scrivito
250
250
  end
251
251
  end
252
252
 
253
+ def create_obj(workspace_id, attributes)
254
+ write_obj(:post, "/workspaces/#{workspace_id}/objs", attributes)
255
+ end
256
+
257
+ def update_obj(workspace_id, obj_id, attributes)
258
+ write_obj(:put, "/workspaces/#{workspace_id}/objs/#{obj_id}", attributes)
259
+ end
260
+
253
261
  private
254
262
 
263
+ def write_obj(verb, path, attributes)
264
+ Backend::ObjDataFromRest.new(CmsRestApi.task_unaware_request(verb, path, attributes))
265
+ end
266
+
255
267
  def update_workspace_cache(id, cached_data_tag, changed_workspace, options)
256
268
  if changed_workspace
257
269
  workspace_data = changed_workspace
@@ -21,7 +21,8 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
21
21
  raise ArgumentError, 'No block allowed for widgetlist fields' if block_given?
22
22
  modifications = modification_info || []
23
23
  rendered_widgets = default_content.each_with_index.map do |widget, index|
24
- WidgetTag.new(view, widget, modifications[index], widget_template_name, inner_tag).render
24
+ WidgetTag.new(view, widget, placement_modification: modifications[index],
25
+ render_context: widget_render_context, inner_tag: inner_tag).render
25
26
  end
26
27
  view.safe_join(rendered_widgets)
27
28
  else
@@ -33,10 +34,11 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
33
34
  return {} unless authenticated_editor?
34
35
 
35
36
  options = {
36
- 'private-field-workspace-id' => current_workspace_id,
37
37
  'field-name' => field_name,
38
- 'field-obj-class' => obj_or_widget.obj_class_name,
38
+ 'field-obj-class' => obj_or_widget.obj_class,
39
39
  'field-type' => field_type,
40
+ 'private-editor' => editing_options[:editor],
41
+ 'private-field-workspace-id' => current_workspace_id,
40
42
  }
41
43
 
42
44
  modification = comparison.modification_for_attribute(obj_or_widget, field_name)
@@ -61,7 +63,7 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
61
63
  if field_type == 'widgetlist'
62
64
  options["private-field-widget-inner-tag"] = inner_tag
63
65
  options['private-field-widget-allowed-classes'] = build_valid_widget_classes.to_json
64
- options['private-field-widget-template'] = widget_template_name
66
+ options['private-field-widget-template'] = widget_render_context
65
67
  end
66
68
 
67
69
  options
@@ -84,8 +86,8 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :editing_option
84
86
  end
85
87
  end
86
88
 
87
- def widget_template_name
88
- editing_options[:widget_template_name]
89
+ def widget_render_context
90
+ editing_options[:widget_render_context] || WidgetTag::DEFAULT_RENDER_CONTEXT
89
91
  end
90
92
 
91
93
  def field_name
@@ -53,7 +53,9 @@ module Scrivito
53
53
 
54
54
  def self.task_unaware_request(method, resource_path, payload = nil)
55
55
  raise "Unexpected method #{method}" unless [:delete, :get, :post, :put].include?(method)
56
- response_for_request_cms_api(method, resource_path, payload, build_timer)
56
+ log_api_request(method, resource_path, payload) do
57
+ response_for_request_cms_api(method, resource_path, payload, build_timer)
58
+ end
57
59
  end
58
60
 
59
61
  def self.count_requests(path)
@@ -64,10 +66,23 @@ module Scrivito
64
66
  @number_of_requests
65
67
  end
66
68
 
67
- def self.upload_file(file, obj_id)
68
- upload_permission = get('blobs/upload_permission')
69
- upload = perform_file_upload(file, upload_permission)
70
- activate_upload(upload: upload, obj_id: obj_id)
69
+ def self.upload_future_binary(binary, obj_id)
70
+ if binary.id_to_be_copied
71
+ {
72
+ content_type: binary.content_type,
73
+ filename: binary.filename,
74
+ id: binary.id_to_be_copied,
75
+ }
76
+ else
77
+ upload_permission = get('blobs/upload_permission')
78
+ upload = perform_file_upload(
79
+ file: binary.file_to_be_uploaded,
80
+ filename: binary.filename,
81
+ content_type: binary.content_type,
82
+ upload_permission: upload_permission,
83
+ )
84
+ activate_upload(upload: upload, obj_id: obj_id)
85
+ end
71
86
  end
72
87
 
73
88
  def self.activate_upload(params)
@@ -84,6 +99,8 @@ module Scrivito
84
99
  private
85
100
 
86
101
  def request_cms_api(action, resource_path, payload, options)
102
+ assert_valid_resource_path(resource_path)
103
+
87
104
  log_api_request(action, resource_path, payload) do
88
105
  @number_of_requests += 1 if resource_path == @count_requests
89
106
 
@@ -171,16 +188,15 @@ module Scrivito
171
188
  end
172
189
  end
173
190
 
174
- def perform_file_upload(file, upload_permission)
191
+ def perform_file_upload(file:, filename:, content_type:, upload_permission:)
175
192
  uri = URI.parse(upload_permission['url'])
176
193
  File.open(file) do |open_file|
177
- content_type = MIME::Types.type_for(file.path).first.content_type
178
194
  upload_io = UploadIO.new(open_file, content_type, File.basename(file))
179
195
  params = upload_permission['fields'].merge('file' => upload_io)
180
196
  request = Net::HTTP::Post::Multipart.new(uri.path, params)
181
197
  response = ConnectionManager.request(uri, request)
182
198
  if response.code.starts_with?('2')
183
- upload_permission['blob'].merge('filename' => File.basename(file.path))
199
+ upload_permission['blob'].merge('filename' => filename, 'content_type' => content_type)
184
200
  else
185
201
  raise ScrivitoError, "File upload failed with code #{response.code}"
186
202
  end
@@ -231,6 +247,10 @@ module Scrivito
231
247
  def method_to_net_http_class(method)
232
248
  METHOD_TO_NET_HTTP_CLASS.fetch(method)
233
249
  end
250
+
251
+ def assert_valid_resource_path(resource_path)
252
+ URI(resource_path)
253
+ end
234
254
  end
235
255
  end
236
256
  end
@@ -15,6 +15,7 @@ module RateLimit
15
15
  time_to_sleep = calculate_time_to_sleep(response['Retry-After'].to_f, retry_count)
16
16
 
17
17
  if request_timer.cover?(Time.now + time_to_sleep.seconds)
18
+ Warning.warn("Rate limit exceeded. Will retry after #{time_to_sleep} seconds.")
18
19
  sleep time_to_sleep
19
20
  internal_retry(request_proc, request_timer, retry_count + 1)
20
21
  else
@@ -98,7 +98,13 @@ class CmsRouting < Struct.new(:request, :main_app, :scrivito_engine, :image_opti
98
98
  "cms_id_#{path_or_url}"
99
99
  end
100
100
 
101
- main_app.public_send(method_name, options.merge(id: obj.id))
101
+ options[:id] = obj.id
102
+
103
+ # Options must have the key slug.
104
+ # Otherwise Rails will use the slug from current request params.
105
+ options[:slug] ||= nil
106
+
107
+ main_app.public_send(method_name, options)
102
108
  end
103
109
 
104
110
  def homepage?(obj)
@@ -273,7 +273,7 @@ module Scrivito
273
273
  '_default' => proc do |obj, user|
274
274
  {
275
275
  id: obj.id,
276
- obj_class_name: obj.obj_class_name,
276
+ obj_class: obj.obj_class,
277
277
  description_for_editor: obj.description_for_editor,
278
278
  modification: obj.modification,
279
279
  has_conflict: obj.has_conflict?,
@@ -40,64 +40,67 @@ module ControllerActions
40
40
 
41
41
  def show_widget
42
42
  widget = load_widget
43
- widget_tag = Scrivito::WidgetTag.new(view_context, widget, nil,
44
- params[:template_name], params[:inner_tag])
43
+ widget_tag = Scrivito::WidgetTag.new(view_context, widget,
44
+ render_context: params[:template_name], inner_tag: params[:inner_tag])
45
45
  render text: widget_tag.render, layout: false
46
46
  end
47
47
 
48
48
  def widget_details
49
49
  assert_dialog_layout
50
50
  widget = load_widget
51
- template_path = "#{widget.obj_class_name.underscore}/details"
52
- @scrivito_default_widget_template = :details
53
- render template_path, layout: 'scrivito_dialog', locals: {widget: widget}
51
+ @scrivito_widget_render_context = :details
52
+ render widget.details_view_path, layout: 'scrivito_dialog', locals: {widget: widget}
54
53
  end
55
54
 
56
55
  def page_details
57
56
  assert_dialog_layout
58
- @scrivito_default_widget_template = :details
57
+ @scrivito_widget_render_context = :details
59
58
  render @obj.details_view_path, layout: 'scrivito_dialog'
60
59
  rescue ActionView::MissingTemplate
61
60
  render 'scrivito/page_details', layout: 'scrivito_dialog'
62
61
  end
63
62
 
64
- # How to handle widget errors in +production+.
63
+ # @!method on_scrivito_widget_error(widget, error)
64
+ # How to handle widget errors in +production+.
65
65
  #
66
- # When an exception is raised from within a widget, the entire page is not available.
67
- # Often, this is the case in production if the developer has forgotten to handle specific
68
- # content scenarios such as empty date attributes, missing titles, unmanaged enum values, etc.
69
- # but also for simple coding mistakes during development.
66
+ # When an exception is raised from within a widget, the entire page is not available.
67
+ # Often, this is the case in production if the developer has forgotten to handle specific
68
+ # content scenarios such as empty date attributes, missing titles, unmanaged enum values, etc.
69
+ # but also for simple coding mistakes during development.
70
70
  #
71
- # Override this method to prevent the entire page from being unavailable due to widget errors.
71
+ # By default, Scrivito handles exceptions raised while rendering a widget by adding a
72
+ # placeholder text to the HTML indicating that the widget couldn't be rendered. In addition,
73
+ # the exception is logged alongside with its backtrace.
72
74
  #
73
- # The overridden method allows to:
74
- # * catch a widget error and replace it with an HTML placeholder.
75
- # * report a widget error to an external service like Honeybadger or Airbrake.
75
+ # Override this method to adapt this behaviour.
76
76
  #
77
- # This method is _not_ called if Rails is in the +development+ or +test+ environment.
78
- # In those environments, all widget errors are just raised.
77
+ # The overridden method allows to:
78
+ # * catch a widget error and replace it with an HTML placeholder.
79
+ # * report a widget error to an external service like Honeybadger or Airbrake.
79
80
  #
80
- # By default, this method just reraises the given error.
81
+ # This method is _not_ called if Rails is in the +development+ or +test+ environment.
82
+ # In those environments, all widget errors are just raised.
81
83
  #
82
- # @param widget [Scrivito::BasicWidget] the flawed widget
83
- # @param error [StandardError] the error that occurred
84
- # @raise [StandardError] if this method is not overridden, the +error+ passed to it is reraised.
84
+ # @param widget [Scrivito::BasicWidget] the flawed widget
85
+ # @param error [StandardError] the error that occurred
86
+ # @raise [StandardError] if this method is not overridden, the +error+ passed to it
87
+ # is reraised.
85
88
  #
86
- # @example Notify external service about widget error and render an HTML placeholder:
87
- # def on_scrivito_widget_error(widget, error)
88
- # # Report error to external service (e.g. Honeybadger or Airbrake):
89
- # Honeybadger.notify(error)
90
- # notify_airbrake(error)
89
+ # @example Notify external service about widget error and render an HTML placeholder:
90
+ # def on_scrivito_widget_error(widget, error)
91
+ # # Report error to external service (e.g. Honeybadger or Airbrake):
92
+ # Honeybadger.notify(error)
93
+ # notify_airbrake(error)
91
94
  #
92
- # # Replace corrupted widget output with a placeholder:
93
- # message = "Rendering #{widget.description_for_editor} failed with #{error.message}"
94
- # return "<!--#{message}-->".html_safe if Rails.env.production?
95
- # "<p>#{message}</p>".html_safe
96
- # end
97
- # @api public
98
- def on_scrivito_widget_error(widget, error)
99
- raise error
100
- end
95
+ # # Replace corrupted widget output with a placeholder:
96
+ # message = "Rendering #{widget.description_for_editor} failed with #{error.message}"
97
+ # return "<!--#{message}-->".html_safe if Rails.env.production?
98
+ # "<p>#{message}</p>".html_safe
99
+ # end
100
+ #
101
+ # @api public
102
+
103
+ # This empty line is necessary for yard to function correctly
101
104
 
102
105
  module ClassMethods
103
106
  #