releaf-content 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|