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.
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