fix an issue with compass + move to rails 3.2 + switch from mongoid_acts_as_tree to mongoid_tree + implement the UI to deal with has_many field types (wip)

This commit is contained in:
did 2012-02-01 02:01:42 +01:00
parent e2bd55fe35
commit dfd437a762
32 changed files with 488 additions and 239 deletions

10
Gemfile
View File

@ -9,7 +9,7 @@ gemspec # Include gemspec dependencies
group :development do
gem 'custom_fields', :path => '../gems/custom_fields' # Locale
# gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => '2.0.0.rc' # Branch on Github
# # gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git', :branch => '2.0.0.rc' # Branch on Github
gem 'rspec-rails', '2.6.1' # In order to have rspec tasks and generators
gem 'rspec-cells'
@ -17,6 +17,14 @@ group :development do
gem 'unicorn' # Using unicorn_rails instead of webrick (default server)
end
group :assets do
gem 'sass-rails', '~> 3.2.4'
gem 'coffee-rails', '~> 3.2.2'
gem 'uglifier', '~> 1.2.3'
gem 'compass', :git => 'git://github.com/chriseppstein/compass.git', :branch => 'no_rails_integration'
gem 'compass-rails', :git => 'git://github.com/Compass/compass-rails.git'
end
group :test do
gem 'launchy'

View File

@ -1,3 +1,21 @@
GIT
remote: git://github.com/Compass/compass-rails.git
revision: c2d47fb846b6fd48272e56ef21ea91fab8fc8510
specs:
compass-rails (1.0.0.rc.1)
compass (~> 0.12.rc.0)
rails
GIT
remote: git://github.com/chriseppstein/compass.git
revision: 0f96e881019c364aa6124845cbd83b1fd75c4ffe
branch: no_rails_integration
specs:
compass (0.12.rc.4.0f96e88)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
PATH
remote: .
specs:
@ -9,41 +27,37 @@ PATH
carrierwave-mongoid (~> 0.1.3)
cells (~> 3.8.0)
codemirror-rails (~> 2.21)
coffee-script (~> 2.2.0)
compass (~> 0.12.alpha.4)
custom_fields (~> 2.0.0.rc2)
custom_fields (~> 2.0.0.rc3)
devise (~> 1.5.3)
dragonfly (~> 0.9.8)
flash_cookie_session (~> 1.1.1)
fog (~> 1.0.0)
formtastic (~> 2.0.2)
haml (~> 3.1.3)
haml (~> 3.1.4)
highline (~> 1.6.2)
httparty (~> 0.8.1)
jquery-rails (~> 1.0.16)
kaminari (~> 0.13.0)
locomotive-aloha-rails (~> 0.20.1)
locomotive-tinymce-rails (~> 3.4.7)
locomotive-aloha-rails (~> 0.20.1.1)
locomotive-mongoid-tree (~> 0.6.2)
locomotive-tinymce-rails (~> 3.4.7.1)
locomotive_liquid (= 2.2.2)
locomotive_mongoid_acts_as_tree (~> 0.1.5.8)
mimetype-fu (~> 0.1.2)
mongo (~> 1.5.2)
mongoid (~> 2.4.3)
rack-cache (~> 1.1)
rails (~> 3.1.3)
rails-backbone (~> 0.5.4)
rails (~> 3.2.1)
rails-backbone (~> 0.6.1)
rake (~> 0.9.2)
responders (~> 0.6.4)
rmagick (~> 2.12.2)
sanitize (~> 2.0.3)
sass-rails (~> 3.1.5)
uglifier (~> 1.2.2)
PATH
remote: ../gems/custom_fields
specs:
custom_fields (2.0.0.rc2)
activesupport (~> 3.1.3)
custom_fields (2.0.0.rc3)
activesupport (~> 3.2.1)
carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.4.3)
@ -52,38 +66,37 @@ GEM
specs:
RedCloth (4.2.9)
ZenTest (4.6.2)
actionmailer (3.1.3)
actionpack (= 3.1.3)
mail (~> 2.3.0)
actionmailer (3.2.1)
actionpack (= 3.2.1)
mail (~> 2.4.0)
actionmailer-with-request (0.3.0)
rails (>= 3)
actionpack (3.1.3)
activemodel (= 3.1.3)
activesupport (= 3.1.3)
actionpack (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
erubis (~> 2.7.0)
i18n (~> 0.6)
rack (~> 1.3.5)
journey (~> 1.0.1)
rack (~> 1.4.0)
rack-cache (~> 1.1)
rack-mount (~> 0.8.2)
rack-test (~> 0.6.1)
sprockets (~> 2.0.3)
activemodel (3.1.3)
activesupport (= 3.1.3)
sprockets (~> 2.1.2)
activemodel (3.2.1)
activesupport (= 3.2.1)
builder (~> 3.0.0)
i18n (~> 0.6)
activerecord (3.1.3)
activemodel (= 3.1.3)
activesupport (= 3.1.3)
arel (~> 2.2.1)
activerecord (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
arel (~> 3.0.0)
tzinfo (~> 0.3.29)
activeresource (3.1.3)
activemodel (= 3.1.3)
activesupport (= 3.1.3)
activesupport (3.1.3)
activeresource (3.2.1)
activemodel (= 3.2.1)
activesupport (= 3.2.1)
activesupport (3.2.1)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.2.6)
arel (2.2.1)
arel (3.0.0)
autotest (4.4.6)
ZenTest (>= 4.4.1)
bcrypt-ruby (3.0.1)
@ -112,14 +125,13 @@ GEM
chunky_png (1.2.5)
codemirror-rails (2.21)
railties (~> 3.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.2.0)
compass (0.12.alpha.4)
chunky_png (~> 1.2)
fssm (>= 0.2.7)
sass (~> 3.1)
cucumber (1.1.4)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
@ -175,6 +187,7 @@ GEM
multi_json
multi_xml
i18n (0.6.0)
journey (1.0.1)
jquery-rails (1.0.19)
railties (~> 3.0)
thor (~> 0.14)
@ -186,13 +199,14 @@ GEM
kgio (2.7.2)
launchy (2.0.5)
addressable (~> 2.2.6)
locomotive-aloha-rails (0.20.1)
actionpack (~> 3.1.3)
locomotive-tinymce-rails (3.4.7)
actionpack (~> 3.1.3)
locomotive-aloha-rails (0.20.1.1)
actionpack (~> 3.2.1)
locomotive-mongoid-tree (0.6.2)
mongoid (~> 2.0)
locomotive-tinymce-rails (3.4.7.1)
actionpack (~> 3.2.1)
locomotive_liquid (2.2.2)
locomotive_mongoid_acts_as_tree (0.1.5.8)
mail (2.3.0)
mail (2.4.1)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
@ -216,36 +230,34 @@ GEM
cucumber (>= 0.8)
rake
polyglot (0.3.3)
rack (1.3.6)
rack (1.4.1)
rack-cache (1.1)
rack (>= 0.4)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-ssl (1.3.2)
rack
rack-test (0.6.1)
rack (>= 1.0)
rails (3.1.3)
actionmailer (= 3.1.3)
actionpack (= 3.1.3)
activerecord (= 3.1.3)
activeresource (= 3.1.3)
activesupport (= 3.1.3)
rails (3.2.1)
actionmailer (= 3.2.1)
actionpack (= 3.2.1)
activerecord (= 3.2.1)
activeresource (= 3.2.1)
activesupport (= 3.2.1)
bundler (~> 1.0)
railties (= 3.1.3)
rails-backbone (0.5.5)
railties (= 3.2.1)
rails-backbone (0.6.1)
coffee-script (~> 2.2.0)
ejs (~> 1.0.0)
rails (~> 3.1.0)
railties (3.1.3)
actionpack (= 3.1.3)
activesupport (= 3.1.3)
railties (>= 3.1.0)
railties (3.2.1)
actionpack (= 3.2.1)
activesupport (= 3.2.1)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
raindrops (0.8.0)
rake (0.9.2)
rake (0.9.2.2)
rdoc (3.12)
json (~> 1.4)
responders (0.6.5)
@ -254,7 +266,8 @@ GEM
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-cells (0.1.1)
rspec-cells (0.1.2)
cells (~> 3.4)
rails (~> 3.0)
rspec-rails (~> 2.2)
rspec-core (2.6.4)
@ -271,18 +284,17 @@ GEM
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.1.12)
sass-rails (3.1.5)
actionpack (~> 3.1.0)
railties (~> 3.1.0)
sass (~> 3.1.10)
tilt (~> 1.3.2)
selenium-webdriver (2.17.0)
sass-rails (3.2.4)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
selenium-webdriver (2.18.0)
childprocess (>= 0.2.5)
ffi (~> 1.0.9)
multi_json (~> 1.0.4)
rubyzip
shoulda-matchers (1.0.0)
sprockets (2.0.3)
sprockets (2.1.2)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
@ -296,10 +308,10 @@ GEM
uglifier (1.2.3)
execjs (>= 0.3.0)
multi_json (>= 1.0.2)
unicorn (4.1.1)
kgio (~> 2.4)
unicorn (4.2.0)
kgio (~> 2.6)
rack
raindrops (~> 0.6)
raindrops (~> 0.7)
warden (1.1.0)
rack (>= 1.0)
xpath (0.1.4)
@ -312,6 +324,9 @@ DEPENDENCIES
ZenTest
autotest
capybara
coffee-rails (~> 3.2.2)
compass!
compass-rails!
cucumber-rails
custom_fields!
database_cleaner
@ -323,6 +338,8 @@ DEPENDENCIES
pickle
rspec-cells
rspec-rails (= 2.6.1)
sass-rails (~> 3.2.4)
shoulda-matchers
uglifier (~> 1.2.3)
unicorn
xpath (~> 0.1.4)

View File

@ -7,6 +7,11 @@ class Locomotive.Models.ContentEntry extends Backbone.Model
initialize: ->
@urlRoot = @urlRoot.replace(':slug', @get('content_type_slug'))
_.each @get('_has_many_fields'), (field) =>
name = field[0]
collection = new Locomotive.Models.ContentEntriesCollection(@get(name))
@set_attribute name, collection
set_attribute: (attribute, value) ->
data = {}
data[attribute] = value
@ -25,6 +30,8 @@ class Locomotive.Models.ContentEntry extends Backbone.Model
unless _.include(@get('safe_attributes'), key)
delete hash[key]
# TODO: insert has_many
class Locomotive.Models.ContentEntriesCollection extends Backbone.Collection
model: Locomotive.Models.ContentEntry

View File

@ -10,10 +10,24 @@ class Locomotive.Models.CustomField extends Backbone.Model
@set
select_options: new Locomotive.Models.CustomFieldSelectOptionsCollection(@get('select_options'))
_undesired_fields:
['select_options', 'type_text', 'text_formatting_text', 'inverse_of_text', 'class_name_text', 'undefined_text', 'undefined', 'created_at', 'updated_at']
_relationship_fields:
['class_name', 'inverse_of', 'ui_enabled']
is_relationship_type: ->
_.include(['belongs_to', 'has_many'], @get('type'))
toJSONForSave: ->
_.tap {}, (hash) =>
for key, value of @.toJSON()
hash[key] = value unless _.include(['select_options', 'type_text', 'text_formatting_text', 'undefined_text', 'undefined', 'created_at', 'updated_at'], key)
unless _.include(@_undesired_fields, key)
if _.include(@_relationship_fields, key)
hash[key] = value if @is_relationship_type()
else
hash[key] = value
hash.select_options_attributes = @get('select_options').toJSONForSave() if @get('select_options')? && @get('select_options').length > 0
class Locomotive.Models.CustomFieldsCollection extends Backbone.Collection

View File

@ -8,6 +8,8 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
_file_field_views: []
_has_many_field_views: []
events:
'submit': 'save'
@ -29,6 +31,8 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
@enable_file_fields()
@enable_has_many_fields()
@slugify_label_field()
return @
@ -55,6 +59,15 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
@$("##{@model.paramRoot}_#{name}_input").append(view.render().el)
enable_has_many_fields: ->
_.each @model.get('_has_many_fields'), (field) =>
name = field[0]; inverse_of = field[1]
view = new Locomotive.Views.Shared.Fields.HasManyView model: @model, name: name, inverse_of: inverse_of
@_has_many_field_views.push(view)
@$("##{@model.paramRoot}_#{name}_input").append(view.render().el)
slugify_label_field: ->
@$('li.input.highlighted > input[type=text]').slugify(target: @$('#content_entry__slug'))
@ -63,5 +76,6 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
remove: ->
_.each @_file_field_views, (view) => view.remove()
_.each @_has_many_field_views, (view) => view.remove()
super

View File

@ -30,7 +30,7 @@ class Locomotive.Views.ContentTypes.FormView extends Locomotive.Views.Shared.For
return @
render_custom_fields: ->
@custom_fields_view = new Locomotive.Views.ContentTypes.CustomFieldsView model: @model
@custom_fields_view = new Locomotive.Views.ContentTypes.CustomFieldsView model: @model, inverse_of_list: @options.inverse_of_list
@$('#custom_fields_input').append(@custom_fields_view.render().el)

View File

@ -11,12 +11,19 @@ class Locomotive.Views.ContentTypes.CustomFieldEntryView extends Backbone.View
'click a.remove': 'remove'
initialize: ->
@inverse_of_list = @options.parent_view.options.inverse_of_list
console.log(@model.get('inverse_of'))
@model.bind 'change', (custom_field) =>
@switch_to_type() if @model.hasChanged('type')
@fetch_inverse_of_list() if @model.hasChanged('class_name') && @model.get('class_name')?
render: ->
$(@el).html(ich.custom_field_entry(@model.toJSON()))
@fetch_inverse_of_list() unless @model.isNew()
Backbone.ModelBinding.bind @, all: 'class'
@make_fields_editable()
@ -44,6 +51,19 @@ class Locomotive.Views.ContentTypes.CustomFieldEntryView extends Backbone.View
when 'belongs_to'
@$('li.input.localized').hide()
@$('li.input.class-name').show()
when 'has_many'
@$('li.input.localized').hide()
@$('li.input.class-name').show()
@$('li.input.inverse-of').show()
@$('li.input.ui-enabled').show()
fetch_inverse_of_list: ->
@$('li.input.inverse-of select option').remove()
_.each @inverse_of_list, (data) =>
if data.class_name == @model.get('class_name')
option = new Option(data.label, data.name, data.class_name == @model.get('inverse_of') || @inverse_of_list.length == 1)
@$('li.input.inverse-of select').append(option)
render_select_options_view: ->
@select_options_view = new Locomotive.Views.ContentTypes.SelectOptionsView

View File

@ -3,4 +3,13 @@ Locomotive.Views.ContentTypes ||= {}
class Locomotive.Views.ContentTypes.EditView extends Locomotive.Views.ContentTypes.FormView
save: (event) ->
@save_in_ajax event
@save_in_ajax event, on_success: (response, xhr) =>
_.each response.entries_custom_fields, (data) =>
custom_field = @model.get('entries_custom_fields').detect (entry) => entry.get('name') == data.name
if custom_field.isNew() # assign an id for each new custom field
custom_field.set id: data._id, _id: data._id
console.log(custom_field)

View File

@ -0,0 +1,72 @@
Locomotive.Views.Shared ||= {}
Locomotive.Views.Shared.Fields ||= {}
class Locomotive.Views.Shared.Fields.HasManyView extends Backbone.View
tagName: 'div'
className: 'list'
events:
'click ul span.actions a.remove': 'remove_entry'
template: ->
ich["#{@options.name}_list"]
entry_template: ->
ich["#{@options.name}_entry"]
initialize: ->
_.bindAll(@, 'refresh_position_entries')
@collection = @model.get(@options.name)
window.model = @model
window.bar = @collection
render: ->
$(@el).html(@template()())
@insert_entries()
@make_entries_sortable()
return @
insert_entries: ->
if @collection.length > 0
@collection.each (entry) => @insert_entry(entry)
else
@$('.empty').show()
insert_entry: (entry) ->
@$('.empty').hide()
entry_html = $(@entry_template()(label: entry.get('_label')))
entry_html.data('data-entry-cid', entry.cid)
@$('> ul').append(entry_html)
make_entries_sortable: ->
@sortable_list = @$('> ul').sortable
handle: '.handle'
items: 'li'
axis: 'y'
update: @refresh_position_entries
refresh_position_entries: ->
@$('> ul > li').each (index, entry_html) =>
cid = $(entry_html).data('data-entry-cid')
entry = @collection.getByCid(cid)
entry.set_attribute "position_in_#{@options.inverse_of}", index
remove_entry: (event) ->
event.stopPropagation() & event.preventDefault()
if confirm($(event.target).attr('data-confirm'))
entry_html = $(event.target).closest('li')
cid = $(entry_html).data('data-entry-cid')
entry = @collection.getByCid(cid)
entry.set _destroy: true
entry_html.remove()
@refresh_position_entries()

View File

@ -111,6 +111,11 @@ form.formtastic {
}
}
.col {
float: left;
margin-right: 20px;
}
span.actions {
position: absolute;
top: 0px;
@ -541,6 +546,31 @@ form.formtastic {
}
} // li.select-options
&.relationship {
.list > ul {
.col {
&.handle {
position: relative;
top: 4px;
margin-left: 5px;
margin-right: 2px;
cursor: move;
}
&.label {
margin-left: 8px;
font-weight: bold;
}
} // ul .col
} // .list > ul
} // li.relationship
&#site_memberships_input {
.entry {
em.email {

View File

@ -7,7 +7,7 @@ module Locomotive
before_filter :set_content_type
respond_to :json, :only => [:edit, :create, :update, :sort]
respond_to :json, :only => [:show, :edit, :create, :update, :sort]
skip_load_and_authorize_resource
@ -18,6 +18,11 @@ module Locomotive
respond_with @content_entries
end
def show
@content_entry = @content_type.entries.find(params[:id])
respond_with @content_entry
end
def new
@content_entry = @content_type.entries.build
respond_with @content_entry

View File

@ -1,7 +1,7 @@
module Locomotive::CustomFieldsHelper
def options_for_custom_field_type
%w(string text select boolean date file belongs_to).map do |type|
%w(string text select boolean date file belongs_to has_many).map do |type|
[t("custom_fields.types.#{type}"), type]
end
end
@ -41,13 +41,27 @@ module Locomotive::CustomFieldsHelper
end
end
def options_for_content_types
def options_for_content_type
current_site.content_types.map do |c|
c != @content_type ? [c.name, c.klass_with_custom_fields(:entries).to_s] : nil
end.compact
end
def options_for_content_type_inverse_of
[].tap do |list|
current_site.content_types.where(:'entries_custom_fields.type' => 'belongs_to').each do |content_type|
content_type.entries_custom_fields.each do |field|
if field.type == 'belongs_to'
list << {
:label => field.label,
:name => field.name,
:class_name => content_type.klass_with_custom_fields(:entries).to_s
}
end
end
end
end
end
# def options_for_association_target
# current_site.reload.content_types.collect { |c| [c.name, c.content_klass.to_s] }

View File

@ -1,5 +1,5 @@
module Locomotive
class EmptyInput # < Formtastic::Inputs::HiddenInput
class EmptyInput
include Formtastic::Inputs::Base
def to_html

View File

@ -1,5 +1,5 @@
module Locomotive
class FileInput # < Formtastic::Inputs::TextInput
class FileInput
include Formtastic::Inputs::Base

View File

@ -9,6 +9,7 @@ module Locomotive
## fields ##
field :_slug
field :_label_field_name
field :_position, :type => Integer, :default => 0
field :_visible, :type => Boolean, :default => true
@ -22,6 +23,7 @@ module Locomotive
## callbacks ##
before_validation :set_slug
before_save :set_visibility
before_save :set_label_field_name
before_create :add_to_list_bottom
after_create :send_notifications
@ -36,8 +38,12 @@ module Locomotive
alias :_permalink= :_slug=
def _label(type = nil)
if self._label_field_name
self.send(self._label_field_name.to_sym)
else
self.send((type || self.content_type).label_field_name.to_sym)
end
end
def visible?
self._visible || self._visible.nil?
@ -114,6 +120,10 @@ module Locomotive
end
end
def set_label_field_name
self._label_field_name = self.content_type.label_field_name
end
def add_to_list_bottom
self._position = self.class.max(:_position).to_i + 1
end

View File

@ -33,6 +33,7 @@ module Locomotive
before_validation :normalize_slug
after_validation :bubble_fields_errors_up
before_save :set_default_values
before_update :update_label_field_name_in_entries
## validations ##
validates_presence_of :site, :name, :slug
@ -114,6 +115,29 @@ module Locomotive
self.errors.set(:entries_custom_fields, hash)
end
def update_label_field_name_in_entries
self.klass_with_custom_fields(:entries).update_all :_label_field_name => self.label_field_name
end
# Makes sure the class_name filled in a belongs_to or has_many field
# does not belong to another site. Adds an error if it presents a
# security problem.
#
# @param [ CustomFields::Field ] field The field to check
#
def ensure_class_name_security(field)
if field.class_name =~ /^Locomotive::Entry([a-z0-9]+)$/
content_type = Locomotive::ContentType.find($1)
if content_type.site_id != self.site_id
field.errors.add :security
end
else
# for now, does not allow external classes
field.errors.add :security
end
end
end
end

View File

@ -6,36 +6,27 @@ module Locomotive
extend ActiveSupport::Concern
included do
include ::Mongoid::Acts::Tree
## fields ##
field :position, :type => Integer
include ::Mongoid::Tree
include ::Mongoid::Tree::Ordering
## indexes ##
index :position
index [[:depth, :asc], [:position, :asc]]
## behaviours ##
acts_as_tree :order => ['position', 'asc']
## callbacks ##
before_validation :reset_parent
before_save { |p| p.send(:write_attribute, :parent_id, nil) if p.parent_id.blank? }
before_save :change_parent
before_create { |p| p.send(:fix_position, false) }
before_create :add_to_list_bottom
before_destroy :remove_from_list
# Fixme (Didier L.): Instances methods are defined before the include itself
alias :fix_position :hacked_fix_position
alias :descendants :hacked_descendants
index [:depth.asc, :position.asc]
end
module ClassMethods
# Warning: should be used only in read-only
# Returns the pages tree from the site with the most minimal amount of queries.
# This method should only be used for read-only purpose since
# the mongodb returns the minimal set of required attributes to build
# the tree.
#
# @param [ Locomotive::Site ] site The site owning the pages
#
# @return [ Array ] The first array of pages (depth = 0)
#
def quick_tree(site, minimal_attributes = true)
pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([[:depth, :asc], [:position, :asc]]).to_a
pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([:depth.asc, :position.asc]).to_a
tmp = []
@ -46,6 +37,7 @@ module Locomotive
tmp
end
#:nodoc:
def _quick_tree(current_page, pages)
i, children = 0, []
@ -75,85 +67,27 @@ module Locomotive
end
module InstanceMethods
def children?
self.class.where(self.parent_id_field => self.id).count
end
# Returns the children of this node but with the minimal set of required attributes
#
# @return [ Array ] The children pages ordered by their position
#
def children_with_minimal_attributes
self.class.where(self.parent_id_field => self.id).
order_by(self.tree_order).
minimal_attributes
self.children.minimal_attributes
end
# Assigns the new position of each child of this node.
#
# @param [ Array ] ids The ordered list of page ids (string)
#
def sort_children!(ids)
cached_children = self.children.to_a
ids.each_with_index do |id, position|
child = self.children.detect { |p| p._id == BSON::ObjectId(id) }
child = cached_children.detect { |p| p._id == BSON::ObjectId(id) }
child.position = position
child.save
end
end
def parent=(owner) # missing in acts_as_tree
@_parent = owner
self.fix_position(false)
self.instance_variable_set :@_will_move, true
end
def hacked_descendants
return [] if new_record?
self.class.all_in(path_field => [self._id]).order_by tree_order
end
protected
def change_parent
if self.parent_id_changed?
self.fix_position(false)
unless self.parent_id_was.nil?
self.position = nil # make it move to bottom
self.add_to_list_bottom
end
self.instance_variable_set :@_will_move, true
end
end
def hacked_fix_position(perform_save = true)
if parent.nil?
self.write_attribute parent_id_field, nil
self[path_field] = []
self[depth_field] = 0
else
self.write_attribute parent_id_field, parent._id
self[path_field] = parent[path_field] + [parent._id]
self[depth_field] = parent[depth_field] + 1
self.save if perform_save
end
end
def reset_parent
if self.parent_id_changed?
@_parent = nil
end
end
def add_to_list_bottom
self.position ||= (self.class.where(:_id.ne => self._id).and(:parent_id => self.parent_id).max(:position) || 0) + 1
end
def remove_from_list
return if (self.site rescue nil).nil?
self.class.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
p.position -= 1
p.save
end
end
end
end
end
end

View File

@ -23,7 +23,7 @@ module Locomotive
field :cache_strategy, :default => 'none'
## associations ##
referenced_in :site, :class_name => 'Locomotive::Site'
belongs_to :site, :class_name => 'Locomotive::Site'
## indexes ##
index :site_id
@ -105,7 +105,7 @@ module Locomotive
if self.index? || self.not_found?
self.fullpath = self.slug
else
slugs = self.self_and_ancestors.sort_by(&:depth).map(&:slug)
slugs = self.ancestors_and_self.map(&:slug)
slugs.shift unless slugs.size == 1
self.fullpath = File.join slugs.compact
end

View File

@ -1,7 +1,7 @@
module Locomotive
class ContentEntryPresenter < BasePresenter
delegate :_slug, :_position, :seo_title, :meta_keywords, :meta_description, :to => :source
delegate :_label, :_slug, :_position, :seo_title, :meta_keywords, :meta_description, :to => :source
# Returns the value of a field in the context of the current entry.
#
@ -33,11 +33,12 @@ module Locomotive
case rule['type'].to_sym
when :date then "formatted_#{rule['name']}"
when :file then [rule['name'], "remove_#{rule['name']}"]
when :select, :belongs_to then "#{rule['name']}_id"
when :select, :belongs_to then ["#{rule['name']}_id", "position_in_#{rule['name']}"]
when :has_many then nil
else
rule['name']
end
end.flatten + %w(_slug seo_title meta_keywords meta_description)
end.compact.flatten + %w(_slug seo_title meta_keywords meta_description _destroy)
end
def errors
@ -52,8 +53,12 @@ module Locomotive
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == 'file' }.map { |rule| rule['name'] }
end
def _has_many_fields
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == 'has_many' }.map { |rule| [rule['name'], rule['inverse_of']] }
end
def included_methods
default_list = %w(_slug _position content_type_slug _file_fields safe_attributes)
default_list = %w(_label _slug _position content_type_slug _file_fields _has_many_fields safe_attributes)
default_list << 'errors' if !!self.options[:include_errors]
super + self.custom_fields_methods + default_list
end
@ -81,6 +86,7 @@ module Locomotive
when :date then "formatted_#{name}"
when :file then "#{name}_url"
when :belongs_to then "#{name}_id"
# when :has_many then nil
else
name
end

View File

@ -7,7 +7,11 @@
= f.inputs :name => :attributes do
- @content_type.ordered_entries_custom_fields.each_with_index do |field, index|
= render "locomotive/custom_fields/types/#{field.type}", :f => f, :name => field.name.to_sym, :field => field, :highlighted => field._id == @content_type.label_field_id
= render "locomotive/custom_fields/types/#{field.type}",
:f => f,
:name => field.name.to_sym,
:field => field,
:highlighted => field._id == @content_type.label_field_id
= f.inputs :name => :advanced_options, :class => "inputs foldable #{'folded' if inputs_folded?(@content_entry)}" do

View File

@ -3,7 +3,10 @@
- content_for :backbone_view_data do
:plain
{ content_type: #{@content_type.persisted? ? @content_type.to_json : 'null'} }
{
content_type: #{@content_type.persisted? ? @content_type.to_json : 'null'},
inverse_of_list: #{options_for_content_type_inverse_of.to_json}
}
= f.inputs :name => :information do

View File

@ -44,9 +44,11 @@
= g.input :text_formatting, :as => :select, :collection => options_for_text_formatting, :include_blank => false, :wrapper_html => { :class => 'extra text-formatting' }, :input_html => { :class => 'text_formatting' }
= g.input :class_name, :as => :select, :collection => options_for_content_types, :include_blank => false, :wrapper_html => { :class => 'extra class-name', :style => 'display: none' }, :input_html => { :class => 'class_name' }
= g.input :class_name, :as => :select, :collection => options_for_content_type, :include_blank => false, :wrapper_html => { :class => 'extra class-name', :style => 'display: none' }, :input_html => { :class => 'class_name' }
/ = g.input :inverse_of, :input_html => { :class => 'inverse_of' }, :wrapper_html => { :style => 'display: none' }
= g.input :inverse_of, :as => :select, :collection => [], :wrapper_html => { :class => 'extra inverse-of', :style => 'display: none' }, :input_html => { :class => 'inverse_of' }
= g.input :ui_enabled, :as => :'Locomotive::Toggle', :wrapper_html => { :class => 'extra ui-enabled' }, :input_html => { :class => 'ui_enabled' }
%span.actions
= link_to 'toggle', '#', :class => 'toggle'

View File

@ -1,33 +1,70 @@
= form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'has-many', :required => required do
- if !f.object.new_record? && field.ui_enabled?
.has-many-selector
= f.input name.to_sym,
:label => field.label,
:hint => field.hint,
:as => :'Locomotive::Empty',
:wrapper_html => { :id => "content_entry_#{name}_input", :class => 'empty relationship input' }
%p{ :style => form.object.send(field._alias.to_sym).empty? ? '' : 'display: none' }
= t('.empty')
- content_for :head do
%script{ :type => 'text/html', :id => "#{name}_list" }
%p.empty{ :style => 'display: none' }!= t('.empty')
%ul
%script{ :type => 'text/x-mustache-template', :name => 'template', :'data-base-input-name' => "content[#{field._alias.to_sym}]" }
%li{ :class => "item {{behaviour_flag}}" }
%span.handle
= image_tag 'admin/form/icons/drag.png'
%hr
{{^if_template}}
%input{ :name => '{{base_name}}[]', :value => '{{{id}}}', :type => 'hidden', :'data-field' => 'id' }
{{/if_template}}
%p
= link_to t('.new_entry'), '#'
%strong {{label}}
%script{ :type => 'text/html', :id => "#{name}_entry" }
%li
.handle.col
= image_tag 'locomotive/form/icons/drag.png'
{{#if_template}}
= select_tag 'label', ''
{{/if_template}}
.label.col
{{label}}
%span.actions
- if field.reverse_lookup?
= link_to image_tag('admin/form/pen.png'), '#', :class => 'edit first'
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove'
%button{ :class => 'button light mini add', :type => 'button' }
%span!= t('locomotive.buttons.new_item')
= link_to 'x', '#', :class => 'remove', :confirm => t('locomotive.messages.confirm')
%script{ :type => 'text/javascript', :name => 'data' }
!= has_many_data_to_js(field, form.object)
/ - collection = f.object.send(name.to_sym)
/ = form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'has-many', :required => required do
/
/ .has-many-selector
/
/ %p{ :style => form.object.send(field._alias.to_sym).empty? ? '' : 'display: none' }
/ = t('.empty')
/
/ %ul
/
/ %script{ :type => 'text/x-mustache-template', :name => 'template', :'data-base-input-name' => "content[#{field._alias.to_sym}]" }
/ %li{ :class => "item {{behaviour_flag}}" }
/ %span.handle
/ = image_tag 'admin/form/icons/drag.png'
/
/ {{^if_template}}
/ %input{ :name => '{{base_name}}[]', :value => '{{{id}}}', :type => 'hidden', :'data-field' => 'id' }
/ {{/if_template}}
/
/ %strong {{label}}
/
/ {{#if_template}}
/ = select_tag 'label', ''
/ {{/if_template}}
/
/ %span.actions
/ - if field.reverse_lookup?
/ = link_to image_tag('admin/form/pen.png'), '#', :class => 'edit first'
/ = link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove'
/ %button{ :class => 'button light mini add', :type => 'button' }
/ %span!= t('locomotive.buttons.new_item')
/
/ %script{ :type => 'text/javascript', :name => 'data' }
/ != has_many_data_to_js(field, form.object)

View File

@ -1,5 +1,5 @@
= f.input :"#{name}_id",
:label => field.label,
:hint => field.hint,
:as => 'select',
:as => :select,
:collection => field.ordered_select_options.map { |option| [option.name, option.id] }

View File

@ -1,5 +1,5 @@
= f.input field.name.to_sym,
:label => field.label,
:hint => field.hint,
:as => field.text_formatting == 'html' ? :'Locomotive::Rte' : 'text',
:as => field.text_formatting == 'html' ? :'Locomotive::Rte' : :text,
:input_html => { :class => field.text_formatting }

View File

@ -17,6 +17,7 @@ en:
liquid_syntax: "Liquid Syntax error ('%{error}' on '%{fullpath}')"
liquid_extend: "The page '%{fullpath}' extends a template which does not exist"
too_few_custom_fields: "At least, one custom field is required"
security: "presents a security problem"
attributes:
defaults:

View File

@ -3,12 +3,24 @@ CustomFields.options = {
:reserved_names => Mongoid.destructive_fields + %w(created_at updated_at)
}
# Set correct paths
module CustomFields
class Field
field :ui_enabled, :type => Boolean, :default => true
protected
def ensure_class_name_security
self._parent.send :ensure_class_name_security, self
end
end
module Types
module File
class FileUploader < ::CarrierWave::Uploader::Base
# Set correct paths
def store_dir
"sites/#{model.site_id}/contents/#{model.class.model_name.underscore}/#{model.id}/files"
end

View File

@ -2,7 +2,7 @@ puts "\t...loading dependencies"
require 'mongoid'
require 'mongoid/railtie'
require 'mongoid_acts_as_tree'
require 'mongoid/tree'
require 'devise'
require 'devise/orm/mongoid'
require 'kaminari'

View File

@ -18,7 +18,7 @@ module Locomotive
end
def breadcrumbs
@breadcrumbs ||= liquify(*self._source.self_and_ancestors)
@breadcrumbs ||= liquify(*self._source.ancestors_and_self)
end
def children

View File

@ -14,13 +14,13 @@ Gem::Specification.new do |s|
s.email = ['didier@nocoffee.fr']
s.homepage = 'http://www.locomotivecms.com'
s.summary = 'A Next Generation Sexy CMS for Rails 3'
s.description = 'Locomotive is a next generation CMS system with sexy admin tools, liquid templating, and inline editing powered by mongodb and rails 3.1'
s.description = 'Locomotive is a next generation CMS system with sexy admin tools, liquid templating, and inline editing powered by mongodb and rails 3.2'
s.required_rubygems_version = '>= 1.3.6'
s.add_dependency 'rake', '~> 0.9.2'
s.add_dependency 'rails', '~> 3.1.3'
s.add_dependency 'rails', '~> 3.2.1'
s.add_dependency 'devise', '~> 1.5.3'
s.add_dependency 'cancan', '~> 1.6.7'
@ -28,22 +28,23 @@ Gem::Specification.new do |s|
s.add_dependency 'mongo', '~> 1.5.2'
s.add_dependency 'bson_ext', '~> 1.5.2'
s.add_dependency 'mongoid', '~> 2.4.3'
s.add_dependency 'locomotive_mongoid_acts_as_tree', '~> 0.1.5.8'
s.add_dependency 'locomotive-mongoid-tree', '~> 0.6.2'
# s.add_dependency 'locomotive_mongoid_acts_as_tree', '~> 0.1.5.8'
s.add_dependency 'custom_fields', '~> 2.0.0.rc2'
s.add_dependency 'custom_fields', '~> 2.0.0.rc3'
s.add_dependency 'kaminari', '~> 0.13.0'
s.add_dependency 'haml', '~> 3.1.3'
s.add_dependency 'sass-rails', '~> 3.1.5'
s.add_dependency 'coffee-script', '~> 2.2.0'
s.add_dependency 'uglifier', '~> 1.2.2'
s.add_dependency 'compass', '~> 0.12.alpha.4'
s.add_dependency 'haml', '~> 3.1.4'
# # s.add_dependency 'sass-rails', '~> 3.1.5'
# # s.add_dependency 'coffee-script', '~> 2.2.0'
# # s.add_dependency 'uglifier', '~> 1.2.2'
# # # s.add_dependency 'compass', '~> 0.12.rc.0'
s.add_dependency 'jquery-rails', '~> 1.0.16'
s.add_dependency 'rails-backbone', '~> 0.5.4'
s.add_dependency 'rails-backbone', '~> 0.6.1'
s.add_dependency 'codemirror-rails', '~> 2.21'
s.add_dependency 'locomotive-tinymce-rails', '~> 3.4.7'
s.add_dependency 'locomotive-aloha-rails', '~> 0.20.1'
s.add_dependency 'locomotive-tinymce-rails', '~> 3.4.7.1'
s.add_dependency 'locomotive-aloha-rails', '~> 0.20.1.1'
s.add_dependency 'flash_cookie_session', '~> 1.1.1'
s.add_dependency 'locomotive_liquid', '2.2.2'

View File

@ -5,7 +5,8 @@ require 'action_mailer/railtie'
require 'active_resource/railtie'
require 'sprockets/railtie'
Bundler.require
# Bundler.require
Bundler.require(*Rails.groups(:assets => %w(development test)))
require 'locomotive/engine'
module Dummy
@ -43,6 +44,10 @@ module Dummy
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
# config.after_initialize do |c|
# c.middleware.delete(Sass::Plugin::Rack)
# end
end
end