locomotive_cms 2.0.0.rc1 → 2.0.0.rc2
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.
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/app/assets/javascripts/locomotive/models/editable_element.js.coffee +5 -0
- data/app/assets/stylesheets/locomotive/backoffice/application.css.scss +12 -0
- data/app/assets/stylesheets/locomotive/backoffice/formtastic_changes.css.scss +5 -3
- data/app/assets/stylesheets/locomotive/shared/_helpers.css.scss +15 -0
- data/app/controllers/locomotive/api/content_entries_controller.rb +6 -1
- data/app/helpers/locomotive/content_types_helper.rb +2 -2
- data/app/helpers/locomotive/custom_fields_helper.rb +1 -1
- data/app/models/locomotive/content_entry.rb +12 -2
- data/app/models/locomotive/content_type.rb +32 -83
- data/app/models/locomotive/editable_file.rb +2 -2
- data/app/models/locomotive/extensions/content_type/default_values.rb +59 -0
- data/app/models/locomotive/extensions/content_type/sync.rb +62 -0
- data/app/models/locomotive/extensions/page/tree.rb +4 -4
- data/app/models/locomotive/extensions/site/locales.rb +0 -1
- data/app/models/locomotive/page.rb +6 -2
- data/app/models/locomotive/snippet.rb +3 -1
- data/app/presenters/locomotive/content_entry_presenter.rb +14 -9
- data/app/views/locomotive/content_entries/_list.html.haml +4 -0
- data/app/views/locomotive/pages/_page.html.haml +3 -0
- data/config/locales/admin_ui.de.yml +98 -100
- data/config/locales/admin_ui.en.yml +2 -0
- data/config/locales/admin_ui.fr.yml +2 -0
- data/config/locales/default.es.yml +2 -2
- data/lib/generators/locomotive/install/templates/carrierwave.rb +8 -5
- data/lib/locomotive.rb +0 -2
- data/lib/locomotive/carrierwave/base.rb +5 -5
- data/lib/locomotive/configuration.rb +1 -1
- data/lib/locomotive/custom_fields.rb +1 -1
- data/lib/locomotive/dependencies.rb +0 -2
- data/lib/locomotive/engine.rb +0 -2
- data/lib/locomotive/liquid/drops/content_entry.rb +19 -2
- data/lib/locomotive/liquid/drops/page.rb +4 -0
- data/lib/locomotive/liquid/drops/uploader.rb +15 -0
- data/lib/locomotive/liquid/tags/nav.rb +18 -5
- data/lib/locomotive/liquid/tags/with_scope.rb +17 -9
- data/lib/locomotive/routing/site_dispatcher.rb +1 -1
- data/lib/locomotive/version.rb +1 -1
- data/lib/tasks/locomotive.rake +7 -185
- metadata +71 -68
data/Gemfile
CHANGED
data/LICENSE
CHANGED
|
@@ -5,6 +5,11 @@ class Locomotive.Models.EditableElement extends Backbone.Model
|
|
|
5
5
|
for key, value of @.toJSON()
|
|
6
6
|
hash[key] = value if _.include(['id', 'source', 'content', 'remove_source'], key)
|
|
7
7
|
|
|
8
|
+
if @get('type') == 'EditableFile'
|
|
9
|
+
delete hash['content']
|
|
10
|
+
else
|
|
11
|
+
delete hash['source']
|
|
12
|
+
|
|
8
13
|
class Locomotive.Models.EditableElementsCollection extends Backbone.Collection
|
|
9
14
|
|
|
10
15
|
model: Locomotive.Models.EditableElement
|
|
@@ -78,6 +78,12 @@ ul.list {
|
|
|
78
78
|
outline: none;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
span.untranslated {
|
|
82
|
+
@include label;
|
|
83
|
+
top: -1px;
|
|
84
|
+
left: 5px;
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
div.more {
|
|
82
88
|
position: absolute;
|
|
83
89
|
top: 0px;
|
|
@@ -212,6 +218,12 @@ ul.list {
|
|
|
212
218
|
|
|
213
219
|
&.hidden a { font-style: italic; font-weight: normal; }
|
|
214
220
|
|
|
221
|
+
span.untranslated {
|
|
222
|
+
@include label;
|
|
223
|
+
top: 3px;
|
|
224
|
+
left: 5px;
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
.more {
|
|
216
228
|
position: absolute;
|
|
217
229
|
top: 0px;
|
|
@@ -55,3 +55,18 @@
|
|
|
55
55
|
border-bottom: 1px dotted #efe4a5;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
@mixin label {
|
|
60
|
+
display: inline-block;
|
|
61
|
+
position: relative;
|
|
62
|
+
|
|
63
|
+
background-color: #858585;
|
|
64
|
+
@include border-radius(3px);
|
|
65
|
+
padding: 1px 3px 2px;
|
|
66
|
+
|
|
67
|
+
line-height: 16px;
|
|
68
|
+
font-size: 11px;
|
|
69
|
+
font-weight: bold;
|
|
70
|
+
color: #fff;
|
|
71
|
+
@include single-text-shadow(rgba(0, 0, 0, 0.6), 0px, -1px, 0px);
|
|
72
|
+
}
|
|
@@ -5,10 +5,15 @@ module Locomotive
|
|
|
5
5
|
before_filter :set_content_type
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
|
-
@content_entries = @content_type.
|
|
8
|
+
@content_entries = @content_type.ordered_entries
|
|
9
9
|
respond_with @content_entries
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def show
|
|
13
|
+
@content_entry = @content_type.entries.any_of({ :_id => params[:id] }, { :_slug => params[:id] }).first
|
|
14
|
+
respond_with @content_entry, :status => @content_entry ? :ok : :not_found
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
def create
|
|
13
18
|
@content_entry = @content_type.entries.create(params[:content_entry])
|
|
14
19
|
respond_with @content_entry, :location => main_app.locomotive_api_content_entries_url(@content_type.slug)
|
|
@@ -16,9 +16,9 @@ module Locomotive
|
|
|
16
16
|
current_site.content_types.ordered.only(:site_id, :name, :slug, :label_field_name).each_with_index do |content_type, index|
|
|
17
17
|
next if !content_type.persisted?
|
|
18
18
|
|
|
19
|
-
if index >= Locomotive.config.ui
|
|
19
|
+
if index >= Locomotive.config.ui[:max_content_types]
|
|
20
20
|
if self.is_content_type_selected(content_type)
|
|
21
|
-
others << visible.delete_at(Locomotive.config.ui
|
|
21
|
+
others << visible.delete_at(Locomotive.config.ui[:max_content_types] - 1) # swap content types
|
|
22
22
|
visible.insert(0, content_type)
|
|
23
23
|
else
|
|
24
24
|
others << content_type # fills the "..." menu
|
|
@@ -17,7 +17,7 @@ module Locomotive
|
|
|
17
17
|
|
|
18
18
|
def options_for_group_by_field(content_type)
|
|
19
19
|
content_type.ordered_entries_custom_fields.find_all do |field|
|
|
20
|
-
%w(select).include?(field.type)
|
|
20
|
+
%w(select belongs_to).include?(field.type)
|
|
21
21
|
end.map do |field|
|
|
22
22
|
[field.label, field._id]
|
|
23
23
|
end
|
|
@@ -30,7 +30,7 @@ module Locomotive
|
|
|
30
30
|
|
|
31
31
|
## named scopes ##
|
|
32
32
|
scope :visible, :where => { :_visible => true }
|
|
33
|
-
scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.ui
|
|
33
|
+
scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.ui[:latest_entries_nb]
|
|
34
34
|
|
|
35
35
|
## methods ##
|
|
36
36
|
|
|
@@ -39,11 +39,21 @@ module Locomotive
|
|
|
39
39
|
alias :_permalink= :_slug=
|
|
40
40
|
|
|
41
41
|
def _label(type = nil)
|
|
42
|
-
if self._label_field_name
|
|
42
|
+
value = if self._label_field_name
|
|
43
43
|
self.send(self._label_field_name.to_sym)
|
|
44
44
|
else
|
|
45
45
|
self.send((type || self.content_type).label_field_name.to_sym)
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
value.respond_to?(:to_label) ? value.to_label : value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def translated?
|
|
52
|
+
if self.respond_to?(:"#{self._label_field_name}_translations")
|
|
53
|
+
self.send(:"#{self._label_field_name}_translations").key?(::Mongoid::Fields::I18n.locale.to_s) #rescue false
|
|
54
|
+
else
|
|
55
|
+
true
|
|
56
|
+
end
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
def next
|
|
@@ -5,7 +5,9 @@ module Locomotive
|
|
|
5
5
|
|
|
6
6
|
## extensions ##
|
|
7
7
|
include CustomFields::Source
|
|
8
|
+
include Extensions::ContentType::DefaultValues
|
|
8
9
|
include Extensions::ContentType::ItemTemplate
|
|
10
|
+
include Extensions::ContentType::Sync
|
|
9
11
|
|
|
10
12
|
## fields ##
|
|
11
13
|
field :name
|
|
@@ -32,7 +34,6 @@ module Locomotive
|
|
|
32
34
|
## callbacks ##
|
|
33
35
|
before_validation :normalize_slug
|
|
34
36
|
after_validation :bubble_fields_errors_up
|
|
35
|
-
before_save :set_default_values
|
|
36
37
|
before_update :update_label_field_name_in_entries
|
|
37
38
|
|
|
38
39
|
## validations ##
|
|
@@ -55,11 +56,12 @@ module Locomotive
|
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def ordered_entries(conditions = {})
|
|
58
|
-
|
|
59
|
+
_order_by_definition = (conditions || {}).delete(:order_by).try(:split) || self.order_by_definition
|
|
60
|
+
self.entries.order_by([_order_by_definition]).where(conditions)
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def groupable?
|
|
62
|
-
!!self.group_by_field && group_by_field.type
|
|
64
|
+
!!self.group_by_field && %w(select belongs_to).include?(group_by_field.type)
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def group_by_field
|
|
@@ -68,7 +70,11 @@ module Locomotive
|
|
|
68
70
|
|
|
69
71
|
def list_or_group_entries
|
|
70
72
|
if self.groupable?
|
|
71
|
-
|
|
73
|
+
if group_by_field.type == 'select'
|
|
74
|
+
self.entries.group_by_select_option(self.group_by_field.name, self.order_by_definition)
|
|
75
|
+
else
|
|
76
|
+
group_by_belongs_to_field(self.group_by_field)
|
|
77
|
+
end
|
|
72
78
|
else
|
|
73
79
|
self.ordered_entries
|
|
74
80
|
end
|
|
@@ -110,30 +116,33 @@ module Locomotive
|
|
|
110
116
|
|
|
111
117
|
protected
|
|
112
118
|
|
|
119
|
+
def group_by_belongs_to_field(field)
|
|
120
|
+
grouped_entries = self.ordered_entries.group_by(&:"#{field.name}_id")
|
|
121
|
+
columns = grouped_entries.keys
|
|
122
|
+
target_content_type = self.class_name_to_content_type(field.class_name)
|
|
123
|
+
all_columns = target_content_type.ordered_entries
|
|
124
|
+
|
|
125
|
+
all_columns.map do |column|
|
|
126
|
+
if columns.include?(column._id)
|
|
127
|
+
{
|
|
128
|
+
:name => column._label(target_content_type),
|
|
129
|
+
:entries => grouped_entries.delete(column._id)
|
|
130
|
+
}
|
|
131
|
+
else
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
end.compact.tap do |groups|
|
|
135
|
+
unless grouped_entries.empty? # "orphans" ?
|
|
136
|
+
groups << { :name => nil, :entries => grouped_entries.values.flatten }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
113
141
|
def order_by_attribute
|
|
114
142
|
return self.order_by if %w(created_at updated_at _position).include?(self.order_by)
|
|
115
143
|
self.entries_custom_fields.find(self.order_by).name rescue 'created_at'
|
|
116
144
|
end
|
|
117
145
|
|
|
118
|
-
def set_default_values
|
|
119
|
-
self.order_by ||= 'created_at'
|
|
120
|
-
|
|
121
|
-
if @new_label_field_name.present?
|
|
122
|
-
self.label_field_id = self.entries_custom_fields.detect { |f| f.name == @new_label_field_name.underscore }._id
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
if self.label_field_id.blank?
|
|
126
|
-
self.label_field_id = self.entries_custom_fields.first._id
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
field = self.entries_custom_fields.find(self.label_field_id)
|
|
130
|
-
|
|
131
|
-
# the label field should always be required
|
|
132
|
-
field.required = true
|
|
133
|
-
|
|
134
|
-
self.label_field_name = field.name
|
|
135
|
-
end
|
|
136
|
-
|
|
137
146
|
def normalize_slug
|
|
138
147
|
self.slug = self.name.clone if self.slug.blank? && self.name.present?
|
|
139
148
|
self.slug.permalink! if self.slug.present?
|
|
@@ -179,63 +188,3 @@ module Locomotive
|
|
|
179
188
|
end
|
|
180
189
|
end
|
|
181
190
|
|
|
182
|
-
# def list_or_group_contents
|
|
183
|
-
# if self.groupable?
|
|
184
|
-
# groups = self.contents.klass.send(:"group_by_#{self.group_by_field._alias}", :ordered_contents)
|
|
185
|
-
#
|
|
186
|
-
# # look for items with no category or unknown ones
|
|
187
|
-
# items_without_category = self.contents.find_all { |c| !self.group_by_field.category_ids.include?(c.send(self.group_by_field_name)) }
|
|
188
|
-
# if not items_without_category.empty?
|
|
189
|
-
# groups << { :name => nil, :items => items_without_category }
|
|
190
|
-
# else
|
|
191
|
-
# groups
|
|
192
|
-
# end
|
|
193
|
-
# else
|
|
194
|
-
# self.ordered_contents
|
|
195
|
-
# end
|
|
196
|
-
# end
|
|
197
|
-
#
|
|
198
|
-
# def latest_updated_contents
|
|
199
|
-
# self.contents.latest_updated.reject { |c| !c.persisted? }
|
|
200
|
-
# end
|
|
201
|
-
#
|
|
202
|
-
# def ordered_contents(conditions = {})
|
|
203
|
-
# column = self.order_by.to_sym
|
|
204
|
-
#
|
|
205
|
-
# list = (if conditions.nil? || conditions.empty?
|
|
206
|
-
# self.contents
|
|
207
|
-
# else
|
|
208
|
-
# conditions_with_names = {}
|
|
209
|
-
#
|
|
210
|
-
# conditions.each do |key, value|
|
|
211
|
-
# # convert alias (key) to name
|
|
212
|
-
# field = self.entries_custom_fields.detect { |f| f._alias == key }
|
|
213
|
-
#
|
|
214
|
-
# case field.kind.to_sym
|
|
215
|
-
# when :category
|
|
216
|
-
# if (category_item = field.category_items.where(:name => value).first).present?
|
|
217
|
-
# conditions_with_names[field._name.to_sym] = category_item._id
|
|
218
|
-
# end
|
|
219
|
-
# else
|
|
220
|
-
# conditions_with_names[field._name.to_sym] = value
|
|
221
|
-
# end
|
|
222
|
-
# end
|
|
223
|
-
#
|
|
224
|
-
# self.contents.where(conditions_with_names)
|
|
225
|
-
# end).sort { |a, b| (a.send(column) && b.send(column)) ? (a.send(column) || 0) <=> (b.send(column) || 0) : 0 }
|
|
226
|
-
#
|
|
227
|
-
# return list if self.order_manually?
|
|
228
|
-
#
|
|
229
|
-
# self.asc_order? ? list : list.reverse
|
|
230
|
-
# end
|
|
231
|
-
#
|
|
232
|
-
# def sort_contents!(ids)
|
|
233
|
-
# ids.each_with_index do |id, position|
|
|
234
|
-
# self.contents.find(BSON::ObjectId(id))._position_in_list = position
|
|
235
|
-
# end
|
|
236
|
-
# self.save
|
|
237
|
-
# end
|
|
238
|
-
#
|
|
239
|
-
# def group_by_field
|
|
240
|
-
# @group_by_field ||= self.entries_custom_fields.detect { |f| f._name == self.group_by_field_name }
|
|
241
|
-
# end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module Locomotive
|
|
2
2
|
class EditableFile < EditableElement
|
|
3
3
|
|
|
4
|
-
mount_uploader
|
|
4
|
+
mount_uploader 'source', EditableFileUploader
|
|
5
5
|
|
|
6
|
-
replace_field
|
|
6
|
+
replace_field 'source', ::String, true
|
|
7
7
|
|
|
8
8
|
def content
|
|
9
9
|
self.source? ? self.source.url : self.default_content
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Locomotive
|
|
2
|
+
module Extensions
|
|
3
|
+
module ContentType
|
|
4
|
+
module DefaultValues
|
|
5
|
+
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
before_save :set_order_by
|
|
10
|
+
before_save :set_label_field
|
|
11
|
+
before_save :set_default_order_by_for_has_many_fields
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def set_order_by
|
|
17
|
+
unless self.order_by.nil? || %w(created_at updated_at _position).include?(self.order_by)
|
|
18
|
+
field = self.entries_custom_fields.where(:name => self.order_by).first || self.entries_custom_fields.find(self.order_by)
|
|
19
|
+
|
|
20
|
+
if field
|
|
21
|
+
self.order_by = field._id
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
self.order_by ||= 'created_at'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_label_field
|
|
29
|
+
if @new_label_field_name.present?
|
|
30
|
+
self.label_field_id = self.entries_custom_fields.detect { |f| f.name == @new_label_field_name.underscore }._id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# unknown label_field_name, get the first one instead
|
|
34
|
+
if self.label_field_id.blank?
|
|
35
|
+
self.label_field_id = self.entries_custom_fields.first._id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
field = self.entries_custom_fields.find(self.label_field_id)
|
|
39
|
+
|
|
40
|
+
# the label field should always be required
|
|
41
|
+
field.required = true
|
|
42
|
+
|
|
43
|
+
self.label_field_name = field.name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_default_order_by_for_has_many_fields
|
|
47
|
+
self.entries_custom_fields.where(:type.in => %w(has_many many_to_many)).each do |field|
|
|
48
|
+
if field.ui_enabled?
|
|
49
|
+
field.order_by = nil
|
|
50
|
+
else
|
|
51
|
+
field.order_by = field.class_name_to_content_type.order_by_definition
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Locomotive
|
|
2
|
+
module Extensions
|
|
3
|
+
module ContentType
|
|
4
|
+
module Sync
|
|
5
|
+
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
after_save :sync_relationships_order_by
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
# If the user changes the order of the content type, we have to make
|
|
15
|
+
# sure that other related content types tied to the current one through
|
|
16
|
+
# a belongs_to / has_many relationship also gets updated.
|
|
17
|
+
#
|
|
18
|
+
def sync_relationships_order_by
|
|
19
|
+
current_class_name = self.klass_with_custom_fields(:entries).name
|
|
20
|
+
|
|
21
|
+
self.entries_custom_fields.where(:type => 'belongs_to').each do |field|
|
|
22
|
+
target_content_type = self.class_name_to_content_type(field.class_name)
|
|
23
|
+
|
|
24
|
+
operations = { '$set' => {} }
|
|
25
|
+
|
|
26
|
+
target_content_type.entries_custom_fields.where(:type.in => %w(has_many many_to_many), :ui_enabled => false, :class_name => current_class_name).each do |target_field|
|
|
27
|
+
if target_field.order_by != self.order_by_definition
|
|
28
|
+
target_field.order_by = self.order_by_definition # needed by the custom_fields_recipe_for method in order to be up to date
|
|
29
|
+
|
|
30
|
+
operations['$set']["entries_custom_fields.#{target_field._index}.order_by"] = self.order_by_definition
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless operations['$set'].empty?
|
|
35
|
+
persist_content_type_changes target_content_type, operations
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Save the changes for the content type passed in parameter without forgetting
|
|
41
|
+
# to bump the version.. It also updates the recipe for related entries.
|
|
42
|
+
# That method does not call the Mongoid API but directly MongoDB.
|
|
43
|
+
#
|
|
44
|
+
# @param [ ContentType ] content_type The content type to update
|
|
45
|
+
# @param [ Hash ] operations The MongoDB atomic operations
|
|
46
|
+
#
|
|
47
|
+
def persist_content_type_changes(content_type, operations)
|
|
48
|
+
content_type.entries_custom_fields_version += 1
|
|
49
|
+
|
|
50
|
+
operations['$set']['entries_custom_fields_version'] = content_type.entries_custom_fields_version
|
|
51
|
+
|
|
52
|
+
self.collection.update({ '_id' => content_type._id }, operations)
|
|
53
|
+
|
|
54
|
+
collection, selector = content_type.entries.collection, content_type.entries.criteria.selector
|
|
55
|
+
|
|
56
|
+
collection.update selector, { '$set' => { 'custom_fields_recipe' => content_type.custom_fields_recipe_for(:entries) } }, :multi => true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|