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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6a8d9437be334b0617ec00d8c8e3e4ea14b35dc6
|
4
|
+
data.tar.gz: 095e4645884b45c38afb81fa0157b80454e14027
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ce189035ffd84c6abdea56c57ad9d3a5d55b7074ab458d746b4faa7374b67b1e37739db42d71609b449fd1ae34c85d072cf1eeeb73c8c4da627ceb48e41980d9
|
7
|
+
data.tar.gz: 65a20465658ed0a1edef3723b909fe6d5522e46546dfbb321176a888428b7ed6956ee97d9ebb5141ff75cf306d938949be98f0da0057a77d725f93cfe507dca6
|
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2012, CubeSystems <info@cubesystems.lv>
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the CubeSystems nor the names of its contributors may
|
12
|
+
be used to endorse or promote products derived from this software without
|
13
|
+
specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL CubeSystems BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,88 @@
|
|
1
|
+
jQuery(function()
|
2
|
+
{
|
3
|
+
var body = jQuery('body.controller-releaf-content-nodes');
|
4
|
+
|
5
|
+
body.on('contentloaded', function(e)
|
6
|
+
{
|
7
|
+
var block = jQuery(e.target);
|
8
|
+
|
9
|
+
// item collapse / expand
|
10
|
+
block.find('.collection li .collapser').click(function()
|
11
|
+
{
|
12
|
+
var item = jQuery(this).closest('.collection li');
|
13
|
+
var should_expand = item.is('.collapsed');
|
14
|
+
var event_name = should_expand ? 'nodeitemexpand' : 'nodeitemcollapse';
|
15
|
+
|
16
|
+
item.trigger(event_name);
|
17
|
+
|
18
|
+
var setting_key = 'content.tree.expanded.' + item.data('id');
|
19
|
+
body.trigger( 'settingssave', [ setting_key, should_expand ] );
|
20
|
+
});
|
21
|
+
|
22
|
+
block.find('.collection li').bind('nodeitemcollapse', function( e )
|
23
|
+
{
|
24
|
+
e.stopPropagation();
|
25
|
+
|
26
|
+
var item = jQuery(e.target);
|
27
|
+
item.addClass('collapsed');
|
28
|
+
item.children('.collapser-cell').find('.collapser i').removeClass('fa-chevron-down').addClass('fa-chevron-right');
|
29
|
+
|
30
|
+
});
|
31
|
+
|
32
|
+
block.find('.collection li').bind('nodeitemexpand', function( e )
|
33
|
+
{
|
34
|
+
e.stopPropagation();
|
35
|
+
|
36
|
+
var item = jQuery(e.target);
|
37
|
+
item.removeClass('collapsed');
|
38
|
+
item.children('.collapser-cell').find('.collapser i').removeClass('fa-chevron-right').addClass('fa-chevron-down');
|
39
|
+
|
40
|
+
});
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
// slug generation
|
45
|
+
var name_input = block.find('.node-fields .field[data-name="name"] input');
|
46
|
+
var slug_field = block.find('.node-fields .field[data-name="slug"]');
|
47
|
+
|
48
|
+
if (name_input.length && slug_field.length)
|
49
|
+
{
|
50
|
+
var slug_input = slug_field.find('input');
|
51
|
+
var slug_button = slug_field.find('.generate');
|
52
|
+
var slug_link = slug_field.find('a');
|
53
|
+
|
54
|
+
slug_input.on('sluggenerate', function()
|
55
|
+
{
|
56
|
+
var url = slug_input.attr('data-generator-url');
|
57
|
+
|
58
|
+
slug_button.trigger('loadingstart');
|
59
|
+
jQuery.get( url, { name: name_input.val() }, function( slug )
|
60
|
+
{
|
61
|
+
slug_input.val( slug );
|
62
|
+
slug_link.find('span').text( encodeURIComponent( slug ) );
|
63
|
+
slug_button.trigger('loadingend');
|
64
|
+
}, 'text');
|
65
|
+
});
|
66
|
+
|
67
|
+
slug_button.click(function()
|
68
|
+
{
|
69
|
+
slug_input.trigger('sluggenerate');
|
70
|
+
});
|
71
|
+
|
72
|
+
if (name_input.val() === '')
|
73
|
+
{
|
74
|
+
// bind onchange slug generation only if starting out with an empty name
|
75
|
+
name_input.change(function()
|
76
|
+
{
|
77
|
+
slug_input.trigger('sluggenerate');
|
78
|
+
});
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
});
|
83
|
+
|
84
|
+
body.on('click', '.dialog .node-cell label', function() {
|
85
|
+
jQuery('.dialog .node-cell label').removeClass('selected');
|
86
|
+
jQuery(this).addClass('selected');
|
87
|
+
});
|
88
|
+
});
|
@@ -0,0 +1,234 @@
|
|
1
|
+
@import 'releaf/environment';
|
2
|
+
|
3
|
+
.controller-releaf-content-nodes
|
4
|
+
{
|
5
|
+
main > section:not(.dialog) > .body:not(.empty)
|
6
|
+
{
|
7
|
+
border-bottom: none;
|
8
|
+
}
|
9
|
+
|
10
|
+
.collection
|
11
|
+
{
|
12
|
+
ul
|
13
|
+
{
|
14
|
+
@include block-list;
|
15
|
+
}
|
16
|
+
|
17
|
+
.collapsed ul
|
18
|
+
{
|
19
|
+
display: none;
|
20
|
+
}
|
21
|
+
|
22
|
+
li
|
23
|
+
{
|
24
|
+
@include clear-inside;
|
25
|
+
}
|
26
|
+
|
27
|
+
.collapser-cell
|
28
|
+
{
|
29
|
+
float: left;
|
30
|
+
}
|
31
|
+
|
32
|
+
.toolbox-cell
|
33
|
+
{
|
34
|
+
border-left: 1px solid $color-border-normal;
|
35
|
+
float: right;
|
36
|
+
}
|
37
|
+
|
38
|
+
.collapser-cell,
|
39
|
+
.toolbox-cell
|
40
|
+
{
|
41
|
+
.trigger
|
42
|
+
{
|
43
|
+
background: transparent;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
.node-cell
|
48
|
+
{
|
49
|
+
border-bottom: 1px solid $color-border-normal;
|
50
|
+
|
51
|
+
.trigger
|
52
|
+
{
|
53
|
+
display: block;
|
54
|
+
color: $color-text-lightest;
|
55
|
+
text-decoration: none;
|
56
|
+
padding: steps(0.5) steps(3);
|
57
|
+
font-weight: bold;
|
58
|
+
|
59
|
+
&.selected,
|
60
|
+
&:hover
|
61
|
+
{
|
62
|
+
background: $color-highlight-lightest;
|
63
|
+
}
|
64
|
+
|
65
|
+
span
|
66
|
+
{
|
67
|
+
position: relative;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
&.active .trigger
|
72
|
+
{
|
73
|
+
color: $color-text-normal;
|
74
|
+
}
|
75
|
+
|
76
|
+
span
|
77
|
+
{
|
78
|
+
margin-left: 0;
|
79
|
+
display: inline-block;
|
80
|
+
}
|
81
|
+
|
82
|
+
input[type="radio"]
|
83
|
+
{
|
84
|
+
display: none;
|
85
|
+
}
|
86
|
+
|
87
|
+
}
|
88
|
+
|
89
|
+
// nested item indentation
|
90
|
+
@for $i from 1 through 10
|
91
|
+
{
|
92
|
+
li[data-level="#{$i}"]
|
93
|
+
{
|
94
|
+
& > .collapser-cell
|
95
|
+
{
|
96
|
+
$previous: $i - 1;
|
97
|
+
position: absolute;
|
98
|
+
left: $previous * steps(2);
|
99
|
+
}
|
100
|
+
|
101
|
+
& > .node-cell span
|
102
|
+
{
|
103
|
+
margin-left: ($i - 1) * steps(2);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
.nothing-found
|
109
|
+
{
|
110
|
+
padding: steps(0.5) steps(2);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
main .collection li ul ul
|
115
|
+
{
|
116
|
+
background: $color-background-normal;
|
117
|
+
}
|
118
|
+
|
119
|
+
.dialog .collection > ul li
|
120
|
+
{
|
121
|
+
position: relative;
|
122
|
+
}
|
123
|
+
|
124
|
+
|
125
|
+
&.view-index > main > section
|
126
|
+
{
|
127
|
+
display: inline-block;
|
128
|
+
min-width: steps(40);
|
129
|
+
}
|
130
|
+
|
131
|
+
|
132
|
+
.dialog
|
133
|
+
{
|
134
|
+
width: steps(40);
|
135
|
+
|
136
|
+
&.content-type
|
137
|
+
{
|
138
|
+
&[data-columns="1"]
|
139
|
+
{
|
140
|
+
width: steps(24);
|
141
|
+
}
|
142
|
+
|
143
|
+
.body
|
144
|
+
{
|
145
|
+
padding: steps(2) steps(3);
|
146
|
+
text-align: center;
|
147
|
+
|
148
|
+
.description
|
149
|
+
{
|
150
|
+
padding: 0 0 steps(2) 0;
|
151
|
+
}
|
152
|
+
|
153
|
+
ul
|
154
|
+
{
|
155
|
+
width: steps(18);
|
156
|
+
margin-left: steps(2);
|
157
|
+
float: left;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
.content-types
|
162
|
+
{
|
163
|
+
@include clear-inside;
|
164
|
+
|
165
|
+
ul
|
166
|
+
{
|
167
|
+
@include block-list;
|
168
|
+
|
169
|
+
li
|
170
|
+
{
|
171
|
+
margin-bottom: steps(1);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
&:not(.content-type)
|
178
|
+
{
|
179
|
+
.body
|
180
|
+
{
|
181
|
+
height: steps(24);
|
182
|
+
overflow-y: auto;
|
183
|
+
}
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
&.view-edit
|
188
|
+
{
|
189
|
+
.node-fields
|
190
|
+
{
|
191
|
+
@include clear-inside;
|
192
|
+
|
193
|
+
background: $color-background-lighter;
|
194
|
+
|
195
|
+
.field[data-name="slug"]
|
196
|
+
{
|
197
|
+
.button.generate
|
198
|
+
{
|
199
|
+
position: absolute;
|
200
|
+
right: 0;
|
201
|
+
top: 0;
|
202
|
+
}
|
203
|
+
|
204
|
+
input[type="text"]
|
205
|
+
{
|
206
|
+
padding-right: steps(4);
|
207
|
+
}
|
208
|
+
|
209
|
+
.link
|
210
|
+
{
|
211
|
+
padding: steps(0.5) 0;
|
212
|
+
span
|
213
|
+
{
|
214
|
+
color: $color-text-normal;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
.field[data-name="active"]
|
220
|
+
{
|
221
|
+
min-height: initial;
|
222
|
+
clear: both;
|
223
|
+
|
224
|
+
.value
|
225
|
+
{
|
226
|
+
padding-top: initial;
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
}
|
231
|
+
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Releaf::Content::Builders
|
2
|
+
module ActionDialog
|
3
|
+
include Releaf::Content::Builders::Dialog
|
4
|
+
|
5
|
+
def form_attributes
|
6
|
+
{
|
7
|
+
method: :post,
|
8
|
+
data: {
|
9
|
+
"remote" => true,
|
10
|
+
"remote-validation" => true,
|
11
|
+
"type" => :json
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def root_level
|
17
|
+
tag(:ul, "data-level" => 0) do
|
18
|
+
tag(:li, class: "root") do
|
19
|
+
[tree_root_resource, super]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tree_root_resource
|
25
|
+
field_id = "new_parent_id_0"
|
26
|
+
tag(:div, class: "node-cell") do
|
27
|
+
[radio_button_tag(:new_parent_id, '', false, id: 'new_parent_id_0'),
|
28
|
+
tag(:label, t("Root node"), class: "trigger", for: field_id)]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def tree_resource_name_button(resource)
|
33
|
+
field_id = "new_parent_id_#{resource.id}"
|
34
|
+
|
35
|
+
[radio_button_tag(:new_parent_id, resource.id, false, id: field_id),
|
36
|
+
tag(:label, tag(:span, resource.name), class: "trigger", for: field_id)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def section_blocks
|
40
|
+
form_tag(url_for(action: action, id: resource.id), form_attributes) do
|
41
|
+
safe_join do
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def section_header_text
|
48
|
+
message = "#{action.capitalize} node '%{node_name}' to"
|
49
|
+
t(message, default: message, node_name: resource.name )
|
50
|
+
end
|
51
|
+
|
52
|
+
def footer_primary_tools
|
53
|
+
super << confirm_button
|
54
|
+
end
|
55
|
+
|
56
|
+
def confirm_button
|
57
|
+
button(t(action), "check", class: "primary", type: "submit", data: { type: 'ok', disable: true })
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Releaf::Content::Builders
|
2
|
+
module Dialog
|
3
|
+
include Releaf::Builders::ResourceDialog
|
4
|
+
include Releaf::Builders::Collection
|
5
|
+
include Releaf::Content::Builders::Tree
|
6
|
+
|
7
|
+
def footer_primary_tools
|
8
|
+
[cancel_button]
|
9
|
+
end
|
10
|
+
|
11
|
+
def cancel_button
|
12
|
+
button(t('Cancel'), "ban", class: "secondary", data: {type: 'cancel'}, href: url_for( action: 'index' ))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Releaf::Content::Builders
|
2
|
+
module Tree
|
3
|
+
def section_body
|
4
|
+
tag(:div, class: body_classes) do
|
5
|
+
tree
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def body_classes
|
10
|
+
classes = [:body]
|
11
|
+
classes << :empty if collection.size < 1
|
12
|
+
classes
|
13
|
+
end
|
14
|
+
|
15
|
+
def tree
|
16
|
+
tag(:div, class: "collection") do
|
17
|
+
root_level
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def root_level
|
22
|
+
return empty_body if collection.size < 1
|
23
|
+
tree_level(collection, 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty_body
|
27
|
+
tag(:div, class: "nothing-found") do
|
28
|
+
t("Nothing found")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def tree_level(list, level)
|
33
|
+
tag(:ul, "data-level" => level) do
|
34
|
+
list.collect do |resource|
|
35
|
+
tree_resource(resource, level)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def tree_resource(resource, level)
|
41
|
+
expanded = (layout_settings("content.tree.expanded.#{resource.id}") == true)
|
42
|
+
classes = []
|
43
|
+
classes << 'collapsed' unless expanded
|
44
|
+
classes << 'has-children' unless resource.children.empty?
|
45
|
+
|
46
|
+
tag(:li, class: classes, data: {level: level, id: resource.id}) do
|
47
|
+
tree_resource_blocks(resource, level, expanded)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def tree_resource_blocks(resource, level, expanded)
|
52
|
+
[tree_resource_collapser(resource, expanded),
|
53
|
+
tree_resource_name(resource), tree_resource_children(resource, level)]
|
54
|
+
end
|
55
|
+
|
56
|
+
def tree_resource_collapser(resource, expanded)
|
57
|
+
return if resource.children.empty?
|
58
|
+
tag(:div, class: "collapser-cell") do
|
59
|
+
button(nil, (expanded ? 'chevron-down' : 'chevron-right'), class: %w(secondary collapser trigger), title: t(expanded ? "collapse" : "expand"))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def tree_resource_children(resource, level)
|
64
|
+
return if resource.children.empty?
|
65
|
+
tree_level(resource.children, level + 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def tree_resource_name(resource)
|
69
|
+
classes = ["node-cell"]
|
70
|
+
classes << "active" if resource.active?
|
71
|
+
|
72
|
+
tag(:div, class: classes) do
|
73
|
+
tree_resource_name_button(resource)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def tree_resource_name_button(resource)
|
78
|
+
title = resource.content_id.present? ? "#{resource.content_type} ##{resource.content_id}" : resource.content_type
|
79
|
+
tag(:a, class: "trigger", href: url_for(action: "edit", id: resource.id), title: title) do
|
80
|
+
tag(:span, resource.name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|