schnitzelpress 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +21 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +9 -0
  4. data/.watchr +15 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +5 -0
  8. data/Rakefile +7 -0
  9. data/bin/schnitzelpress +5 -0
  10. data/lib/public/.gitkeep +0 -0
  11. data/lib/public/favicon.ico +0 -0
  12. data/lib/schnitzelpress.rb +30 -0
  13. data/lib/schnitzelpress/actions/admin.rb +49 -0
  14. data/lib/schnitzelpress/actions/auth.rb +29 -0
  15. data/lib/schnitzelpress/actions/blog.rb +70 -0
  16. data/lib/schnitzelpress/app.rb +45 -0
  17. data/lib/schnitzelpress/cli.rb +28 -0
  18. data/lib/schnitzelpress/helpers.rb +72 -0
  19. data/lib/schnitzelpress/post.rb +168 -0
  20. data/lib/schnitzelpress/rake.rb +23 -0
  21. data/lib/schnitzelpress/static.rb +17 -0
  22. data/lib/schnitzelpress/version.rb +3 -0
  23. data/lib/templates/new_blog/.gitignore +5 -0
  24. data/lib/templates/new_blog/Gemfile +19 -0
  25. data/lib/templates/new_blog/Rakefile +2 -0
  26. data/lib/templates/new_blog/app.rb.tt +28 -0
  27. data/lib/templates/new_blog/config.ru.tt +2 -0
  28. data/lib/templates/new_blog/public/.gitkeep +0 -0
  29. data/lib/views/404.haml +4 -0
  30. data/lib/views/admin/admin.haml +12 -0
  31. data/lib/views/admin/edit.haml +3 -0
  32. data/lib/views/admin/new.haml +3 -0
  33. data/lib/views/atom.haml +23 -0
  34. data/lib/views/blog.scss +1 -0
  35. data/lib/views/index.haml +6 -0
  36. data/lib/views/layout.haml +25 -0
  37. data/lib/views/partials/_admin_post_list.haml +11 -0
  38. data/lib/views/partials/_disqus.haml +17 -0
  39. data/lib/views/partials/_form_field.haml +29 -0
  40. data/lib/views/partials/_gauges.haml +12 -0
  41. data/lib/views/partials/_google_analytics.haml +10 -0
  42. data/lib/views/partials/_post.haml +44 -0
  43. data/lib/views/partials/_post_form.haml +18 -0
  44. data/lib/views/post.haml +7 -0
  45. data/lib/views/schnitzelpress.scss +106 -0
  46. data/schnitzelpress.gemspec +60 -0
  47. data/spec/app_spec.rb +56 -0
  48. data/spec/factories.rb +28 -0
  49. data/spec/post_spec.rb +78 -0
  50. data/spec/spec_helper.rb +36 -0
  51. metadata +426 -0
@@ -0,0 +1,10 @@
1
+ :javascript
2
+ var _gaq = _gaq || [];
3
+ _gaq.push(['_setAccount', '#{settings.google_analytics_id}']);
4
+ _gaq.push(['_trackPageview']);
5
+
6
+ (function() {
7
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
8
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
9
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
10
+ })();
@@ -0,0 +1,44 @@
1
+ - complete ||= false
2
+ - show_title ||= post.article_post? && post.title.present?
3
+ - show_link ||= post.link_post?
4
+ - show_summary ||= post.summary.present?
5
+ - show_body ||= complete || !show_summary
6
+ - show_read_more ||= !complete && post.summary.present?
7
+ - show_permalink ||= admin_logged_in? || (post.post? && !show_read_more)
8
+ - show_twitter ||= complete && post.post? && settings.twitter_id.present?
9
+
10
+ %article.post{class: [post.status, post.link_post? ? 'link' : 'article']}
11
+ %header
12
+ - if show_title
13
+ %h1
14
+ %a{href: post.to_url}= post.title
15
+ - if show_link
16
+ %h1
17
+ %a{href: post.link}= post.title
18
+
19
+
20
+ - if show_summary
21
+ .summary
22
+ ~ markdown post.summary
23
+ - if show_read_more
24
+ %p
25
+ %a{href: url_for(post)}= post.read_more.presence || settings.read_more
26
+
27
+
28
+ - if show_body
29
+ ~ post.to_html
30
+
31
+ %footer
32
+ - if show_permalink
33
+ %p.permalink
34
+ %a{href: url_for(post)}= post.published_at.try(:to_date) || "∞"
35
+ - if admin_logged_in?
36
+ ·
37
+ %a{href: "/admin/edit/#{post.id}"} edit
38
+
39
+ - if show_twitter
40
+ .social_media_buttons
41
+ - if show_twitter
42
+ %a{ href: "https://twitter.com/share", class: "twitter-share-button", data: { via: settings.twitter_id } }
43
+ :javascript
44
+ !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");
@@ -0,0 +1,18 @@
1
+ %form.post{ action: @post.new_record? ? '/admin/new' : "/admin/edit/#{@post.id}", method: 'post' }
2
+ = form_field @post, :title, placeholder: "Title of your post."
3
+ = form_field @post, :link, placeholder: "Optional link to external URL."
4
+ = form_field @post, :body, type: :textarea, placeholder: "Your post's body of text. Markdown formatting available."
5
+ = form_field @post, :summary, placeholder: "An optional summary of your post. Markdown formatting available."
6
+ = form_field @post, :read_more, label: "'Read More' Link Text", placeholder: "When supplying a summary text, this will be the link to the full post."
7
+
8
+ .row
9
+ .four.columns
10
+ = form_field @post, :slug, label: 'URL Slug', placeholder: "Your post's URL slug.", hint: @post.previous_slugs.any? ? "Previous slugs: #{@post.previous_slugs.join ', '}" : nil
11
+ .four.columns
12
+ = form_field @post, :published_at, placeholder: 'Try "now", "in 3 days", ...'
13
+ .four.columns
14
+ = form_field @post, :status, type: :dropdown, options: [[:draft, "Draft"], [:published, "Published"]]
15
+
16
+ .row
17
+ .buttons.twelve.columns
18
+ %input{ type: 'submit', value: @post.new_record? ? 'Create Post' : 'Update Post' }
@@ -0,0 +1,7 @@
1
+ - set_page_title @post.title.presence
2
+
3
+ = partial @post, complete: true
4
+
5
+ - if show_disqus? && @post.disqus?
6
+ %section.disqus
7
+ = partial 'disqus', :disqus_identifier => @post.disqus_identifier
@@ -0,0 +1,106 @@
1
+ // load stuff!
2
+ @import 'schnitzelstyle/complete';
3
+
4
+ /* misc crap */
5
+ .social_media_buttons {
6
+ margin: 1em 0;
7
+ }
8
+
9
+ @mixin smallish {
10
+ font: $font-footer;
11
+ font-size: 80%;
12
+ color: lighten($color-text, 30%);
13
+ a {
14
+ color: lighten($color-text, 30%);
15
+ border: none;
16
+ }
17
+ a:hover {
18
+ text-decoration: underline;
19
+ }
20
+ }
21
+
22
+ /* posts */
23
+ article.post {
24
+ &.draft header h1 { background-color: #ccc; }
25
+ &.link header {
26
+ h1 {
27
+ font-size: 100%;
28
+ background: none;
29
+ text-transform: none;
30
+ padding: none;
31
+ }
32
+ font: $font-header;
33
+ font-weight: bold;
34
+ background-color: darken($color-page-background, 5%);
35
+ display: inline-block;
36
+ line-height: 150%;
37
+ border-bottom: 2px solid $color-link-underline;
38
+ color: darken($color-page-background, 30%);
39
+ a {
40
+ border: none;
41
+ }
42
+ }
43
+
44
+ div.summary {
45
+ margin-bottom: 2em;
46
+
47
+ p {
48
+ font: $font-header;
49
+ font-size: 125%;
50
+ line-height: 150%;
51
+ color: #000;
52
+
53
+ @media only screen and (max-width: 640px) {
54
+ line-height: 130%;
55
+
56
+ section.posts & {
57
+ font-size: 100%;
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ footer {
64
+ margin-top: 0.5em;
65
+
66
+ p.permalink {
67
+ @include smallish;
68
+ }
69
+ }
70
+ }
71
+
72
+ /* admin post lists */
73
+ ul.admin-post-list {
74
+ max-height: 300px;
75
+ padding-bottom: 5px;
76
+ overflow: auto;
77
+ }
78
+
79
+ /* admin links */
80
+ ul.admin {
81
+ @include smallish;
82
+ list-style: none;
83
+
84
+ li {
85
+ display: inline;
86
+ margin-right: 0.5em;
87
+ }
88
+ }
89
+
90
+ /* forms */
91
+ .input.post_published_at input { width: 180px; }
92
+ .input.post_slug input { width: 180px }
93
+
94
+ /* disqus */
95
+ #dsq-content {
96
+ a {
97
+ border: 0;
98
+ }
99
+ #dsq-reply {
100
+ margin-bottom: 2em;
101
+ }
102
+ h3 {
103
+ margin-top: 1em !important;
104
+ @include clearfix;
105
+ }
106
+ }
@@ -0,0 +1,60 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/schnitzelpress/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Hendrik Mans"]
6
+ gem.email = ["hendrik@mans.de"]
7
+ gem.description = %q{A simple blog engine for sane hackers.}
8
+ gem.summary = %q{A simple blog engine for sane hackers.}
9
+ gem.homepage = "http://www.schnitzelpress.org"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "schnitzelpress"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SchnitzelPress::VERSION
17
+
18
+ # base dependencies
19
+ gem.add_dependency 'rack', '~> 1.4.0'
20
+ gem.add_dependency 'sinatra', '~> 1.3.2'
21
+ gem.add_dependency 'activesupport', '~> 3.2.0'
22
+ gem.add_dependency 'rack-cache'
23
+
24
+ # database related
25
+ gem.add_dependency 'mongoid', '~> 2.4'
26
+ gem.add_dependency 'bson_ext', '~> 1.5'
27
+
28
+ # authentication
29
+ gem.add_dependency 'omniauth'
30
+ gem.add_dependency 'omniauth-browserid'
31
+
32
+ # frontend/views/assets related
33
+ gem.add_dependency 'haml'
34
+ gem.add_dependency 'sass'
35
+ gem.add_dependency 'redcarpet'
36
+ gem.add_dependency 'coderay'
37
+ gem.add_dependency 'schnitzelstyle', '~> 0.0.4'
38
+ gem.add_dependency 'i18n'
39
+ gem.add_dependency 'tilt', '~> 1.3.0'
40
+
41
+ # CLI related
42
+ gem.add_dependency 'thor'
43
+ gem.add_dependency 'rake'
44
+ gem.add_dependency 'wirble'
45
+
46
+ # misc
47
+ gem.add_dependency 'chronic'
48
+
49
+ # development dependencies
50
+ gem.add_development_dependency 'rspec', '>= 2.8.0'
51
+ gem.add_development_dependency 'rspec-html-matchers'
52
+ gem.add_development_dependency 'database_cleaner'
53
+ gem.add_development_dependency 'factory_girl'
54
+ gem.add_development_dependency 'ffaker'
55
+ gem.add_development_dependency 'timecop'
56
+ gem.add_development_dependency 'shotgun'
57
+ gem.add_development_dependency 'rack-test'
58
+ gem.add_development_dependency 'watchr'
59
+ gem.add_development_dependency 'awesome_print'
60
+ end
data/spec/app_spec.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
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
10
+ include Rack::Test::Methods
11
+
12
+ def app
13
+ TestApp
14
+ end
15
+
16
+ describe 'the home page' do
17
+ before do
18
+ 2.times { Factory(:draft_post) }
19
+ 5.times { Factory(:published_post) }
20
+ get '/'
21
+ end
22
+
23
+ subject { last_response }
24
+
25
+ it { should be_ok }
26
+ its(:body) { should have_tag 'title', text: "A Test Blog" }
27
+ its(:body) { should have_tag 'section.posts > article.post.published', count: 5 }
28
+ its(:body) { should_not have_tag 'section.posts > article.post.draft' }
29
+ end
30
+
31
+ describe 'the public feed url' do
32
+ before do
33
+ TestApp.set :feed_url, 'http://feeds.feedburner.com/example_org'
34
+ get '/feed'
35
+ end
36
+
37
+ subject { last_response }
38
+ it { should be_redirect }
39
+ its(:status) { should == 302 }
40
+ specify { subject["Location"].should == 'http://feeds.feedburner.com/example_org' }
41
+ end
42
+
43
+ describe 'viewing a single post' do
44
+ context 'when the post has multiple slugs' do
45
+ before do
46
+ @post = Factory(:post, slugs: ['ancient-slug', 'old-slug', 'current-slug'])
47
+ end
48
+
49
+ it 'should enforce the canonical URL' do
50
+ get "/#{@post.year}/#{@post.month}/#{@post.day}/ancient-slug/"
51
+ last_response.should be_redirect
52
+ last_response["Location"].should == "http://example.org/#{@post.year}/#{@post.month}/#{@post.day}/current-slug/"
53
+ end
54
+ end
55
+ end
56
+ end
data/spec/factories.rb ADDED
@@ -0,0 +1,28 @@
1
+ FactoryGirl.define do
2
+ factory :post, class: SchnitzelPress::Post do
3
+ title { Faker::Lorem.sentence }
4
+ body { Faker::Lorem.paragraphs }
5
+ published_at { rand(1.year).minutes.ago }
6
+ end
7
+
8
+ factory :published_post, parent: :post do
9
+ status :published
10
+ end
11
+
12
+ factory :draft_post, parent: :post do
13
+ status :draft
14
+ end
15
+
16
+ factory :page, parent: :post do
17
+ published_at nil
18
+ end
19
+
20
+ factory :published_page, parent: :page do
21
+ status :published
22
+ published_at nil
23
+ end
24
+
25
+ factory :draft_page, parent: :page do
26
+ status :draft
27
+ end
28
+ end
data/spec/post_spec.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe SchnitzelPress::Post do
4
+ subject do
5
+ Factory.build(:post)
6
+ end
7
+
8
+ context 'slugs' do
9
+ before do
10
+ subject.slugs = ['some-slug', 'another-slug']
11
+ subject.slug = 'a-new-slug'
12
+ end
13
+
14
+ its(:slugs) { should == ['some-slug', 'another-slug', 'a-new-slug'] }
15
+ its(:slug) { should == 'a-new-slug'}
16
+ end
17
+
18
+ context 'saving' do
19
+ context "when no slug is set" do
20
+ before do
21
+ subject.title = "Team Schnitzel is AWESOME!"
22
+ subject.slug = nil
23
+ end
24
+
25
+ it "should set its slug to a sluggified version of its title" do
26
+ expect { subject.save }.to change(subject, :slug).from(nil).to('team-schnitzel-is-awesome')
27
+ end
28
+ end
29
+
30
+ context "when another post on the same day is already using the same slug" do
31
+ before do
32
+ @other_post = Factory(:published_post, slugs: ["amazing-slug"])
33
+ subject.published_at = @other_post.published_at
34
+ subject.slug = "amazing-slug"
35
+ end
36
+
37
+ it { should_not be_valid }
38
+ end
39
+
40
+ context "when another page is using the same slug" do
41
+ subject { Factory.build(:draft_page) }
42
+
43
+ before do
44
+ @other_page = Factory(:published_page, slugs: ["amazing-slug"])
45
+ subject.slug = "amazing-slug"
46
+ end
47
+
48
+ it { should_not be_valid }
49
+ end
50
+
51
+ it "should store blank attributes as nil" do
52
+ subject.link = ""
53
+ expect { subject.save }.to change(subject, :link).from("").to(nil)
54
+ end
55
+
56
+ it "should remove leading and trailing spaces from string attributes" do
57
+ subject.link = " moo "
58
+ subject.link.should == " moo "
59
+ subject.save
60
+ subject.link.should == "moo"
61
+ end
62
+ end
63
+
64
+ describe '.latest' do
65
+ it 'should return the latest published posts' do
66
+ 2.times { Factory :draft_post }
67
+ 5.times { Factory :published_post }
68
+ SchnitzelPress::Post.latest.size.should == 5
69
+ end
70
+ end
71
+
72
+ context 'date methods' do
73
+ before { subject.published_at = "2012-01-02 12:23:13" }
74
+ its(:year) { should == 2012 }
75
+ its(:month) { should == 01 }
76
+ its(:day) { should == 02 }
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ SPEC_DIR = File.dirname(__FILE__)
2
+ lib_path = File.expand_path("#{SPEC_DIR}/../lib")
3
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
4
+
5
+ require 'rubygems'
6
+ require 'bundler/setup'
7
+
8
+ require 'schnitzelpress'
9
+
10
+ SchnitzelPress.mongo_uri = 'mongodb://localhost/_schreihals_test'
11
+
12
+ require 'awesome_print'
13
+ require 'rack/test'
14
+ require 'rspec-html-matchers'
15
+ require 'database_cleaner'
16
+ require 'ffaker'
17
+ require 'factory_girl'
18
+ require File.expand_path("../factories.rb", __FILE__)
19
+ require 'timecop'
20
+ Timecop.freeze
21
+
22
+ set :environment, :test
23
+
24
+ RSpec.configure do |config|
25
+ config.before(:suite) do
26
+ DatabaseCleaner[:mongoid].strategy = :truncation
27
+ end
28
+
29
+ config.before(:each) do
30
+ DatabaseCleaner[:mongoid].start
31
+ end
32
+
33
+ config.after(:each) do
34
+ DatabaseCleaner[:mongoid].clean
35
+ end
36
+ end