big refactoring (wip) + add/edit entries for content types

This commit is contained in:
did 2012-01-02 05:54:01 -08:00
parent e9ef4d48c3
commit 912251d49b
54 changed files with 628 additions and 170 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

View File

@ -20,3 +20,7 @@
//= require_tree ../../../vendor/assets/javascripts //= require_tree ../../../vendor/assets/javascripts
//= require ./locomotive/application //= require ./locomotive/application
$(document).ready(function() {
$.datepicker.setDefaults($.datepicker.regional[window.locale]);
});

View File

@ -2,10 +2,30 @@ class Locomotive.Models.ContentEntry extends Backbone.Model
paramRoot: 'content_entry' paramRoot: 'content_entry'
urlRoot: "#{Locomotive.mount_on}/content_type/:slug/entries" urlRoot: "#{Locomotive.mount_on}/content_types/:slug/entries"
initialize: ->
@urlRoot = @urlRoot.replace(':slug', @get('content_type_slug'))
set_attribute: (attribute, value) ->
data = {}
data[attribute] = value
@set data
update_attributes: (attributes) ->
_.each attributes._file_fields, (field) => # special treatment for files
attribute = "#{field}_url"
@set_attribute attribute, attributes[attribute]
@set_attribute "remove_#{field}", false
toJSON: ->
_.tap super, (hash) =>
_.each _.keys(hash), (key) =>
unless _.include(@get('safe_attributes'), key)
delete hash[key]
class Locomotive.Models.ContentEntriesCollection extends Backbone.Collection class Locomotive.Models.ContentEntriesCollection extends Backbone.Collection
model: Locomotive.Models.Content model: Locomotive.Models.ContentEntry
url: "#{Locomotive.mount_on}/content_type/:slug/entries" url: "#{Locomotive.mount_on}/content_types/:slug/entries"

View File

@ -6,16 +6,65 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
el: '#content' el: '#content'
_file_field_views: []
events: events:
'submit': 'save' 'submit': 'save'
initialize: -> initialize: ->
@model = new Locomotive.Models.ContentEntry(@options.content_entry) @model = new Locomotive.Models.ContentEntry(@options.content_entry)
console.log(@model.urlRoot)
window.foo = @model
Backbone.ModelBinding.bind @ Backbone.ModelBinding.bind @
render: -> render: ->
super() super()
@enable_checkboxes()
@enable_datepickers()
@enable_richtexteditor()
@enable_file_fields()
@slugify_label_field()
return @ return @
enable_checkboxes: ->
@$('li.input.toggle input[type=checkbox]').checkToggle()
enable_datepickers: ->
@$('li.input.date input[type=text]').datepicker()
enable_richtexteditor: ->
_.each @$('li.input.rte textarea.html'), (textarea) =>
settings = _.extend {}, window.Locomotive.tinyMCE.defaultSettings,
onchange_callback: (editor) =>
$(textarea).val(editor.getBody().innerHTML).trigger('change')
$(textarea).tinymce(settings)
enable_file_fields: ->
_.each @model.get('_file_fields'), (name) =>
view = new Locomotive.Views.Shared.Fields.FileView model: @model, name: name
@_file_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'))
refresh_file_fields: ->
console.log('refresh_file_fields')
_.each @_file_field_views, (view) => view.refresh()
remove: ->
_.each @_file_field_views, (view) => view.remove()
super

View File

@ -3,4 +3,7 @@ Locomotive.Views.ContentEntries ||= {}
class Locomotive.Views.ContentEntries.EditView extends Locomotive.Views.ContentEntries.FormView class Locomotive.Views.ContentEntries.EditView extends Locomotive.Views.ContentEntries.FormView
save: (event) -> save: (event) ->
@save_in_ajax event @save_in_ajax event,
on_success: (response, xhr) =>
@model.update_attributes(response)
@refresh_file_fields()

View File

@ -56,7 +56,7 @@ class Locomotive.Views.ContentTypes.FormView extends Locomotive.Views.Shared.For
enable_order_by_toggler: -> enable_order_by_toggler: ->
@$('#content_type_order_by_input').bind 'change', (event) => @$('#content_type_order_by_input').bind 'change', (event) =>
target = @$('#content_type_order_direction_input') target = @$('#content_type_order_direction_input')
if $(event.target).val() == '_position_in_list' if $(event.target).val() == '_position'
target.hide() target.hide()
else else
target.show() target.show()

View File

@ -0,0 +1,87 @@
Locomotive.Views.Shared ||= {}
Locomotive.Views.Shared.Fields ||= {}
class Locomotive.Views.Shared.Fields.FileView extends Backbone.View
tagName: 'span'
className: 'file'
states:
change: false
delete: false
events:
'click a.change': 'toggle_change'
'click a.delete': 'toggle_delete'
template: ->
ich["#{@options.name}_file_input"]
render: ->
url = @model.get("#{@options.name}_url") || ''
data =
filename: url.split('/').pop()
url: url
$(@el).html(@template()(data))
# only in HTML 5
@$('input[type=file]').bind 'change', (event) =>
input = $(event.target)[0]
if input.files?
name = $(input).attr('name')
hash = {}
hash[name.replace("#{@model.paramRoot}[", '').replace(/]$/, '')] = input.files[0]
@model.set(hash)
return @
refresh: ->
# @remove()
@$('input[type=file]').unbind 'change'
@states = { 'change': false, 'delete': false }
@render()
# @$('input[type=file]').unbind 'change'
# @states = { 'change': false, 'delete': false }
# @render()
toggle_change: (event) ->
@_toggle event, 'change',
on_change: =>
@$('a:first').hide() & @$('input[type=file]').show() & @$('a.delete').hide()
on_cancel: =>
@$('a:first').show() & @$('input[type=file]').hide() & @$('a.delete').show()
toggle_delete: (event) ->
@_toggle event, 'delete',
on_change: =>
@$('a:first').addClass('deleted') & @$('a.change').hide()
@$('input[type=hidden].remove-flag').val('1')
@model.set_attribute("remove_#{@options.name}", true)
on_cancel: =>
@$('a:first').removeClass('deleted') & @$('a.change').show()
@$('input[type=hidden].remove-flag').val('0')
@model.set_attribute("remove_#{@options.name}", false)
_toggle: (event, state, options) ->
event.stopPropagation() & event.preventDefault()
button = $(event.target)
label = button.attr('data-alt-label')
unless @states[state]
options.on_change()
else
options.on_cancel()
button.attr('data-alt-label', button.html())
button.html(label)
@states[state] = !@states[state]
remove: ->
@$('input[type=file]').unbind 'change'
super

View File

@ -78,11 +78,19 @@
border: 0; border: 0;
} }
.locomotiveSkin table.mceLayout tr.mceFirst td.mceToolbar {
padding-bottom: 1px;
}
.locomotiveSkin table.mceLayout tr.mceFirst td { .locomotiveSkin table.mceLayout tr.mceFirst td {
border: none; border: none;
background: transparent; background: transparent;
} }
.locomotiveSkin table.mceLayout tr.mceFirst td.mceToolbarStart {
display: none;
}
.locomotiveSkin #mce_fullscreen_toolbargroup { .locomotiveSkin #mce_fullscreen_toolbargroup {
background: #ebedf4; background: #ebedf4;
} }
@ -92,6 +100,7 @@
} }
.locomotiveSkin table.mceLayout tr.mceLast td { .locomotiveSkin table.mceLayout tr.mceLast td {
margin-top: 2px;
border-bottom: 1px solid #CCC; border-bottom: 1px solid #CCC;
background: url('img/toolbarbg.png') center repeat-x; background: url('img/toolbarbg.png') center repeat-x;
/* background: transparent;*/ /* background: transparent;*/

View File

@ -0,0 +1,169 @@
@import "compass/css3";
@import "compass/css3/border-radius";
@import "compass/css3/images";
@import "compass/css3/text-shadow";
.ui-datepicker {
display: none;
width: auto;
z-index: 999 !important;
background: #f1f1f1;
padding: 3px 0 0 0;
margin-top: 9px;
border: 1px solid #bec8cc;
@include border-radius(6px);
@include box-shadow(rgba(255, 255, 255, 0.9) 0px 1px 1px 0px inset);
.ui-datepicker-header {
&:before {
position: absolute;
top: -10px;
display: block;
width: 100%;
height: 8px;
background: transparent image-url("locomotive/datepicker/ui-widget-content-top.png") no-repeat center 0;
content: " ";
}
background: transparent;
padding: 0px;
border: 0px;
@include border-radius(0px);
@include background-image(linear-gradient(#edf0f1, #d7dadc));
.ui-datepicker-title {
margin: 0px;
color: #646f75;
font-size: 11px;
line-height: 14px;
font-weight: normal;
@include text-shadow(rgba(255, 255, 255, 0.8), 0px, 1px, 1px);
}
.ui-datepicker-prev, .ui-datepicker-next {
top: 4px;
width: 7px;
height: 13px;
span {
background-position: 0 0;
cursor: pointer;
}
&.ui-state-hover {
border: 0px;
@include border-radius(0px);
top: 5px;
}
}
.ui-datepicker-prev {
left: 10px;
span {
background-image: image-url("locomotive/datepicker/ui-widget-left-icon.png");
}
}
.ui-datepicker-next {
right: 10px;
span {
background-image: image-url("locomotive/datepicker/ui-widget-right-icon.png");
}
}
}
.ui-datepicker-calendar {
margin: 0px;
font-size: 12px;
font-weight: normal;
thead {
th {
background: #d7dadc;
padding: 3px 0px;
border-bottom: 1px solid #bcbfc2;
color: #96a7b0;
font-size: 9px;
font-weight: normal;
text-transform: uppercase;
}
}
tbody {
tr {
td {
border-top: 1px solid #f8f8f8;
border-right: 1px solid #e0e0e0;
width: 24px;
height: 20px;
line-height: 20px;
padding: 0px;
vertical-align: middle;
text-align: center;
a {
display: block;
background: none;
padding: 0px;
border: 0px;
border-bottom: 1px solid #e0e0e0;
color: #63696d;
font-size: 11px;
font-weight: normal;
text-align: center;
&.ui-state-active {
border-top-color: transparent;
border-bottom-color: transparent;
}
}
&.ui-datepicker-current-day {
border-top-color: transparent;
border-bottom-color: transparent;
@include background-image(linear-gradient(top, #e1e4e5, #e1e4e5 40%, #ccd1d3));
}
&.ui-datepicker-today a {
font-weight: bold;
}
&:last-child {
border-right: 0px;
}
}
&:last-child td a {
border-bottom: 0px;
}
}
}
}
}

View File

@ -65,28 +65,6 @@
ol { ol {
min-height: 400px; min-height: 400px;
li.file {
span.file {
a:first-child {
@include hover-link;
margin-right: 20px;
color: #1F82BC;
&.deleted {
text-decoration: line-through;
}
}
a.change, a.delete {
@include blue-button;
margin-left: 5px;
}
}
}
} }
} // fieldset } // fieldset

View File

@ -338,6 +338,44 @@ form.formtastic {
} // li.code } // li.code
&.toggle {
p.inline-hints {
margin-top: 9px;
}
} // li.toggle
&.date {
input[type=text] {
width: 90px;
}
} // li.date
&.file {
span.file {
a:first-child {
@include hover-link;
margin-right: 20px;
color: #1F82BC;
&.deleted {
text-decoration: line-through;
}
}
a.change, a.delete {
@include blue-button;
margin-left: 5px;
}
}
} // li.file
&.subdomain { &.subdomain {
em { em {

View File

@ -1,11 +1,11 @@
module Locomotive module Locomotive
class ContentsController < BaseController class ContentEntriesController < BaseController
sections 'contents' sections 'contents'
before_filter :set_content_type before_filter :set_content_type
respond_to :json, :only => [:update, :sort] respond_to :json, :only => [:edit, :create, :update, :sort]
skip_load_and_authorize_resource skip_load_and_authorize_resource
@ -18,23 +18,23 @@ module Locomotive
def new def new
@content_entry = @content_type.entries.build @content_entry = @content_type.entries.build
respond_with @content respond_with @content_entry
end end
def create def create
@content_entry = @content_type.entries.create(params[:content_entry]) @content_entry = @content_type.entries.create(params[:content_entry])
respond_with @content, :location => edit_content_entry_url(@content_type.slug, @content_entry._id) respond_with @content_entry, :location => edit_content_entry_url(@content_type.slug, @content_entry._id)
end end
def edit def edit
@content_entry = @content_type.entries.find(params[:id]) @content_entry = @content_type.entries.find(params[:id])
respond_with @content respond_with @content_entry
end end
def update def update
@content_entry = @content_type.entries.find(params[:id]) @content_entry = @content_type.entries.find(params[:id])
@content_entry.update_attributes(params[:content_entry]) @content_entry.update_attributes(params[:content_entry])
respond_with @content, :location => edit_content_entry_url(@content_type.slug, @content_entry._id) respond_with @content_entry, :location => edit_content_entry_url(@content_type.slug, @content_entry._id)
end end
def sort def sort
@ -48,7 +48,7 @@ module Locomotive
def destroy def destroy
@content_entry = @content_type.entries.find(params[:id]) @content_entry = @content_type.entries.find(params[:id])
@content_entry.destroy @content_entry.destroy
respond_with @content, :location => pages_url respond_with @content_entry, :location => pages_url
end end
protected protected

View File

@ -1,7 +1,7 @@
module Locomotive::ContentTypesHelper module Locomotive::ContentTypesHelper
def each_content_type_menu_item(&block) def each_content_type_menu_item(&block)
current_site.content_types.ordered.only(:site_id, :name, :slug).each do |content_type| current_site.content_types.ordered.only(:site_id, :name, :slug, :label_field_name).each do |content_type|
next unless content_type.persisted? next unless content_type.persisted?
item_on = (content_type.slug == @content_type.slug) rescue nil item_on = (content_type.slug == @content_type.slug) rescue nil

View File

@ -6,9 +6,9 @@ module Locomotive::CustomFieldsHelper
end end
end end
def options_for_highlighted_field(content_type) def options_for_label_field(content_type)
content_type.ordered_entries_custom_fields.find_all do |field| content_type.ordered_entries_custom_fields.find_all do |field|
%w(string date select).include?(field.type) %w(string date).include?(field.type)
end.map do |field| end.map do |field|
[field.label, field._id] [field.label, field._id]
end end
@ -23,10 +23,10 @@ module Locomotive::CustomFieldsHelper
end end
def options_for_order_by(content_type) def options_for_order_by(content_type)
options = %w{created_at updated_at _position_in_list}.map do |type| options = %w{created_at updated_at _position}.map do |type|
[t("locomotive.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type] [t("locomotive.content_types.form.order_by.#{type.gsub(/^_/, '')}"), type]
end end
options + options_for_highlighted_field(content_type) options + options_for_label_field(content_type)
end end
def options_for_order_direction def options_for_order_direction

View File

@ -0,0 +1,45 @@
module Locomotive
class FileInput # < Formtastic::Inputs::TextInput
include Formtastic::Inputs::Base
def to_html
input_wrapping do
label_html
end
end
def input_wrapping(&block)
template.content_tag(:li,
[template.capture(&block), file_wrapper_html, error_html, hint_html].join("\n").html_safe,
wrapper_html_options
)
end
def file_wrapper_html
template.content_tag(:script,
template.content_tag(:span, %(
{{#if url}}
#{with_file_html}
{{else}}
#{without_file_html}
{{/if}}).html_safe, :class => 'file'),
:type => 'text/html', :id => "#{method}_file_input")
end
def with_file_html
cancel_message = I18n.t('locomotive.shared.form.cancel')
html = template.link_to '{{filename}}', '{{url}}', :target => '_blank'
html += builder.file_field(method, input_html_options.merge(:style => 'display: none'))
html += template.link_to I18n.t('locomotive.shared.form.change_file'), '#', :class => 'change', :'data-alt-label' => cancel_message
html += template.link_to I18n.t('locomotive.shared.form.delete_file'), '#', :class => 'delete', :'data-alt-label' => cancel_message
html += builder.hidden_field "remove_#{method}", :class => 'remove-flag'
end
def without_file_html
builder.file_field(method, input_html_options).html_safe
end
end
end

View File

@ -0,0 +1,13 @@
module Locomotive
class RteInput < Formtastic::Inputs::TextInput
def to_html
input_wrapping do
label_html <<
builder.text_area(method, input_html_options) <<
template.content_tag(:span, '', :class => 'error-anchor')
end
end
end
end

View File

@ -7,29 +7,27 @@ module Locomotive
include ::CustomFields::Target include ::CustomFields::Target
include Extensions::Shared::Seo include Extensions::Shared::Seo
## fields (dynamic fields) ## ## fields ##
# field :_highlighted_field
field :_slug field :_slug
field :_position_in_list, :type => Integer, :default => 0 field :_position, :type => Integer, :default => 0
field :_visible, :type => Boolean, :default => true field :_visible, :type => Boolean, :default => true
## validations ## ## validations ##
# validate :require_highlighted_field
validates :_slug, :presence => true, :uniqueness => { :scope => :content_type_id } validates :_slug, :presence => true, :uniqueness => { :scope => :content_type_id }
## associations ## ## associations ##
belongs_to :site belongs_to :site
belongs_to :content_type, :class_name => 'Locomotive::ContentType', :inverse_of => :contents belongs_to :content_type, :class_name => 'Locomotive::ContentType', :inverse_of => :entries
## callbacks ## ## callbacks ##
before_validation :set_slug before_validation :set_slug
# before_save :set_visibility before_save :set_visibility
before_create :add_to_list_bottom before_create :add_to_list_bottom
# after_create :send_notifications # after_create :send_notifications
## named scopes ## ## named scopes ##
scope :visible, :where => { :_visible => true } scope :visible, :where => { :_visible => true }
scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.lastest_items_nb scope :latest_updated, :order_by => :updated_at.desc, :limit => Locomotive.config.lastest_entries_nb
## methods ## ## methods ##
@ -37,36 +35,22 @@ module Locomotive
alias :_permalink :_slug alias :_permalink :_slug
alias :_permalink= :_slug= alias :_permalink= :_slug=
# def highlighted_field_value def _label(type = nil)
# self.send(self.content_type.highlighted_field_name) self.send((type || self.content_type).label_field_name.to_sym)
# end end
#
# alias :_label :highlighted_field_value
def visible? def visible?
self._visible || self._visible.nil? self._visible || self._visible.nil?
end end
def next # TODO def next
# content_type.contents.where(:_position_in_list => _position_in_list + 1).first() next_or_previous :gt
end end
def previous # TODO def previous
# content_type.contents.where(:_position_in_list => _position_in_list - 1).first() next_or_previous :lt
end end
# def errors_to_hash # TODO
# Hash.new.replace(self.errors)
# end
# def reload_parent! # TODO
# self.class.reload_parent!
# end
#
# def self.reload_parent! # TODO
# self._parent = self._parent.reload
# end
def to_liquid def to_liquid
Locomotive::Liquid::Drops::Content.new(self) Locomotive::Liquid::Drops::Content.new(self)
end end
@ -81,6 +65,14 @@ module Locomotive
protected protected
def next_or_previous(matcher = :gt)
attribute = self.content_type.order_by.to_sym
direction = self.content_type.order_direction || 'asc'
criterion = attribute.send(matcher)
self.class.where(criterion => self.send(attribute)).order_by([[attribute, direction]]).limit(1).first
end
# Sets the slug of the instance by using the value of the highlighted field # Sets the slug of the instance by using the value of the highlighted field
# (if available). If a sibling content instance has the same permalink then a # (if available). If a sibling content instance has the same permalink then a
# unique one will be generated # unique one will be generated
@ -106,33 +98,19 @@ module Locomotive
# _parent.contents.where(:_id.ne => _id, :_slug => _slug).any? # _parent.contents.where(:_id.ne => _id, :_slug => _slug).any?
# end # end
# def set_visibility def set_visibility
# %w(visible active).map(&:to_sym).each do |_alias| [:visible, :active].each do |meth|
# if self.methods.include?(_alias) if self.respond_to?(meth)
# self._visible = self.send(_alias) self._visible = self.send(meth)
# return return
# end end
# end end
# # field = self.content_type.contents_custom_fields.detect { |f| %w{visible active}.include?(f._alias) } end
# # self._visible = self.send(field._name) rescue true
# end def add_to_list_bottom
self._position = self.class.max(:_position).to_i + 1
def add_to_list_bottom
# TODO
# self._position_in_list = self.content_type.contents.size
end end
# def require_highlighted_field
# _alias = self.highlighted_field_alias
# if self.send(_alias).blank?
# self.errors.add(_alias, :blank)
# end
# end
#
# def highlighted_field_alias
# self.content_type.highlighted_field._alias.to_sym
# end
#
# def send_notifications # def send_notifications
# return unless self.content_type.api_enabled? && !self.content_type.api_accounts.blank? # return unless self.content_type.api_enabled? && !self.content_type.api_accounts.blank?
# #

View File

@ -11,7 +11,8 @@ module Locomotive
field :name field :name
field :description field :description
field :slug field :slug
field :highlighted_field_id, :type => BSON::ObjectId field :label_field_id, :type => BSON::ObjectId
field :label_field_name
field :group_by_field_id, :type => BSON::ObjectId field :group_by_field_id, :type => BSON::ObjectId
field :order_by field :order_by
field :order_direction, :default => 'asc' field :order_direction, :default => 'asc'
@ -32,7 +33,6 @@ module Locomotive
before_validation :normalize_slug before_validation :normalize_slug
after_validation :bubble_fields_errors_up after_validation :bubble_fields_errors_up
before_save :set_default_values before_save :set_default_values
# after_destroy :remove_uploaded_files
## validations ## ## validations ##
validates_presence_of :site, :name, :slug validates_presence_of :site, :name, :slug
@ -48,16 +48,12 @@ module Locomotive
# self.group_by_field && group_by_field.category? # self.group_by_field && group_by_field.category?
# end # end
def highlighted_field
self.entries_custom_fields.find(self.highlighted_field_id)
end
def group_by_field def group_by_field
self.entries_custom_fields.find(self.group_by_field_id) self.entries_custom_fields.find(self.group_by_field_id)
end end
def order_manually? def order_manually?
self.order_by == '_position_in_list' self.order_by == '_position'
end end
def asc_order? def asc_order?
@ -141,7 +137,8 @@ module Locomotive
def set_default_values def set_default_values
self.order_by ||= 'created_at' self.order_by ||= 'created_at'
self.highlighted_field_id ||= self.entries_custom_fields.first._id field = self.entries_custom_fields.find(self.label_field_id) rescue self.entries_custom_fields.first
self.label_field_name = field.name
end end
def normalize_slug def normalize_slug

View File

@ -41,7 +41,7 @@ module Locomotive
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 } validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
## named scopes ## ## named scopes ##
scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_entries_nb
scope :root, :where => { :slug => 'index', :depth => 0 } scope :root, :where => { :slug => 'index', :depth => 0 }
scope :not_found, :where => { :slug => '404', :depth => 0 } scope :not_found, :where => { :slug => '404', :depth => 0 }
scope :published, :where => { :published => true } scope :published, :where => { :published => true }

View File

@ -19,7 +19,9 @@ class Locomotive::BasePresenter
end end
def id def id
@source._id.to_s self.source.persisted? || self.source.embedded? ? self.source._id.to_s : nil
# self.persisted? ? self.source._id.to_s : nil
# self.source._id.to_s
end end
def ability? def ability?

View File

@ -1,15 +1,51 @@
module Locomotive module Locomotive
class ContentEntryPresenter < BasePresenter class ContentEntryPresenter < BasePresenter
# delegate :name, :description, :slug, :order_by, :order_direction, :highlighted_field_name, :group_by_field_name, :api_accounts, :to => :source delegate :_slug, :_position, :seo_title, :meta_keywords, :meta_description, :to => :source
# def contents_custom_fields def custom_fields_methods
# self.source.ordered_contents_custom_fields.collect(&:as_json) source.custom_fields_recipe['rules'].map do |rule|
# end case rule['type']
# when 'select' then [rule['name'], "#{rule['name']}_id"]
# def included_methods when 'date' then "formatted_#{rule['name']}"
# super + %w(name description slug order_by order_direction highlighted_field_name group_by_field_name api_accounts contents_custom_fields) when 'file' then "#{rule['name']}_url"
# end else
rule['name']
end
end.flatten
end
def safe_attributes
source.custom_fields_recipe['rules'].map do |rule|
case rule['type']
when 'select' then "#{rule['name']}_id"
when 'date' then "formatted_#{rule['name']}"
when 'file' then [rule['name'], "remove_#{rule['name']}"]
else
rule['name']
end
end.flatten + %w(_slug seo_title meta_keywords meta_description)
end
def content_type_slug
self.source.content_type.slug
end
def _file_fields
self.source.custom_fields_recipe['rules'].find_all { |rule| rule['type'] == 'file' }.map { |rule| rule['name'] }
end
def included_methods
super + self.custom_fields_methods + %w(_slug _position content_type_slug _file_fields safe_attributes persisted)
end
def method_missing(meth, *arguments, &block)
if self.custom_fields_methods.include?(meth.to_s)
self.source.send(meth) rescue nil
else
super
end
end
end end
end end

View File

@ -0,0 +1,17 @@
- content_for :head do
= render '/locomotive/content_assets/picker'
- content_for :backbone_view_data do
:plain
{ content_entry: #{@content_entry.to_json} }
= 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
= f.inputs :name => :advanced_options, :class => "inputs foldable #{'folded' if inputs_folded?(@content_entry)}" do
= f.input :_slug
= f.input :seo_title
= f.input :meta_keywords
= f.input :meta_description

View File

@ -1,7 +1,7 @@
- if contents.empty? - if contents.empty?
%p.no-items!= t('.no_items', :url => new_content_entry_url(@content_type.slug)) %p.no-items!= t('.no_items', :url => new_content_entry_url(@content_type.slug))
- else - else
%ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == '_position_in_list'}", :'data-url' => sort_admin_contents_path(@content_type.slug, :json) } %ul{ :id => 'contents-list', :class => "list #{'sortable' if @content_type.order_by == '_position'}", :'data-url' => sort_admin_contents_path(@content_type.slug, :json) }
- contents.each do |content| - contents.each do |content|
%li.content{ :id => "content-#{content._id}" } %li.content{ :id => "content-#{content._id}" }
%em %em

View File

@ -14,7 +14,7 @@
%p= @content_type.description %p= @content_type.description
= semantic_form_for @content, :as => :content, :url => content_entry_url(@content_type.slug, @content), :html => { :multipart => true } do |form| = semantic_form_for @content_entry, :as => :content_entry, :url => content_entry_url(@content_type.slug, @content_entry), :html => { :multipart => true } do |form|
= render 'form', :f => form = render 'form', :f => form

View File

@ -12,7 +12,7 @@
%p= @content_type.description %p= @content_type.description
= semantic_form_for @content, :as => :content, :url => content_entries_url(@content_type.slug), :html => { :multipart => true } do |form| = semantic_form_for @content_entry, :as => :content_entry, :url => content_entries_url(@content_type.slug), :html => { :multipart => true } do |form|
= render 'form', :f => form = render 'form', :f => form

View File

@ -21,7 +21,7 @@
= f.inputs :name => :presentation, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do = f.inputs :name => :presentation, :class => "inputs foldable #{'folded' if inputs_folded?(@content_type)}" do
= f.input :highlighted_field_id, :as => :select, :collection => options_for_highlighted_field(@content_type), :include_blank => false = f.input :label_field_id, :as => :select, :collection => options_for_label_field(@content_type), :include_blank => false
= f.input :group_by_field_id, :as => :select, :collection => options_for_group_by_field(@content_type) = f.input :group_by_field_id, :as => :select, :collection => options_for_group_by_field(@content_type)

View File

@ -1,12 +0,0 @@
- content_for :backbone_view_data do
:plain
{ content: #{@content.persisted? ? @content.to_json : 'null'} }
/ = render 'locomotive/custom_fields/custom_form', :form => f, :title => :attributes, :parent => @content_type
= f.inputs :name => :advanced_options, :class => "inputs foldable #{'folded' if inputs_folded?(@content)}" do
= f.input :_slug
= f.input :seo_title
= f.input :meta_keywords
= f.input :meta_description

View File

@ -1,2 +1 @@
= form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle', :required => required do = f.input name, :label => field.label, :as => :'Locomotive::Toggle', :hint => field.hint
= form.check_box field._alias.to_sym

View File

@ -1 +1 @@
= form.input :"formatted_#{field._alias}", :label => field.label, :hint => field.hint, :input_html => { :class => 'date' }, :wrapper_html => { :class => 'date' }, :required => required = f.input :"formatted_#{name}", :label => field.label, :hint => field.hint, :input_html => { :maxlength => 10 }, :wrapper_html => { :class => 'date' }

View File

@ -1,10 +1 @@
= form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'file', :required => required do = f.input name, :label => field.label, :hint => field.hint, :as => :'Locomotive::File'
= form.file_field field._name.to_sym
- if form.object.send(:"#{field._name}?")
%p.remove
%strong
= link_to File.basename(form.object.send(field._name).url), form.object.send(field._name).url
%span
&nbsp;/&nbsp;
!= t('.delete_file')
= form.check_box :"remove_#{field._name}"

View File

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

View File

@ -1 +1 @@
= form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :required => required, :wrapper_html => { :class => "#{'highlighted' if highlighted}" } = f.input name, :label => field.label, :hint => field.hint, :wrapper_html => { :class => "#{'highlighted' if highlighted}" }

View File

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

View File

@ -4,7 +4,7 @@
.header .header
%p= link_to t('locomotive.pages.index.new'), new_page_url %p= link_to t('locomotive.pages.index.new'), new_page_url
.inner .inner
%h2!= t('locomotive.pages.index.lastest_items') %h2!= t('locomotive.pages.index.lastest_entries')
%ul %ul
- current_site.pages.latest_updated.minimal_attributes.each do |page| - current_site.pages.latest_updated.minimal_attributes.each do |page|
%li %li
@ -20,12 +20,12 @@
%p.edit= link_to t('locomotive.content_entries.index.edit'), edit_content_type_url(content_type) %p.edit= link_to t('locomotive.content_entries.index.edit'), edit_content_type_url(content_type)
.inner .inner
%h2!= t('locomotive.content_entries.index.lastest_items') %h2!= t('locomotive.content_entries.index.lastest_entries')
/ %ul %ul
/ - content_type.latest_updated_contents.each do |content| - content_type.entries.latest_updated.each do |entry|
/ %li %li
/ = link_to truncate(content.send(content_type.highlighted_field_name).to_s, :length => 20), edit_admin_content_path(content_type.slug, content) = link_to truncate(entry._label(content_type), :length => 20), edit_content_entry_url(content_type.slug, entry)
/ %span= time_ago_in_words(content.updated_at) %span= time_ago_in_words(entry.updated_at)
/ - other_content_types do |content_types| / - other_content_types do |content_types|
/ .inner / .inner

View File

@ -46,7 +46,7 @@
# config.delayed_job = false # config.delayed_job = false
# #
# # configure how many items we display in sub menu in the "Contents" section. # # configure how many items we display in sub menu in the "Contents" section.
# # config.lastest_items_nb = 5 # # config.lastest_entries = 5
# #
# # default locale (for now, only en, de, fr, pt-BR and it are supported) # # default locale (for now, only en, de, fr, pt-BR and it are supported)
# config.default_locale = :en # config.default_locale = :en

View File

@ -87,7 +87,7 @@ de:
help: "Seiten sind als Baum organisiert. Du kannst die Seiten also wie Ordner sortieren und verschachteln." help: "Seiten sind als Baum organisiert. Du kannst die Seiten also wie Ordner sortieren und verschachteln."
no_items: "Momentan gibt es keine Seiten. Klicke einfach <a href='%{url}'>hier</a>, um die erste Seite zu erstellen." no_items: "Momentan gibt es keine Seiten. Klicke einfach <a href='%{url}'>hier</a>, um die erste Seite zu erstellen."
new: neue Seite new: neue Seite
lastest_items: Neueste Seiten lastest_entries: Neueste Seiten
new: new:
title: Neue Seite title: Neue Seite
help: "Fülle das folgende Formular aus, um eine neue Seite zu erstellen. Nur zur Info: Die Seite wird nicht standardmäßig publiziert!" help: "Fülle das folgende Formular aus, um eine neue Seite zu erstellen. Nur zur Info: Die Seite wird nicht standardmäßig publiziert!"
@ -201,7 +201,7 @@ de:
help: "Der Name der Buchung kann durch darauf klicken bearbeitet werden." help: "Der Name der Buchung kann durch darauf klicken bearbeitet werden."
new: neue Buchung new: neue Buchung
no_items: "Momentan gibt es keine Buchungen. Klicke einfach <a href='%{url}'>hier</a>, um eine neue Buchung zu erstellen." no_items: "Momentan gibt es keine Buchungen. Klicke einfach <a href='%{url}'>hier</a>, um eine neue Buchung zu erstellen."
lastest_items: Letzte Buchungen lastest_entries: Letzte Buchungen
updated_at: bearbeitet am updated_at: bearbeitet am
new: new:
title: Neue Buchung title: Neue Buchung

View File

@ -28,13 +28,17 @@ en:
account: My account account: My account
site: Site site: Site
theme_assets: Theme files theme_assets: Theme files
footer: form:
who_is_behind: "Product developed by %{development} and designed by <a href=\"http://www.sachagreif.com\">Sacha Greif</a>" change_file: change
delete_file: delete
cancel: cancel
form_actions: form_actions:
back: Back without saving back: Back without saving
create: Create create: Create
update: Save update: Save
send: Send send: Send
footer:
who_is_behind: "LocomotiveCMS is developed by %{development} and designed by <a href=\"http://www.sachagreif.com\">Sacha Greif</a>"
errors: errors:
"500": "500":
@ -96,7 +100,7 @@ en:
help: "Pages are organized as a tree. You can order pages as well as folders" help: "Pages are organized as a tree. You can order pages as well as folders"
no_items: "There are no pages for now. Just click <a href=\"%{url}\">here</a> to create the first one." no_items: "There are no pages for now. Just click <a href=\"%{url}\">here</a> to create the first one."
new: new page new: new page
lastest_items: Lastest pages lastest_entries: Lastest pages
new: new:
title: New page title: New page
help: "Please fill in the below form to create your page. Be careful, by default, the page is not published." help: "Please fill in the below form to create your page. Be careful, by default, the page is not published."

View File

@ -91,7 +91,7 @@ es:
no_items: "No se ha creado ninguna página aún. Pulse <a href=\"%{url}\">aquí</a> to para crear la primera." no_items: "No se ha creado ninguna página aún. Pulse <a href=\"%{url}\">aquí</a> to para crear la primera."
new: Nueva página new: Nueva página
lastest_items: Últimas páginas lastest_entries: Últimas páginas
new: new:
title: Nueva página title: Nueva página
help: "Por favor rellene el formulario de más abajo para crear su página. ¡Atención! Las páginas, por defecto, están 'no publicadas'." help: "Por favor rellene el formulario de más abajo para crear su página. ¡Atención! Las páginas, por defecto, están 'no publicadas'."

View File

@ -93,7 +93,7 @@ fr:
help: "Les pages sont organisées sous forme d'un arbre. Vous pouvez classes les pages ainsi que les dossiers." help: "Les pages sont organisées sous forme d'un arbre. Vous pouvez classes les pages ainsi que les dossiers."
no_items: "Il n'existe pas de page. Vous pouvez commencer par créer une <a href='%{url}'>ici</a>." no_items: "Il n'existe pas de page. Vous pouvez commencer par créer une <a href='%{url}'>ici</a>."
new: nouvelle page new: nouvelle page
lastest_items: Dernières pages lastest_entries: Dernières pages
new: new:
title: Nouvelle page title: Nouvelle page
help: "Remplissez le formulaire ci-dessous pour créer votre page. Attention, par défaut, la page n'est pas publiée." help: "Remplissez le formulaire ci-dessous pour créer votre page. Attention, par défaut, la page n'est pas publiée."

View File

@ -91,7 +91,7 @@ it:
help: "Le pagine sono organizzate ad albero. Puoi ordinarle come se fossero delle cartelle" help: "Le pagine sono organizzate ad albero. Puoi ordinarle come se fossero delle cartelle"
no_items: "Per ora non ci sono pagine. Clicca <a href=\"%{url}\">qui</a> per creare la prima pagina." no_items: "Per ora non ci sono pagine. Clicca <a href=\"%{url}\">qui</a> per creare la prima pagina."
new: nuova pagina new: nuova pagina
lastest_items: Ultime pagine lastest_entries: Ultime pagine
new: new:
title: Nuova pagina title: Nuova pagina
help: "Prego, compila il seguente modulo per creare la tua pagina. Fai attenzione, per default, la pagina non è pubblicata." help: "Prego, compila il seguente modulo per creare la tua pagina. Fai attenzione, per default, la pagina non è pubblicata."

View File

@ -84,7 +84,7 @@ nl:
help: "Pagina's zijn georganiseerd als een boom. U kunt zowel pagina's als mappen ordenen" help: "Pagina's zijn georganiseerd als een boom. U kunt zowel pagina's als mappen ordenen"
no_items: "Er zijn momenteel geen pagina's. Klik <a href=\"%{url}\">hier</a> om de eerste pagina te maken." no_items: "Er zijn momenteel geen pagina's. Klik <a href=\"%{url}\">hier</a> om de eerste pagina te maken."
new: Nieuwe pagina new: Nieuwe pagina
lastest_items: Laatste pagina's lastest_entries: Laatste pagina's
new: new:
title: Nieuwe pagina title: Nieuwe pagina
help: "Voer onderstaande formulier in om uw pagina te maken. Opgelet, de pagina is niet gepubliseerd" help: "Voer onderstaande formulier in om uw pagina te maken. Opgelet, de pagina is niet gepubliseerd"

View File

@ -93,7 +93,7 @@
help: "Sidene er organisert i en trestruktur. Du kan sortere både sider og mapper." help: "Sidene er organisert i en trestruktur. Du kan sortere både sider og mapper."
no_items: "Det har ikke blitt opprettet noen sider ennå. Klikk <a href=\"%{url}\">her</a> for lage den første." no_items: "Det har ikke blitt opprettet noen sider ennå. Klikk <a href=\"%{url}\">her</a> for lage den første."
new: ny side new: ny side
lastest_items: Siste sider lastest_entries: Siste sider
new: new:
title: Ny side title: Ny side
help: "Fyll ut skjemaet nedenfor for å opprette en ny side. Publisering er skrudd av som standard." help: "Fyll ut skjemaet nedenfor for å opprette en ny side. Publisering er skrudd av som standard."

View File

@ -82,7 +82,7 @@ pt-BR:
help: "As páginas são organizadas como uma árvore. Você pode reordenar as páginas como se fossem pastas" help: "As páginas são organizadas como uma árvore. Você pode reordenar as páginas como se fossem pastas"
no_items: "Não existe nenhuma página ainda. Clique <a href=\"%{url}\">aqui</a> para criar a primeira." no_items: "Não existe nenhuma página ainda. Clique <a href=\"%{url}\">aqui</a> para criar a primeira."
new: nova página new: nova página
lastest_items: Últmas páginas lastest_entries: Últmas páginas
new: new:
title: Nova página title: Nova página
help: "Por favor, preencha o formulário a seguir para criar sua página. Atenção: Por padrão a página não é publicada." help: "Por favor, preencha o formulário a seguir para criar sua página. Atenção: Por padrão a página não é publicada."

View File

@ -93,7 +93,7 @@ ru:
help: "Страницы организованы в виде дерева. Вы можете сортировать как страницы, так и папки" help: "Страницы организованы в виде дерева. Вы можете сортировать как страницы, так и папки"
no_items: "There are no pages for now. Just click <a href=\"%{url}\">here</a> to create the first one." no_items: "There are no pages for now. Just click <a href=\"%{url}\">here</a> to create the first one."
new: новая страница new: новая страница
lastest_items: Страницы за последнее время lastest_entries: Страницы за последнее время
new: new:
title: Новая страница title: Новая страница
help: "Заполните форму, приведенную ниже, для создания страницы. Будьте осторожны, по умолчанию, страница не опубликована." help: "Заполните форму, приведенную ниже, для создания страницы. Будьте осторожны, по умолчанию, страница не опубликована."

View File

@ -52,7 +52,15 @@ x edit my site
- change in main menu - change in main menu
- manage contents - manage contents
- list (highlighted field) - list (highlighted field)
- slugify
- crud - crud
x new
x date
x checkbox
x html
x file
x edit
- destroy
- public_form (previously api something) - public_form (previously api something)
- disallow to click twice on the submit form button (spinner ?) - disallow to click twice on the submit form button (spinner ?)

View File

@ -7,6 +7,12 @@
- EditableXXX => Locomotive::EditableXXX (in mongodb) - EditableXXX => Locomotive::EditableXXX (in mongodb)
- content_types: - content_types:
- category => select - category => select
- highlighted_field_name => highlighted_field_id - highlighted_field_name => highlighted_field_id (label_field_id)
- api_enabled => public_form_enabled - api_enabled => public_form_enabled
- api_accounts => public_form_accounts - api_accounts => public_form_accounts
- contents (content_entries now)
- _position_in_list => _position
collection, selector = Locomotive::ContentType.collection, Locomotive::ContentType.criteria.selector
collection.update selector, { '$rename' => { 'contents_custom_fields' => 'entries_custom_fields' } }

View File

@ -44,7 +44,7 @@ Locomotive.configure do |config|
config.delayed_job = false config.delayed_job = false
# configure how many items we display in sub menu in the "Contents" section. # configure how many items we display in sub menu in the "Contents" section.
# config.lastest_items_nb = 5 # config.lastest_entries_nb = 5
# default locale (for now, only en, de, fr, pt-BR and it are supported) # default locale (for now, only en, de, fr, pt-BR and it are supported)
config.default_locale = :en config.default_locale = :en

View File

@ -16,7 +16,7 @@ module Locomotive
:mailer_sender => 'support@example.com', :mailer_sender => 'support@example.com',
:manage_subdomain => false, :manage_subdomain => false,
:manage_manage_domains => false, :manage_manage_domains => false,
:lastest_items_nb => 5, :lastest_entries_nb => 5,
:rack_cache => { :rack_cache => {
:verbose => true, :verbose => true,
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded in case of spaces

View File

@ -131,7 +131,7 @@ module Locomotive
content = content_type.contents.where(content_type.highlighted_field_name.to_sym => value).first content = content_type.contents.where(content_type.highlighted_field_name.to_sym => value).first
if content.nil? if content.nil?
content = content_type.contents.build(content_type.highlighted_field_name.to_sym => value, :_position_in_list => position) content = content_type.contents.build(content_type.highlighted_field_name.to_sym => value, :_position => position)
end end
%w(_permalink seo_title meta_description meta_keywords).each do |attribute| %w(_permalink seo_title meta_description meta_keywords).each do |attribute|
@ -221,7 +221,7 @@ module Locomotive
self.log "order by #{content_type.order_by}" self.log "order by #{content_type.order_by}"
order_by = (case content_type.order_by order_by = (case content_type.order_by
when 'manually', '_position_in_list' then '_position_in_list' when 'manually', '_position_in_list', '_position' then '_position'
when 'default', 'created_at' then 'created_at' when 'default', 'created_at' then 'created_at'
else else
content_type.entries_custom_fields.detect { |f| f.name == content_type.order_by }._name rescue nil content_type.entries_custom_fields.detect { |f| f.name == content_type.order_by }._name rescue nil
@ -229,7 +229,7 @@ module Locomotive
self.log "order by (after) #{order_by}" self.log "order by (after) #{order_by}"
content_type.order_by = order_by || '_position_in_list' content_type.order_by = order_by || '_position'
end end
def set_group_by_value(content_type) def set_group_by_value(content_type)

View File

@ -45,7 +45,7 @@ Locomotive.configure do |config|
config.delayed_job = true # false config.delayed_job = true # false
# configure how many items we display in sub menu in the "Contents" section. # configure how many items we display in sub menu in the "Contents" section.
# config.lastest_items_nb = 5 # config.lastest_entries_nb = 5
# default locale (for now, only en, de, fr, pt-BR and it are supported) # default locale (for now, only en, de, fr, pt-BR and it are supported)
config.default_locale = :en config.default_locale = :en

View File

@ -0,0 +1,16 @@
/* French initialisation for the jQuery UI date picker plugin. */
/* Written by Keith Wood (kbwood@virginbroadband.com.au) and Stéphane Nahmani (sholby@sholby.net). */
jQuery(function($){
$.datepicker.regional['fr'] = {
closeText: 'Fermer',
prevText: '&#x3c;Préc',
nextText: 'Suiv&#x3e;',
currentText: 'Courant',
monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin', 'Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun', 'Jul','Aoû','Sep','Oct','Nov','Déc'],
dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
dayNamesMin: ['Di','Lu','Ma','Me','Je','Ve','Sa'],
dateFormat: 'dd/mm/yy', firstDay: 1,
isRTL: false};
});