Merge branch 'master' into api_auth

Conflicts:
	features/step_definitions/api_steps.rb
This commit is contained in:
Alex Sanford 2012-04-24 13:22:35 -03:00
commit 7dcc1ba3f6
85 changed files with 975 additions and 264 deletions

View File

@ -9,7 +9,9 @@ gemspec # Include gemspec dependencies
group :development do group :development do
# gem 'custom_fields', :path => '../gems/custom_fields' # for Developers # gem 'custom_fields', :path => '../gems/custom_fields' # for Developers
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 'locomotive-aloha-rails', :path => '../gems/aloha-rails' # for Developers
gem 'rspec-rails', '~> 2.8.0' # In order to have rspec tasks and generators gem 'rspec-rails', '~> 2.8.0' # In order to have rspec tasks and generators
gem 'rspec-cells' gem 'rspec-cells'
@ -45,5 +47,7 @@ group :test do
gem 'xpath', '~> 0.1.4' gem 'xpath', '~> 0.1.4'
gem 'json_spec'
gem 'database_cleaner' gem 'database_cleaner'
end end

View File

@ -15,20 +15,10 @@ GIT
fssm (>= 0.2.7) fssm (>= 0.2.7)
sass (~> 3.1) sass (~> 3.1)
GIT
remote: git://github.com/locomotivecms/custom_fields.git
revision: 5b0e68859eaca41ac9d7a0231c6cd68ad66018b8
branch: 2.0.0.rc
specs:
custom_fields (2.0.0.rc9)
activesupport (~> 3.2.1)
carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.4.7)
PATH PATH
remote: . remote: .
specs: specs:
locomotive_cms (2.0.0.rc4) locomotive_cms (2.0.0.rc7)
RedCloth (~> 4.2.8) RedCloth (~> 4.2.8)
actionmailer-with-request (~> 0.3.0) actionmailer-with-request (~> 0.3.0)
bson_ext (~> 1.5.2) bson_ext (~> 1.5.2)
@ -37,24 +27,25 @@ PATH
carrierwave-mongoid (~> 0.1.3) carrierwave-mongoid (~> 0.1.3)
cells (~> 3.8.0) cells (~> 3.8.0)
codemirror-rails (~> 2.21) codemirror-rails (~> 2.21)
custom_fields (~> 2.0.0.rc9) custom_fields (~> 2.0.0.rc10)
devise (~> 1.5.3) devise (~> 1.5.3)
dragonfly (~> 0.9.8) dragonfly (~> 0.9.8)
flash_cookie_session (~> 1.1.1) flash_cookie_session (~> 1.1.1)
fog (~> 1.1.2) fog (~> 1.3.1)
formtastic (~> 2.0.2) formtastic (~> 2.0.2)
haml (~> 3.1.4) haml (~> 3.1.4)
highline (~> 1.6.2) highline (~> 1.6.2)
httparty (~> 0.8.1) httparty (~> 0.8.1)
jquery-rails (~> 1.0.16) jquery-rails (~> 1.0.16)
kaminari (~> 0.13.0) kaminari (~> 0.13.0)
locomotive-aloha-rails (~> 0.20.1.2) locomotive-aloha-rails (~> 0.20.1.4)
locomotive-mongoid-tree (~> 0.6.2) locomotive-mongoid-tree (~> 0.6.2)
locomotive-tinymce-rails (~> 3.4.7.2) locomotive-tinymce-rails (~> 3.4.7.2)
locomotive_liquid (= 2.2.2) locomotive_liquid (= 2.2.2)
mimetype-fu (~> 0.1.2) mimetype-fu (~> 0.1.2)
mongo (~> 1.5.2) mongo (~> 1.5.2)
mongoid (~> 2.4.6) mongoid (~> 2.4.6)
multi_json (= 1.2.0)
rack-cache (~> 1.1) rack-cache (~> 1.1)
rails (~> 3.2.3) rails (~> 3.2.3)
rails-backbone (~> 0.6.1) rails-backbone (~> 0.6.1)
@ -124,7 +115,7 @@ GEM
childprocess (0.3.1) childprocess (0.3.1)
ffi (~> 1.0.6) ffi (~> 1.0.6)
chunky_png (1.2.5) chunky_png (1.2.5)
codemirror-rails (2.22) codemirror-rails (2.23)
railties (~> 3.0) railties (~> 3.0)
coffee-rails (3.2.2) coffee-rails (3.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
@ -143,6 +134,10 @@ GEM
capybara (>= 1.1.2) capybara (>= 1.1.2)
cucumber (>= 1.1.8) cucumber (>= 1.1.8)
nokogiri (>= 1.5.0) nokogiri (>= 1.5.0)
custom_fields (2.0.0.rc10)
activesupport (~> 3.2.1)
carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.4.7)
database_cleaner (0.7.2) database_cleaner (0.7.2)
devise (1.5.3) devise (1.5.3)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
@ -153,7 +148,7 @@ GEM
rack rack
ejs (1.0.0) ejs (1.0.0)
erubis (2.7.0) erubis (2.7.0)
excon (0.9.6) excon (0.13.4)
execjs (1.3.0) execjs (1.3.0)
multi_json (~> 1.0) multi_json (~> 1.0)
factory_girl (2.5.2) factory_girl (2.5.2)
@ -164,12 +159,12 @@ GEM
ffi (1.0.11) ffi (1.0.11)
flash_cookie_session (1.1.3) flash_cookie_session (1.1.3)
rails (~> 3.0) rails (~> 3.0)
fog (1.1.2) fog (1.3.1)
builder builder
excon (~> 0.9.0) excon (~> 0.13.0)
formatador (~> 0.2.0) formatador (~> 0.2.0)
mime-types mime-types
multi_json (~> 1.0.3) multi_json (~> 1.0)
net-scp (~> 1.0.4) net-scp (~> 1.0.4)
net-ssh (>= 2.1.3) net-ssh (>= 2.1.3)
nokogiri (~> 1.5.0) nokogiri (~> 1.5.0)
@ -183,8 +178,8 @@ GEM
haml (3.1.4) haml (3.1.4)
highline (1.6.11) highline (1.6.11)
hike (1.2.1) hike (1.2.1)
httparty (0.8.2) httparty (0.8.3)
multi_json multi_json (~> 1.0)
multi_xml multi_xml
i18n (0.6.0) i18n (0.6.0)
journey (1.0.3) journey (1.0.3)
@ -192,6 +187,9 @@ GEM
railties (~> 3.0) railties (~> 3.0)
thor (~> 0.14) thor (~> 0.14)
json (1.6.6) json (1.6.6)
json_spec (1.0.0)
multi_json (~> 1.0)
rspec (~> 2.0)
kaminari (0.13.0) kaminari (0.13.0)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@ -201,7 +199,7 @@ GEM
addressable (~> 2.2.6) addressable (~> 2.2.6)
libwebsocket (0.1.3) libwebsocket (0.1.3)
addressable addressable
locomotive-aloha-rails (0.20.1.2) locomotive-aloha-rails (0.20.1.4)
actionpack (~> 3.2.1) actionpack (~> 3.2.1)
locomotive-mongoid-tree (0.6.2) locomotive-mongoid-tree (0.6.2)
mongoid (~> 2.0) mongoid (~> 2.0)
@ -221,8 +219,8 @@ GEM
activemodel (~> 3.1) activemodel (~> 3.1)
mongo (~> 1.3) mongo (~> 1.3)
tzinfo (~> 0.3.22) tzinfo (~> 0.3.22)
multi_json (1.0.4) multi_json (1.2.0)
multi_xml (0.4.2) multi_xml (0.4.4)
net-scp (1.0.4) net-scp (1.0.4)
net-ssh (>= 1.99.1) net-ssh (>= 1.99.1)
net-ssh (2.3.0) net-ssh (2.3.0)
@ -290,13 +288,14 @@ GEM
railties (~> 3.2.0) railties (~> 3.2.0)
sass (>= 3.1.10) sass (>= 3.1.10)
tilt (~> 1.3) tilt (~> 1.3)
selenium-webdriver (2.21.0) selenium-webdriver (2.21.1)
childprocess (>= 0.2.5) childprocess (>= 0.2.5)
ffi (~> 1.0) ffi (~> 1.0)
libwebsocket (~> 0.1.3) libwebsocket (~> 0.1.3)
multi_json (~> 1.0) multi_json (< 1.3)
rubyzip rubyzip
shoulda-matchers (1.0.0) shoulda-matchers (1.1.0)
activesupport (>= 3.0.0)
sprockets (2.1.2) sprockets (2.1.2)
hike (~> 1.2) hike (~> 1.2)
rack (~> 1.0) rack (~> 1.0)
@ -330,9 +329,9 @@ DEPENDENCIES
compass! compass!
compass-rails! compass-rails!
cucumber-rails cucumber-rails
custom_fields!
database_cleaner database_cleaner
factory_girl_rails (~> 1.6.0) factory_girl_rails (~> 1.6.0)
json_spec
launchy launchy
locomotive_cms! locomotive_cms!
mocha (= 0.9.12) mocha (= 0.9.12)

View File

@ -0,0 +1,3 @@
button.aloha-locomotive-media-insert {
background: url(../img/image.gif) !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1,96 @@
define(
['aloha/jquery', 'aloha/plugin', 'aloha/floatingmenu', 'i18n!aloha/nls/i18n', 'i18n!locomotive_media/nls/i18n', 'css!locomotive_media/css/image.css'],
function(aQuery, Plugin, FloatingMenu, i18nCore, i18n) {
var jQuery = aQuery;
var $ = aQuery;
var GENTICS = window.GENTICS, Aloha = window.Aloha;
return Plugin.create('locomotive_media', {
init: function() {
FloatingMenu.createScope(this.name, 'Aloha.continuoustext');
this._addUIInsertButton(i18nCore.t('floatingmenu.tab.insert'));
},
openDialog: function() {
var that = this;
var picker = window.parent.application_view.content_assets_picker_view;
picker.options.on_select = function(asset) {
if (asset.get('image') == true)
that.insertImg(asset);
else
that.insertLink(asset);
picker.close();
}
picker.render()
},
/**
* This method will insert a new image dom element into the dom tree
*/
insertImg: function(asset) {
var range = Aloha.Selection.getRangeObject(),
imageUrl = asset.get('url'),
imagestyle, imagetag, newImg;
if (range.isCollapsed()) {
imagestyle = "max-width: " + asset.get('width') + "; max-height: " + asset.get('height');
imagetag = '<img style="'+ imagestyle + '" src="' + imageUrl + '" title="" />';
newImg = jQuery(imagetag);
GENTICS.Utils.Dom.insertIntoDOM(newImg, range, jQuery(Aloha.activeEditable.obj));
} else {
Aloha.Log.error('media cannot markup a selection');
}
},
/**
* This method will insert a new link dom element into the dom tree
*/
insertLink: function(asset) {
var range = Aloha.Selection.getRangeObject(),
linkText = asset.get('filename'),
linkUrl = asset.get('url'),
linktag, newLink;
if (range.isCollapsed()) {
linktag = '<a href="' + linkUrl + '">' + linkText + '</a>';
newLink = jQuery(linktag);
GENTICS.Utils.Dom.insertIntoDOM(newLink, range, jQuery(Aloha.activeEditable.obj));
range.startContainer = range.endContainer = newLink.contents().get(0);
range.startOffset = 0;
range.endOffset = linkText.length;
} else {
linktag = '<a href="' + linkUrl + '"></a>';
newLink = jQuery(linktag);
GENTICS.Utils.Dom.addMarkup(range, newLink, false);
}
},
/**
* Adds the insert button to the floating menu
*/
_addUIInsertButton: function(tabId) {
var that = this;
this.insertMediaButton = new Aloha.ui.Button({
'name' : 'insertlocomotivemedia',
'iconClass': 'aloha-button aloha-locomotive-media-insert',
'size' : 'small',
'onclick' : function () { that.openDialog(); },
'tooltip' : i18n.t('button.addimg.tooltip'),
'toggle' : false
});
FloatingMenu.addButton(
'Aloha.continuoustext',
this.insertMediaButton,
tabId,
1
);
},
});
}
);

View File

@ -0,0 +1 @@
define({ 'button.addimg.tooltip': 'insérer média' });

View File

@ -0,0 +1,4 @@
define({
root: { "button.addimg.tooltip": "insert media" },
fr: true
});

View File

@ -1,2 +1,2 @@
#= require ./utils/aloha_settings #= require ./utils/aloha_settings
#= require aloha #= require aloha

View File

@ -12,6 +12,7 @@
#= require_self #= require_self
#= require_tree ./utils #= require_tree ./utils
#= require_tree ./models #= require_tree ./models
#= require_tree ./views/content_assets
#= require_tree ./views/inline_editor #= require_tree ./views/inline_editor
window.Locomotive = window.Locomotive =

View File

@ -16,7 +16,7 @@ class Locomotive.Models.Page extends Backbone.Model
toJSON: -> toJSON: ->
_.tap super, (hash) => _.tap super, (hash) =>
_.each ['content_type_id_text', 'edit_url', 'parent_id_text', 'response_type_text'], (key) => delete hash[key] _.each ['fullpath', 'localized_fullpaths', 'templatized_from_parent', 'target_klass_name_text', 'content_type_id_text', 'edit_url', 'parent_id_text', 'response_type_text'], (key) => delete hash[key]
delete hash['editable_elements'] delete hash['editable_elements']
hash.editable_elements = @get('editable_elements').toJSONForSave() if @get('editable_elements')? && @get('editable_elements').length > 0 hash.editable_elements = @get('editable_elements').toJSONForSave() if @get('editable_elements')? && @get('editable_elements').length > 0

View File

@ -23,6 +23,11 @@ window.Aloha.settings =
editables: editables:
'.editable-short-text': [ ] '.editable-short-text': [ ]
image:
ui:
insert: false
crop: false
i18n: i18n:
available: ['en', 'fr', 'pt-BR', 'es', 'de', 'no', 'ru', 'nl'] available: ['en', 'fr', 'pt-BR', 'es', 'de', 'no', 'ru', 'nl']

View File

@ -20,7 +20,7 @@
if (typeof sep == 'undefined') sep = '_'; if (typeof sep == 'undefined') sep = '_';
var alphaNumRegexp = new RegExp('[^\\w\\' + sep + ']', 'g'); var alphaNumRegexp = new RegExp('[^\\w\\' + sep + ']', 'g');
var avoidDuplicateRegexp = new RegExp('[\\' + sep + ']{2,}', 'g'); var avoidDuplicateRegexp = new RegExp('[\\' + sep + ']{2,}', 'g');
return this.replace(/\s/g, sep).replace(alphaNumRegexp, '').replace(avoidDuplicateRegexp, sep).toLowerCase() return this.replace(/\s/g, sep).replace(alphaNumRegexp, '').replace(avoidDuplicateRegexp, sep).toLowerCase();
} }
window.addParameterToURL = function(key, value, context) { // code from http://stackoverflow.com/questions/486896/adding-a-parameter-to-the-url-with-javascript window.addParameterToURL = function(key, value, context) { // code from http://stackoverflow.com/questions/486896/adding-a-parameter-to-the-url-with-javascript

View File

@ -11,5 +11,4 @@ class Locomotive.Views.ContentTypes.EditView extends Locomotive.Views.ContentTyp
if custom_field.isNew() # assign an id for each new custom field if custom_field.isNew() # assign an id for each new custom field
custom_field.set id: data._id, _id: data._id custom_field.set id: data._id, _id: data._id
console.log(custom_field)

View File

@ -38,7 +38,8 @@ class Locomotive.Views.EditableElements.FileView extends Backbone.View
on_change: => on_change: =>
@$('a:first').hide() & @$('input[type=file]').show() & @$('a.delete').hide() @$('a:first').hide() & @$('input[type=file]').show() & @$('a.delete').hide()
on_cancel: => on_cancel: =>
@$('a:first').show() & @$('input[type=file]').hide() & @$('a.delete').show() @model.set(source: null)
@$('a:first').show() & @$('input[type=file]').val('').hide() & @$('a.delete').show()
toggle_delete: (event) -> toggle_delete: (event) ->
@_toggle event, 'delete', @_toggle event, 'delete',

View File

@ -2,7 +2,35 @@
Locomotive.Views.EditableElements ||= {} Locomotive.Views.EditableElements ||= {}
class Locomotive.Views.EditableElements.LongTextView extends Locomotive.Views.EditableElements.ShortTextView class Locomotive.Views.EditableElements.LongTextView extends Backbone.View
tagName: 'li'
className: 'text input html'
render: ->
$(@el).html(ich.editable_text_input(@model.toJSON()))
return @
after_render: ->
settings = _.extend {}, @tinymce_settings(),
oninit: ((editor) =>
$.cmd 'S', (() =>
@model.set(content: editor.getBody().innerHTML)
$(@el).parents('form').trigger('submit')
), [], ignoreCase: true, document: editor.dom.doc),
onchange_callback: (editor) =>
@model.set(content: editor.getBody().innerHTML)
@$('textarea').tinymce(settings)
tinymce_settings: -> tinymce_settings: ->
window.Locomotive.tinyMCE.defaultSettings window.Locomotive.tinyMCE.defaultSettings
refresh: ->
# do nothing
remove: ->
@$('textarea').tinymce().destroy()
super

View File

@ -4,31 +4,19 @@ class Locomotive.Views.EditableElements.ShortTextView extends Backbone.View
tagName: 'li' tagName: 'li'
className: 'text input html' className: 'text input short'
render: -> render: ->
$(@el).html(ich.editable_text_input(@model.toJSON())) $(@el).html(ich.editable_text_input(@model.toJSON()))
@$('textarea').bind 'keyup', (event) =>
input = $(event.target)
@model.set(content: input.val())
return @ return @
after_render: -> after_render: ->
settings = _.extend {}, @tinymce_settings(),
oninit: ((editor) =>
$.cmd 'S', (() =>
@model.set(content: editor.getBody().innerHTML)
$(@el).parents('form').trigger('submit')
), [], ignoreCase: true, document: editor.dom.doc),
onchange_callback: (editor) =>
@model.set(content: editor.getBody().innerHTML)
@$('textarea').tinymce(settings)
tinymce_settings: ->
window.Locomotive.tinyMCE.minimalSettings
refresh: ->
# do nothing # do nothing
remove: -> refresh: ->
@$('textarea').tinymce().destroy() # do nothing
super

View File

@ -1,8 +1,8 @@
Locomotive.Views.InlinEditor ||= {} Locomotive.Views.InlineEditor ||= {}
#= require ./toolbar_view #= require ./toolbar_view
class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View class Locomotive.Views.InlineEditor.ApplicationView extends Backbone.View
el: 'body' el: 'body'
@ -13,7 +13,9 @@ class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View
_.bindAll(@, '_$') _.bindAll(@, '_$')
@toolbar_view = new Locomotive.Views.InlinEditor.ToolbarView(target: @iframe) @toolbar_view = new Locomotive.Views.InlineEditor.ToolbarView(target: @iframe)
@content_assets_picker_view = new Locomotive.Views.ContentAssets.PickerView(collection: new Locomotive.Models.ContentAssetsCollection())
render: -> render: ->
super super
@ -26,8 +28,6 @@ class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View
iframe = @iframe iframe = @iframe
iframe.load => iframe.load =>
console.log('iframe loading')
if @_$('meta[name=inline-editor]').size() > 0 if @_$('meta[name=inline-editor]').size() > 0
# bind the resize event. When the iFrame's size changes, update its height # bind the resize event. When the iFrame's size changes, update its height
iframe_content = iframe.contents() iframe_content = iframe.contents()
@ -46,8 +46,6 @@ class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View
@enhance_iframe_links() @enhance_iframe_links()
set_page: (attributes) -> set_page: (attributes) ->
console.log('set_page')
@page = new Locomotive.Models.Page(attributes) @page = new Locomotive.Models.Page(attributes)
@toolbar_view.model = @page @toolbar_view.model = @page
@ -78,7 +76,7 @@ class Locomotive.Views.InlinEditor.ApplicationView extends Backbone.View
_jQuery('a').each -> _jQuery('a').each ->
link = _jQuery(this) link = _jQuery(this)
url = link.attr('href') url = link.attr('href')
if url? && url.indexOf('#') != 0 && /^(www|http)/.exec(url) == null && /(\/_edit)$/.exec(url) == null if url? && url.indexOf('#') != 0 && /^(www|http)/.exec(url) == null && /(\/_edit)$/.exec(url) == null && /^\/sites\//.exec(url) == null
url = '/index' if url == '/' url = '/index' if url == '/'
unless url.indexOf('_edit') > 0 unless url.indexOf('_edit') > 0

View File

@ -1,6 +1,6 @@
Locomotive.Views.InlinEditor ||= {} Locomotive.Views.InlineEditor ||= {}
class Locomotive.Views.InlinEditor.ToolbarView extends Backbone.View class Locomotive.Views.InlineEditor.ToolbarView extends Backbone.View
el: '#toolbar .inner' el: '#toolbar .inner'
@ -13,8 +13,6 @@ class Locomotive.Views.InlinEditor.ToolbarView extends Backbone.View
render: -> render: ->
super super
console.log('render toolbar')
@enable_editing_mode_checkbox() @enable_editing_mode_checkbox()
@enable_content_locale_picker() @enable_content_locale_picker()
@ -22,8 +20,6 @@ class Locomotive.Views.InlinEditor.ToolbarView extends Backbone.View
@ @
notify: (aloha_editable) -> notify: (aloha_editable) ->
console.log('editable_element has been modified...')
window.bar = aloha_editable window.bar = aloha_editable
element_id = aloha_editable.obj.attr('data-element-id') element_id = aloha_editable.obj.attr('data-element-id')
@ -125,8 +121,6 @@ class Locomotive.Views.InlinEditor.ToolbarView extends Backbone.View
context.find('span.text').html(values[1]) context.find('span.text').html(values[1])
refresh: -> refresh: ->
console.log('refreshing toolbar...')
@$('h1').html(@model.get('title')).removeClass() @$('h1').html(@model.get('title')).removeClass()
if @$('.editing-mode input[type=checkbox]').is(':checked') if @$('.editing-mode input[type=checkbox]').is(':checked')

View File

@ -116,7 +116,7 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
@$('li#page_slug_input').show() @$('li#page_slug_input').show()
@$('li#page_templatized_input, li#page_target_klass_name_input').hide() @$('li#page_templatized_input, li#page_target_klass_name_input').hide()
else else
@$('li#page_templatized_input, li#page_target_klass_name_input').show() @$('li#page_templatized_input').show() unless @model.get('redirect')
enable_response_type_select: -> enable_response_type_select: ->
@$('li#page_response_type_input').change (event) => @$('li#page_response_type_input').change (event) =>

View File

@ -5,10 +5,14 @@ class Locomotive.Views.Pages.EditView extends Locomotive.Views.Pages.FormView
save: (event) -> save: (event) ->
event.stopPropagation() & event.preventDefault() event.stopPropagation() & event.preventDefault()
form = $(event.target).trigger('ajax:beforeSend')
@clear_errors() @clear_errors()
@model.save {}, @model.save {},
success: (model, response, xhr) => success: (model, response, xhr) =>
form.trigger('ajax:complete')
model._normalize() model._normalize()
if model.get('template_changed') == true if model.get('template_changed') == true
@ -17,9 +21,8 @@ class Locomotive.Views.Pages.EditView extends Locomotive.Views.Pages.FormView
@refresh_editable_elements() @refresh_editable_elements()
error: (model, xhr) => error: (model, xhr) =>
form.trigger('ajax:complete')
errors = JSON.parse(xhr.responseText) errors = JSON.parse(xhr.responseText)
@show_errors errors @show_errors errors

View File

@ -16,6 +16,9 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
# allow users to save with CTRL+S or CMD+s # allow users to save with CTRL+S or CMD+s
@enable_save_with_keys_combination() @enable_save_with_keys_combination()
# enable form notifications
@enable_form_notifications()
return @ return @
save: (event) -> save: (event) ->
@ -24,6 +27,8 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
save_in_ajax: (event, options) -> save_in_ajax: (event, options) ->
event.stopPropagation() & event.preventDefault() event.stopPropagation() & event.preventDefault()
form = $(event.target).trigger('ajax:beforeSend')
@clear_errors() @clear_errors()
options ||= { headers: {}, on_success: null, on_error: null } options ||= { headers: {}, on_success: null, on_error: null }
@ -33,11 +38,15 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
@model.save {}, @model.save {},
headers: options.headers headers: options.headers
success: (model, response, xhr) => success: (model, response, xhr) =>
form.trigger('ajax:complete')
model.attributes = previous_attributes model.attributes = previous_attributes
options.on_success(response, xhr) if options.on_success options.on_success(response, xhr) if options.on_success
error: (model, xhr) => error: (model, xhr) =>
form.trigger('ajax:complete')
errors = JSON.parse(xhr.responseText) errors = JSON.parse(xhr.responseText)
@show_errors errors @show_errors errors
@ -72,7 +81,10 @@ class Locomotive.Views.Shared.FormView extends Backbone.View
content.slideUp 100, -> parent.addClass('folded') content.slideUp 100, -> parent.addClass('folded')
enable_save_with_keys_combination: -> enable_save_with_keys_combination: ->
$.cmd 'S', (() => @$('form').trigger('submit')), [], ignoreCase: true $.cmd 'S', (() => @$('form input[type=submit]').trigger('click')), [], ignoreCase: true
enable_form_notifications: ->
@$('form').formSubmitNotification()
after_inputs_fold: -> after_inputs_fold: ->
# overide this method if necessary # overide this method if necessary

View File

@ -7,7 +7,7 @@ class Locomotive.Views.ThemeAssets.IndexView extends Backbone.View
_lists_views: [] _lists_views: []
initialize: -> initialize: ->
_.bindAll(@, 'add_asset') _.bindAll(@, 'insert_asset')
render: -> render: ->
@build_uploader() @build_uploader()
@ -29,16 +29,24 @@ class Locomotive.Views.ThemeAssets.IndexView extends Backbone.View
input = form.find('input[type=file]') input = form.find('input[type=file]')
link = form.find('a.new') link = form.find('a.new')
form.formSubmitNotification()
link.bind 'click', (event) -> link.bind 'click', (event) ->
event.stopPropagation() & event.preventDefault() event.stopPropagation() & event.preventDefault()
input.click() input.click()
input.bind 'change', (event) => input.bind 'change', (event) =>
form.trigger('ajax:beforeSend')
_.each event.target.files, (file) => _.each event.target.files, (file) =>
asset = new Locomotive.Models.ThemeAsset(source: file) asset = new Locomotive.Models.ThemeAsset(source: file)
asset.save {}, success: @add_asset, headers: { 'X-Flash': true } asset.save {},
success: (model, response, xhr) =>
form.trigger('ajax:complete')
@insert_asset(model)
error: (() => form.trigger('ajax:complete'))
headers: { 'X-Flash': true }
add_asset: (model) -> insert_asset: (model) ->
list_view = @pick_list_view(model.get('content_type')) list_view = @pick_list_view(model.get('content_type'))
list_view.collection.add(model) list_view.collection.add(model)

View File

@ -33,24 +33,6 @@
/* ___ list ___ */ /* ___ list ___ */
p.no-items {
background: #fffbe6;
border: 5px solid #eee3a8;
@include border-radius(25px);
padding: 15px 0px;
text-align: center;
color: #9d8963;
font-size: 16px !important;
@include single-text-shadow(rgba(255, 255, 255, 1), 1px, 1px, 1px);
a {
@include hover-link;
color: #ff2900;
}
}
ul.list { ul.list {
background: #fff; background: #fff;
list-style: none; list-style: none;
@ -314,6 +296,29 @@ ul.list {
} }
} }
/* ___ form notification ___ */
#form-submit-notification {
position: fixed;
top: 0px;
right: 0px;
z-index: 9999;
> div {
padding: 5px 10px;
background-color: #fffbe5;
border-left: 4px solid #efe4a5;
border-bottom: 4px solid #efe4a5;
text-align: center;
@include single-text-shadow(rgba(255, 255, 255, 1), 0px, 1px, 0px);
font-weight: bold;
font-size: 12px;
color: #aa9a79;
}
}
/* ___ paragraph (for help for example) ___ */ /* ___ paragraph (for help for example) ___ */
p span.code { p span.code {
@ -323,19 +328,3 @@ p span.code {
color: #8B8D9A; color: #8B8D9A;
@include single-text-shadow(#fff, 0px, 0px, 1px); @include single-text-shadow(#fff, 0px, 0px, 1px);
} }
/* ___ quick upload ___ */
form.quick-upload {
display: inline;
input[type=file] {
visibility: hidden;
}
}

View File

@ -6,6 +6,10 @@
ul.content-assets { ul.content-assets {
list-style: none;
margin: 0px;
padding: 0px;
li.asset { li.asset {
position: relative; position: relative;
float: left; float: left;

View File

@ -295,6 +295,17 @@ form.formtastic {
} }
} // li.string, li.password } // li.string, li.password
&.text {
&.short textarea {
padding: 5px;
height: 28px;
width: 696px;
overflow-y: hidden;
}
} // li.text
&.locale, &.locales { &.locale, &.locales {
.list { .list {
margin-left: 150px; margin-left: 150px;

View File

@ -176,7 +176,7 @@ body {
} }
} }
input[type=submit] { input[type=submit], button[type=submit] {
@include light-button; @include light-button;
} }

View File

@ -3,7 +3,10 @@
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope. * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require locomotive/blueprint/screen.css *= require locomotive/blueprint/screen.css
*= require locomotive/jquery
*= require locomotive/toggle.css *= require locomotive/toggle.css
*= require locomotive/backoffice/dialog_changes.css
*= require locomotive/backoffice/content_assets.css
*= require_tree ./shared *= require_tree ./shared
*= require_tree ./inline_editor *= require_tree ./inline_editor
*/ */

View File

@ -0,0 +1,34 @@
@import "compass/css3/border-radius";
@import "compass/css3/text-shadow";
@import "locomotive/shared/helpers";
/* ___ quick upload ___ */
form.quick-upload {
display: inline;
input[type=file] {
visibility: hidden;
}
}
/* ___ list ___ */
p.no-items {
background: #fffbe6;
border: 5px solid #eee3a8;
@include border-radius(25px);
padding: 15px 0px;
text-align: center;
color: #9d8963;
font-size: 16px !important;
@include single-text-shadow(rgba(255, 255, 255, 1), 1px, 1px, 1px);
a {
@include hover-link;
color: #ff2900;
}
}

View File

@ -8,6 +8,8 @@ module Locomotive
skip_before_filter :verify_authenticity_token skip_before_filter :verify_authenticity_token
skip_load_and_authorize_resource
self.responder = Locomotive::ActionController::PublicResponder # custom responder self.responder = Locomotive::ActionController::PublicResponder # custom responder
respond_to :html, :json respond_to :html, :json
@ -15,6 +17,7 @@ module Locomotive
def create def create
@entry = @content_type.entries.create(params[:entry] || params[:content]) @entry = @content_type.entries.create(params[:entry] || params[:content])
flash[@content_type.slug.singularize] = @entry.to_presenter(:include_errors => true).as_json flash[@content_type.slug.singularize] = @entry.to_presenter(:include_errors => true).as_json
Rails.logger.debug @entry.to_presenter(:include_errors => true).as_json
respond_with @entry, :location => self.callback_url respond_with @entry, :location => self.callback_url
end end

View File

@ -3,7 +3,7 @@ module Locomotive
default :from => Locomotive.config.mailer_sender default :from => Locomotive.config.mailer_sender
def new_content_entry(entry, account) def new_content_entry(account, entry)
@account, @entry, @type = account, entry, entry.content_type @account, @entry, @type = account, entry, entry.content_type
subject = t('locomotive.notifications.new_content_entry.subject', :type => @type.name, :locale => account.locale) subject = t('locomotive.notifications.new_content_entry.subject', :type => @type.name, :locale => account.locale)

View File

@ -71,6 +71,11 @@ module Locomotive
new_el.copy_attributes_from(el) new_el.copy_attributes_from(el)
else else
existing_el.disabled = false existing_el.disabled = false
# only the type and hint properties can be modified from the parent element
%w(_type hint).each do |attr|
existing_el.send(:"#{attr}=", el.send(attr.to_sym))
end
end end
end end
end end

View File

@ -10,8 +10,12 @@ module Locomotive
## fields ## ## fields ##
field :locales, :type => 'RawArray', :default => [] field :locales, :type => 'RawArray', :default => []
## validations ##
validate :can_not_remove_default_locale
## callbacks ## ## callbacks ##
after_validation :add_default_locale after_validation :add_default_locale
before_update :verify_localized_default_pages_integrity
end end
@ -48,6 +52,10 @@ module Locomotive
self.locales.first || Locomotive.config.site_locales.first self.locales.first || Locomotive.config.site_locales.first
end end
def default_locale_was
self.locales_was.first || Locomotive.config.site_locales.first
end
def locale_fallbacks(locale) def locale_fallbacks(locale)
[locale.to_s] + (locales - [locale.to_s]) [locale.to_s] + (locales - [locale.to_s])
end end
@ -58,6 +66,29 @@ module Locomotive
self.locales = [Locomotive.config.site_locales.first] if self.locales.blank? self.locales = [Locomotive.config.site_locales.first] if self.locales.blank?
end end
def can_not_remove_default_locale
if self.persisted? && !self.locales.include?(self.default_locale_was)
self.errors.add :locales, I18n.t(:default_locale_removed, :scope => [:errors, :messages, :site])
end
end
def verify_localized_default_pages_integrity
if self.persisted? && self.locales_changed?
self.pages.where(:"slug.#{self.default_locale_was}".in => %w(index 404), :depth => 0).each do |page|
modifications = { 'title' => {}, 'slug' => {} }
self.locales.each do |locale|
slug = page.attributes['slug'][self.default_locale_was]
modifications['slug'][locale] = slug
modifications['title'][locale] = page.attributes['title'][locale] || ::I18n.t("attributes.defaults.pages.#{slug}.title", :locale => locale)
end
page.collection.update({ :_id => page._id }, { '$set' => modifications })
end
end
end
end end
end end

View File

@ -1,7 +1,7 @@
module Locomotive module Locomotive
class ContentAssetPresenter < BasePresenter class ContentAssetPresenter < BasePresenter
delegate :content_type, :vignette_url, :to => :source delegate :content_type, :width, :height, :vignette_url, :to => :source
def full_filename def full_filename
self.source.source_filename self.source.source_filename
@ -29,7 +29,7 @@ module Locomotive
end end
def included_methods def included_methods
super + %w(full_filename filename short_name extname content_type content_type_text url vignette_url) super + %w(full_filename filename short_name extname content_type content_type_text url vignette_url width height)
end end
end end

View File

@ -36,7 +36,7 @@
= f.input :templatized, :as => :'Locomotive::Toggle', :style => "#{'display: none' if @page.redirect? || @page.templatized_from_parent?}" = f.input :templatized, :as => :'Locomotive::Toggle', :style => "#{'display: none' if @page.redirect? || @page.templatized_from_parent?}"
= f.input :target_klass_name, :as => :select, :collection => options_for_target_klass_name, :include_blank => false, :wrapper_html => { :style => "#{'display: none' unless @page.templatized? && !@page.templatized_from_parent?}" } = f.input :target_klass_name, :as => :select, :collection => options_for_target_klass_name, :include_blank => false, :wrapper_html => { :style => "#{'display: none' if !@page.templatized? || @page.templatized_from_parent?}" }
= f.input :published, :as => :'Locomotive::Toggle' = f.input :published, :as => :'Locomotive::Toggle'

View File

@ -25,11 +25,13 @@
$(document).ready(function() { $(document).ready(function() {
window.application_view = new Locomotive.Views.InlinEditor.ApplicationView(); window.application_view = new Locomotive.Views.InlineEditor.ApplicationView();
window.application_view.render(); window.application_view.render();
}); });
= render '/locomotive/content_assets/picker'
%body %body
#page #page
%iframe{ :src => request.fullpath.gsub('_admin', '_edit'), :scrolling => 'no', :frameborder => '0' } %iframe{ :src => request.fullpath.gsub('_admin', '_edit'), :scrolling => 'no', :frameborder => '0' }

View File

@ -9,6 +9,6 @@
.span-12.last .span-12.last
%p %p
= submit_tag button_label.is_a?(Symbol) ? t(".#{button_label}") : button_label = submit_tag button_label.is_a?(Symbol) ? t(".#{button_label}") : button_label, :disable_with => t('.disable_with'), :'data-sending-form-message' => t('locomotive.messages.sending_form')
.clear .clear

View File

@ -27,7 +27,7 @@
- content_for :buttons do - content_for :buttons do
- if can?(:manage, Locomotive::ThemeAsset) - if can?(:manage, Locomotive::ThemeAsset)
= form_tag theme_assets_url(:json), :id => 'theme-assets-quick-upload', :class => 'quick-upload' do = form_tag theme_assets_url(:json), :id => 'theme-assets-quick-upload', :class => 'quick-upload', :'data-sending-form-message' => t('locomotive.messages.sending_form') do
= file_field_tag 'theme_asset[source]', :multiple => 'multiple' = file_field_tag 'theme_asset[source]', :multiple => 'multiple'
= local_action_button :quick_upload, '#', :class => 'new' = local_action_button :quick_upload, '#', :class => 'new'

View File

@ -1 +1,2 @@
# Haml::Template.options[:ugly] = true # improve performance in dev Haml::Template.options[:format] = :html5
Haml::Template.options[:ugly] = true # improve performance in dev

View File

@ -48,6 +48,7 @@ de:
create: Neu create: Neu
update: Speichern update: Speichern
send: Senden send: Senden
disable_with: "locomotive.disable_with.form_actions"
footer: footer:
who_is_behind: "Dienst entwickelt von %{development} und entworfen von <a href=\"http://www.sachagreif.com\">Sacha Greif</a> &mdash; <small>version</small> %{version}" who_is_behind: "Dienst entwickelt von %{development} und entworfen von <a href=\"http://www.sachagreif.com\">Sacha Greif</a> &mdash; <small>version</small> %{version}"

View File

@ -22,6 +22,7 @@ en:
messages: messages:
confirm: Are you sure ? confirm: Are you sure ?
sending_form: Your form is being submitted
shared: shared:
header: header:
@ -48,6 +49,7 @@ en:
create: Create create: Create
update: Save update: Save
send: Send send: Send
disable_with: Pending...
list: list:
untranslated: untranslated untranslated: untranslated
footer: footer:
@ -193,6 +195,7 @@ en:
index: index:
title: Listing theme files title: Listing theme files
help: "The theme files section is the place where you manage the files needed by your layout, snippets...etc. If you need to manage an image gallery, create a new content type instead.<br/><b>Warning:</b> you may not see all the assets depending on your rights." help: "The theme files section is the place where you manage the files needed by your layout, snippets...etc. If you need to manage an image gallery, create a new content type instead.<br/><b>Warning:</b> you may not see all the assets depending on your rights."
quick_upload: Quick upload
new: new file new: new file
snippets: Snippets snippets: Snippets
css_and_js: Style and javascript css_and_js: Style and javascript

View File

@ -37,6 +37,7 @@ es:
account: Mi Cuenta account: Mi Cuenta
site: Sitio site: Sitio
theme_assets: Ficheros del Tema theme_assets: Ficheros del Tema
disable_with: "locomotive.disable_with.form_actions"
footer: footer:
who_is_behind: "Servicio desarrollado por %{development} y diseñado por <a href=\"http://www.sachagreif.com\">Sacha Greif</a>" who_is_behind: "Servicio desarrollado por %{development} y diseñado por <a href=\"http://www.sachagreif.com\">Sacha Greif</a>"
form_actions: form_actions:

View File

@ -31,6 +31,7 @@ fr:
messages: messages:
confirm: "Êtes-vous sûr(e) ?" confirm: "Êtes-vous sûr(e) ?"
sending_form: "Votre formulaire est en cours d'envoi"
shared: shared:
header: header:
@ -57,6 +58,7 @@ fr:
create: Créer create: Créer
update: Mettre à jour update: Mettre à jour
send: Envoyer send: Envoyer
disable_with: En cours...
notifications: notifications:
new_content_entry: new_content_entry:

View File

@ -34,6 +34,7 @@ it:
create: Crea create: Crea
update: Salva update: Salva
send: Invia send: Invia
disable_with: "locomotive.disable_with.form_actions"
errors: errors:
"500": "500":

View File

@ -31,6 +31,7 @@ nl:
create: Maak create: Maak
update: Update update: Update
send: Verstuur send: Verstuur
disable_with: "locomotive.disable_with.form_actions"
errors: errors:
"500": "500":

View File

@ -34,6 +34,7 @@
create: Opprett create: Opprett
update: Lagre update: Lagre
send: Send send: Send
disable_with: "locomotive.disable_with.form_actions"
errors: errors:
"500": "500":

View File

@ -31,6 +31,7 @@ pt-BR:
create: Criar create: Criar
update: Atualizar update: Atualizar
send: Enviar send: Enviar
disable_with: "locomotive.disable_with.form_actions"
errors: errors:
"500": "500":

View File

@ -7,9 +7,22 @@ ru:
new_item: "+ добавить" new_item: "+ добавить"
switch_to_site: Сайт switch_to_site: Сайт
delete: "Удалить" delete: "Удалить"
close: "Закрыть"
locales:
en: Английский
de: Немецкий
fr: Французский
pt-BR: "Браз. - Португальский"
it: Итальянский
nl: Голландский
"no": Норвежский
es: Испанский
ru: Русский
messages: messages:
confirm: Вы уверены ? confirm: Вы уверены ?
sending_form: Ваша форма отправляется
shared: shared:
header: header:
@ -27,6 +40,10 @@ ru:
account: Аккаунт account: Аккаунт
site: Сайт site: Сайт
theme_assets: Файлы темы theme_assets: Файлы темы
form:
change_file: изменить
delete_file: удалить
cancel: отменить
footer: footer:
who_is_behind: "Service developed by %{development} and designed by <a href=\"http://www.sachagreif.com\">Sacha Greif</a>" who_is_behind: "Service developed by %{development} and designed by <a href=\"http://www.sachagreif.com\">Sacha Greif</a>"
form_actions: form_actions:
@ -34,6 +51,9 @@ ru:
create: Создать create: Создать
update: Сохранить update: Сохранить
send: Отправить send: Отправить
disable_with: В ожидании...
list:
untranslated: непереведено
errors: errors:
"500": "500":
@ -65,9 +85,15 @@ ru:
delete_file: Удалить файл delete_file: Удалить файл
has_many: has_many:
empty: Пусто empty: Пусто
index: new_entry: + Добавить новый элемент
is_required: является обязательным many_to_many:
empty: Список пуст. Добавьте элемент из селекта ниже.
form:
required: Обязательное
optional: Опциональное
default_label: Название поля default_label: Название поля
select_options:
ask_name: "Введите название элемента"
sessions: sessions:
new: new:
@ -104,7 +130,9 @@ ru:
help: "Заголовок страницы может быть изменен, если кликнуть на нем. Чтобы применить изменения, нажмите кнопку \"Сохранить\"." help: "Заголовок страницы может быть изменен, если кликнуть на нем. Чтобы применить изменения, нажмите кнопку \"Сохранить\"."
ask_for_title: "Пожалуйста введите новое имя страницы" ask_for_title: "Пожалуйста введите новое имя страницы"
form: form:
change_file: Изменить
delete_file: Удалить файл delete_file: Удалить файл
cancel: Отмена
default_block: По умолчанию default_block: По умолчанию
cache_strategy: cache_strategy:
none: Нет none: Нет
@ -133,11 +161,11 @@ ru:
new: new:
title: Новый сайт title: Новый сайт
help: "Заполните форму, приведенную ниже, чтобы создать новый сайт." help: "Заполните форму, приведенную ниже, чтобы создать новый сайт."
domains:
empty: "Пока нет ни одного домена, привязанного к этому сайту. Просто добавьте домены ниже. <b>Не забудьте обновить ваши DNS.</b>"
current_site: current_site:
edit: edit:
export: экспорт
import: импорт
new_membership: добавить аккаунт new_membership: добавить аккаунт
help: "Название сайта может быть изменено, если кликнуть на нем. Чтобы применить изменения, нажмите кнопку \"Save\"." help: "Название сайта может быть изменено, если кликнуть на нем. Чтобы применить изменения, нажмите кнопку \"Save\"."
ask_for_name: "Пожалуйста введите новое имя сайта" ask_for_name: "Пожалуйста введите новое имя сайта"
@ -160,20 +188,13 @@ ru:
edit: edit:
help: "Вы можете изменить логин просто кликнув на нем. Чтобы применить изменения, нажмите кнопку \"Сохранить\"." help: "Вы можете изменить логин просто кликнув на нем. Чтобы применить изменения, нажмите кнопку \"Сохранить\"."
new_site: новый сайт new_site: новый сайт
en: Английский
de: Немецкий
fr: Французский
pt-BR: "Бразильский - Португальский"
it: Итальянский
nl: Голландский
"no": норвежский
es: Испанский
ask_for_name: "Пожалуйста введите ваш новый логин" ask_for_name: "Пожалуйста введите ваш новый логин"
theme_assets: theme_assets:
index: index:
title: Список файлов темы title: Список файлов темы
help: "Секция файлов темы это место, где вы можете управлять файлами, необходимыми для вашего шаблона, сниппетов и т.д. Если вам необходимо управление галереей изображений, создайте новый тип контента.<br/><b>Внимание:</b> вы можете не увидеть всех файлов - в зависимости от ваших прав." help: "Секция файлов темы это место, где вы можете управлять файлами, необходимыми для вашего шаблона, сниппетов и т.д. Если вам необходимо управление галереей изображений, создайте новый тип контента.<br/><b>Внимание:</b> вы можете не увидеть всех файлов - в зависимости от ваших прав."
quick_upload: Быстрая загрузка
new: новый файл new: новый файл
snippets: Сниппеты snippets: Сниппеты
css_and_js: Стили и javascript css_and_js: Стили и javascript
@ -196,29 +217,29 @@ ru:
picker_link: Вставить файл в код picker_link: Вставить файл в код
choose_file: Выбрать файл choose_file: Выбрать файл
choose_plain_text: Выбрать простой текст choose_plain_text: Выбрать простой текст
images: image_picker:
title: Список изображений title: "Вставить изображение"
no_items: "Пока нет ни одного файла." no_items: "Нет ни одного изображения."
upload: "Upload theme images"
assets: content_assets:
new: picker:
title: Новый файл title: "Вставьте медиа-файл"
help: "Заполните форму, приведенную ниже, для создания файла (asset)." no_items: "Нет ни одного медиа-файла."
edit: upload: "Загрузить медиа"
title: Редактировать файл
help: "Заполните форму, приведенную ниже, для изменения файла."
content_types: content_types:
index: index:
new: новая модель new: новая модель
edit: редактировать модель
new: new:
title: Новая модель title: Новая модель
help: "Создайте ваши собственные модели данных (Проекты, Персоны, ...и т.д.). Модель должна иметь по крайней мере одно поле. Элементы, созданные из этого типа содержимого, будут иметь первое поле как обязательное." help: "Создайте ваши собственные модели данных (Проекты, Персоны, ...и т.д.). Модель должна иметь по крайней мере одно поле. Элементы, созданные из этого типа содержимого, будут иметь первое поле как обязательное."
edit: edit:
title: Редактирование модели title: Редактирование модели
help: "Ваша модель должна иметь по крайней мере одно поле. Элементы, созданные из этого типа содержимого, будут иметь это поле как обязательное." help: "Ваша модель должна иметь по крайней мере одно поле. Элементы, созданные из этого типа содержимого, будут иметь это поле как обязательное."
show_items: смотреть элементы show_entries: смотреть элементы
new_item: новый элемент new_entry: новый элемент
form: form:
order_by: order_by:
created_at: 'По дате создания' created_at: 'По дате создания'
@ -256,23 +277,6 @@ ru:
title: Кроссдоменная аутентификация title: Кроссдоменная аутентификация
notice: Вы будете перенаправлены на вебсайт в течение нескольких секунд. notice: Вы будете перенаправлены на вебсайт в течение нескольких секунд.
import:
new:
title: Импортировать шаблон сайта
help: "Будьте осторожны при загрузке нового шаблона для существующего сайта, ваши текущие данные могут быть изменены или даже удалены."
show:
title: Выполняется импорт
help: "Ваш сайт обновляется из zip файла темы, который вы только что загрузили. Это займет несколько секунд."
steps:
site: Информация сайта
content_types: Пользовательские типы содержимого
assets: Файлы темы
snippets: Сниппеты
pages: Страницы
messages:
success: "Ваш сайт был успешно обновлен."
failure: "Импорт не работает."
installation: installation:
common: common:
title: Первая установка Locomotive title: Первая установка Locomotive
@ -288,5 +292,21 @@ ru:
step_2: step_2:
title: "Шаг 2/2 &mdash; Создайте первый сайт" title: "Шаг 2/2 &mdash; Создайте первый сайт"
explanations: "Если вы уже загрузили шаблон сайта по умолчанию (см. инструкцию), вы можете использовать его прямо сейчас. Или вы можете загрузить шаблон сайта как zip файл (доступные бесплатные шаблоны <a href=\"http://www.locomotivecms.com/support/themes\">здесь</a>)." explanations: "Если вы уже загрузили шаблон сайта по умолчанию (см. инструкцию), вы можете использовать его прямо сейчас. Или вы можете загрузить шаблон сайта как zip файл (доступные бесплатные шаблоны <a href=\"http://www.locomotivecms.com/support/themes\">здесь</a>)."
back_to_default_template: "Нажмите <a href='#'>здесь</a> для выбора шаблона сайта по умолчанию" default_site_locale: Локаль сайта по умолчанию
default_site_locales_hints: Вы всегда можете добавить больше локалей в разделе Настройки.
next: Создать сайт next: Создать сайт
public:
pages:
show_toolbar:
statuses:
loading: "Загрузка...."
disabled: "Встроенный редактор отключен"
labels:
save_changes: "Сохранить изменения: "
editing_mode: "Режим редактирования: "
lang: "Язык: "
buttons:
back: Назад в админку
confirm: Подтвердить
cancel: Отменить

View File

@ -12,6 +12,8 @@ en:
extname_changed: "New file does not have the original extension" extname_changed: "New file does not have the original extension"
array_too_short: "is too small (minimum element number is %{count})" array_too_short: "is too small (minimum element number is %{count})"
invalid_theme_file: "can't be blank or isn't a zip file" invalid_theme_file: "can't be blank or isn't a zip file"
site:
default_locale_removed: The previous default locale can not be removed right away.
page: page:
liquid_syntax: "Liquid Syntax error ('%{error}' on '%{fullpath}')" liquid_syntax: "Liquid Syntax error ('%{error}' on '%{fullpath}')"
liquid_extend: "The page '%{fullpath}' extends a template which does not exist" liquid_extend: "The page '%{fullpath}' extends a template which does not exist"

View File

@ -33,6 +33,8 @@ fr:
extname_changed: "Nouveau fichier n'a pas l'extension original" extname_changed: "Nouveau fichier n'a pas l'extension original"
array_too_short: "est trop petit (le nombre minimum d'éléments est %{count})" array_too_short: "est trop petit (le nombre minimum d'éléments est %{count})"
security: "présente un problème de sécurité" security: "présente un problème de sécurité"
site:
default_locale_removed: La langue par défaut ne peut pas être supprimée de cette façon.
page: page:
liquid_syntax: "Erreur de syntaxe dans les sections de page, veuillez vérifier la syntaxe ('%{error}'/'%{fullpath}')" liquid_syntax: "Erreur de syntaxe dans les sections de page, veuillez vérifier la syntaxe ('%{error}'/'%{fullpath}')"
liquid_extend: "La page '%{fullpath}' étend le contenu d'une page qui n'existe pas" liquid_extend: "La page '%{fullpath}' étend le contenu d'une page qui n'existe pas"

View File

@ -1,5 +1,4 @@
ru: ru:
errors: errors:
messages: messages:
domain_taken: "%{value} уже занято" domain_taken: "%{value} уже занято"
@ -8,16 +7,15 @@ ru:
protected_page: "Вы не можете удалять стартовую или 404 страницы" protected_page: "Вы не можете удалять стартовую или 404 страницы"
extname_changed: "Новый файл не имеет оригинального расширения" extname_changed: "Новый файл не имеет оригинального расширения"
array_too_short: "слишком мал (минимальное число элементов %{count})" array_too_short: "слишком мал (минимальное число элементов %{count})"
liquid_syntax: "Ошибка синтаксиса Liquid ('%{error}')"
invalid_theme_file: "не может быть пустым или не является zip файлом" invalid_theme_file: "не может быть пустым или не является zip файлом"
site:
too_short: "слишком короткий (не менее %{count} символов)" default_locale_removed: Предыдущая локаль не может быть удалена.
blank: "не может быть пустым"
invalid: "имеет неверное значение"
confirmation: "не совпадает с подтверждением"
page: page:
liquid_syntax: "Ошибка синтаксиса Liquid ('%{error}' в '%{fullpath}')" liquid_syntax: "Ошибка синтаксиса Liquid ('%{error}' в '%{fullpath}')"
liquid_extend: "Страница '%{fullpath}' расширяет шаблон, который не существует" liquid_extend: "Страница '%{fullpath}' наследует (расширяет) несуществующий шаблон"
liquid_translation: "Страница '%{fullpath}' наследует (расширяет) непереведенный шаблон"
too_few_custom_fields: "По крайней мере одно поле является обязательным"
security: "проблема безопасности"
attributes: attributes:
defaults: defaults:

View File

@ -7,7 +7,7 @@ ru:
devise: devise:
failure: failure:
locomotive: locomotive_account:
unauthenticated: 'Вам необходимо войти или зарегистрироваться перед тем, как продолжить.' unauthenticated: 'Вам необходимо войти или зарегистрироваться перед тем, как продолжить.'
unconfirmed: 'Вы должны подтвердить аккаунт перед продолжением.' unconfirmed: 'Вы должны подтвердить аккаунт перед продолжением.'
locked: 'Ваш аккаунт заблокирован.' locked: 'Ваш аккаунт заблокирован.'
@ -17,34 +17,34 @@ ru:
timeout: 'Срок действия вашей сессии истек, пожалуйста залогиньтесь для продолжения.' timeout: 'Срок действия вашей сессии истек, пожалуйста залогиньтесь для продолжения.'
inactive: 'Ваш аккаунт еще не был активирован.' inactive: 'Ваш аккаунт еще не был активирован.'
sessions: sessions:
locomotive: locomotive_account:
signed_in: 'Вход выполнен успешно.' signed_in: 'Вход выполнен успешно.'
signed_out: 'Выход выполнен успешно.' signed_out: 'Выход выполнен успешно.'
passwords: passwords:
locomotive: locomotive_account:
send_instructions: 'Вы получите письмо с инструкциями о том, как сбросить ваш пароль, через несколько минут.' send_instructions: 'Вы получите письмо с инструкциями о том, как сбросить ваш пароль, через несколько минут.'
updated: 'Ваш пароль был успешно изменен. Вы вошли в систему.' updated: 'Ваш пароль был успешно изменен. Вы вошли в систему.'
confirmations: confirmations:
locomotive: locomotive_account:
send_instructions: 'Вы получите письмо с инструкциями о том, как подтвердить ваш аккаунт, через несколько минут.' send_instructions: 'Вы получите письмо с инструкциями о том, как подтвердить ваш аккаунт, через несколько минут.'
confirmed: 'Ваша учетная запись была успешно подтверждена. Вы вошли в систему.' confirmed: 'Ваша учетная запись была успешно подтверждена. Вы вошли в систему.'
registrations: registrations:
locomotive: locomotive_account:
signed_up: 'Вы успешно зарегистрировались.' signed_up: 'Вы успешно зарегистрировались.'
updated: 'Вы успешно обновили ваш аккаунт.' updated: 'Вы успешно обновили ваш аккаунт.'
destroyed: 'До свидания! Ваш аккаунт был успешно отменен. Мы надеемся скоро увидеть вас снова.' destroyed: 'До свидания! Ваш аккаунт был успешно отменен. Мы надеемся скоро увидеть вас снова.'
unlocks: unlocks:
locomotive: locomotive_account:
send_instructions: 'Вам будет отправлено письмо с инструкциями о том, как разблокировать ваш аккаунт, в течение нескольких минут.' send_instructions: 'Вам будет отправлено письмо с инструкциями о том, как разблокировать ваш аккаунт, в течение нескольких минут.'
unlocked: 'Ваша учетная запись была успешно разблокирована. Вы вошли в систему.' unlocked: 'Ваша учетная запись была успешно разблокирована. Вы вошли в систему.'
mailer: mailer:
locomotive: locomotive_account:
confirmation_instructions: 'Инструкции подтверждения' confirmation_instructions: 'Инструкции подтверждения'
reset_password_instructions: 'Инструкции по сбросу пароля' reset_password_instructions: 'Инструкции по сбросу пароля'
unlock_instructions: 'Инструкции по разблокированию' unlock_instructions: 'Инструкции по разблокированию'
locomotive: locomotive_account:
mailer: mailer:
common: common:
hello: Здравствуйте hello: Здравствуйте

View File

@ -33,37 +33,40 @@ ru:
custom_fields: custom_fields:
field: field:
name: Алиас name: Алиас
import:
new:
source: Файл
samples: Копировать образцы
reset: Сбросить сайт
default_site_template: "Используйте шаблон сайта по умолчанию. Нажмите <a href='#'>здесь</a> для загрузки шаблона сайта в виде zip файла."
content_type: content_type:
item_template: Шаблон элемента raw_item_template: Шаблон элемента
api_accounts: Уведомленные аккаунты public_submission_enabled: Публичное представление
public_submission_accounts: Уведомленные аккаунты
"custom_fields/field":
select_options: "Опции"
content_entry: content_entry:
_slug: Постоянная ссылка _slug: Постоянная ссылка
account: account:
edit: edit:
locale: Язык интерфейса пользователя
password: Новый пароль password: Новый пароль
password_confirmation: Подтверждение нового пароля password_confirmation: Подтверждение нового пароля
page: page:
seo_title: Название seo_title: Название
target_klass_name: Model
site:
locales: Языки
hints: hints:
page: page:
handle: "Уникальный идентификатор для поиска этой страницы из экземпляра внешнего rails-контроллера"
published: "Только аутентифицированным пользователям разрешается просматривать неопубликованные страницы." published: "Только аутентифицированным пользователям разрешается просматривать неопубликованные страницы."
cache_strategy: "Кэшировать страницу для лучшей производительности. Вариант 'Простое' является хорошим компромиссом." cache_strategy: "Кэшировать страницу для лучшей производительности. Вариант 'Простое' является хорошим компромиссом."
templatized: "Используйте страницу в качестве шаблона для определенной вами модели." templatized: "Используйте страницу в качестве шаблона для определенной вами модели."
listed: "Контролируйте возможность показа страницы из сгенерированных меню." listed: "Контролируйте возможность показа страницы из сгенерированных меню."
content_type_id: "Тип содержимого, для которого эта страница будет выступать в качестве шаблона." target_klass_name: "Тип контента (модели), для которого эта страница будет выступать в качестве шаблона."
seo_title: "Определите заголовок страницы, который будет использоваться как значение тэга title в секции head. Оставьте пустым, если вы хотите использовать значение по умолчанию из настроек сайта." seo_title: "Определите заголовок страницы, который будет использоваться как значение тэга title в секции head. Оставьте пустым, если вы хотите использовать значение по умолчанию из настроек сайта."
meta_keywords: "Переопределяет meta keywords сайта, используемые внутри тэга head страницы. Они разделены запятыми." meta_keywords: "Переопределяет meta keywords сайта, используемые внутри тэга head страницы. Они разделены запятыми."
meta_description: "Переопределяет meta description сайта, используемые внутри тэга head страницы." meta_description: "Переопределяет meta description сайта, используемые внутри тэга head страницы."
snippet: snippet:
slug: "Вам необходимо знать это для вставки сниппета в страницу" slug: "Вам необходимо знать это для вставки сниппета в страницу"
site: site:
locales: "Перетащите и отпустите флаг на первую позицию, чтобы сделать ее локалью по умолчанию."
seo_title: "Задайте глобальное значение здесь, которое будет использовано как значение для тэга title в секции head." seo_title: "Задайте глобальное значение здесь, которое будет использовано как значение для тэга title в секции head."
meta_keywords: "Meta keywords используются внутри тэга head страницы. Они разделяются запятыми. Требуется для SEO." meta_keywords: "Meta keywords используются внутри тэга head страницы. Они разделяются запятыми. Требуется для SEO."
meta_description: "Meta description используются для тэга head страницы. Необходимо для SEO." meta_description: "Meta description используются для тэга head страницы. Необходимо для SEO."
@ -79,10 +82,6 @@ ru:
source: "Текущий файл доступен здесь %{url}" source: "Текущий файл доступен здесь %{url}"
update: update:
source: "Текущий файл доступен здесь %{url}" source: "Текущий файл доступен здесь %{url}"
custom_fields:
field:
name: "Свойство, доступное в шаблонах liquid"
hint: "Текст, отображенный в форме модели, находится ниже поля"
content_entry: content_entry:
_slug: "Свойство, используемое для генерации ссылки (url) на страницу, работающей как шаблон для этого типа содержимого (ex: \"template_page/{{ your_object._permalink }})\"." _slug: "Свойство, используемое для генерации ссылки (url) на страницу, работающей как шаблон для этого типа содержимого (ex: \"template_page/{{ your_object._permalink }})\"."
seo_title: "Значение, вводимое вами, будет заменять SEO заголовок шаблонизированной страницы, связанной с вашей моделью." seo_title: "Значение, вводимое вами, будет заменять SEO заголовок шаблонизированной страницы, связанной с вашей моделью."
@ -93,7 +92,12 @@ ru:
samples: "Если включено, процесс импорта также скопирует содержимое и файлы" samples: "Если включено, процесс импорта также скопирует содержимое и файлы"
reset: "Если включено, все данные вашего сайта будут уничтожены перед импортом нового сайта" reset: "Если включено, все данные вашего сайта будут уничтожены перед импортом нового сайта"
content_type: content_type:
item_template: "Вы можете задавать текст, отображаемый для каждого элемента в списке. Просто используйте Liquid. Пр.: {{ entry.name }})" name: "Необходимо вводить название модели во множественном числе. Например: Projects, Recipes, Posts, Articles, ...и т.д."
api_enabled: "Это используется для того, чтобы дать людям извне возможность создавать новые экземляры (пример: сообщения в форме контакта)" slug: "Поле будет использовано как имя коллекции в шаблонах liquid. Пр.: <span class='code'>{{ contents.my_projects }}</span>"
api_accounts: "Письмо с уведомлением будет отправлено на каждый аккаунт из списка выше, когда создан новый экземпляр" raw_item_template: "Вы можете задавать текст, отображаемый для каждого элемента в списке. Просто используйте Liquid. Пр.: {{ entry.name }})"
public_submission_enabled: "Это используется для того, чтобы дать людям извне возможность создавать новые экземляры (пример: сообщения в форме контакта)"
public_submission_accounts: "Письмо с уведомлением будет отправлено на каждый аккаунт из списка выше каждый раз, когда создается новый экземпляр (если включена опция публичного представления)"
"custom_fields/field":
name: "Имя свойства для шаблонов liquid. Пр.: <span class='code'>&#123;&#123; your_object.&lt;name_of_your_field&gt; &#125;&#125;</span>"
hint: "Текст-подсказка, отображаемый на форме модели под полем"

View File

@ -124,11 +124,11 @@ x override sort for contents
x unable to remove a field x unable to remove a field
x "back to admin" link does not work if inline editor disabled x "back to admin" link does not work if inline editor disabled
x unable to delete memberships x unable to delete memberships
x disallow to click twice on the submit form button (spinner ?)
x weird behaviour when changing the default locale of a site
- editable_elements does not display the first time they get created (and if there are no existing ones) - editable_elements does not display the first time they get created (and if there are no existing ones)
- display by categories does not work when localized - display by categories does not work when localized
- disallow to click twice on the submit form button (spinner ?)
- message to notify people if their browser is too old - message to notify people if their browser is too old
- weird behaviour when changing the default locale of a site
? install a site by default at the first installation (without asking) ? install a site by default at the first installation (without asking)

View File

@ -0,0 +1,19 @@
Feature: Authentication
In order to consume the API
As an admin
I need to get an authentication token
Background:
Given I have the site: "test site" set up
Scenario: Fail to get a token without an email and a password
When I post to "/locomotive/api/tokens.json"
Then the JSON response at "message" should be "The request must contain the user email and password."
Scenario: Get a token
When I post to "/locomotive/api/tokens.json" with:
"""
{ "email": "admin@locomotiveapp.org", "password": "easyone" }
"""
Then the JSON response at "token" should be a string
And the JSON response should not have "message"

View File

@ -0,0 +1,29 @@
Feature: Pages
In order to access the Page resources
As an admin
I will perform the basic RESTFUL actions on them
Background:
Given I have the site: "test site" set up
And I have an "admin" API token
And a page named "hello world" with the template:
"""
Hello world :-)
"""
And a page named "goodbye-world" with the template:
"""
Goodbye world :-(
"""
Scenario: Protect the pages resources if not authenticated
Given I do not have an API token
When I visit "/locomotive/api/pages.json"
Then the JSON response at "error" should be "You need to sign in or sign up before continuing."
Scenario: Accessing pages
When I visit "/locomotive/api/pages.json"
Then the JSON should have the following:
| 0/fullpath | "index" |
| 1/fullpath | "hello-world" |
| 2/fullpath | "404" |
| 3/fullpath | "goodbye-world" |

View File

@ -0,0 +1,62 @@
Feature: Contact form
As a visitor
In order to keep in touch with the site
I want to be able to send them a message
Background:
Given I have the site: "test site" set up
And I have a custom model named "Messages" with
| label | type | required |
| Email | string | true |
| Message | text | true |
And I enable the public submission of the "Messages" model
And a page named "contact" with the template:
"""
<html>
<head></head>
<body>
<form action="{{ contents.messages.public_submission_url }}" method="post">
<input type="hidden" value="/success" name="success_callback" />
<input type="hidden" value="/contact" name="error_callback" />
<label for="email">E-Mail Address</label>
<input type="text" id="email" name="content[email]" />
{% if message.errors.email %}Email is required{% endif %}
<label for="message">Message</label>
<textarea name="content[message]" id="message"></textarea>
<input name="submit" type="submit" id="submit" value="Submit" />
</form>
</body>
</html>
"""
And a page named "success" with the template:
"""
Thanks {{ message.email }}
"""
Scenario: Setting the right url for the contact form
When I view the rendered page at "/contact"
Then the rendered output should look like:
"""
<form action="http://test.example.com/entry_submissions/messages" method="post">
"""
Scenario: Prevents users to post messages if the public submission option is disabled
Given I disable the public submission of the "Messages" model
When I view the rendered page at "/contact"
And I fill in "E-Mail Address" with "did@locomotivecms.com"
And I fill in "Message" with "LocomotiveCMS rocks"
And I press "Submit"
Then I should not see "Thanks did@locomotivecms.com"
Scenario: Sending a message with success
When I view the rendered page at "/contact"
And I fill in "E-Mail Address" with "did@locomotivecms.com"
And I fill in "Message" with "LocomotiveCMS rocks"
And I press "Submit"
Then I should see "Thanks did@locomotivecms.com"
Scenario: Display errors
When I view the rendered page at "/contact"
And I fill in "Message" with "LocomotiveCMS rocks"
And I press "Submit"
Then I should see "Email is required"

View File

@ -1,35 +1,62 @@
def api_base_url def api_base_url
'/locomotive/api' "http://#{@site.domains.first}/locomotive/api/"
end end
def do_api_request(type, url, param_string = nil) def do_api_request(type, url, param_string = nil)
begin begin
params = param_string && JSON.parse(param_string) || {} params = param_string && JSON.parse(param_string) || {}
@raw_response = do_request(type, api_base_url, url, params) @json_response = do_request(type, api_base_url, url,
@response = JSON.parse(@raw_response.body) params.merge({ 'CONTENT_TYPE' => 'application/json' }))
rescue Exception rescue Exception
@error = $! @error = $!
end end
end end
Given /^I have an? "([^"]*)" token$/ do |role| def last_json
@json_response.try(:body) || page.source
end
def parsed_response
@parsed_response ||= JSON.parse(last_json)
end
Given /^I have an? "([^"]*)" API token$/ do |role|
@membership = Locomotive::Site.first.memberships.where(:role => role.downcase).first \ @membership = Locomotive::Site.first.memberships.where(:role => role.downcase).first \
|| FactoryGirl.create(role.downcase.to_sym, :site => Locomotive::Site.first) || FactoryGirl.create(role.downcase.to_sym, :site => Locomotive::Site.first)
login_params = { login_params = {
:email => @membership.account.email, 'email' => @membership.account.email,
:password => 'easyone' 'password' => 'easyone'
} }
response = do_request('POST', api_base_url, 'tokens.json', login_params)
response = do_request('POST', api_base_url, 'tokens.json',
login_params.merge({ 'CONTENT_TYPE' => 'application/json' }))
if response.status == 200 if response.status == 200
@token = JSON.parse(response.body)['token'] @auth_token = JSON.parse(response.body)['token']
else else
raise JSON.parse(response.body)['message'] raise JSON.parse(response.body)['message']
end end
add_default_params(:auth_token => @token) add_default_params(:auth_token => @auth_token)
end
Given /^I do not have an API token$/ do
@auth_token = nil
end
When /^I visit "([^"]*)"$/ do |path|
@json_response = get("http://#{@site.domains.first}#{path}", { auth_token: @auth_token }, { 'CONTENT_TYPE' => 'application/json' })
end
# http://stackoverflow.com/questions/9009392/racktest-put-method-with-json-fails-to-convert-json-to-params
When /^I post to "([^"]*)"$/ do |path|
@json_response = post("http://#{@site.domains.first}#{path}", '', { 'CONTENT_TYPE' => 'application/json' })
end
When /^I post to "([^"]*)" with:$/ do |path, json_string|
@json_response = post("http://#{@site.domains.first}#{path}", json_string, { 'CONTENT_TYPE' => 'application/json' })
end end
When /^I do an API (\w+) (?:request )?to ([\w.\/]+)$/ do |request_type, url| When /^I do an API (\w+) (?:request )?to ([\w.\/]+)$/ do |request_type, url|
@ -40,25 +67,22 @@ When /^I do an API (\w+) (?:request )?to ([\w.\/]+) with:$/ do |request_type, ur
do_api_request(request_type, url, param_string) do_api_request(request_type, url, param_string)
end end
Then /^the JSON response should be the following:$/ do |json|
@response.should == JSON.parse(json)
end
Then /^the JSON response should contain all pages$/ do Then /^the JSON response should contain all pages$/ do
page_ids_in_response = @response.collect { |page| page['id'].to_s }.sort page_ids_in_response = parsed_response.collect { |page| page['id'].to_s }.sort
all_page_ids = Locomotive::Page.all.collect { |page| page.id.to_s }.sort all_page_ids = Locomotive::Page.all.collect { |page| page.id.to_s }.sort
page_ids_in_response.should == all_page_ids page_ids_in_response.should == all_page_ids
end end
Then /^the JSON response should contain (\d+) pages$/ do |n| Then /^the JSON response should contain (\d+) pages$/ do |n|
@response.count.should == n.to_i parsed_response.count.should == n.to_i
end end
Then /^the JSON response should be an access denied error$/ do Then /^an access denied error should occur$/ do
@error.should_not be_nil @error.should_not be_nil
@error.message.should == 'You are not authorized to access this page.' @error.message.should == 'You are not authorized to access this page.'
end end
=begin
Then /^the JSON response hash should contain:$/ do |json| Then /^the JSON response hash should contain:$/ do |json|
sub_response = {} sub_response = {}
parsed_json = JSON.parse(json) parsed_json = JSON.parse(json)
@ -67,3 +91,4 @@ Then /^the JSON response hash should contain:$/ do |json|
end end
sub_response.should == parsed_json sub_response.should == parsed_json
end end
=end

View File

@ -39,6 +39,14 @@ Given %r{^I have entries for "([^"]*)" with$} do |name, entries|
content_type.save.should be_true content_type.save.should be_true
end end
Given %r{^I (enable|disable) the public submission of the "([^"]*)" model$} do |action, name|
enabled = action == 'enable'
content_type = Locomotive::ContentType.where(:name => name).first
content_type.public_submission_enabled = enabled
content_type.public_submission_accounts = enabled ? [Locomotive::Account.first._id] : []
content_type.save.should be_true
end
When %r{^I change the presentation of the "([^"]*)" model by grouping items by "([^"]*)"$} do |name, field| When %r{^I change the presentation of the "([^"]*)" model by grouping items by "([^"]*)"$} do |name, field|
content_type = Locomotive::ContentType.where(:name => name).first content_type = Locomotive::ContentType.where(:name => name).first
field = content_type.entries_custom_fields.detect { |f| f.label == field } field = content_type.entries_custom_fields.detect { |f| f.label == field }

View File

@ -67,7 +67,8 @@ end
# checks if the rendered body matches a string # checks if the rendered body matches a string
Then /^the rendered output should look like:$/ do |body_contents| Then /^the rendered output should look like:$/ do |body_contents|
page.source.should == body_contents # page.source.should == body_contents
page.source.index(body_contents).should_not be_nil
end end
Then /^I should see delete page buttons$/ do Then /^I should see delete page buttons$/ do

View File

@ -19,6 +19,8 @@ require 'capybara/rails'
require 'capybara/cucumber' require 'capybara/cucumber'
require 'capybara/session' require 'capybara/session'
require 'json_spec/cucumber'
require 'resolv' require 'resolv'
require 'uri' require 'uri'

View File

@ -2,6 +2,7 @@ require 'locomotive/version'
require 'locomotive/core_ext' require 'locomotive/core_ext'
require 'locomotive/configuration' require 'locomotive/configuration'
require 'locomotive/logger' require 'locomotive/logger'
require 'locomotive/haml'
require 'locomotive/formtastic' require 'locomotive/formtastic'
require 'locomotive/dragonfly' require 'locomotive/dragonfly'
require 'locomotive/kaminari' require 'locomotive/kaminari'

View File

@ -9,6 +9,18 @@ module Locomotive
super || has_errors? super || has_errors?
end end
def options
current_site = self.controller.send(:current_site)
current_account = self.controller.send(:current_locomotive_account)
ability = current_account.nil? ? nil : self.controller.send(:current_ability)
super.merge({
:current_site => current_site,
:current_account => current_account,
:ability => ability
})
end
def to_json def to_json
if get? if get?
display resource display resource

View File

@ -7,6 +7,8 @@ module Locomotive
if source.is_a?(String) || source.is_a?(Hash) # simple string or theme asset if source.is_a?(String) || source.is_a?(Hash) # simple string or theme asset
source = source['url'] if source.is_a?(Hash) source = source['url'] if source.is_a?(Hash)
source.strip!
if source =~ /^http/ if source =~ /^http/
file = self.app.fetch_url(source) file = self.app.fetch_url(source)
else else

34
lib/locomotive/haml.rb Normal file
View File

@ -0,0 +1,34 @@
require 'haml/helpers/action_view_mods'
module ActionView
module Helpers
module TagHelper
# Only preserve whitespace in the tag's content: https://github.com/nex3/haml/pull/503
def content_tag_with_haml_and_preserve(name, content_or_options_with_block = nil, *args, &block)
return content_tag_without_haml(name, content_or_options_with_block, *args, &block) unless is_haml?
preserve = haml_buffer.options[:preserve].include?(name.to_s)
if block_given?
if block_is_haml?(block) && preserve
content_tag_without_haml(name, content_or_options_with_block, *args) {preserve(&block)}
else
content_tag_without_haml(name, content_or_options_with_block, *args, &block)
end
else
if name == 'textarea'
tab_down(haml_buffer.tabulation)
elsif preserve && content_or_options_with_block
content_or_options_with_block = Haml::Helpers.preserve(content_or_options_with_block)
end
content_tag_without_haml(name, content_or_options_with_block, *args, &block)
end
end
alias_method :content_tag_without_haml_and_preserve, :content_tag
alias_method :content_tag, :content_tag_with_haml_and_preserve
alias_method :content_tag_with_haml, :content_tag_with_haml_and_preserve
end
end
end

View File

@ -63,12 +63,9 @@ module Locomotive
conditions = HashWithIndifferentAccess.new(@context['with_scope']) conditions = HashWithIndifferentAccess.new(@context['with_scope'])
order_by = conditions.delete(:order_by).try(:split) order_by = conditions.delete(:order_by).try(:split)
if order_by.nil? list.filtered(conditions, order_by)
list.where(conditions).ordered
else
list.where(conditions).order_by(order_by)
end
else else
# no filter, default order
list.ordered list.ordered
end end
end end

View File

@ -0,0 +1,21 @@
module Locomotive
module Liquid
module Drops
class CurrentUser < Base
def logged_in?
_source.present?
end
def name
_source.name if logged_in?
end
def email
_source.email if logged_in?
end
end
end
end
end

View File

@ -27,8 +27,10 @@ module Locomotive
@context[:parent_page] = @context[:page].parent @context[:parent_page] = @context[:page].parent
end end
else else
locale = ::Mongoid::Fields::I18n.locale
@context[:parent_page] = @context[:cached_pages].try(:[], @template_name) || @context[:parent_page] = @context[:cached_pages].try(:[], @template_name) ||
@context[:site].pages.where(:fullpath => @template_name).first @context[:site].pages.where("fullpath.#{locale}" => @template_name).first
end end
raise PageNotFound.new("Page with fullpath '#{@template_name}' was not found") if @context[:parent_page].nil? raise PageNotFound.new("Page with fullpath '#{@template_name}' was not found") if @context[:parent_page].nil?

View File

@ -8,7 +8,7 @@ module Liquid
controller = context.registers[:controller] controller = context.registers[:controller]
plugins = 'common/format,common/table,common/list,common/link,common/highlighteditables,common/block,common/undo,common/contenthandler,common/paste,common/commands,common/abbr,common/horizontalruler' plugins = 'common/format,common/table,common/list,common/link,common/highlighteditables,common/block,common/undo,common/contenthandler,common/paste,common/commands,common/abbr,common/align,common/horizontalruler,custom/locomotive_media'
%{ %{
<meta content="true" name="inline-editor" /> <meta content="true" name="inline-editor" />

View File

@ -55,7 +55,8 @@ module Locomotive
'today' => Date.today, 'today' => Date.today,
'locale' => I18n.locale, 'locale' => I18n.locale,
'default_locale' => current_site.default_locale.to_s, 'default_locale' => current_site.default_locale.to_s,
'locales' => current_site.locales 'locales' => current_site.locales,
'current_user' => Locomotive::Liquid::Drops::CurrentUser.new(current_locomotive_account)
} }
assigns.merge!(Locomotive.config.context_assign_extensions) assigns.merge!(Locomotive.config.context_assign_extensions)
@ -88,7 +89,8 @@ module Locomotive
fresh_when :etag => @page, :last_modified => @page.updated_at.utc, :public => true fresh_when :etag => @page, :last_modified => @page.updated_at.utc, :public => true
if @page.cache_strategy != 'simple' # varnish if @page.cache_strategy != 'simple' # varnish
response.cache_control[:max_age] = @page.cache_strategy response.headers['Editable'] = ''
response.cache_control[:max_age] = @page.cache_strategy
end end
end end
@ -104,4 +106,4 @@ module Locomotive
end end
end end
end end

View File

@ -1,3 +1,3 @@
module Locomotive #:nodoc module Locomotive #:nodoc
VERSION = '2.0.0.rc4' VERSION = '2.0.0.rc7'
end end

View File

@ -30,7 +30,7 @@ Gem::Specification.new do |s|
s.add_dependency 'mongoid', '~> 2.4.6' s.add_dependency 'mongoid', '~> 2.4.6'
s.add_dependency 'locomotive-mongoid-tree', '~> 0.6.2' s.add_dependency 'locomotive-mongoid-tree', '~> 0.6.2'
s.add_dependency 'custom_fields', '~> 2.0.0.rc9' s.add_dependency 'custom_fields', '~> 2.0.0.rc10'
s.add_dependency 'kaminari', '~> 0.13.0' s.add_dependency 'kaminari', '~> 0.13.0'
@ -39,7 +39,7 @@ Gem::Specification.new do |s|
s.add_dependency 'rails-backbone', '~> 0.6.1' s.add_dependency 'rails-backbone', '~> 0.6.1'
s.add_dependency 'codemirror-rails', '~> 2.21' s.add_dependency 'codemirror-rails', '~> 2.21'
s.add_dependency 'locomotive-tinymce-rails', '~> 3.4.7.2' s.add_dependency 'locomotive-tinymce-rails', '~> 3.4.7.2'
s.add_dependency 'locomotive-aloha-rails', '~> 0.20.1.2' s.add_dependency 'locomotive-aloha-rails', '~> 0.20.1.4'
s.add_dependency 'flash_cookie_session', '~> 1.1.1' s.add_dependency 'flash_cookie_session', '~> 1.1.1'
s.add_dependency 'locomotive_liquid', '2.2.2' s.add_dependency 'locomotive_liquid', '2.2.2'
@ -54,11 +54,12 @@ Gem::Specification.new do |s|
s.add_dependency 'rmagick', '~> 2.12.2' s.add_dependency 'rmagick', '~> 2.12.2'
s.add_dependency 'carrierwave-mongoid', '~> 0.1.3' s.add_dependency 'carrierwave-mongoid', '~> 0.1.3'
s.add_dependency 'carrierwave', '~> 0.6.0' s.add_dependency 'carrierwave', '~> 0.6.0'
s.add_dependency 'fog', '~> 1.1.2' s.add_dependency 'fog', '~> 1.3.1'
s.add_dependency 'dragonfly', '~> 0.9.8' s.add_dependency 'dragonfly', '~> 0.9.8'
s.add_dependency 'rack-cache', '~> 1.1' s.add_dependency 'rack-cache', '~> 1.1'
s.add_dependency 'mimetype-fu', '~> 0.1.2' s.add_dependency 'mimetype-fu', '~> 0.1.2'
s.add_dependency 'multi_json', '1.2.0'
s.add_dependency 'httparty', '~> 0.8.1' s.add_dependency 'httparty', '~> 0.8.1'
s.add_dependency 'actionmailer-with-request', '~> 0.3.0' s.add_dependency 'actionmailer-with-request', '~> 0.3.0'

View File

@ -5,17 +5,6 @@ rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks' puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end end
Locomotive::Page.each do |page|
page.editable_elements.each_with_index do |el, index|
next if el._type != 'Locomotive::EditableFile' || el.attributes['source'].is_a?(Hash)
value = el.attributes['source']
page.collection.update({ '_id' => page._id }, { '$set' => { "editable_elements.#{index}.source" => { 'en' => value } } })
end
end
# ================ GLOBAL VARIABLES ============== # ================ GLOBAL VARIABLES ==============
$database = 'locomotive_hosting_production' $database = 'locomotive_hosting_production'
@ -24,12 +13,12 @@ $default_locale = 'en'
# $locale_exceptions = {} # $locale_exceptions = {}
# Example: # Example:
$locale_exceptions = { # $locale_exceptions = {
'4c082a9393d4330812000002' => 'fr', # '4c082a9393d4330812000002' => 'fr',
'4c2330706f40d50ae2000005' => 'fr', # '4c2330706f40d50ae2000005' => 'fr',
'4dc07643d800a53aea00035a' => 'fr', # '4dc07643d800a53aea00035a' => 'fr',
'4eb6aca89a976a0001000ebb' => 'fr' # '4eb6aca89a976a0001000ebb' => 'fr'
} # }
def get_locale(site_id) def get_locale(site_id)
$locale_exceptions[site_id.to_s] || $default_locale $locale_exceptions[site_id.to_s] || $default_locale
@ -42,8 +31,18 @@ require 'mongoid'
Mongoid.configure do |config| Mongoid.configure do |config|
name = $database name = $database
host = 'localhost' host = 'localhost'
config.master = Mongo::Connection.new.db(name)
# simple connection
# config.master = Mongo::Connection.new.db(name)
# a more complicated connection
# config.master = Mongo::Connection.new('localhost', '27017', :logger => Logger.new($stdout)).db(name) # config.master = Mongo::Connection.new('localhost', '27017', :logger => Logger.new($stdout)).db(name)
# connection with authentication
# db = config.master = Mongo::Connection.new(host, '27019').db(name)
# db.authenticate('username', 'password').tap do |auth|
# puts auth.inspect
# end
end end
db = Mongoid.config.master db = Mongoid.config.master
@ -305,6 +304,17 @@ if collection = db.collections.detect { |c| c.name == 'pages' }
modifications["editable_elements.#{index}.source"] = { locale => editable_element['source_filename'] } modifications["editable_elements.#{index}.source"] = { locale => editable_element['source_filename'] }
removals["editable_elements.#{index}.source_filename"] = '1' removals["editable_elements.#{index}.source_filename"] = '1'
end end
# FIXME: do not remember why I needed to run this code.
# Locomotive::Page.each do |page|
# page.editable_elements.each_with_index do |el, index|
# next if el._type != 'Locomotive::EditableFile' || el.attributes['source'].is_a?(Hash)
#
# value = el.attributes['source']
#
# page.collection.update({ '_id' => page._id }, { '$set' => { "editable_elements.#{index}.source" => { 'en' => value } } })
# end
# end
end end
if page['depth'] == 0 && page['fullpath'] == '404' if page['depth'] == 0 && page['fullpath'] == '404'

View File

@ -0,0 +1,2 @@
# FIXME: just to demonstrate how easy it is to change the tinymce settings for the LocomotiveCMS back-office
# window.Locomotive.tinyMCE.defaultSettings.theme_advanced_buttons2 = 'formatselect,fontselect,fontsizeselect'

View File

@ -0,0 +1 @@
= javascript_include_tag 'locomotive_misc'

View File

@ -1 +1,2 @@
Haml::Template.options[:ugly] = true # improve performance in dev Haml::Template.options[:format] = :html5
Haml::Template.options[:ugly] = true # improve performance in dev

View File

@ -21,8 +21,7 @@ describe Locomotive::Liquid::Drops::ContentEntry do
it 'filters the list' do it 'filters the list' do
template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %}) template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
@list.expects(:order_by).with(['name', 'ASC']).returns(%w(a b)) @list.expects(:filtered).with({ 'active' => true }, ['name', 'ASC']).returns(%w(a b))
@list.expects(:where).with({ 'active' => true }).returns(@list)
render(template, { 'category' => @category }).should == 'a,b,' render(template, { 'category' => @category }).should == 'a,b,'
end end
@ -30,8 +29,7 @@ describe Locomotive::Liquid::Drops::ContentEntry do
it 'filters the list and uses the default order' do it 'filters the list and uses the default order' do
template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %}) template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
@list.expects(:ordered).returns(%w(a b)) @list.expects(:filtered).with({ 'active' => true }, nil).returns(%w(a b))
@list.expects(:where).with({ 'active' => true }).returns(@list)
render(template, { 'category' => @category }).should == 'a,b,' render(template, { 'category' => @category }).should == 'a,b,'
end end

View File

@ -0,0 +1,63 @@
require 'spec_helper'
describe Locomotive::Liquid::Drops::CurrentUser do
before(:each) do
@page = FactoryGirl.build(:sub_page)
@site = @page.site
@site.pages.expects(:any_in).returns([@page])
@controller = Locomotive::TestController.new
@controller.stubs(:flash).returns(ActionDispatch::Flash::FlashHash.new())
@controller.stubs(:params).returns(:url => '/subpage')
@controller.stubs(:request).returns(OpenStruct.new(:url => '/subpage', :fullpath => '/subpage'))
@controller.current_site = @site
@admin = FactoryGirl.build(:admin).account
end
def expect_render(template, text)
@page.raw_template = template
@page.send(:serialize_template)
@controller.expects(:render).with(:text => text, :layout => false, :status => :ok).returns(true)
@controller.send(:render_locomotive_page)
end
context '#logged_in?' do
it 'returns false when no user is logged in' do
expect_render('{{ current_user.logged_in? }}', 'false')
end
it 'returns true when there is a user logged in' do
@controller.expects(:current_admin).twice.returns(true)
expect_render('{{ current_user.logged_in? }}', 'true')
end
end
context '#name' do
it 'returns nothing when no user is logged in' do
expect_render('{{ current_user.name }}', '')
end
it 'returns the username when the user is logged in' do
@controller.expects(:current_admin).twice.returns(@admin)
expect_render('{{ current_user.name }}', 'Admin')
end
end
context '#email' do
it 'returns nothing when no user is logged in' do
expect_render('{{ current_user.email }}', '')
end
it 'returns the username when the user is logged in' do
@controller.expects(:current_admin).twice.returns(@admin)
expect_render('{{ current_user.email }}', 'admin@locomotiveapp.org')
end
end
context '#logout_path' do
it 'returns the logout url' do
expect_render('{{ current_user.logout_path }}', '/admin/sign_out')
end
end
end

View File

@ -35,4 +35,10 @@ describe Locomotive::Liquid::Filters::Date do
format_date(@date).should == '06/29/2007' format_date(@date).should == '06/29/2007'
end end
it 'prints a date within a template (from the documentation)' do
template = Liquid::Template.parse("{{ today | localized_date: '%d %B', 'fr' }}")
context = Liquid::Context.new({}, { 'today' => @date }, {})
template.render(context).should == '29 juin'
end
end end

View File

@ -17,13 +17,19 @@ describe Locomotive::Liquid::Filters::Resize do
@template = Liquid::Template.parse('{{ asset_url | resize: "40x30" }}') @template = Liquid::Template.parse('{{ asset_url | resize: "40x30" }}')
end end
it 'should return the location of the resized image' do it 'returns the location of the resized image' do
@template.render(@context).should =~ /images\/dynamic\/.*\/5k.png/ @template.render(@context).should =~ /images\/dynamic\/.*\/5k.png/
end end
it 'should use the path in the public folder to generate a location' do it 'uses the path in the public folder to generate a location' do
@template.render(@context).should == Locomotive::Dragonfly.resize_url(@asset_path, '40x30') @template.render(@context).should == Locomotive::Dragonfly.resize_url(@asset_path, '40x30')
end end
it 'accepts strings with leading and trailing empty characters' do
@context['asset_url'] = " \t #{@context['asset_url']} \n\n "
@template.render(@context).should == Locomotive::Dragonfly.resize_url(@asset_path, '40x30')
end
end end
context 'when a theme asset is given' do context 'when a theme asset is given' do
@ -31,23 +37,25 @@ describe Locomotive::Liquid::Filters::Resize do
@template = Liquid::Template.parse("{{ theme_asset | resize: '300x400' }}") @template = Liquid::Template.parse("{{ theme_asset | resize: '300x400' }}")
end end
it 'should return the location of the resized image' do it 'returns the location of the resized image' do
@template.render(@context).should =~ /images\/dynamic\/.*\/5k.png/ @template.render(@context).should =~ /images\/dynamic\/.*\/5k.png/
end end
it 'should use the path of the theme asset to generate a location' do it 'uses the path of the theme asset to generate a location' do
@template.render(@context).should == Locomotive::Dragonfly.resize_url(@theme_asset_path, '300x400') @template.render(@context).should == Locomotive::Dragonfly.resize_url(@theme_asset_path, '300x400')
end end
end end
context 'when no resize string is given' do context 'when no resize string is given' do
before :each do before :each do
@template = Liquid::Template.parse('{{ asset | resize: }}') @template = Liquid::Template.parse('{{ asset | resize: }}')
end end
it 'should return a liquid error' do it 'returns a liquid error' do
@template.render(@context).should include 'Liquid error: wrong number of arguments' @template.render(@context).should include 'Liquid error: wrong number of arguments'
end end
end end
end end
end end

View File

@ -12,17 +12,29 @@ describe Locomotive::Liquid::Tags::Extends do
end end
it 'works' do it 'works' do
lambda { page = FactoryGirl.build(:page, :slug => 'sub_page_1', :parent => @home)
parse('parent', page).render.should == 'Hello world'
end
it 'looks for the index with the right locale' do
::Mongoid::Fields::I18n.with_locale 'fr' do
@home.raw_template = 'Bonjour le monde'
@home.send :serialize_template
end
@site.pages.expects(:where).with('fullpath.fr' => 'index').returns([@home])
::Mongoid::Fields::I18n.with_locale 'fr' do
page = FactoryGirl.build(:page, :slug => 'sub_page_1', :parent => @home) page = FactoryGirl.build(:page, :slug => 'sub_page_1', :parent => @home)
parse('parent', page) parse('index', page).render.should == 'Bonjour le monde'
}.should_not raise_error end
end end
context '#errors' do context '#errors' do
it 'raises an error if the source page does not exist' do it 'raises an error if the source page does not exist' do
lambda { lambda {
@site.pages.expects(:where).with(:fullpath => 'foo').returns([]) @site.pages.expects(:where).with('fullpath.en' => 'foo').returns([])
parse('foo') parse('foo')
}.should raise_error(Locomotive::Liquid::PageNotFound, "Page with fullpath 'foo' was not found") }.should raise_error(Locomotive::Liquid::PageNotFound, "Page with fullpath 'foo' was not found")
end end

View File

@ -0,0 +1,43 @@
require 'spec_helper'
describe Locomotive::Extensions::Page::EditableElements do
before(:each) do
@site = FactoryGirl.create(:site)
@home = @site.pages.root.first
@home.update_attributes :raw_template => "{% editable_short_text 'body' %}Lorem ipsum{% endeditable_short_text %}"
@sub_page_1 = FactoryGirl.create(:page, :slug => 'sub_page_1', :parent => @home, :raw_template => "{% extends 'parent' %}")
@sub_page_2 = FactoryGirl.create(:page, :slug => 'sub_page_2', :parent => @home, :raw_template => "{% extends 'parent' %}")
@sub_page_1_el = @sub_page_1.editable_elements.first
@sub_page_1_1 = FactoryGirl.create(:page, :slug => 'sub_page_1_1', :parent => @sub_page_1, :raw_template => "{% extends 'parent' %}")
end
describe 'modification of an element within the home page' do
before(:each) do
@home = Locomotive::Page.find(@home._id)
end
it 'changes the type of the element in all the children' do
@home.update_attributes :raw_template => "{% editable_long_text 'body' %}Lorem ipsum{% endeditable_long_text %}"
@sub_page_1.reload
@sub_page_1.editable_elements.first._type.should == 'Locomotive::EditableLongText'
@sub_page_1_1.reload
@sub_page_1_1.editable_elements.first._type.should == 'Locomotive::EditableLongText'
end
it 'changes the hint of the element in all the children' do
@home.update_attributes :raw_template => "{% editable_long_text 'body', hint: 'My very useful hint' %}Lorem ipsum{% endeditable_long_text %}"
@sub_page_1.reload
@sub_page_1.editable_elements.first.hint.should == 'My very useful hint'
@sub_page_1_1.reload
@sub_page_1_1.editable_elements.first.hint.should == 'My very useful hint'
end
end
end

View File

@ -1,6 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe Locomotive::Extensions::Site::SubdomainDomains do describe Locomotive::Extensions::Site::SubdomainDomains do
describe '#subdomain=' do describe '#subdomain=' do
let(:site) { Locomotive::Site.new } let(:site) { Locomotive::Site.new }
@ -20,4 +21,5 @@ describe Locomotive::Extensions::Site::SubdomainDomains do
site.domains.should == ['first.com', 'second.com', 'third.com'] site.domains.should == ['first.com', 'second.com', 'third.com']
end end
end end
end end

View File

@ -1,3 +1,5 @@
# coding: utf-8
require 'spec_helper' require 'spec_helper'
describe Locomotive::Site do describe Locomotive::Site do
@ -114,6 +116,47 @@ describe Locomotive::Site do
@site.pages.map(&:fullpath).sort.should == %w{404 index} @site.pages.map(&:fullpath).sort.should == %w{404 index}
end end
it 'translates the index/404 pages if a new locale is added' do
@site.update_attributes :locales => %w(en fr)
@site.errors.should be_empty
::Mongoid::Fields::I18n.with_locale('fr') do
@site.pages.root.first.tap do |page|
page.title.should == "Page d'accueil"
page.slug.should == 'index'
end
@site.pages.not_found.first.tap do |page|
page.title.should == 'Page non trouvée'
page.slug.should == '404'
end
end
end
it 'translates the index/404 pages if the default locale changes' do
@site.update_attributes :locales => %w(fr en)
@site.errors.should be_empty
::Mongoid::Fields::I18n.with_locale('fr') do
@site.pages.root.first.tap do |page|
page.title.should == "Page d'accueil"
page.slug.should == 'index'
end
@site.pages.not_found.first.tap do |page|
page.title.should == 'Page non trouvée'
page.slug.should == '404'
end
end
end
it 'does not allow to remove the default locale' do
@site.update_attributes :locales => %w(fr)
@site.errors[:locales].should == ['The previous default locale can not be removed right away.']
end
end end
describe 'deleting in cascade' do describe 'deleting in cascade' do

View File

@ -0,0 +1,39 @@
/**
* Version 0.0.1
* Display a message letting the user know the form is being submitted
* Didier Lafforgue
*/
$.fn.formSubmitNotification = function(settings) {
function show() {
$('#form-submit-notification').fadeIn()
}
function hide() {
$('#form-submit-notification').fadeOut()
}
function create(message) {
if ($('#form-submit-notification').size() == 0) {
var element = $("<div id=\"form-submit-notification\"><div>" + message + "</div></div>").hide();
$('body').append(element);
}
}
return this.each(function() {
var form = $(this);
var message = form.attr('data-sending-form-message');
if (typeof(message) == 'undefined')
message = form.find('input[type=submit]').attr('data-sending-form-message');
if (typeof(message) == 'undefined')
return ;
create(message);
form.bind('ajax:beforeSend', function(event) { show() });
form.bind('ajax:complete', function(event) { hide() });
});
}