schnitzelpress 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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