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.
- checksums.yaml +5 -5
- data/app/assets/javascripts/para/admin.coffee +2 -1
- data/app/assets/javascripts/para/admin/table.coffee +3 -5
- data/app/assets/javascripts/para/admin/tree.coffee +68 -16
- data/app/assets/javascripts/para/inputs/multi-select-input.coffee +2 -3
- data/app/assets/javascripts/para/inputs/nested_many.coffee +7 -7
- data/app/assets/stylesheets/para/admin/src/_list.sass +7 -3
- data/app/helpers/para/admin/nested_inputs_helper.rb +17 -0
- data/app/helpers/para/admin/page_helper.rb +30 -3
- data/app/models/application_record.rb +3 -0
- data/app/models/para/application_record.rb +20 -0
- data/app/models/para/cache/item.rb +1 -1
- data/app/models/para/component/base.rb +16 -2
- data/app/models/para/component_resource.rb +1 -1
- data/app/models/para/component_section.rb +1 -1
- data/app/models/para/library/file.rb +1 -1
- data/app/models/para/page/section.rb +3 -10
- data/app/models/para/page/section_resource.rb +1 -1
- data/app/views/para/admin/resources/_navigation.html.haml +10 -0
- data/app/views/para/admin/shared/_navigation.html.haml +1 -1
- data/app/views/para/inputs/nested_many/_add_with_subclasses.html.haml +1 -1
- data/app/views/para/inputs/nested_one/_add_with_subclasses.html.haml +1 -1
- data/db/migrate/20201210152223_add_parent_component_to_para_components.rb +6 -0
- data/lib/para/components_configuration.rb +64 -13
- data/lib/para/job/base.rb +2 -2
- data/lib/para/version.rb +1 -1
- data/vendor/assets/javascripts/Sortable.js +4144 -0
- data/vendor/assets/javascripts/jquery.sortable.js +76 -0
- metadata +9 -5
- data/vendor/assets/javascripts/html5-sortable.js +0 -142
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9cd266c603b5629183643836cce701deb33e3322af0a98f021fb48f6922127c6
|
4
|
+
data.tar.gz: 36ff9796de7371559684be64fdb6f402acd5267997e94fb9f739e294d508a6d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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("
|
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
|
-
|
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.
|
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
|
-
|
126
|
-
placeholder: "<tr><td colspan='#{ columnsCount }'></td></tr>"
|
125
|
+
animation: 150
|
127
126
|
|
128
|
-
@$selectedItems.on('
|
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
|
-
|
16
|
+
animation: 150
|
17
|
+
onUpdate: $.proxy(@handleOrderingUpdated, this)
|
17
18
|
|
18
|
-
|
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('
|
35
|
-
@
|
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
|
-
@
|
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
|
-
|
16
|
-
|
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
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Para
|
2
2
|
module Component
|
3
|
-
class 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
|
-
|
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,21 +1,14 @@
|
|
1
1
|
module Para
|
2
2
|
module Page
|
3
|
-
class Section <
|
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
|
-
|
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
|
@@ -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
|