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.
Files changed (41) hide show
  1. data/Gemfile +1 -1
  2. data/LICENSE +1 -1
  3. data/app/assets/javascripts/locomotive/models/editable_element.js.coffee +5 -0
  4. data/app/assets/stylesheets/locomotive/backoffice/application.css.scss +12 -0
  5. data/app/assets/stylesheets/locomotive/backoffice/formtastic_changes.css.scss +5 -3
  6. data/app/assets/stylesheets/locomotive/shared/_helpers.css.scss +15 -0
  7. data/app/controllers/locomotive/api/content_entries_controller.rb +6 -1
  8. data/app/helpers/locomotive/content_types_helper.rb +2 -2
  9. data/app/helpers/locomotive/custom_fields_helper.rb +1 -1
  10. data/app/models/locomotive/content_entry.rb +12 -2
  11. data/app/models/locomotive/content_type.rb +32 -83
  12. data/app/models/locomotive/editable_file.rb +2 -2
  13. data/app/models/locomotive/extensions/content_type/default_values.rb +59 -0
  14. data/app/models/locomotive/extensions/content_type/sync.rb +62 -0
  15. data/app/models/locomotive/extensions/page/tree.rb +4 -4
  16. data/app/models/locomotive/extensions/site/locales.rb +0 -1
  17. data/app/models/locomotive/page.rb +6 -2
  18. data/app/models/locomotive/snippet.rb +3 -1
  19. data/app/presenters/locomotive/content_entry_presenter.rb +14 -9
  20. data/app/views/locomotive/content_entries/_list.html.haml +4 -0
  21. data/app/views/locomotive/pages/_page.html.haml +3 -0
  22. data/config/locales/admin_ui.de.yml +98 -100
  23. data/config/locales/admin_ui.en.yml +2 -0
  24. data/config/locales/admin_ui.fr.yml +2 -0
  25. data/config/locales/default.es.yml +2 -2
  26. data/lib/generators/locomotive/install/templates/carrierwave.rb +8 -5
  27. data/lib/locomotive.rb +0 -2
  28. data/lib/locomotive/carrierwave/base.rb +5 -5
  29. data/lib/locomotive/configuration.rb +1 -1
  30. data/lib/locomotive/custom_fields.rb +1 -1
  31. data/lib/locomotive/dependencies.rb +0 -2
  32. data/lib/locomotive/engine.rb +0 -2
  33. data/lib/locomotive/liquid/drops/content_entry.rb +19 -2
  34. data/lib/locomotive/liquid/drops/page.rb +4 -0
  35. data/lib/locomotive/liquid/drops/uploader.rb +15 -0
  36. data/lib/locomotive/liquid/tags/nav.rb +18 -5
  37. data/lib/locomotive/liquid/tags/with_scope.rb +17 -9
  38. data/lib/locomotive/routing/site_dispatcher.rb +1 -1
  39. data/lib/locomotive/version.rb +1 -1
  40. data/lib/tasks/locomotive.rake +7 -185
  41. metadata +71 -68
data/Gemfile CHANGED
@@ -33,7 +33,7 @@ group :test do
33
33
 
34
34
  # gem 'growl-glue'
35
35
 
36
- gem 'cucumber-rails'
36
+ gem 'cucumber-rails', :require => false
37
37
  gem 'rspec-rails', '~> 2.8.0'
38
38
  gem 'shoulda-matchers'
39
39
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  == MIT License
2
2
 
3
- Copyright (c) 2010, Didier Lafforgue.
3
+ Copyright (c) 2010-2012, Didier Lafforgue.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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;
@@ -568,9 +568,11 @@ form.formtastic {
568
568
  }
569
569
 
570
570
  &.label {
571
- margin-left: 8px;
572
- font-weight: bold;
573
- color: #000;
571
+ margin-left: 8px;
572
+ font-weight: bold;
573
+ color: #000;
574
+ height: 31px;
575
+ width: auto;
574
576
  }
575
577
 
576
578
  } // ul .col
@@ -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.list_or_group_entries
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.max_content_types
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.max_content_types - 1) # swap content types
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.latest_entries_nb
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
- self.entries.order_by([order_by_definition]).where(conditions)
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 == 'select'
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
- self.entries.group_by_select_option(self.group_by_field.name, self.order_by_definition)
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 :source, EditableFileUploader
4
+ mount_uploader 'source', EditableFileUploader
5
5
 
6
- replace_field :source, ::String, true
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