releaf-content 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +24 -0
  3. data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
  4. data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
  5. data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
  6. data/app/builders/releaf/content/builders/dialog.rb +15 -0
  7. data/app/builders/releaf/content/builders/tree.rb +84 -0
  8. data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
  9. data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
  10. data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
  11. data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
  12. data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
  13. data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
  14. data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
  15. data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
  16. data/app/controllers/releaf/content/nodes_controller.rb +166 -0
  17. data/app/middleware/releaf/content/routes_reloader.rb +25 -0
  18. data/app/validators/releaf/content/node/parent_validator.rb +48 -0
  19. data/app/validators/releaf/content/node/root_validator.rb +43 -0
  20. data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
  21. data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
  22. data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
  23. data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
  24. data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
  25. data/lib/releaf-content.rb +6 -0
  26. data/lib/releaf/content/acts_as_node.rb +73 -0
  27. data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
  28. data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
  29. data/lib/releaf/content/builders_autoload.rb +18 -0
  30. data/lib/releaf/content/engine.rb +40 -0
  31. data/lib/releaf/content/node.rb +280 -0
  32. data/lib/releaf/content/node_mapper.rb +9 -0
  33. data/lib/releaf/content/route.rb +93 -0
  34. data/lib/releaf/content/router_proxy.rb +23 -0
  35. data/releaf-content.gemspec +20 -0
  36. data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
  37. data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
  38. data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
  39. data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
  40. data/spec/features/nodes_spec.rb +239 -0
  41. data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
  42. data/spec/lib/releaf/content/node_spec.rb +779 -0
  43. data/spec/lib/releaf/content/route_spec.rb +85 -0
  44. data/spec/middleware/routes_reloader_spec.rb +48 -0
  45. data/spec/routing/node_mapper_spec.rb +142 -0
  46. data/spec/validators/content/node/parent_validator_spec.rb +56 -0
  47. data/spec/validators/content/node/root_validator_spec.rb +69 -0
  48. data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
  49. 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,9 @@
1
+ module Releaf::Content
2
+ class CopyDialogBuilder
3
+ include Releaf::Content::Builders::ActionDialog
4
+
5
+ def action
6
+ :copy
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Releaf::Content
2
+ class GoToDialogBuilder
3
+ include Releaf::Content::Builders::Dialog
4
+
5
+ def section_header_text
6
+ t("Go to node")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Releaf::Content
2
+ class MoveDialogBuilder
3
+ include Releaf::Content::Builders::ActionDialog
4
+
5
+ def action
6
+ :move
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Releaf::Content::Nodes
2
+ class ContentFormBuilder < Releaf::Builders::FormBuilder
3
+ def field_names
4
+ object.class.acts_as_node_fields
5
+ end
6
+ end
7
+ 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