para 0.8.3.3 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 34cf9dd56413ef5e25dc49969075a9cfe6344c04b49bcc2411dac16b58b6f2da
4
- data.tar.gz: 7e6ce611948b6d096f2479a91f451e9ff98708ddca65de0aa3d7cf44aa0b8912
2
+ SHA1:
3
+ metadata.gz: 2699b3fe934e113bc1679347cbe97ac94b0959c1
4
+ data.tar.gz: af695377ab693265ad2d1a28ae0e6588565dcc2d
5
5
  SHA512:
6
- metadata.gz: a956b05c65552b0c407891a52237e268d2c04d67cce364d7faf1163776d672ff8cc1a6259d2b6f8af024ffe588d6d55f2bd1dcc541c7085f2b7550c8a4f781c9
7
- data.tar.gz: 54fd0283d7cb5e928bf3a5b866feba4a8e2aa1a5978fc0756c6af5e4ff4228f292cc87beb142b0040060cfd1af4f8095a1ea2ce28326bd80524f9d29565d5eb2
6
+ metadata.gz: 44bf7f39afb5ac20c01ad50f91cf1c6ac34dbead25f7aa0d94dd4f9fa1fcd8c26f3228e85769a3d13859564fc6575da90b69294681da8dc4baabae733bbe2e91
7
+ data.tar.gz: ffeb5936f4db9c9b58a9e0d92135da5eb04ef24a5d5b4311a7ebf6cad355ba7b9b5fe8e50930ea981f1e812e4a483b26a38b67a6e3286573d38a0181c3193e14
@@ -6,7 +6,8 @@
6
6
  #= require jasny-bootstrap
7
7
  #= require simple_form_extension
8
8
  #= require jquery.scrollto
9
- #= require html5-sortable
9
+ #= require Sortable
10
+ #= require jquery.sortable
10
11
  #= require cocoon
11
12
  #= require jquery.remote-modal-form
12
13
  #= require jquery.iframe-transport
@@ -12,11 +12,9 @@ class Para.ResourceTable
12
12
 
13
13
  @$tbody.sortable
14
14
  handle: '.order-anchor'
15
- forcePlaceholderSize: true
16
- items: 'tr'
17
- placeholder: "<tr><td colspan=\"100%\" class=\"sortable-placeholder\"></td></tr>"
18
-
19
- @$tbody.on('sortupdate', $.proxy(@sortUpdate, this))
15
+ draggable: 'tr'
16
+ ghostClass: 'sortable-placeholder'
17
+ onUpdate: $.proxy(@sortUpdate, this)
20
18
 
21
19
  sortUpdate: ->
22
20
  @$tbody.find('tr').each (i, el) ->
@@ -6,22 +6,75 @@ class Para.ResourceTree
6
6
  @orderUrl = @$el.data('url')
7
7
  @maxDepth = parseInt @$el.data('max-depth')
8
8
 
9
- $(".tree")
10
- .sortable
11
- handle: ".handle"
12
- items: ".node"
13
- connectWith: ".tree"
14
- .on('sortupdate', $.proxy(@sortUpdate, this))
15
-
16
- sortUpdate: (e, data) ->
17
- @handlePlaceholder($el) for $el in [data.endparent, data.startparent, data.item.find('.tree')]
9
+ # Each is needed here as the sortable jQuery plugin doesn't loop over each found node
10
+ # but initializes the tree on the first found element.
11
+ $(".tree").each(@initializeSubTree)
12
+
13
+ initializeSubTree: (_i, el) =>
14
+ $(el).sortable(
15
+ group: "tree"
16
+ handle: ".handle"
17
+ draggable: ".node"
18
+ fallbackOnBody: true
19
+ swapThreshold: 0.65
20
+ animation: 150
21
+ onSort: @handleOrderUpdated
22
+ onMove: @isMovementValid
23
+ )
24
+
25
+ # Note : This method is called often (many times per second while we're dragging) and
26
+ # takes quite some processing.
27
+ isMovementValid: (e) =>
28
+ $movedNode = $(e.dragged)
29
+ $target = $(e.related)
30
+
31
+ # Calculate the deepness of the moved and target nodes
32
+ movedNodeDeepness = $movedNode.parents(".node").length - 1
33
+ # If the target is a node, the moved node root deepness is gonna be the same as the
34
+ # the target one, else the tree's parent node is counted also
35
+ targetDeepness = $target.parents(".node").length - 1
36
+
37
+ # Find the deepest node in the subtree of the moved node
38
+ $movedNodeSubtrees = $movedNode.find(".tree")
39
+ movedNodeTreeDeepness = 0
40
+
41
+ # The movedNodeTreeDeepness is the maximum deepness of a child node of the current
42
+ # moved node, relative to the moved node
43
+ $movedNodeSubtrees.each (i, el) =>
44
+ subtreeDeepness = $(el).parents(".node").length - 1
45
+ subtreeRelativeDeepness = subtreeDeepness - movedNodeDeepness
46
+ movedNodeTreeDeepness = Math.max(movedNodeTreeDeepness, subtreeRelativeDeepness)
47
+
48
+ # Calculate the final subtree deepness once we move the whole moved node subtree to
49
+ # its target position
50
+ finalSubtreeDeepnessAfterMove = movedNodeTreeDeepness + targetDeepness
51
+
52
+ # We finally validate the move only if the final subtree deepness is lower than the
53
+ # maximum allowed depth
54
+ finalSubtreeDeepnessAfterMove <= @maxDepth
55
+
56
+ handleOrderUpdated: (e) =>
57
+
58
+ # Get all involved tree leaves that may include a subtree
59
+ treeLeaves = [$(e.target), $(e.from), $(e.item).find('.tree')]
60
+
61
+ # Update their placeholder display wether they can be a drop target or not
62
+ @handlePlaceholder($el) for $el in treeLeaves
63
+ # Save the tree structure on the server
18
64
  @updateOrder()
19
65
 
66
+ # This method checks wether a given tree leaf can be a drop target, depending
67
+ # on wether it's located at the maximum allowed depth for the tree or not, and adds or
68
+ # remove a the visual placeholder to indicate its droppable state.
69
+ #
20
70
  handlePlaceholder: ($el) ->
21
71
  $placeholder = $el.find("> .placeholder")
22
- if $el.parents('.tree').length - 1 >= @maxDepth or $el.find('> .node').length
72
+ parentsCount = $el.parents('.node').length - 1
73
+ hasChildren = $el.find('> .node').length
74
+
75
+ if parentsCount >= @maxDepth or hasChildren
23
76
  $placeholder.hide()
24
- $el.children("> .tree").each (index, el) => @handlePlaceholder $(el)
77
+ $el.children(".tree").each (index, el) => @handlePlaceholder($(el))
25
78
  else
26
79
  $placeholder.show()
27
80
 
@@ -29,9 +82,8 @@ class Para.ResourceTree
29
82
  Para.ajax(
30
83
  url: @orderUrl
31
84
  method: 'patch'
32
- data:
33
- resources: @buildOrderedData()
34
- success: $.proxy(@orderUpdated, this)
85
+ data: { resources: @buildOrderedData() }
86
+ success: @orderUpdated
35
87
  )
36
88
 
37
89
  buildOrderedData: ->
@@ -41,11 +93,11 @@ class Para.ResourceTree
41
93
  data[index] = {
42
94
  id: $this.data("id"),
43
95
  position: index,
44
- parent_id: $this.parent().parent().data("id")
96
+ parent_id: $this.parents(".node:first").data("id")
45
97
  }
46
98
  data
47
99
 
48
- orderUpdated: ->
100
+ orderUpdated: =>
49
101
  # TODO: Add flash message to display ordering success
50
102
 
51
103
  $(document).on 'page:change turbolinks:load', ->
@@ -122,10 +122,9 @@ class Para.MultiSelectInput extends Vertebra.View
122
122
 
123
123
  @$selectedItems.sortable
124
124
  handle: '.order-anchor'
125
- forcePlaceholderSize: true
126
- placeholder: "<tr><td colspan='#{ columnsCount }'></td></tr>"
125
+ animation: 150
127
126
 
128
- @$selectedItems.on('sortupdate', @selectedItemsSorted)
127
+ @$selectedItems.on('sort', @selectedItemsSorted)
129
128
 
130
129
  selectedItemsSorted: =>
131
130
  indices = {}
@@ -13,17 +13,17 @@ class Para.NestedManyField
13
13
 
14
14
  @$fieldsList.sortable
15
15
  handle: '.order-anchor'
16
- forcePlaceholderSize: true
16
+ animation: 150
17
+ onUpdate: $.proxy(@handleOrderingUpdated, this)
17
18
 
18
- @$fieldsList.on('sortupdate', $.proxy(@sortUpdate, this))
19
-
20
- sortUpdate: ->
21
- @$fieldsList.find('.form-fields').each (i, el) ->
19
+ handleOrderingUpdated: ->
20
+ @$fieldsList.find('.form-fields:visible').each (i, el) ->
22
21
  $(el).find('.resource-position-field').val(i)
23
22
 
24
23
  initializeCocoon: ->
25
24
  @$fieldsList.on 'cocoon:after-insert', $.proxy(@afterInsertField, this)
26
25
  @$fieldsList.on 'cocoon:before-remove', $.proxy(@beforeRemoveField, this)
26
+ @$fieldsList.on 'cocoon:after-remove', $.proxy(@afterRemoveField, this)
27
27
 
28
28
  afterInsertField: (e, $element) ->
29
29
  if ($collapsible = $element.find('[data-open-on-insert="true"]')).length
@@ -32,7 +32,7 @@ class Para.NestedManyField
32
32
  if @orderable
33
33
  @$fieldsList.sortable('destroy')
34
34
  @initializeOrderable()
35
- @sortUpdate()
35
+ @handleOrderingUpdated()
36
36
 
37
37
  $element.simpleForm()
38
38
 
@@ -42,6 +42,10 @@ class Para.NestedManyField
42
42
  # create an empty nested resource otherwise
43
43
  $nextEl.remove() if $nextEl.is('[data-attributes-mappings]') and not $element.is('[data-persisted]')
44
44
 
45
+ # When a sub field is removed, update every sub field position
46
+ afterRemoveField: ->
47
+ @handleOrderingUpdated();
48
+
45
49
  openInsertedField: ($field) ->
46
50
  $target = $($field.attr('href'))
47
51
  $target.collapse('show')
@@ -9,7 +9,11 @@
9
9
  .actions-control
10
10
  list-style: none
11
11
  padding: 0
12
- margin: 0
12
+ margin: 0 15px 0 0
13
+
14
+ &:last-child
15
+ margin-right: 0
16
+
13
17
  li
14
18
  +inline-block
15
19
  padding: 0 5px
@@ -42,7 +46,7 @@
42
46
 
43
47
  .page-list-heading
44
48
  border-bottom: 1px solid $gray-light
45
-
49
+
46
50
  .page-list-body:last-child
47
51
  padding-bottom: 0
48
52
 
@@ -76,7 +80,7 @@
76
80
  color: $gray
77
81
  border-color: transparent
78
82
  &:hover
79
- color: $gray-dark
83
+ color: $gray-dark
80
84
 
81
85
 
82
86
  .page-entries-info
@@ -0,0 +1,17 @@
1
+ module Para
2
+ module Admin
3
+ module NestedInputsHelper
4
+ # Helper that allows filling a parent association for a given resource, based on the
5
+ # inverse_of option of the parent resource association.
6
+ #
7
+ def with_inverse_association_for(resource, attribute_name, parent_resource)
8
+ resource.tap do
9
+ association_name = parent_resource.association(attribute_name).options[:inverse_of]
10
+ return resource unless association_name
11
+
12
+ resource.association(association_name).replace(parent_resource)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -12,8 +12,11 @@ module Para
12
12
  end
13
13
 
14
14
  def build_action(action)
15
- content_tag(:div, class: 'actions-control pull-right') do
16
- link_to(action[:url], class: 'btn btn-default btn-shadow') do
15
+ link_options = action.fetch(:link_options, {})
16
+ link_options[:class] ||= "btn btn-default btn-shadow"
17
+
18
+ content_tag(:div, class: 'actions-control pull-right') do
19
+ link_to(action[:url], link_options) do
17
20
  (
18
21
  (fa_icon(action[:icon], class: 'fa-fw') if action[:icon]) +
19
22
  action[:label]
@@ -21,7 +21,7 @@ module Para
21
21
  allow_adding_resource = options.fetch(:addable, true)
22
22
 
23
23
  partial = :list
24
- partial = :tree if model.respond_to? :roots
24
+ partial = :tree if model.respond_to?(:roots) && can?(:tree, model)
25
25
 
26
26
  render(
27
27
  partial: find_partial_for(relation, partial),
@@ -48,6 +48,8 @@ module Para
48
48
  end
49
49
 
50
50
  def add_button_for(component, relation, model)
51
+ return unless can?(:create, model)
52
+
51
53
  partial_name = if component.subclassable?
52
54
  :subclassable_add_button
53
55
  else
@@ -4,6 +4,10 @@
4
4
  = render partial: find_partial_for(relation, :actions), locals: { relation: relation, component: component, model: model, allow_adding_resource: allow_adding_resource }
5
5
 
6
6
  - if resources.length > 0
7
+ - if resources.total_count > 100
8
+ .page-entries-info
9
+ = page_entries(resources)
10
+
7
11
  %ul.tree.root-tree{ data: { url: component.relation_path(model.model_name.route_key, action: :tree), :"max-depth" => max_depth_for(model) } }
8
12
  = render partial: find_partial_for(model, 'tree_item'), collection: resources.roots, as: :root, locals: { model: model}
9
13
 
@@ -1,10 +1,10 @@
1
1
  .nested-many-field{ class: [('orderable' if orderable), ('nested-many-field-inset' if inset)] }
2
2
  .fields-list{ id: dom_identifier }
3
3
  = form.simple_fields_for attribute_name, resources, nested_attribute_name: attribute_name, orderable: orderable, track_attribute_mappings: render_partial do |nested_form|
4
- = render partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), locals: { form: nested_form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params }
4
+ = render partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), locals: { form: nested_form, model: nested_form.object.class, subclass: subclass, nested_locals: nested_locals, inset: inset, uncollapsed: uncollapsed, render_partial: render_partial, remote_partial_params: remote_partial_params }
5
5
  -# Add button
6
6
  - if add_button
7
7
  - if subclass
8
- = render partial: 'para/inputs/nested_many/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, subclasses: subclasses, inset: inset, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
8
+ = render partial: 'para/inputs/nested_many/add_with_subclasses', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, subclasses: subclasses, inset: inset, uncollapsed: uncollapsed, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
9
9
  - else
10
- = render partial: 'para/inputs/nested_many/add', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, inset: inset, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
10
+ = render partial: 'para/inputs/nested_many/add', locals: { form: form, model: model, dom_identifier: dom_identifier, nested_locals: nested_locals, attribute_name: attribute_name, orderable: orderable, inset: inset, uncollapsed: uncollapsed, add_button_label: add_button_label, add_button_class: add_button_class, render_partial: render_partial, remote_partial_params: remote_partial_params }
@@ -1,2 +1,2 @@
1
- = link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: "btn btn-shadow add-button nested-many-inset-add-button #{add_button_class}", data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
1
+ = link_to_add_association form, attribute_name, partial: find_partial_for(model, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: "btn btn-shadow add-button nested-many-inset-add-button #{add_button_class}", data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: model, nested_locals: nested_locals, inset: inset, uncollapsed: uncollapsed, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
2
2
  = add_button_label
@@ -5,5 +5,5 @@
5
5
  %ul.dropdown-menu
6
6
  - subclasses.each do |submodel|
7
7
  %li
8
- = link_to_add_association form, attribute_name, wrap_object: proc { submodel.new }, partial: find_partial_for(submodel, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: submodel, nested_locals: nested_locals, inset: inset, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
8
+ = link_to_add_association form, attribute_name, wrap_object: proc { with_inverse_association_for(submodel.new, attribute_name, form.object) }, partial: find_partial_for(submodel, 'nested_many/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, orderable: orderable, locals: { model: submodel, nested_locals: nested_locals, inset: inset, uncollapsed: uncollapsed, render_partial: render_partial, remote_partial_params: remote_partial_params } } do
9
9
  = submodel.model_name.human
@@ -11,7 +11,7 @@
11
11
 
12
12
  = form.remove_association_button
13
13
 
14
- .panel-collapse.form-inputs.collapse{ id: form.nested_resource_dom_id, class: ('in' if inset && form.object.persisted?), data: { rendered: render_partial, render_path: @component.path(remote_partial_params), id: form.object.id, :"object-name" => form.object_name, :"model-name" => model.name } }
14
+ .panel-collapse.form-inputs.collapse{ id: form.nested_resource_dom_id, class: ('in' if uncollapsed && form.object.persisted?), data: { rendered: render_partial, render_path: @component.path(remote_partial_params), id: form.object.id, :"object-name" => form.object_name, :"model-name" => model.name } }
15
15
  .panel-body{ data: { :"nested-form-container" => true } }
16
16
  - if render_partial
17
17
  = render partial: find_partial_for(model, :fields), locals: { form: form }.merge(nested_locals)
@@ -6,5 +6,5 @@
6
6
  %ul.dropdown-menu
7
7
  - subclasses.each do |submodel|
8
8
  %li
9
- = link_to_add_association form, attribute_name, wrap_object: proc { submodel.new }, partial: find_partial_for(submodel, 'nested_one/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, locals: { form: form, model: submodel, subclass: subclass, nested_locals: nested_locals } } do
9
+ = link_to_add_association form, attribute_name, wrap_object: proc { with_inverse_association_for(submodel.new, attribute_name, form.object) }, partial: find_partial_for(submodel, 'nested_one/container', partial_dir: 'inputs'), form_name: 'form', class: 'dropdown-link', data: { :'association-insertion-node' => "##{ dom_identifier }", :'association-insertion-method' => 'append' }, render_options: { nested_attribute_name: attribute_name, locals: { form: form, model: submodel, subclass: subclass, nested_locals: nested_locals } } do
10
10
  = submodel.model_name.human
@@ -27,9 +27,17 @@ Para.config do |config|
27
27
  # config.resource_name_methods += [:full_name]
28
28
 
29
29
  # Configure the theme navigation bar color not gray but blue darker
30
- #
30
+ #
31
31
  # config.dark_theme = true
32
32
 
33
+ # Do not uncollapse by default "inset" nested fields, which makes all such nested field
34
+ # blocks get collapsed on page load, and can be easier to read for big blocks.
35
+ #
36
+ # This also speeds up page loading when used in conjunction to remote partial loading
37
+ # for nested fields
38
+ #
39
+ # config.uncollapse_inset_nested_fields = false
40
+
33
41
  # Set if the Para::Breadcrumb::Controller module should be included into the
34
42
  # app, allowing easy breadcrumbs management in the app.
35
43
  #
@@ -42,6 +42,9 @@ module Para
42
42
  mattr_accessor :dark_theme
43
43
  @@dark_theme = false
44
44
 
45
+ mattr_accessor :uncollapse_inset_nested_fields
46
+ @@uncollapse_inset_nested_fields = true
47
+
45
48
  # Allows changing default cache store used by Para to store jobs through
46
49
  # the ActiveJob::Status gem
47
50
  #
@@ -27,7 +27,9 @@ module Para
27
27
  end
28
28
 
29
29
  def actions_buttons_for(options)
30
- names = [:submit, :submit_and_edit, :submit_and_add_another, :cancel]
30
+ names = [:submit, :submit_and_edit]
31
+ names << :submit_and_add_another if template.can?(:create, object.class)
32
+ names << :cancel
31
33
 
32
34
  names.select! { |name| Array.wrap(options[:only]).include?(name) } if options[:only]
33
35
  names.reject! { |name| Array.wrap(options[:except]).include?(name) } if options[:except]
@@ -32,6 +32,7 @@ module Para
32
32
  add_button_label: add_button_label,
33
33
  add_button_class: add_button_class,
34
34
  inset: inset?,
35
+ uncollapsed: uncollapsed?,
35
36
  render_partial: render_partial?,
36
37
  remote_partial_params: remote_partial_params
37
38
  }
@@ -50,9 +51,13 @@ module Para
50
51
  def inset?
51
52
  options.fetch(:inset, false)
52
53
  end
53
-
54
+
55
+ def uncollapsed?
56
+ inset? && Para.config.uncollapse_inset_nested_fields
57
+ end
58
+
54
59
  def render_partial?
55
- options[:render_partial] || object.errors.any? || (object.persisted? && inset?)
60
+ options[:render_partial] || object.errors.any? || (object.persisted? && uncollapsed?)
56
61
  end
57
62
 
58
63
  def remote_partial_params
@@ -33,9 +33,9 @@ module Para
33
33
  end
34
34
 
35
35
  def ensure_total_progress
36
- return if @total_progress
36
+ return if @_total_progress
37
37
 
38
- @total_progress ||= if respond_to?(:total_progress, true)
38
+ @_total_progress ||= if respond_to?(:total_progress, true)
39
39
  progress.total = total_progress
40
40
  else
41
41
  progress[:total]
@@ -2,14 +2,14 @@ module Para
2
2
  module Markup
3
3
  class ResourcesButtons < Para::Markup::Component
4
4
  attr_reader :component
5
-
5
+
6
6
  def initialize(component, view)
7
7
  @component = component
8
8
  super(view)
9
9
  end
10
10
 
11
11
  def clone_button(resource)
12
- return unless resource.class.cloneable?
12
+ return unless resource.class.cloneable? && view.can?(:clone, resource)
13
13
 
14
14
  path = component.relation_path(
15
15
  resource, action: :clone, return_to: view.request.fullpath
@@ -29,6 +29,8 @@ module Para
29
29
  end
30
30
 
31
31
  def edit_button(resource)
32
+ return unless view.can?(:edit, resource)
33
+
32
34
  path = component.relation_path(
33
35
  resource, action: :edit, return_to: view.request.fullpath
34
36
  )
@@ -39,6 +41,8 @@ module Para
39
41
  end
40
42
 
41
43
  def delete_button(resource)
44
+ return unless view.can?(:delete, resource)
45
+
42
46
  path = component.relation_path(resource, return_to: view.request.fullpath)
43
47
 
44
48
  options = {