para 0.8.7 → 0.8.12

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 (30) hide show
  1. checksums.yaml +5 -5
  2. data/app/assets/javascripts/para/admin.coffee +2 -1
  3. data/app/assets/javascripts/para/admin/table.coffee +3 -5
  4. data/app/assets/javascripts/para/admin/tree.coffee +68 -16
  5. data/app/assets/javascripts/para/inputs/multi-select-input.coffee +2 -3
  6. data/app/assets/javascripts/para/inputs/nested_many.coffee +7 -7
  7. data/app/assets/stylesheets/para/admin/src/_list.sass +7 -3
  8. data/app/helpers/para/admin/nested_inputs_helper.rb +17 -0
  9. data/app/helpers/para/admin/page_helper.rb +30 -3
  10. data/app/models/application_record.rb +3 -0
  11. data/app/models/para/application_record.rb +20 -0
  12. data/app/models/para/cache/item.rb +1 -1
  13. data/app/models/para/component/base.rb +16 -2
  14. data/app/models/para/component_resource.rb +1 -1
  15. data/app/models/para/component_section.rb +1 -1
  16. data/app/models/para/library/file.rb +1 -1
  17. data/app/models/para/page/section.rb +3 -10
  18. data/app/models/para/page/section_resource.rb +1 -1
  19. data/app/views/para/admin/resources/_navigation.html.haml +10 -0
  20. data/app/views/para/admin/shared/_navigation.html.haml +1 -1
  21. data/app/views/para/inputs/nested_many/_add_with_subclasses.html.haml +1 -1
  22. data/app/views/para/inputs/nested_one/_add_with_subclasses.html.haml +1 -1
  23. data/db/migrate/20201210152223_add_parent_component_to_para_components.rb +6 -0
  24. data/lib/para/components_configuration.rb +64 -13
  25. data/lib/para/job/base.rb +2 -2
  26. data/lib/para/version.rb +1 -1
  27. data/vendor/assets/javascripts/Sortable.js +4144 -0
  28. data/vendor/assets/javascripts/jquery.sortable.js +76 -0
  29. metadata +9 -5
  30. data/vendor/assets/javascripts/html5-sortable.js +0 -142
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8f7d2729a7b4605f42e736da01ed85636b39232c
4
- data.tar.gz: 42a119d77586ef8db3eee57231c3a46154141e1c
2
+ SHA256:
3
+ metadata.gz: 9cd266c603b5629183643836cce701deb33e3322af0a98f021fb48f6922127c6
4
+ data.tar.gz: 36ff9796de7371559684be64fdb6f402acd5267997e94fb9f739e294d508a6d3
5
5
  SHA512:
6
- metadata.gz: 44f44a2d7340fa890359d5f259f90b591f2436609399028bb77a54237f1e49b95d81435a807c452234cc58904a2a7773bd469be58b373d4074244a540a325442
7
- data.tar.gz: 25280f88dd481faf6882df01416d85b31cc0a3fcc7204487ad53d7837bd7220463b0280141e1cdc203c577960ac103977abbd7d560ca9570fb00dd76325ec166
6
+ metadata.gz: ddab0a864ddf77239b958c1d03fc5a67c69bfbf551893c45fd000c8b2970f14d865e01b71c786935d0dd27907149c090335ec13413365805f29f25658bb5949e
7
+ data.tar.gz: f288a5bd00b8bea61093546d9d5ce9ff649c18165564f3fa5876cedfe856f486604ef6ad46b1b57155dc62c21859e4de24936f65169428386da5b42f5b64044b
@@ -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,11 +13,10 @@ 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: ->
19
+ handleOrderingUpdated: ->
21
20
  @$fieldsList.find('.form-fields:visible').each (i, el) ->
22
21
  $(el).find('.resource-position-field').val(i)
23
22
 
@@ -31,8 +30,9 @@ class Para.NestedManyField
31
30
  @openInsertedField($collapsible)
32
31
 
33
32
  if @orderable
34
- @$fieldsList.sortable('reload')
35
- @sortUpdate()
33
+ @$fieldsList.sortable('destroy')
34
+ @initializeOrderable()
35
+ @handleOrderingUpdated()
36
36
 
37
37
  $element.simpleForm()
38
38
 
@@ -44,7 +44,7 @@ class Para.NestedManyField
44
44
 
45
45
  # When a sub field is removed, update every sub field position
46
46
  afterRemoveField: ->
47
- @sortUpdate();
47
+ @handleOrderingUpdated();
48
48
 
49
49
  openInsertedField: ($field) ->
50
50
  $target = $($field.attr('href'))
@@ -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
@@ -2,18 +2,25 @@ module Para
2
2
  module Admin
3
3
  module PageHelper
4
4
  def page_top_bar(options = {})
5
- content_tag(:div, class: 'page-title row') do
5
+ top_bar = content_tag(:div, class: 'page-title row') do
6
6
  content_tag(:h1, options[:title]) +
7
7
 
8
8
  if (actions = actions_for(options[:type]))
9
9
  actions.map(&method(:build_action)).join('').html_safe
10
10
  end
11
11
  end
12
+
13
+ # Return both top bar and component navigation to be displayed at the top of the
14
+ # page.
15
+ top_bar + component_navigation
12
16
  end
13
17
 
14
18
  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
19
+ link_options = action.fetch(:link_options, {})
20
+ link_options[:class] ||= "btn btn-default btn-shadow"
21
+
22
+ content_tag(:div, class: 'actions-control pull-right') do
23
+ link_to(action[:url], link_options) do
17
24
  (
18
25
  (fa_icon(action[:icon], class: 'fa-fw') if action[:icon]) +
19
26
  action[:label]
@@ -27,6 +34,26 @@ module Para
27
34
  instance_eval(&action)
28
35
  end.compact
29
36
  end
37
+
38
+ def component_navigation
39
+ parent_component = @component && (
40
+ @component.parent_component ||
41
+ @component.child_components.any? && @component
42
+ )
43
+
44
+ return unless parent_component
45
+
46
+ # If the component has a `model_type` option, therefore, an associated model,
47
+ # we try to render the partial from the relative path of the model, else we
48
+ # use the component class as the base target path
49
+ partial_target = parent_component.try(:model_type) || parent_component
50
+
51
+ render partial: find_partial_for(partial_target, :navigation),
52
+ locals: {
53
+ parent_component: parent_component,
54
+ active_component: @component
55
+ }
56
+ end
30
57
  end
31
58
  end
32
59
  end
@@ -1,3 +1,6 @@
1
+ # Base class used for the `AdminUser` model class as parent but automatically overriden
2
+ # by application's own ApplicationRecord definition in Rails 5+
3
+ #
1
4
  class ApplicationRecord < ActiveRecord::Base
2
5
  self.abstract_class = true
3
6
  end
@@ -0,0 +1,20 @@
1
+ module Para
2
+ # Base class for all para-specific models.
3
+ #
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+
7
+ private
8
+
9
+ # Adds the `optional: true` option to the belongs_to calls inside the provided block,
10
+ # but only for Rails 5.1+
11
+ #
12
+ def self.with_belongs_to_optional_option_if_needed(&block)
13
+ if ActiveRecord::Associations::Builder::BelongsTo.valid_options({}).include?(:optional)
14
+ with_options(optional: true, &block)
15
+ else
16
+ block.call
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  module Para
5
5
  module Cache
6
- class Item < ActiveRecord::Base
6
+ class Item < Para::ApplicationRecord
7
7
  def value
8
8
  Marshal.load(::Base64.decode64(self[:value])) if self[:value].present?
9
9
  end
@@ -1,6 +1,6 @@
1
1
  module Para
2
2
  module Component
3
- class Base < ActiveRecord::Base
3
+ class Base < Para::ApplicationRecord
4
4
  self.table_name = 'para_components'
5
5
 
6
6
  class_attribute :component_name
@@ -16,7 +16,14 @@ module Para
16
16
 
17
17
  configurable_on :controller
18
18
 
19
- belongs_to :component_section, class_name: 'Para::ComponentSection'
19
+ with_belongs_to_optional_option_if_needed do
20
+ belongs_to :component_section, class_name: 'Para::ComponentSection'
21
+ belongs_to :parent_component, class_name: 'Para::Component::Base'
22
+ end
23
+
24
+ has_many :child_components, -> { ordered },
25
+ class_name: 'Para::Component::Base',
26
+ foreign_key: 'parent_component_id'
20
27
 
21
28
  validates :identifier, :type, presence: true
22
29
 
@@ -31,6 +38,13 @@ module Para
31
38
  )
32
39
  end
33
40
 
41
+ def main_navigation_name
42
+ ::I18n.t(
43
+ "components.main_navigation.#{ identifier }",
44
+ default: name
45
+ )
46
+ end
47
+
34
48
  def exportable?
35
49
  false
36
50
  end
@@ -1,5 +1,5 @@
1
1
  module Para
2
- class ComponentResource < ActiveRecord::Base
2
+ class ComponentResource < Para::ApplicationRecord
3
3
  belongs_to :component, class_name: 'Para::Component::Base'
4
4
  belongs_to :resource, polymorphic: true
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module Para
2
- class ComponentSection < ActiveRecord::Base
2
+ class ComponentSection < Para::ApplicationRecord
3
3
  has_many :components, -> { ordered }, class_name: 'Para::Component::Base',
4
4
  autosave: true, foreign_key: :component_section_id,
5
5
  dependent: :destroy
@@ -1,6 +1,6 @@
1
1
  module Para
2
2
  module Library
3
- class File < ActiveRecord::Base
3
+ class File < Para::ApplicationRecord
4
4
  if defined?(ActiveStorage)
5
5
  has_one_attached :attachment
6
6
 
@@ -1,21 +1,14 @@
1
1
  module Para
2
2
  module Page
3
- class Section < ActiveRecord::Base
3
+ class Section < Para::ApplicationRecord
4
4
  self.table_name = 'para_page_sections'
5
5
 
6
6
  acts_as_orderable parent: :page, as: :sections
7
7
 
8
- page_relation_options = { polymorphic: true }
9
-
10
- # Make Rails 5+ belongs_to relation optional for the parent page, to allow
11
- # using sections in other contexts that directly included into pages
12
- #
13
- if ActiveRecord::Associations::Builder::BelongsTo.valid_options({}).include?(:optional)
14
- page_relation_options[:optional] = true
8
+ with_belongs_to_optional_option_if_needed do
9
+ belongs_to :page, polymorphic: true
15
10
  end
16
11
 
17
- belongs_to :page, page_relation_options
18
-
19
12
  def css_class
20
13
  @css_class ||= self.class.name.demodulize.underscore.gsub(/_/, '-')
21
14
  end
@@ -1,6 +1,6 @@
1
1
  module Para
2
2
  module Page
3
- class SectionResource < ActiveRecord::Base
3
+ class SectionResource < Para::ApplicationRecord
4
4
  self.table_name = 'para_page_section_resources'
5
5
 
6
6
  acts_as_orderable parent: :section, as: :section_resources
@@ -0,0 +1,10 @@
1
+ .top-nav-tabs-affix-placeholder
2
+ %ul.top-nav-tabs-affix.nav.nav-tabs{ role: "tablist", "data-top-level-affix": true }
3
+ %li{ class: ("active" if active_component == parent_component) }
4
+ = link_to parent_component.path do
5
+ = parent_component.name
6
+
7
+ - parent_component.child_components.each do |child_component|
8
+ %li{ class: ("active" if active_component == child_component) }
9
+ = link_to child_component.path do
10
+ = child_component.name