locomotive_cms 2.0.0.rc1 → 2.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|