locomotive_cms 0.0.2.9 → 0.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/Gemfile +1 -0
  2. data/app/controllers/admin/accounts_controller.rb +5 -11
  3. data/app/controllers/admin/asset_collections_controller.rb +5 -46
  4. data/app/controllers/admin/assets_controller.rb +12 -30
  5. data/app/controllers/admin/base_controller.rb +15 -19
  6. data/app/controllers/admin/content_types_controller.rb +1 -46
  7. data/app/controllers/admin/contents_controller.rb +15 -42
  8. data/app/controllers/admin/current_sites_controller.rb +10 -11
  9. data/app/controllers/admin/custom_fields_controller.rb +1 -1
  10. data/app/controllers/admin/layouts_controller.rb +2 -46
  11. data/app/controllers/admin/memberships_controller.rb +5 -18
  12. data/app/controllers/admin/my_accounts_controller.rb +11 -9
  13. data/app/controllers/admin/pages_controller.rb +6 -45
  14. data/app/controllers/admin/sitemaps_controller.rb +16 -0
  15. data/app/controllers/admin/sites_controller.rb +12 -19
  16. data/app/controllers/admin/snippets_controller.rb +2 -45
  17. data/app/controllers/admin/theme_assets_controller.rb +13 -54
  18. data/app/helpers/admin/assets_helper.rb +1 -1
  19. data/app/helpers/admin/base_helper.rb +2 -2
  20. data/app/helpers/admin/pages_helper.rb +13 -0
  21. data/app/models/membership.rb +4 -1
  22. data/app/models/page.rb +8 -1
  23. data/app/models/page_part.rb +1 -4
  24. data/app/views/admin/asset_collections/_asset.html.haml +1 -1
  25. data/app/views/admin/asset_collections/edit.html.haml +7 -7
  26. data/app/views/admin/asset_collections/new.html.haml +1 -1
  27. data/app/views/admin/assets/_form.html.haml +1 -1
  28. data/app/views/admin/assets/edit.html.haml +3 -3
  29. data/app/views/admin/assets/new.html.haml +2 -2
  30. data/app/views/admin/content_types/edit.html.haml +1 -1
  31. data/app/views/admin/contents/edit.html.haml +1 -1
  32. data/app/views/admin/contents/index.html.haml +1 -1
  33. data/app/views/admin/contents/new.html.haml +1 -1
  34. data/app/views/admin/current_sites/edit.html.haml +3 -3
  35. data/app/views/admin/layouts/edit.html.haml +2 -2
  36. data/app/views/admin/layouts/index.html.haml +1 -1
  37. data/app/views/admin/my_accounts/edit.html.haml +3 -3
  38. data/app/views/admin/pages/edit.html.haml +1 -1
  39. data/app/views/admin/pages/index.html.haml +1 -1
  40. data/app/views/admin/shared/_head.html.haml +1 -1
  41. data/app/views/admin/shared/menu/_assets.html.haml +2 -2
  42. data/app/views/admin/sitemaps/show.xml.builder +18 -0
  43. data/app/views/admin/snippets/edit.html.haml +2 -2
  44. data/app/views/admin/snippets/index.html.haml +1 -1
  45. data/app/views/admin/theme_assets/_form.html.haml +9 -9
  46. data/app/views/admin/theme_assets/edit.html.haml +4 -4
  47. data/app/views/admin/theme_assets/index.html.haml +1 -1
  48. data/app/views/admin/theme_assets/new.html.haml +1 -1
  49. data/app/views/layouts/admin/application.html.haml +1 -0
  50. data/config/initializers/locomotive.rb +21 -0
  51. data/config/locales/admin_ui_en.yml +2 -66
  52. data/config/locales/admin_ui_fr.yml +0 -64
  53. data/config/locales/flash.en.yml +116 -0
  54. data/config/locales/flash.fr.yml +116 -0
  55. data/config/routes.rb +3 -0
  56. data/lib/generators/locomotive/copy_assets/copy_assets_generator.rb +14 -0
  57. data/lib/generators/locomotive/install/install_generator.rb +15 -2
  58. data/lib/generators/locomotive/install/templates/README +17 -0
  59. data/lib/locomotive.rb +3 -0
  60. data/lib/locomotive/admin_responder.rb +28 -0
  61. data/lib/locomotive/inherited_resources.rb +46 -0
  62. data/lib/locomotive/liquid/drops/page.rb +1 -1
  63. data/lib/locomotive/liquid/filters/text.rb +1 -1
  64. data/lib/locomotive/liquid/tags/nav.rb +7 -4
  65. data/public/images/admin/icons/actions.png +0 -0
  66. data/public/images/admin/menu/icons/assets.png +0 -0
  67. data/public/javascripts/admin/application.js +3 -0
  68. data/public/javascripts/admin/pages.js +1 -1
  69. data/public/javascripts/admin/plugins/shortcut.js +55 -0
  70. data/public/stylesheets/admin/application.css +1 -1
  71. data/public/stylesheets/admin/buttons.css +8 -13
  72. data/public/stylesheets/admin/formtastic_changes.css +1 -2
  73. data/public/stylesheets/admin/layout.css +24 -12
  74. data/public/stylesheets/admin/menu.css +3 -3
  75. data/spec/factories.rb +1 -1
  76. data/spec/lib/locomotive/liquid/tags/nav_spec.rb +11 -2
  77. data/spec/models/membership_spec.rb +5 -4
  78. data/spec/models/page_spec.rb +10 -8
  79. metadata +30 -6
  80. data/lib/generators/locomotive/assets/assets_generator.rb +0 -16
data/lib/locomotive.rb CHANGED
@@ -7,7 +7,10 @@ require 'locomotive/carrierwave'
7
7
  require 'locomotive/heroku'
8
8
  require 'locomotive/custom_fields'
9
9
  require 'locomotive/httparty'
10
+ require 'locomotive/inherited_resources'
11
+ require 'locomotive/admin_responder'
10
12
 
13
+ require 'redcloth'
11
14
  require 'mongo_session_store/mongoid'
12
15
 
13
16
  module Locomotive
@@ -0,0 +1,28 @@
1
+ require 'responders'
2
+
3
+ module Locomotive
4
+ class AdminResponder < ::ActionController::Responder
5
+
6
+ include ::Responders::FlashResponder
7
+
8
+ def api_behavior(error)
9
+ raise error unless resourceful?
10
+
11
+ # generate flash messages
12
+ set_flash_message!
13
+
14
+ if get?
15
+ display resource
16
+ elsif has_errors?
17
+ display({ :errors => resource.errors, :model => controller.send(:resource_instance_name), :alert => controller.flash[:alert] })
18
+ elsif post?
19
+ display resource, :status => :created, :location => resource_location
20
+ else
21
+ display({ :notice => controller.flash[:notice] })
22
+ end
23
+
24
+ controller.flash.discard # reset flash messages !
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ require 'inherited_resources'
2
+ require 'inherited_resources/actions'
3
+ require 'inherited_resources/responder'
4
+
5
+ module InheritedResources
6
+ # redirect to edit_resource_url instead of resource_url
7
+ module Actions
8
+
9
+ def create(options={}, &block)
10
+ object = build_resource
11
+
12
+ if create_resource(object)
13
+ options[:location] ||= edit_resource_url rescue nil # change here
14
+ end
15
+
16
+ respond_with_dual_blocks(object, options, &block)
17
+ end
18
+ alias :create! :create
19
+
20
+ # PUT /resources/1
21
+ def update(options={}, &block)
22
+ object = resource
23
+
24
+ if update_resource(object, params[resource_instance_name])
25
+ options[:location] ||= edit_resource_url rescue nil # change here
26
+ end
27
+
28
+ respond_with_dual_blocks(object, options, &block)
29
+ end
30
+ alias :update! :update
31
+
32
+ # DELETE /resources/1
33
+ def destroy(options={}, &block)
34
+ object = resource
35
+ options[:location] ||= collection_url rescue nil
36
+
37
+ destroy_resource(object)
38
+
39
+ options[:alert] = object.errors.full_messages.first # display the first error if present
40
+
41
+ respond_with_dual_blocks(object, options, &block)
42
+ end
43
+ alias :destroy! :destroy
44
+
45
+ end
46
+ end
@@ -3,7 +3,7 @@ module Locomotive
3
3
  module Drops
4
4
  class Page < Base
5
5
 
6
- liquid_attributes << :title
6
+ liquid_attributes << :title << :slug
7
7
 
8
8
  def children
9
9
  @children ||= liquify(*@source.children)
@@ -4,7 +4,7 @@ module Locomotive
4
4
  module Text
5
5
 
6
6
  def textile(input)
7
- RedCloth.new(input).to_html
7
+ ::RedCloth.new(input).to_html
8
8
  end
9
9
 
10
10
  end
@@ -15,8 +15,10 @@ module Locomotive
15
15
  def initialize(tag_name, markup, tokens)
16
16
  if markup =~ Syntax
17
17
  @site_or_page = $1 || 'page'
18
+ @options = {}
19
+ markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value }
18
20
  else
19
- raise ::Liquid::SyntaxError.new("Syntax Error in 'nav' - Valid syntax: nav <page|site>")
21
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'nav' - Valid syntax: nav <page|site> <options>")
20
22
  end
21
23
 
22
24
  super
@@ -27,8 +29,6 @@ module Locomotive
27
29
 
28
30
  source = context.registers[@site_or_page.to_sym]
29
31
 
30
- # puts "[Nav] source = #{source.inspect}"
31
-
32
32
  if source.respond_to?(:name) # site ?
33
33
  source = source.pages.first # start from home page
34
34
  else
@@ -46,9 +46,12 @@ module Locomotive
46
46
  def render_child_link(page)
47
47
  selected = @current_page._id == page._id ? ' on' : ''
48
48
 
49
+ icon = @options[:icon] ? '<span></span>' : ''
50
+ label = %{#{icon if @options[:icon] != 'after' }#{page.title}#{icon if @options[:icon] == 'after' }}
51
+
49
52
  %{
50
53
  <li id="#{page.slug.dasherize}" class="link#{selected}">
51
- <a href="/#{page.fullpath}">#{page.title}</a>
54
+ <a href="/#{page.fullpath}">#{label}</a>
52
55
  </li>
53
56
  }.strip
54
57
  end
@@ -80,6 +80,9 @@ $(document).ready(function() {
80
80
  });
81
81
  $('.formtastic li.error input').eq(0).focus();
82
82
 
83
+ // save form in AJAX
84
+ $('form.save-with-shortcut').saveWithShortcut();
85
+
83
86
  // editable title (page, ...etc)
84
87
  $('#content h2 a.editable').each(function() {
85
88
  var target = $('#' + $(this).attr('rel')),
@@ -26,7 +26,7 @@ $(document).ready(function() {
26
26
  params += '&_method=put';
27
27
 
28
28
  $.post($(this).attr('data_url'), params, function(data) {
29
- $.growl('success', data.message);
29
+ $.growl('success', data.notice);
30
30
  }, 'json');
31
31
  }
32
32
  });
@@ -0,0 +1,55 @@
1
+ // Save -> Command S (need a form)
2
+ jQuery.fn.saveWithShortcut = function() {
3
+
4
+ var resetFormErrors = function(form) {
5
+ jQuery('div.form-errors').remove();
6
+ jQuery('div.formError').remove();
7
+ jQuery('p.inline-errors').remove();
8
+ form.find('li.error').removeClass('error');
9
+ }
10
+
11
+ var updateFromCodeMirror = function() {
12
+ if (typeof CodeMirror == undefined)
13
+ return;
14
+ jQuery.each(CodeMirrorEditors, function() {
15
+ this.el.val(this.editor.getCode());
16
+ });
17
+ }
18
+
19
+ var save = function(form) {
20
+ $.post(form.attr('action'), form.serializeArray(), function(data) {
21
+ onSaveCallback(form, data)
22
+ }, 'json');
23
+ };
24
+
25
+ var onSaveCallback = function(form, data) {
26
+ resetFormErrors(form);
27
+
28
+ if (data.alert != undefined) {
29
+ $.growl('error', data.alert);
30
+ for (var field in data.errors) {
31
+ var error = data.errors[field];
32
+ var node = form.find('li:has(#' + data.model + '_' + field + ')');
33
+ node.addClass('error');
34
+ node.append("<p class='inline-errors'>" + error + "</p>");
35
+ }
36
+ form.find('li.error input').eq(0).focus();
37
+ } else {
38
+ $.growl('success', data.notice);
39
+ }
40
+ };
41
+
42
+ return this.each(function() {
43
+ var form = jQuery(this);
44
+
45
+ jQuery(document).bind('keypress.shortcut', function(event) {
46
+ if (!(event.which == 115 && (event.ctrlKey || event.metaKey))) return true;
47
+ updateFromCodeMirror();
48
+ save(form);
49
+ event.preventDefault();
50
+ return false;
51
+ });
52
+
53
+ });
54
+
55
+ };
@@ -7,7 +7,7 @@ div.notice {
7
7
  height: 90px;
8
8
  }
9
9
 
10
- div.notice.error {
10
+ div.notice.error, div.notice.alert {
11
11
  background-image: url(/images/admin/form/growl-error.png);
12
12
  }
13
13
 
@@ -37,31 +37,26 @@
37
37
  }
38
38
 
39
39
  .button.small {
40
- background: transparent url(/images/admin/buttons/action-left.png) no-repeat left -40px;
40
+ background: #ebedf4;
41
+ outline: none;
42
+ -moz-border-radius : 10px;
43
+ -webkit-border-radius: 10px;
41
44
  color: #787a89;
42
45
  height: 20px;
43
46
  font-size: 0.7em;
44
- padding: 0px 0px 0px 12px;
47
+ padding: 0px 12px 0px 12px;
45
48
  color: #8B8D9A !important;
46
49
  text-decoration: none;
47
- }
48
-
49
- .button.small span {
50
- background-image: url(/images/admin/buttons/action-right.png);
51
- text-shadow: 1px 1px 1px #fff;
52
- padding: 0px 12px 10px 0px;
53
- top: 0px;
54
50
  color: #8B8D9A;
51
+ text-shadow: 1px 1px 1px #fff;
55
52
  }
56
53
 
57
54
  .button.small.add {
58
- padding-left: 24px;
59
- background-position: 0 0;
60
55
  }
61
56
 
62
- .button.remove, .button.remove span {
57
+ .button.remove {
63
58
  color: #ff092c !important;
64
59
  font-size: 1.1em;
65
60
  }
66
61
 
67
- .button.remove:hover span { text-decoration: underline; }
62
+ .button.remove:hover { text-decoration: underline; }
@@ -13,7 +13,6 @@ form.formtastic legend {
13
13
  margin: 0;
14
14
  float: left;
15
15
  white-space: normal;
16
- *margin-left: -7px;
17
16
  position: relative;
18
17
  }
19
18
 
@@ -289,7 +288,7 @@ form.formtastic fieldset.editable-list ol li.added input {
289
288
  color: #17171D;
290
289
  font-size: 0.9em;
291
290
  font-weight: bold;
292
- cursor: normal;
291
+ cursor: pointer;
293
292
  }
294
293
 
295
294
  form.formtastic fieldset.editable-list ol li.added input:hover {
@@ -105,32 +105,44 @@ body {
105
105
 
106
106
  #content #local-actions-bar {
107
107
  position: absolute;
108
- top: 15px;
108
+ top: 13px;
109
109
  right: 15px;
110
110
  }
111
111
 
112
112
  #content #local-actions-bar a {
113
- display: block;
114
- float: left;
115
- height: 20px;
113
+ position: relative;
114
+ padding: 2px 10px 3px 31px;
116
115
  color: #8b8d9a;
117
116
  text-decoration: none;
118
117
  font-size: 0.7em;
119
- padding-left: 24px;
120
118
  margin-left: 10px;
121
- background: transparent url(/images/admin/buttons/action-left.png) no-repeat 0 0;
119
+ background-color: #ebedf4;
122
120
  outline: none;
121
+ -moz-border-radius : 10px;
122
+ -webkit-border-radius: 10px;
123
123
  }
124
124
 
125
- #content #local-actions-bar a:hover span { text-decoration: underline; }
125
+ #content #local-actions-bar a em {
126
+ position: absolute;
127
+ display: block;
128
+ top: 5px;
129
+ left: 10px;
130
+ height: 16px;
131
+ width: 16px;
132
+ background: transparent url(/images/admin/icons/actions.png) no-repeat 0 0;
133
+ }
126
134
 
127
- #content #local-actions-bar a span {
128
- display: inline-block;
129
- height: 13px;
130
- background: transparent url(/images/admin/buttons/action-right.png) no-repeat right 0;
131
- padding: 1px 10px 8px 2px;
135
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
136
+ #content #local-actions-bar a em { top: 4px; }
132
137
  }
133
138
 
139
+ #content #local-actions-bar a.show em { background-position: 0 0; }
140
+ #content #local-actions-bar a.edit em { background-position: 0 -16px; top: 2px; left: 12px; }
141
+ #content #local-actions-bar a.download em { background-position: 0 -32px; }
142
+ #content #local-actions-bar a.new em { background-position: 0 -48px; left: 13px; }
143
+
144
+ #content #local-actions-bar a:hover { text-decoration: none; color: #333; }
145
+
134
146
  /* ___ footer ___ */
135
147
 
136
148
  #footer {
@@ -48,12 +48,12 @@ body.contents ul#menu li.contents a em { background-position: 0 -12px; }
48
48
  body.settings ul#menu li.contents { background-position: 0 -80px; }
49
49
  body.settings ul#menu li.contents a { background-position: 0 -80px; }
50
50
 
51
- ul#menu li.assets { background: url(/images/admin/menu/left.png) no-repeat 0 -40px; padding-left: 35px; }
52
- ul#menu li.assets a em { background: transparent url(/images/admin/menu/icons/settings.png) no-repeat 0 0; height: 14px; }
51
+ ul#menu li.assets { background: url(/images/admin/menu/left.png) no-repeat 0 -40px; padding-left: 40px; }
52
+ ul#menu li.assets a em { background: transparent url(/images/admin/menu/icons/assets.png) no-repeat 0 0; height: 20px; width: 20px; float: left; top: 0px; }
53
53
  body.contents ul#menu li.assets { background-position: 0 0px; }
54
54
  body.assets ul#menu li.assets { background-position: 0 -80px; }
55
55
  body.assets ul#menu li.assets a { background-position: right -80px; color: white; text-shadow: none; }
56
- body.assets ul#menu li.assets a em { background-position: 0 -15px; }
56
+ body.assets ul#menu li.assets a em { background-position: 0 -20px; }
57
57
 
58
58
  ul#menu li.assets { background: url(/images/admin/menu/left.png) no-repeat 0 -40px; padding-left: 35px; }
59
59
  ul#menu li.assets a { background: url(/images/admin/menu/right.png) no-repeat right 0px; }
data/spec/factories.rb CHANGED
@@ -45,7 +45,7 @@ Factory.define :layout do |l|
45
45
  </head>
46
46
  <body>
47
47
  <div id="sidebar">\{\{ content_for_left_sidebar \}\}</div>
48
- <div id="main">\{\{ content_for_layout \}\}</div>
48
+ <div id="main">\{\{ content_for_layout | textile \}\}</div>
49
49
  </body>
50
50
  </html>}
51
51
  end
@@ -28,13 +28,22 @@ describe Locomotive::Liquid::Tags::Nav do
28
28
  output.should == '<ul id="nav"><li id="sub-child-1" class="link on"><a href="/child_2/sub_child_1">Child #2.1</a></li><li id="sub-child-2" class="link"><a href="/child_2/sub_child_2">Child #2.2</a></li></ul>'
29
29
  end
30
30
 
31
+ it 'adds an icon before the link' do
32
+ render_nav('site', {}, 'icon: true').should match /<li id="child-1" class="link"><a href="\/child_1"><span><\/span>Child #1<\/a>/
33
+ render_nav('site', {}, 'icon: before').should match /<li id="child-1" class="link"><a href="\/child_1"><span><\/span>Child #1<\/a>/
34
+ end
35
+
36
+ it 'adds an icon after the link' do
37
+ render_nav('site', {}, 'icon: after').should match /<li id="child-1" class="link"><a href="\/child_1">Child #1<span><\/span><\/a><\/li>/
38
+ end
39
+
31
40
  end
32
41
 
33
- def render_nav(source = 'site', registers = {})
42
+ def render_nav(source = 'site', registers = {}, template_option = '')
34
43
  registers = { :site => @site, :page => @home }.merge(registers)
35
44
  liquid_context = ::Liquid::Context.new({}, registers)
36
45
 
37
- output = Liquid::Template.parse("{% nav #{source} %}").render(liquid_context)
46
+ output = Liquid::Template.parse("{% nav #{source} #{template_option} %}").render(liquid_context)
38
47
  output.gsub(/\n\s{0,}/, '')
39
48
  end
40
49
 
@@ -26,29 +26,30 @@ describe Membership do
26
26
  before(:each) do
27
27
  @membership = Factory.build(:membership, :site => Factory.build(:site))
28
28
  @account = Factory.build(:account)
29
+ @account.stubs(:save).returns(true)
29
30
  Account.stubs(:where).returns([@account])
30
31
  Account.stubs(:find).returns(@account)
31
32
  end
32
33
 
33
34
  it 'should tell error' do
34
- @membership.action_to_take.should == :error
35
+ @membership.process!.should == :error
35
36
  end
36
37
 
37
38
  it 'should tell we need to create a new account' do
38
39
  Account.stubs(:where).returns([])
39
40
  @membership.email = 'homer@simpson'
40
- @membership.action_to_take.should == :create_account
41
+ @membership.process!.should == :create_account
41
42
  end
42
43
 
43
44
  it 'should tell nothing to do' do
44
45
  @membership.email = 'bart@simpson.net'
45
46
  @membership.site.stubs(:memberships).returns([@membership, @membership])
46
- @membership.action_to_take.should == :nothing
47
+ @membership.process!.should == :nothing
47
48
  end
48
49
 
49
50
  it 'should tell membership has to be saved' do
50
51
  @membership.email = 'bart@simpson.net'
51
- @membership.action_to_take.should == :save_it
52
+ @membership.process!.should == :save_it
52
53
  end
53
54
  end
54
55
 
@@ -81,24 +81,26 @@ describe Page do
81
81
 
82
82
  end
83
83
 
84
- describe 'delete' do
84
+ describe '#deleting' do
85
85
 
86
86
  before(:each) do
87
87
  @page = Factory.build(:page)
88
88
  end
89
89
 
90
- it 'should delete index page' do
90
+ it 'does not delete the index page' do
91
91
  @page.stubs(:index?).returns(true)
92
92
  lambda {
93
- @page.destroy
94
- }.should raise_error(Exception, 'You can not remove index or 404 pages')
93
+ @page.destroy.should be_false
94
+ @page.errors.first == 'You can not remove index or 404 pages'
95
+ }.should_not change(Page, :count)
95
96
  end
96
97
 
97
- it 'should delete 404 page' do
98
+ it 'does not delete the 404 page' do
98
99
  @page.stubs(:not_found?).returns(true)
99
100
  lambda {
100
- @page.destroy
101
- }.should raise_error(Exception, 'You can not remove index or 404 pages')
101
+ @page.destroy.should be_false
102
+ @page.errors.first == 'You can not remove index or 404 pages'
103
+ }.should_not change(Page, :count)
102
104
  end
103
105
 
104
106
  end
@@ -323,7 +325,7 @@ describe Page do
323
325
  </head>
324
326
  <body>
325
327
  <div id="sidebar">A sidebar...</div>
326
- <div id="main">Hello world !</div>
328
+ <div id="main"><p>Hello world !</p></div>
327
329
  </body>
328
330
  </html>}
329
331
  end