para 0.7.4 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/helpers/para/markup_helper.rb +2 -2
- data/app/views/para/admin/resources/_table.html.haml +1 -1
- data/app/views/para/admin/resources/_tree_item.html.haml +5 -5
- data/lib/generators/para/table/templates/_table.html.haml +1 -1
- data/lib/para/active_storage_downloader.rb +23 -0
- data/lib/para/cloneable/attachments_cloner.rb +36 -0
- data/lib/para/cloneable/include_tree_builder.rb +67 -0
- data/lib/para/cloneable.rb +17 -3
- data/lib/para/job/base.rb +1 -1
- data/lib/para/markup/resources_buttons.rb +61 -0
- data/lib/para/markup/resources_table.rb +9 -52
- data/lib/para/markup.rb +1 -0
- data/lib/para/version.rb +1 -1
- data/lib/para.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47cd4fad8f6308c2c3ae1948bea744640d0ce772
|
4
|
+
data.tar.gz: e6f7cf338d48a9ef60f696221413c263d34dcc10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e38d091236ea396e0836560be1128c551393495ea6e967ee6a40f73ccfc301e1ded6e5682b5b23f65e1ae7406c8fc1e820d57a8c9edb3d53fc109735620b1960
|
7
|
+
data.tar.gz: c31d521f7c6a38eadb0646ecdc48f23ca8e258c4b0b285c5b90301013175fde19792716654d7ceeca9180895853d7c4c237dc9c1a10e41d7d5202cad8fb33c41
|
@@ -4,8 +4,8 @@ module Para
|
|
4
4
|
Para::Markup::Alert.new(self).container(message, options, &block)
|
5
5
|
end
|
6
6
|
|
7
|
-
def resources_table(options
|
8
|
-
Para::Markup::ResourcesTable.new(self).container(options, &block)
|
7
|
+
def resources_table(component:, **options, &block)
|
8
|
+
Para::Markup::ResourcesTable.new(component, self).container(options, &block)
|
9
9
|
end
|
10
10
|
|
11
11
|
def panel(options = {}, &block)
|
@@ -4,13 +4,13 @@
|
|
4
4
|
%i.sort-dots
|
5
5
|
%span.node-name
|
6
6
|
= resource_name(root)
|
7
|
+
|
8
|
+
- buttons = Para::Markup::ResourcesButtons.new(@component, self)
|
9
|
+
- actions = Para::Markup::ResourcesTable.default_actions
|
7
10
|
|
8
11
|
.pull-right
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
= link_to @component.relation_path(root), method: :delete, data: { confirm: ::I18n.t('para.list.delete_confirmation') }, class: 'btn btn-icon-danger btn-shadow btn-sm hint--left', :'aria-label' => ::I18n.t('para.shared.destroy') do
|
13
|
-
%i.fa.fa-times
|
12
|
+
- actions.each do |action|
|
13
|
+
= buttons.send(:"#{action}_button", root)
|
14
14
|
|
15
15
|
.clearfix
|
16
16
|
%ul.tree
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Para
|
2
|
+
class ActiveStorageDownloader
|
3
|
+
if defined?(ActiveStorage)
|
4
|
+
include ActiveStorage::Downloading
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :attachment
|
8
|
+
|
9
|
+
delegate :blob, to: :attachment
|
10
|
+
|
11
|
+
def initialize(attachment)
|
12
|
+
@attachment = attachment
|
13
|
+
end
|
14
|
+
|
15
|
+
if defined?(ActiveStorage)
|
16
|
+
public :download_blob_to_tempfile
|
17
|
+
else
|
18
|
+
define_method(:download_blob_to_tempfile) do
|
19
|
+
raise NoMethodError, "ActiveStorage is not included in your application"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Para
|
2
|
+
module Cloneable
|
3
|
+
class AttachmentsCloner
|
4
|
+
attr_reader :original, :clone
|
5
|
+
|
6
|
+
def initialize(original, clone)
|
7
|
+
@original = original
|
8
|
+
@clone = clone
|
9
|
+
end
|
10
|
+
|
11
|
+
def clone!
|
12
|
+
return unless defined?(ActiveStorage)
|
13
|
+
|
14
|
+
attachment_reflections = original.class.reflections.select { |k, v|
|
15
|
+
k.to_s.match(/_attachment\z/) &&
|
16
|
+
v.options[:class_name] == "ActiveStorage::Attachment"
|
17
|
+
}
|
18
|
+
|
19
|
+
attachment_reflections.each do |name, reflection|
|
20
|
+
original_attachment = original.send(name)
|
21
|
+
next unless original_attachment
|
22
|
+
|
23
|
+
association_name = name.gsub(/_attachment\z/, "")
|
24
|
+
|
25
|
+
Para::ActiveStorageDownloader.new(original_attachment).download_blob_to_tempfile do |tempfile|
|
26
|
+
clone.send(association_name).attach({
|
27
|
+
io: tempfile,
|
28
|
+
filename: original_attachment.blob.filename,
|
29
|
+
content_type: original_attachment.blob.content_type
|
30
|
+
})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Para
|
2
|
+
module Cloneable
|
3
|
+
class IncludeTreeBuilder
|
4
|
+
attr_reader :resource, :cloneable_options
|
5
|
+
|
6
|
+
def initialize(resource, cloneable_options)
|
7
|
+
@resource = resource
|
8
|
+
@cloneable_options = cloneable_options.deep_dup
|
9
|
+
end
|
10
|
+
|
11
|
+
def build
|
12
|
+
include_tree = build_cloneable_tree(resource, cloneable_options[:include])
|
13
|
+
cloneable_options[:include] = clean_include_tree(include_tree)
|
14
|
+
cloneable_options
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_cloneable_tree(resource, include)
|
20
|
+
include.each_with_object({}) do |reflection_name, hash|
|
21
|
+
hash[reflection_name] = {}
|
22
|
+
|
23
|
+
if (reflection = resource.class.reflections[reflection_name.to_s])
|
24
|
+
reflection_options = hash[reflection_name]
|
25
|
+
association_target = resource.send(reflection_name)
|
26
|
+
|
27
|
+
if reflection.collection?
|
28
|
+
association_target.each do |nested_resource|
|
29
|
+
add_reflection_options(reflection_options, nested_resource)
|
30
|
+
end
|
31
|
+
else
|
32
|
+
add_reflection_options(reflection_options, association_target)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_reflection_options(reflection_options, nested_resource)
|
39
|
+
options = nested_resource.class.try(:cloneable_options)
|
40
|
+
return reflection_options unless options
|
41
|
+
|
42
|
+
include_options = options[:include]
|
43
|
+
target_options = build_cloneable_tree(nested_resource, include_options)
|
44
|
+
reflection_options.deep_merge!(target_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def clean_include_tree(tree)
|
48
|
+
shallow_relations = []
|
49
|
+
deep_relations = {}
|
50
|
+
|
51
|
+
tree.each do |key, value|
|
52
|
+
if !value || value.empty?
|
53
|
+
shallow_relations << key
|
54
|
+
else
|
55
|
+
deep_relations[key] = clean_include_tree(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if deep_relations.empty?
|
60
|
+
shallow_relations
|
61
|
+
else
|
62
|
+
shallow_relations + [deep_relations]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/para/cloneable.rb
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
module Para
|
2
2
|
module Cloneable
|
3
3
|
extend ActiveSupport::Concern
|
4
|
+
extend ActiveSupport::Autoload
|
4
5
|
|
5
6
|
included do
|
6
7
|
class_attribute :cloneable_options
|
7
8
|
end
|
8
9
|
|
10
|
+
eager_autoload do
|
11
|
+
autoload :IncludeTreeBuilder
|
12
|
+
autoload :AttachmentsCloner
|
13
|
+
end
|
14
|
+
|
9
15
|
# Wraps the deep_cloneable gem #deep_clone method to allow using the
|
10
16
|
# predefined associations and options from our Cloneable.acts_as_cloneable
|
11
17
|
# macro.
|
12
18
|
#
|
13
19
|
def deep_clone!(options = {})
|
14
|
-
|
20
|
+
processed_options = Para::Cloneable::IncludeTreeBuilder.new(self, cloneable_options).build
|
21
|
+
options = options.reverse_merge(processed_options)
|
15
22
|
callback = build_clone_callback(options.delete(:prepare))
|
16
|
-
|
17
|
-
deep_clone(options,
|
23
|
+
|
24
|
+
deep_clone(options) do |original, clone|
|
25
|
+
Para::Cloneable::AttachmentsCloner.new(original, clone).clone!
|
26
|
+
callback&.call(original, clone)
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
30
|
private
|
@@ -49,6 +59,10 @@ module Para
|
|
49
59
|
|
50
60
|
options = args.extract_options!
|
51
61
|
|
62
|
+
# Allow nested STI resources to define their own relations to clone even
|
63
|
+
# if other sibling models don't define those relations
|
64
|
+
options[:skip_missing_associations] = true
|
65
|
+
|
52
66
|
self.cloneable_options = options.reverse_merge({
|
53
67
|
include: args
|
54
68
|
})
|
data/lib/para/job/base.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Para
|
2
|
+
module Markup
|
3
|
+
class ResourcesButtons < Para::Markup::Component
|
4
|
+
attr_reader :component
|
5
|
+
|
6
|
+
def initialize(component, view)
|
7
|
+
@component = component
|
8
|
+
super(view)
|
9
|
+
end
|
10
|
+
|
11
|
+
def clone_button(resource)
|
12
|
+
return unless resource.class.cloneable?
|
13
|
+
|
14
|
+
path = component.relation_path(
|
15
|
+
resource, action: :clone, return_to: view.request.fullpath
|
16
|
+
)
|
17
|
+
|
18
|
+
options = {
|
19
|
+
method: :post,
|
20
|
+
class: 'btn btn-sm btn-icon-info btn-shadow hint--left',
|
21
|
+
aria: {
|
22
|
+
label: ::I18n.t('para.shared.copy')
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
view.link_to(path, options) do
|
27
|
+
content_tag(:i, '', class: 'fa fa-copy')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def edit_button(resource)
|
32
|
+
path = component.relation_path(
|
33
|
+
resource, action: :edit, return_to: view.request.fullpath
|
34
|
+
)
|
35
|
+
|
36
|
+
view.link_to(path, class: 'btn btn-sm btn-icon-primary btn-shadow hint--left', aria: { label: ::I18n.t('para.shared.edit') }) do
|
37
|
+
content_tag(:i, '', class: 'fa fa-pencil')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_button(resource)
|
42
|
+
path = component.relation_path(resource, return_to: view.request.fullpath)
|
43
|
+
|
44
|
+
options = {
|
45
|
+
method: :delete,
|
46
|
+
data: {
|
47
|
+
confirm: ::I18n.t('para.list.delete_confirmation')
|
48
|
+
},
|
49
|
+
class: 'btn btn-sm btn-icon-danger btn-shadow hint--left',
|
50
|
+
aria: {
|
51
|
+
label: ::I18n.t('para.shared.destroy')
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
view.link_to(path, options) do
|
56
|
+
content_tag(:i, '', class: 'fa fa-times')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -4,11 +4,15 @@ module Para
|
|
4
4
|
class_attribute :default_actions
|
5
5
|
self.default_actions = [:edit, :clone, :delete]
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :component, :model, :orderable, :actions
|
8
|
+
|
9
|
+
def initialize(component, view)
|
10
|
+
@component = component
|
11
|
+
super(view)
|
12
|
+
end
|
8
13
|
|
9
14
|
def container(options = {}, &block)
|
10
15
|
@model = options.delete(:model)
|
11
|
-
@component = options.delete(:component)
|
12
16
|
|
13
17
|
if !options.key?(:orderable) || options.delete(:orderable)
|
14
18
|
@orderable = model.orderable?
|
@@ -131,62 +135,15 @@ module Para
|
|
131
135
|
end
|
132
136
|
|
133
137
|
def actions_cell(resource)
|
138
|
+
buttons = ResourcesButtons.new(component, view)
|
139
|
+
|
134
140
|
content_tag(:td, class: 'table-row-actions') do
|
135
141
|
actions.map do |type|
|
136
|
-
send(:"#{ type }_button", resource)
|
142
|
+
buttons.send(:"#{ type }_button", resource)
|
137
143
|
end.compact.join.html_safe
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
141
|
-
def clone_button(resource)
|
142
|
-
return unless resource.class.cloneable?
|
143
|
-
|
144
|
-
path = component.relation_path(
|
145
|
-
resource, action: :clone, return_to: view.request.fullpath
|
146
|
-
)
|
147
|
-
|
148
|
-
options = {
|
149
|
-
method: :post,
|
150
|
-
class: 'btn btn-sm btn-icon-info btn-shadow hint--left',
|
151
|
-
aria: {
|
152
|
-
label: ::I18n.t('para.shared.copy')
|
153
|
-
}
|
154
|
-
}
|
155
|
-
|
156
|
-
view.link_to(path, options) do
|
157
|
-
content_tag(:i, '', class: 'fa fa-copy')
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def edit_button(resource)
|
162
|
-
path = component.relation_path(
|
163
|
-
resource, action: :edit, return_to: view.request.fullpath
|
164
|
-
)
|
165
|
-
|
166
|
-
view.link_to(path, class: 'btn btn-sm btn-icon-primary btn-shadow hint--left', aria: { label: ::I18n.t('para.shared.edit') }) do
|
167
|
-
content_tag(:i, '', class: 'fa fa-pencil')
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def delete_button(resource)
|
172
|
-
path = component.relation_path(resource, return_to: view.request.fullpath)
|
173
|
-
|
174
|
-
options = {
|
175
|
-
method: :delete,
|
176
|
-
data: {
|
177
|
-
confirm: ::I18n.t('para.list.delete_confirmation')
|
178
|
-
},
|
179
|
-
class: 'btn btn-sm btn-icon-danger btn-shadow hint--left',
|
180
|
-
aria: {
|
181
|
-
label: ::I18n.t('para.shared.destroy')
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
view.link_to(path, options) do
|
186
|
-
content_tag(:i, '', class: 'fa fa-times')
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
147
|
private
|
191
148
|
|
192
149
|
def search
|
data/lib/para/markup.rb
CHANGED
data/lib/para/version.rb
CHANGED
data/lib/para.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: para
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Valentin Ballestrino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -701,6 +701,7 @@ files:
|
|
701
701
|
- lib/generators/para/table/templates/_table.html.haml
|
702
702
|
- lib/generators/para/tree_item/tree_item_generator.rb
|
703
703
|
- lib/para.rb
|
704
|
+
- lib/para/active_storage_downloader.rb
|
704
705
|
- lib/para/attribute_field.rb
|
705
706
|
- lib/para/attribute_field/base.rb
|
706
707
|
- lib/para/attribute_field/belongs_to.rb
|
@@ -726,6 +727,8 @@ files:
|
|
726
727
|
- lib/para/cache.rb
|
727
728
|
- lib/para/cache/database_store.rb
|
728
729
|
- lib/para/cloneable.rb
|
730
|
+
- lib/para/cloneable/attachments_cloner.rb
|
731
|
+
- lib/para/cloneable/include_tree_builder.rb
|
729
732
|
- lib/para/component.rb
|
730
733
|
- lib/para/component/exportable.rb
|
731
734
|
- lib/para/component/history.rb
|
@@ -782,6 +785,7 @@ files:
|
|
782
785
|
- lib/para/markup/component.rb
|
783
786
|
- lib/para/markup/modal.rb
|
784
787
|
- lib/para/markup/panel.rb
|
788
|
+
- lib/para/markup/resources_buttons.rb
|
785
789
|
- lib/para/markup/resources_table.rb
|
786
790
|
- lib/para/markup/resources_tree.rb
|
787
791
|
- lib/para/model_field_parsers.rb
|