releaf-content 0.2.1
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 +7 -0
- data/LICENSE +24 -0
- data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
- data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
- data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
- data/app/builders/releaf/content/builders/dialog.rb +15 -0
- data/app/builders/releaf/content/builders/tree.rb +84 -0
- data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
- data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
- data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
- data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
- data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
- data/app/controllers/releaf/content/nodes_controller.rb +166 -0
- data/app/middleware/releaf/content/routes_reloader.rb +25 -0
- data/app/validators/releaf/content/node/parent_validator.rb +48 -0
- data/app/validators/releaf/content/node/root_validator.rb +43 -0
- data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
- data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
- data/lib/releaf-content.rb +6 -0
- data/lib/releaf/content/acts_as_node.rb +73 -0
- data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
- data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
- data/lib/releaf/content/builders_autoload.rb +18 -0
- data/lib/releaf/content/engine.rb +40 -0
- data/lib/releaf/content/node.rb +280 -0
- data/lib/releaf/content/node_mapper.rb +9 -0
- data/lib/releaf/content/route.rb +93 -0
- data/lib/releaf/content/router_proxy.rb +23 -0
- data/releaf-content.gemspec +20 -0
- data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
- data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
- data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
- data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
- data/spec/features/nodes_spec.rb +239 -0
- data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
- data/spec/lib/releaf/content/node_spec.rb +779 -0
- data/spec/lib/releaf/content/route_spec.rb +85 -0
- data/spec/middleware/routes_reloader_spec.rb +48 -0
- data/spec/routing/node_mapper_spec.rb +142 -0
- data/spec/validators/content/node/parent_validator_spec.rb +56 -0
- data/spec/validators/content/node/root_validator_spec.rb +69 -0
- data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
- metadata +145 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Releaf::Content
|
2
|
+
class ContentTypeDialogBuilder
|
3
|
+
include Releaf::Content::Builders::Dialog
|
4
|
+
attr_accessor :content_types
|
5
|
+
|
6
|
+
def initialize(template)
|
7
|
+
super
|
8
|
+
self.content_types = template_variable("content_types")
|
9
|
+
end
|
10
|
+
|
11
|
+
def content_types_slices
|
12
|
+
min_items_per_column = 4
|
13
|
+
items_per_column = (content_types.length / 2.0).ceil
|
14
|
+
|
15
|
+
if items_per_column < min_items_per_column
|
16
|
+
items_per_column = min_items_per_column
|
17
|
+
end
|
18
|
+
|
19
|
+
slices = []
|
20
|
+
slices.push content_types[0...items_per_column]
|
21
|
+
if items_per_column < content_types.length
|
22
|
+
slices.push content_types[items_per_column..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
slices
|
26
|
+
end
|
27
|
+
|
28
|
+
def section_attributes
|
29
|
+
attributes = super
|
30
|
+
attributes['data-columns'] = content_types_slices.length
|
31
|
+
attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
def section_body
|
35
|
+
tag(:div, class: "body") do
|
36
|
+
[section_body_description, content_types_list]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def content_types_list
|
41
|
+
tag(:div, class: "content-types") do
|
42
|
+
content_types_slices.collect do |slice|
|
43
|
+
content_type_slice(slice)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def content_type_slice(slice)
|
49
|
+
tag(:ul) do
|
50
|
+
slice.collect do|content_type|
|
51
|
+
content_type_item(content_type)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def content_type_item(content_type)
|
57
|
+
url = url_for(controller: "/releaf/content/nodes", action: "new", parent_id: params[:parent_id], content_type: content_type.name)
|
58
|
+
tag(:li) do
|
59
|
+
link_to(I18n.t(content_type.name.underscore, scope: 'admin.content_types'), url)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def section_body_description
|
65
|
+
tag(:div, class: "description") do
|
66
|
+
t("Select content type of new node")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def section_header_text
|
71
|
+
t("Add new node")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Releaf::Content::Nodes
|
2
|
+
class FormBuilder < Releaf::Builders::FormBuilder
|
3
|
+
def field_names
|
4
|
+
%w(node_fields_block content_fields_block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def node_fields
|
8
|
+
[:parent_id, :name, :content_type, :slug, :item_position, :active, :locale]
|
9
|
+
end
|
10
|
+
|
11
|
+
def render_node_fields_block
|
12
|
+
tag(:div, class: ["section", "node-fields"]) do
|
13
|
+
releaf_fields(node_fields)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_parent_id
|
18
|
+
hidden_field(:parent_id) if object.new_record?
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_content_fields_block?
|
22
|
+
object.content_class.respond_to?(:acts_as_node_fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
def render_content_fields_block
|
26
|
+
return unless render_content_fields_block?
|
27
|
+
tag(:div, class: ["section", "content-fields"]) do
|
28
|
+
fields_for(:content, object.content, builder: content_builder_class) do |form|
|
29
|
+
form.releaf_fields(form.field_names.to_a)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def content_builder_class
|
35
|
+
Releaf::Content::Nodes::ContentFormBuilder
|
36
|
+
end
|
37
|
+
|
38
|
+
def render_locale
|
39
|
+
releaf_item_field(:locale, options: render_locale_options) if object.locale_selection_enabled?
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_locale_options
|
43
|
+
{
|
44
|
+
select_options: I18n.available_locales,
|
45
|
+
include_blank: object.locale.blank?
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def render_content_type
|
50
|
+
input = {disabled: true, value: t(object.content_type.underscore, scope: 'admin.content_types')}
|
51
|
+
releaf_text_field(:content_type, input: input) do
|
52
|
+
hidden_field_tag(:content_type, params[:content_type]) if object.new_record?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def render_slug
|
57
|
+
url = url_for(controller: "/releaf/content/nodes", action: "generate_url", parent_id: object.parent_id, id: object.id)
|
58
|
+
input = {
|
59
|
+
data: {'generator-url' => url}
|
60
|
+
}
|
61
|
+
|
62
|
+
releaf_field(:slug, input: input) do
|
63
|
+
slug_button << wrapper(slug_link, class: "link")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_item_position
|
68
|
+
releaf_item_field(:item_position, options: item_position_options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def item_position_options
|
72
|
+
{
|
73
|
+
include_blank: false,
|
74
|
+
select_options: options_for_select(item_position_select_options, object.item_position)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def item_position_select_options
|
79
|
+
after_text = t("After")
|
80
|
+
list = [[t("First"), 0]]
|
81
|
+
order_nodes.each do |node|
|
82
|
+
list.push [after_text + ' ' + node.name, node.lower_item ? node.lower_item.item_position : node.item_position + 1 ]
|
83
|
+
end
|
84
|
+
|
85
|
+
list
|
86
|
+
end
|
87
|
+
|
88
|
+
def order_nodes
|
89
|
+
object.class.where(parent_id: object.parent_id).where('id <> ?', object.id.to_i)
|
90
|
+
end
|
91
|
+
|
92
|
+
def slug_base_url
|
93
|
+
"#{request.protocol}#{request.host_with_port}#{object.parent.try(:path)}/"
|
94
|
+
end
|
95
|
+
|
96
|
+
def slug_link
|
97
|
+
link_to(object.path) do
|
98
|
+
safe_join do
|
99
|
+
[slug_base_url, tag(:span, object.slug), '/']
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def slug_button
|
105
|
+
button(nil, "keyboard-o", title: t('Suggest slug'), class: "secondary generate")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Releaf::Content::Nodes
|
2
|
+
class IndexBuilder < Releaf::Builders::IndexBuilder
|
3
|
+
include Releaf::Content::Builders::Tree
|
4
|
+
include Releaf::Builders::Toolbox
|
5
|
+
|
6
|
+
def tree_resource_blocks(resource, level, expanded)
|
7
|
+
[tree_resource_toolbox(resource)] + super
|
8
|
+
end
|
9
|
+
|
10
|
+
def tree_resource_toolbox(resource)
|
11
|
+
tag(:div, class: "only-icon toolbox-cell") do
|
12
|
+
toolbox(resource, index_url: index_url)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def pagination?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def resource_creation_button
|
21
|
+
button(t("Create new resource"), "plus", class: %w(primary ajaxbox), href: url_for(controller: controller.controller_name, action: "content_type_dialog"))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Releaf::Content::Nodes
|
2
|
+
class ToolboxBuilder < Releaf::Builders::ToolboxBuilder
|
3
|
+
def items
|
4
|
+
list = []
|
5
|
+
|
6
|
+
unless resource.new_record?
|
7
|
+
list << add_child_button
|
8
|
+
list << go_to_button unless params[:context] == "index"
|
9
|
+
list << copy_button
|
10
|
+
list << move_button
|
11
|
+
end
|
12
|
+
|
13
|
+
list + super
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_child_button
|
17
|
+
button(t('Add child'), nil, class: "ajaxbox", href: url_for(action: "content_type_dialog", parent_id: resource.id))
|
18
|
+
end
|
19
|
+
|
20
|
+
def go_to_button
|
21
|
+
button(t('Go to'), nil, class: "ajaxbox", href: url_for(action: "go_to_dialog"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def copy_button
|
25
|
+
button(t('Copy'), nil, class: "ajaxbox", href: url_for(action: "copy_dialog", id: resource.id))
|
26
|
+
end
|
27
|
+
|
28
|
+
def move_button
|
29
|
+
button(t('Move'), nil, class: "ajaxbox", href: url_for(action: "move_dialog", id: resource.id))
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Releaf::Content
|
2
|
+
class NodesController < Releaf::BaseController
|
3
|
+
respond_to :json, only: [:create, :update, :copy, :move]
|
4
|
+
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@features[:create_another] = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_url
|
11
|
+
tmp_resource = prepare_resource
|
12
|
+
tmp_resource.name = params[:name]
|
13
|
+
tmp_resource.reasign_slug
|
14
|
+
|
15
|
+
respond_to do |format|
|
16
|
+
format.js { render text: tmp_resource.slug }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def content_type_dialog
|
21
|
+
@content_types = resource_class.valid_node_content_classes(params[:parent_id]).sort do |a, b|
|
22
|
+
I18n.t(a.name.underscore, scope: 'admin.content_types') <=> I18n.t(b.name.underscore, scope: 'admin.content_types')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def builder_scopes
|
27
|
+
[node_builder_scope] + super
|
28
|
+
end
|
29
|
+
|
30
|
+
def node_builder_scope
|
31
|
+
[application_scope, "Nodes"].reject(&:blank?).join("::")
|
32
|
+
end
|
33
|
+
|
34
|
+
def copy_dialog
|
35
|
+
copy_move_dialog_common
|
36
|
+
end
|
37
|
+
|
38
|
+
def move_dialog
|
39
|
+
copy_move_dialog_common
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy
|
43
|
+
copy_move_common do |resource|
|
44
|
+
resource.copy params[:new_parent_id]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def move
|
49
|
+
copy_move_common do |resource|
|
50
|
+
resource.move params[:new_parent_id]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def go_to_dialog
|
55
|
+
@collection = resource_class.roots
|
56
|
+
|
57
|
+
respond_to do |format|
|
58
|
+
format.html do
|
59
|
+
render layout: nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# override base_controller method for adding content tree ancestors
|
65
|
+
# to breadcrumbs
|
66
|
+
def add_resource_breadcrumb(resource)
|
67
|
+
ancestors = []
|
68
|
+
if resource.new_record?
|
69
|
+
if resource.parent_id
|
70
|
+
ancestors = resource.parent.ancestors
|
71
|
+
ancestors += [resource.parent]
|
72
|
+
end
|
73
|
+
else
|
74
|
+
ancestors = resource.ancestors
|
75
|
+
end
|
76
|
+
|
77
|
+
ancestors.each do |ancestor|
|
78
|
+
@breadcrumbs << { name: ancestor, url: url_for( action: :edit, id: ancestor.id ) }
|
79
|
+
end
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.resource_class
|
85
|
+
::Node
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def prepare_index
|
91
|
+
@collection = resource_class.roots
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def copy_move_common(&block)
|
97
|
+
@resource = resource_class.find(params[:id])
|
98
|
+
|
99
|
+
if params[:new_parent_id].nil?
|
100
|
+
@resource.errors.add(:base, 'parent not selected')
|
101
|
+
respond_with(@resource)
|
102
|
+
else
|
103
|
+
begin
|
104
|
+
@resource = yield(@resource)
|
105
|
+
rescue ActiveRecord::RecordInvalid => e
|
106
|
+
respond_with(e.record)
|
107
|
+
else
|
108
|
+
resource_class.updated
|
109
|
+
respond_with(@resource, redirect: true, location: url_for(action: :index))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def action_responders
|
115
|
+
super.merge(
|
116
|
+
copy: Releaf::Core::Responders::AfterSaveResponder,
|
117
|
+
move: Releaf::Core::Responders::AfterSaveResponder
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def copy_move_dialog_common
|
122
|
+
@resource = resource_class.find params[:id]
|
123
|
+
@collection = resource_class.roots
|
124
|
+
end
|
125
|
+
|
126
|
+
def prepare_resource
|
127
|
+
if params[:id]
|
128
|
+
resource_class.find(params[:id])
|
129
|
+
elsif params[:parent_id].present?
|
130
|
+
parent = resource_class.find(params[:parent_id])
|
131
|
+
parent.children.new
|
132
|
+
else
|
133
|
+
resource_class.new
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def new_resource
|
138
|
+
super
|
139
|
+
@resource.content_type = node_content_class.name
|
140
|
+
@resource.parent_id = params[:parent_id]
|
141
|
+
@resource.item_position ||= resource_class.children_max_item_position(@resource.parent) + 1
|
142
|
+
|
143
|
+
if node_content_class < ActiveRecord::Base
|
144
|
+
@resource.build_content
|
145
|
+
@resource.content_id_will_change!
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns valid content type class
|
150
|
+
def node_content_class
|
151
|
+
raise ArgumentError, "invalid content_type" unless ActsAsNode.classes.include? params[:content_type]
|
152
|
+
params[:content_type].constantize
|
153
|
+
end
|
154
|
+
|
155
|
+
def permitted_params
|
156
|
+
list = super
|
157
|
+
list += [{content_attributes: permitted_content_attributes}]
|
158
|
+
list -= %w[content_type]
|
159
|
+
list
|
160
|
+
end
|
161
|
+
|
162
|
+
def permitted_content_attributes
|
163
|
+
@resource.content_class.acts_as_node_params if @resource.content_class.respond_to? :acts_as_node_params
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|