schnitzelpress 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGES.md +20 -3
  2. data/README.md +4 -2
  3. data/bin/schnitzelpress +1 -1
  4. data/lib/assets/js/jquery-1.7.1.js +673 -0
  5. data/lib/assets/js/jquery-ujs.js +373 -0
  6. data/lib/assets/js/jquery.cookie.js +47 -0
  7. data/lib/{public → assets}/js/schnitzelpress.js +7 -2
  8. data/lib/public/font/fontawesome-webfont.eot +0 -0
  9. data/lib/public/font/fontawesome-webfont.svg +175 -0
  10. data/lib/public/font/fontawesome-webfont.svgz +0 -0
  11. data/lib/public/font/fontawesome-webfont.ttf +0 -0
  12. data/lib/public/font/fontawesome-webfont.woff +0 -0
  13. data/lib/schnitzelpress.rb +21 -5
  14. data/lib/schnitzelpress/actions/admin.rb +21 -1
  15. data/lib/schnitzelpress/actions/assets.rb +36 -0
  16. data/lib/schnitzelpress/actions/auth.rb +16 -3
  17. data/lib/schnitzelpress/actions/blog.rb +10 -12
  18. data/lib/schnitzelpress/app.rb +16 -19
  19. data/lib/schnitzelpress/cache_control.rb +17 -0
  20. data/lib/schnitzelpress/cli.rb +40 -9
  21. data/lib/schnitzelpress/config.rb +43 -0
  22. data/lib/schnitzelpress/env.rb +5 -0
  23. data/lib/schnitzelpress/helpers.rb +168 -4
  24. data/lib/schnitzelpress/markdown_renderer.rb +2 -2
  25. data/lib/schnitzelpress/post.rb +2 -6
  26. data/lib/schnitzelpress/static.rb +1 -1
  27. data/lib/schnitzelpress/version.rb +2 -2
  28. data/lib/templates/new_blog/Gemfile +4 -5
  29. data/lib/templates/new_blog/Gemfile.lock.tt +137 -0
  30. data/lib/templates/new_blog/Procfile +1 -1
  31. data/lib/templates/new_blog/config.ru.tt +6 -17
  32. data/lib/views/admin/admin.haml +3 -2
  33. data/lib/views/admin/config.haml +27 -0
  34. data/lib/views/atom.haml +3 -3
  35. data/lib/views/index.haml +1 -1
  36. data/lib/views/layout.haml +15 -11
  37. data/lib/views/login.haml +4 -0
  38. data/lib/views/partials/_admin_post_list.haml +8 -8
  39. data/lib/views/partials/_disqus.haml +1 -1
  40. data/lib/views/partials/_gauges.haml +1 -1
  41. data/lib/views/partials/_google_analytics.haml +1 -1
  42. data/lib/views/partials/_post.haml +6 -5
  43. data/lib/views/partials/_post_form.haml +3 -1
  44. data/lib/views/post.haml +4 -1
  45. data/lib/views/schnitzelpress.scss +67 -3
  46. data/schnitzelpress.gemspec +5 -2
  47. data/spec/app_spec.rb +5 -11
  48. data/spec/assets_spec.rb +26 -0
  49. data/spec/factories.rb +1 -1
  50. data/spec/post_spec.rb +9 -2
  51. data/spec/spec_helper.rb +2 -1
  52. metadata +115 -71
  53. data/lib/schnitzelpress/rake.rb +0 -20
  54. data/lib/templates/new_blog/Rakefile +0 -2
  55. data/lib/templates/new_blog/app.rb.tt +0 -34
  56. data/lib/templates/new_blog/config/unicorn.rb +0 -5
  57. data/lib/templates/new_blog/public/.gitkeep +0 -0
@@ -0,0 +1,27 @@
1
+ - set_page_title "Configuration"
2
+
3
+ %section
4
+ %form.post{:action => '/admin/config', :method => 'post'}
5
+ %h2 Blog
6
+ = form_field config, :blog_title
7
+ = form_field config, :blog_description
8
+ = form_field config, :blog_footer
9
+
10
+ %h2 Author
11
+ = form_field config, :author_name
12
+
13
+ %h2 External Services
14
+ = form_field config, :blog_feed_url, :label => "ATOM Feed URL", :hint => "This is what the /feed URL will redirect to. You can change this to eg. your FeedBurner URL."
15
+ .row
16
+ .six.columns
17
+ = form_field config, :disqus_id, :label => "Disqus shortname"
18
+ .six.columns
19
+ = form_field config, :twitter_id, :label => "Twitter user name"
20
+ .row
21
+ .six.columns
22
+ = form_field config, :google_analytics_id, :label => "Google Analytics ID"
23
+ .six.columns
24
+ = form_field config, :gauges_id, :label => "Gauges ID"
25
+
26
+ .buttons
27
+ %input{:type => 'submit', :value => 'Update Configuration'}
data/lib/views/atom.haml CHANGED
@@ -1,13 +1,13 @@
1
1
  !!! XML
2
2
  %feed{:xmlns => 'http://www.w3.org/2005/Atom'}
3
- %title= settings.blog_title
3
+ %title= config.blog_title
4
4
  %link{:href => url_for('/', :absolute => true)}
5
5
  %id= base_url
6
6
  - if @posts.any?
7
7
  %updated= @posts.first.published_at
8
8
 
9
9
  %author
10
- %name= settings.author_name
10
+ %name= config.author_name
11
11
 
12
12
  - @posts.each do |post|
13
13
  %entry
@@ -17,7 +17,7 @@
17
17
  %published= post.published_at
18
18
  %updated= post.published_at
19
19
  %author
20
- %name= settings.author_name
20
+ %name= config.author_name
21
21
  %content{:type => 'html'}
22
22
  :cdata
23
23
  #{post.to_html}
data/lib/views/index.haml CHANGED
@@ -5,7 +5,7 @@
5
5
  - else
6
6
  #welcome
7
7
  :redcarpet
8
- **Congratulations on setting up your new SchnitzelPress blog!**
8
+ **Congratulations on setting up your new Schnitzelpress blog!**
9
9
  A world of hackery and adventure awaits.
10
10
  <span class="heart">♥</span>
11
11
 
@@ -1,28 +1,32 @@
1
1
  !!! 5
2
2
  %html
3
3
  %head
4
- %title= [@page_title, settings.blog_title].compact.join(" | ")
4
+ %title= [@page_title, config.blog_title].compact.join(" | ")
5
5
  %meta{ :"http-equiv" => "content-type", :content => "text/html; charset=UTF-8" }
6
6
  %meta{ :name => "viewport", :content => "width=device-width, initial-scale=1.0" }
7
- %link{ :href => '/blog.css', :media => "screen", :rel => "stylesheet", :type => "text/css" }
8
- %link{ :href => settings.feed_url, :title => "Subscribe via Atom Feed", :rel => 'alternate', :type => 'application/atom+xml' }
9
- %script{ :type => 'text/javascript', :src => '//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js' }
10
- %script{ :type => 'text/javascript', :src => '//browserid.org/include.js' }
11
- %script{ :type => 'text/javascript', :src => '/js/schnitzelpress.js' }
7
+ %link{ :href => "/assets/schnitzelpress.#{ASSET_TIMESTAMP}.css", :media => "screen", :rel => "stylesheet", :type => "text/css" }
8
+ %link{ :href => config.blog_feed_url, :title => "Subscribe via Atom Feed", :rel => 'alternate', :type => 'application/atom+xml' }
12
9
  %body
13
10
  .container
14
11
  %header
15
12
  .site-title
16
- %a{:href => '/'}= settings.blog_title
13
+ %a{:href => '/'}= h config.blog_title
17
14
  - if @show_description
18
- ~ markdown settings.blog_description
15
+ ~ markdown config.blog_description
16
+
17
+ #actions.admin_only
18
+ = yield_content :actions
19
+ %a{:href => '/admin/'} #{icon 'cog'} Go To Admin
20
+ %a{:href => '/logout'} #{icon 'signout'} Logout
19
21
 
20
22
  = yield
21
23
 
22
24
  %footer
23
- ~ markdown settings.footer
25
+ ~ markdown config.blog_footer
24
26
 
25
- - if production? && settings.google_analytics_id.present?
27
+ - if production? && config.google_analytics_id.present?
26
28
  = partial 'google_analytics'
27
- - if production? && settings.gauges_id.present?
29
+ - if production? && config.gauges_id.present?
28
30
  = partial 'gauges'
31
+
32
+ %script{ :type => 'text/javascript', :src => "/assets/schnitzelpress.#{ASSET_TIMESTAMP}.js" }
data/lib/views/login.haml CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  %p
8
8
  %a.button#browser_id{:href => '/auth/browser_id'} Log in with BrowserID
9
+ - if Schnitzelpress.env.development?
10
+ %a.green.button{:href => '/auth/developer'} Developer Login
9
11
 
10
12
  %form{:method => 'post', :action => '/auth/browser_id/callback', :noValidate => 'noValidate'}
11
13
  %input{:type => 'hidden', :name => 'assertion'}
14
+
15
+ %script{ :type => 'text/javascript', :src => '//browserid.org/include.js' }
@@ -1,11 +1,11 @@
1
1
  - if posts.any?
2
2
  %h2= title
3
- %ul.admin-post-list
3
+ .admin-post-list
4
4
  - posts.each do |post|
5
- %ul.admin
6
- %li
7
- %a.button{:href => "/admin/edit/#{post.id}"} edit
8
- %li
9
- %a.button{:href => url_for(post)} view
10
- %li
11
- = h (post.title || ("%s..." % post.body.first(50)))
5
+ .row
6
+ .ten.columns
7
+ %a{:href => "/admin/edit/#{post.id}"}= h (post.title || ("%s..." % post.body.first(50)))
8
+ .two.columns.icons
9
+ %a{:href => url_for(post)}= icon 'eye-open'
10
+ %a{:href => "/admin/edit/#{post.id}"}= icon 'edit'
11
+ = link_to_delete_post icon('trash'), post
@@ -2,7 +2,7 @@
2
2
 
3
3
  :javascript
4
4
  /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
5
- var disqus_shortname = '#{settings.disqus_name}';
5
+ var disqus_shortname = '#{config.disqus_id}';
6
6
  var disqus_developer = #{production? ? 0 : 1};
7
7
  var disqus_identifier = '#{disqus_identifier}';
8
8
 
@@ -5,7 +5,7 @@
5
5
  t.type = 'text/javascript';
6
6
  t.async = true;
7
7
  t.id = 'gauges-tracker';
8
- t.setAttribute('data-site-id', '#{settings.gauges_id}');
8
+ t.setAttribute('data-site-id', '#{config.gauges_id}');
9
9
  t.src = '//secure.gaug.es/track.js';
10
10
  var s = document.getElementsByTagName('script')[0];
11
11
  s.parentNode.insertBefore(t, s);
@@ -1,6 +1,6 @@
1
1
  :javascript
2
2
  var _gaq = _gaq || [];
3
- _gaq.push(['_setAccount', '#{settings.google_analytics_id}']);
3
+ _gaq.push(['_setAccount', '#{config.google_analytics_id}']);
4
4
  _gaq.push(['_trackPageview']);
5
5
 
6
6
  (function() {
@@ -4,13 +4,13 @@
4
4
  - show_body ||= complete || !show_summary
5
5
  - show_read_more ||= !complete && post.summary.present?
6
6
  - show_permalink ||= true
7
- - show_twitter ||= complete && post.post? && settings.twitter_id.present?
7
+ - show_twitter ||= complete && post.post? && config.twitter_id.present?
8
8
 
9
9
  %article.post{:class => [post.status, post.link_post? ? 'link' : 'article']}
10
10
  %header
11
11
  - if show_title
12
12
  %h1
13
- %a{:href => post.link || post.to_url}= h post.title
13
+ %a.instapaper_title{:href => post.link || post.to_url}= h post.title
14
14
  - if post.link_post?
15
15
  %span.link-arrow ➝
16
16
 
@@ -19,11 +19,12 @@
19
19
  ~ markdown post.summary
20
20
  - if show_read_more
21
21
  %p
22
- %a{:href => url_for(post)}= post.read_more.presence || settings.read_more
22
+ %a{:href => url_for(post)}= post.read_more.presence || "Read Complete Article"
23
23
 
24
24
 
25
25
  - if show_body
26
- ~ post.to_html
26
+ .instapaper_body
27
+ ~ post.to_html
27
28
 
28
29
  %footer
29
30
  - if show_permalink
@@ -33,6 +34,6 @@
33
34
  - if show_twitter
34
35
  .social_media_buttons
35
36
  - if show_twitter
36
- %a{:href => "https://twitter.com/share", :class => "twitter-share-button", :data => {:via => settings.twitter_id}}
37
+ %a{:href => "https://twitter.com/share", :class => "twitter-share-button", :data => {:via => config.twitter_id}}
37
38
  :javascript
38
39
  !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
@@ -17,4 +17,6 @@
17
17
  = form_field @post, :disqus
18
18
 
19
19
  .buttons
20
- %input{:type => 'submit', :value => @post.new_record? ? 'Create Post' : 'Update Post' }
20
+ %button.green{:type => 'submit'} #{icon 'ok'} #{@post.new_record? ? 'Create Post' : 'Update Post'}
21
+ or
22
+ = link_to_delete_post "delete this post", @post
data/lib/views/post.haml CHANGED
@@ -5,7 +5,7 @@
5
5
  - if @extra_posts
6
6
  %section.extra_posts
7
7
  %h1
8
- %a{href: '/blog'}= @extra_posts.first
8
+ %a{:href => '/blog'}= @extra_posts.first
9
9
  %ul
10
10
  - for post in @extra_posts.second
11
11
  %li
@@ -14,3 +14,6 @@
14
14
  - if show_disqus? && @post.disqus?
15
15
  %section.disqus
16
16
  = partial 'disqus', :disqus_identifier => @post.disqus_identifier
17
+
18
+ - content_for :actions do
19
+ %a{:href => "/admin/edit/%s" % @post.id} #{icon 'edit'} Edit
@@ -8,6 +8,34 @@ body, .container {
8
8
  background-image: url(/img/background.png);
9
9
  }
10
10
 
11
+ .admin_only {
12
+ display: none;
13
+ body.show_admin & { display: block; }
14
+ }
15
+
16
+ /* action panel */
17
+ #actions {
18
+ @include small-type;
19
+ margin: 5px;
20
+
21
+ a {
22
+ display: inline-block;
23
+ padding: 3px 6px;
24
+ background-color: $color-text;
25
+ color: $color-background;
26
+ font-weight: bold;
27
+ text-decoration: none;
28
+ border-radius: 3px;
29
+ @include appear-on-hover;
30
+ }
31
+
32
+ @media only screen and (min-width: 641px) {
33
+ position: fixed;
34
+ top: 0px;
35
+ right: 0px;
36
+ }
37
+ }
38
+
11
39
  /* posts */
12
40
  article.post {
13
41
  header {
@@ -31,8 +59,7 @@ article.post {
31
59
  @include animated;
32
60
 
33
61
  margin: 2em 0;
34
- opacity: 0.3;
35
- &:hover { opacity: 1 }
62
+ @include appear-on-hover;
36
63
  }
37
64
  }
38
65
  }
@@ -52,12 +79,35 @@ ul.admin {
52
79
  }
53
80
  }
54
81
 
55
- ul.admin-post-list {
82
+ div.admin-post-list {
56
83
  padding: 5px 0;
84
+ margin: 1em 0;
57
85
  max-height: 300px;
58
86
  overflow: auto;
59
87
  border-bottom: 1px dotted rgba($color-text, 0.5);
60
88
  border-top: 1px dotted rgba($color-text, 0.5);
89
+
90
+ .icons {
91
+ display: none;
92
+ text-align: right;
93
+ a {
94
+ color: $color-footer;
95
+ &:hover { color: $color-text };
96
+ }
97
+ }
98
+ .row {
99
+ padding: 3px;
100
+ margin-bottom: 0;
101
+ &:hover {
102
+ background-color: rgba($color-text, 0.05);
103
+ .icons {
104
+ display: block;
105
+ }
106
+ }
107
+ }
108
+ a {
109
+ border: none;
110
+ }
61
111
  }
62
112
 
63
113
  // forms
@@ -87,3 +137,17 @@ section.posts #welcome {
87
137
  @include clearfix;
88
138
  }
89
139
  }
140
+
141
+ // font-awesome
142
+ @font-face {
143
+ font-family: 'FontAwesome';
144
+ src: url('/font/fontawesome-webfont.eot');
145
+ src: url('/font/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), url('/font/fontawesome-webfont.woff') format('woff'), url('/font/fontawesome-webfont.ttf') format('truetype'), url('/font/fontawesome-webfont.svgz#FontAwesomeRegular') format('svg'), url('/font/fontawesome-webfont.svg#FontAwesomeRegular') format('svg');
146
+ font-weight: normal;
147
+ font-style: normal;
148
+ }
149
+ .font-awesome {
150
+ font-family: FontAwesome;
151
+ font-weight: normal;
152
+ font-style: normal;
153
+ }
@@ -13,12 +13,14 @@ Gem::Specification.new do |gem|
13
13
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
14
  gem.name = "schnitzelpress"
15
15
  gem.require_paths = ["lib"]
16
- gem.version = SchnitzelPress::VERSION
16
+ gem.version = Schnitzelpress::VERSION
17
17
 
18
18
  # base dependencies
19
19
  gem.add_dependency 'rack', '~> 1.4.1'
20
20
  gem.add_dependency 'rack-contrib', '~> 1.1.0'
21
+ gem.add_dependency 'rack-cache', '~> 1.1.0'
21
22
  gem.add_dependency 'sinatra', '~> 1.3.2'
23
+ gem.add_dependency 'sinatra-contrib', '~> 1.3.1'
22
24
  gem.add_dependency 'activesupport', '~> 3.2.0'
23
25
 
24
26
  # database related
@@ -35,10 +37,11 @@ Gem::Specification.new do |gem|
35
37
  gem.add_dependency 'sass', '~> 3.1.15'
36
38
  gem.add_dependency 'redcarpet', '~> 2.1.0'
37
39
  gem.add_dependency 'coderay', '~> 1.0.5'
38
- gem.add_dependency 'schnitzelstyle', '~> 0.1.0'
40
+ gem.add_dependency 'schnitzelstyle', '~> 0.1.1'
39
41
  gem.add_dependency 'i18n', '~> 0.6.0'
40
42
  gem.add_dependency 'tilt', '~> 1.3.0'
41
43
  gem.add_dependency 'ruby-oembed', '~> 0.8.5'
44
+ gem.add_dependency 'packr', '~> 3.1.1'
42
45
 
43
46
  # CLI related
44
47
  gem.add_dependency 'thor', '~> 0.14.6'
data/spec/app_spec.rb CHANGED
@@ -1,16 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- class TestApp < SchnitzelPress::App
4
- configure do
5
- set :blog_title, "A Test Blog"
6
- end
7
- end
8
-
9
- describe SchnitzelPress::App do
3
+ describe Schnitzelpress::App do
10
4
  include Rack::Test::Methods
11
5
 
12
6
  def app
13
- TestApp
7
+ Schnitzelpress::App
14
8
  end
15
9
 
16
10
  describe 'the home page' do
@@ -23,7 +17,7 @@ describe SchnitzelPress::App do
23
17
  subject { last_response }
24
18
 
25
19
  it { should be_ok }
26
- its(:body) { should have_tag 'title', :text => "A Test Blog" }
20
+ its(:body) { should have_tag 'title', :text => Schnitzelpress::Config.instance.blog_title }
27
21
  its(:body) { should have_tag 'section.posts > article.post.published', :count => 5 }
28
22
  its(:body) { should_not have_tag 'section.posts > article.post.draft' }
29
23
  end
@@ -36,14 +30,14 @@ describe SchnitzelPress::App do
36
30
 
37
31
  describe 'the public feed url' do
38
32
  before do
39
- TestApp.set :feed_url, 'http://feeds.feedburner.com/example_org'
33
+ Schnitzelpress::Config.instance.blog_feed_url = 'http://feeds.feedburner.com/example_org'
40
34
  get '/feed'
41
35
  end
42
36
 
43
37
  subject { last_response }
44
38
  it { should be_redirect }
45
39
  its(:status) { should == 307 }
46
- specify { subject["Location"].should == 'http://feeds.feedburner.com/example_org' }
40
+ specify { subject["Location"].should == 'http://example.org/blog.atom' }
47
41
  end
48
42
 
49
43
  describe 'viewing a single post' do
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Schnitzelpress::Actions::Assets' do
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ Schnitzelpress::App
8
+ end
9
+
10
+ describe '/assets/schnitzelpress.*.js' do
11
+ before do
12
+ Schnitzelpress::JavascriptPacker.should_receive(:pack_javascripts!).and_return('{123}')
13
+ get '/assets/schnitzelpress.123.js'
14
+ end
15
+ subject { last_response }
16
+ it { should be_ok }
17
+ its(:body) { should == '{123}' }
18
+ end
19
+
20
+ describe '/assets/schnitzelpress.*.css' do
21
+ before { get '/assets/schnitzelpress.123.css' }
22
+ subject { last_response }
23
+ it { should be_ok }
24
+ its(:content_type) { should == 'text/css;charset=utf-8' }
25
+ end
26
+ end
data/spec/factories.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  FactoryGirl.define do
2
- factory :post, :class => SchnitzelPress::Post do
2
+ factory :post, :class => Schnitzelpress::Post do
3
3
  title { Faker::Lorem.sentence }
4
4
  body { Faker::Lorem.paragraphs }
5
5
  published_at { Time.now }
data/spec/post_spec.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe SchnitzelPress::Post do
3
+ describe Schnitzelpress::Post do
4
4
  subject do
5
5
  Factory.build(:post)
6
6
  end
@@ -81,7 +81,7 @@ describe SchnitzelPress::Post do
81
81
  it 'should return the latest published posts' do
82
82
  2.times { Factory :draft_post }
83
83
  5.times { Factory :published_post }
84
- SchnitzelPress::Post.latest.size.should == 5
84
+ Schnitzelpress::Post.latest.size.should == 5
85
85
  end
86
86
  end
87
87
 
@@ -91,4 +91,11 @@ describe SchnitzelPress::Post do
91
91
  its(:month) { should == 01 }
92
92
  its(:day) { should == 02 }
93
93
  end
94
+
95
+ context 'to_url' do
96
+ it 'should produce double-digit months and days' do
97
+ @post = Factory.build(:post, :published_at => '2012-1-1 12:00:00', :slug => 'test')
98
+ @post.to_url.should == '/2012/01/01/test/'
99
+ end
100
+ end
94
101
  end