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,168 @@
1
+ require 'tilt'
2
+ require 'coderay'
3
+
4
+ module SchnitzelPress
5
+ class MarkdownRenderer < Redcarpet::Render::HTML
6
+ include Redcarpet::Render::SmartyPants
7
+
8
+ def block_code(code, language)
9
+ CodeRay.highlight(code, language)
10
+ end
11
+ end
12
+
13
+ class Post
14
+ include Mongoid::Document
15
+ store_in :posts
16
+
17
+ # basic data
18
+ field :title, type: String
19
+ field :body, type: String
20
+ field :slugs, type: Array, default: []
21
+
22
+ # optional fields
23
+ field :summary, type: String
24
+ field :link, type: String
25
+ field :read_more, type: String
26
+
27
+ # times & status
28
+ field :published_at, type: DateTime
29
+ field :status, type: Symbol, default: :draft
30
+
31
+ # flags
32
+ field :disqus, type: Boolean, default: false
33
+
34
+ # extra
35
+ field :body_html, type: String
36
+
37
+ validates_presence_of :status, :slug
38
+ validates_inclusion_of :status, in: [:draft, :published]
39
+
40
+ scope :published, where(:status => :published)
41
+ scope :drafts, where(:status => :draft)
42
+ scope :pages, where(:published_at.exists => false)
43
+ scope :posts, where(:published_at.exists => true)
44
+ scope :article_posts, -> { posts.where(:link => nil) }
45
+ scope :link_posts, -> { posts.where(:link.ne => nil) }
46
+ scope :for_year, ->(year) { d = Date.new(year) ; where(published_at: (d.beginning_of_year)..(d.end_of_year)) }
47
+ scope :for_month, ->(year, month) { d = Date.new(year, month) ; where(published_at: (d.beginning_of_month)..(d.end_of_month)) }
48
+ scope :for_day, ->(year, month, day) { d = Date.new(year, month, day) ; where(published_at: (d.beginning_of_day)..(d.end_of_day)) }
49
+ scope :latest, -> { published.posts.desc(:published_at) }
50
+
51
+ before_validation :nil_if_blank
52
+ before_validation :set_defaults
53
+ validate :validate_slug
54
+ before_save :update_body_html
55
+
56
+ def disqus_identifier
57
+ slug
58
+ end
59
+
60
+ def slug
61
+ slugs.try(:last)
62
+ end
63
+
64
+ def previous_slugs
65
+ slugs[0..-2]
66
+ end
67
+
68
+ def published_at=(v)
69
+ v = Chronic.parse(v) if v.is_a?(String)
70
+ super(v)
71
+ end
72
+
73
+ def slug=(v)
74
+ unless v.blank?
75
+ slugs.delete(v)
76
+ slugs << v
77
+ end
78
+ end
79
+
80
+ def set_defaults
81
+ if slug.blank? && title.present?
82
+ self.slug = title.parameterize
83
+ end
84
+ end
85
+
86
+ def validate_slug
87
+ conflicting_posts = Post.where(slugs: slug)
88
+ if published_at.present?
89
+ conflicting_posts = conflicting_posts.for_day(published_at.year, published_at.month, published_at.day)
90
+ end
91
+
92
+ if conflicting_posts.any? && conflicting_posts.first != self
93
+ errors[:slug] = "This slug is already in use by another post."
94
+ end
95
+ end
96
+
97
+ def nil_if_blank
98
+ attributes.keys.each do |attr|
99
+ self[attr].strip! if self[attr].is_a?(String)
100
+ self[attr] = nil if self[attr] == ""
101
+ end
102
+ end
103
+
104
+ def update_body_html
105
+ self.body_html = render
106
+ end
107
+
108
+ def to_html
109
+ if body_html.nil?
110
+ update_body_html
111
+ save
112
+ end
113
+
114
+ body_html
115
+ end
116
+
117
+ def render
118
+ @@markdown ||= Redcarpet::Markdown.new(MarkdownRenderer,
119
+ autolink: true, space_after_headers: true, fenced_code_blocks: true)
120
+
121
+ @@markdown.render(body)
122
+ end
123
+
124
+ def post?
125
+ published_at.present?
126
+ end
127
+
128
+ def page?
129
+ !post?
130
+ end
131
+
132
+ def published?
133
+ status == :published
134
+ end
135
+
136
+ def draft?
137
+ status == :draft
138
+ end
139
+
140
+ def link_post?
141
+ link.present?
142
+ end
143
+
144
+ def article_post?
145
+ link.nil?
146
+ end
147
+
148
+ def year
149
+ published_at.year
150
+ end
151
+
152
+ def month
153
+ published_at.month
154
+ end
155
+
156
+ def day
157
+ published_at.day
158
+ end
159
+
160
+ def to_url
161
+ published_at.present? ? "/#{year}/#{month}/#{day}/#{slug}/" : "/#{slug}/"
162
+ end
163
+
164
+ def disqus?
165
+ disqus && published?
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,23 @@
1
+ require 'schnitzelpress'
2
+
3
+ desc 'Run the SchnitzelPress console'
4
+ task :console do
5
+ require 'irb'
6
+ require 'wirble'
7
+ ARGV.clear
8
+ Wirble.init
9
+ Wirble.colorize
10
+ IRB.start
11
+ end
12
+
13
+ namespace :db do
14
+ desc 'Import Heroku database to local database'
15
+ task :pull do
16
+ system "MONGO_URL=\"#{SchnitzelPress.mongo_uri}\" heroku mongo:pull"
17
+ end
18
+
19
+ desc 'Push local database to Heroku'
20
+ task :push do
21
+ system "MONGO_URL=\"#{SchnitzelPress.mongo_uri}\" heroku mongo:push"
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module SchnitzelPress
2
+ class Static
3
+ def initialize(app, public_dir = './public')
4
+ @file = Rack::File.new(public_dir)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, body = @file.call(env)
10
+ if status > 400
11
+ @app.call(env)
12
+ else
13
+ [status, headers, body]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module SchnitzelPress
2
+ VERSION = "0.0.5"
3
+ end
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ .rbfu-version
3
+ .powenv
4
+ .sass-cache
5
+ tmp
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'shotgun'
5
+ gem 'heroku'
6
+ end
7
+
8
+ # For now, we're going to be using the development versions of
9
+ # schnitzelstyle and schnitzelpress. I told you you were about
10
+ # to live dangerously!
11
+ #
12
+ gem 'schnitzelstyle', git: 'git://github.com/teamschnitzel/schnitzelstyle.git'
13
+ gem 'schnitzelpress', git: 'git://github.com/teamschnitzel/schnitzelpress.git'
14
+
15
+ # If you'd prefer to use the officially released versions,
16
+ # use these instead:
17
+ #
18
+ # gem 'schnitzelstyle', :path => '../schnitzelstyle'
19
+ # gem 'schnitzelpress', :path => '../schnitzelpress'
@@ -0,0 +1,2 @@
1
+ require File.expand_path("../app.rb", __FILE__)
2
+ require 'schnitzelpress/rake'
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler.require
5
+
6
+ SchnitzelPress.mongo_uri =
7
+ ENV['MONGOLAB_URI'] ||
8
+ ENV['MONGOHQ_URL'] ||
9
+ ENV['MONGO_URL'] ||
10
+ 'mongodb://localhost/<%= @name %>' # used for local development
11
+
12
+ class App < SchnitzelPress::App
13
+ configure do
14
+ set :blog_title, "<%= @name %>"
15
+ set :blog_description, "A new blog powered by SchnitzelPress."
16
+ set :author_name, "Your Name"
17
+ set :footer, "powered by [SchnitzelPress](http://schnitzelpress.org)"
18
+ set :administrator, "browser_id:hendrik@mans.de"
19
+
20
+ # The following are optional:
21
+ #
22
+ # set :disqus_name, "..."
23
+ # set :google_analytics_id, "..."
24
+ # set :gauges_id, "..."
25
+ # set :twitter_id, '...'
26
+ # set :read_more, "Read ALL the things"
27
+ end
28
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path("../app.rb", __FILE__)
2
+ run App
File without changes
@@ -0,0 +1,4 @@
1
+ %section
2
+ %h2 400 + 4
3
+ %p
4
+ The document you requested could not be found.
@@ -0,0 +1,12 @@
1
+ %section
2
+ %h2 Administration
3
+ %p
4
+ You're logged in as #{session[:user]}.
5
+ %ul.admin
6
+ %li
7
+ %a.green.button{href: '/admin/new'} Create new Post
8
+ %a.red.button{href: '/logout'} Logout
9
+
10
+ = partial "admin_post_list", posts: @drafts, title: "Drafts"
11
+ = partial "admin_post_list", posts: @posts, title: "Published Posts"
12
+ = partial "admin_post_list", posts: @pages, title: "Pages"
@@ -0,0 +1,3 @@
1
+ %section
2
+ %h2 Edit Post
3
+ = partial 'post_form', post: @post
@@ -0,0 +1,3 @@
1
+ %section
2
+ %h2 New Post
3
+ = partial 'post_form', post: @post
@@ -0,0 +1,23 @@
1
+ !!! XML
2
+ %feed{xmlns: 'http://www.w3.org/2005/Atom'}
3
+ %title= settings.blog_title
4
+ %link{href: url_for('/', absolute: true)}
5
+ %id= base_url
6
+ - if @posts.any?
7
+ %updated= @posts.first.published_at
8
+
9
+ %author
10
+ %name= settings.author_name
11
+
12
+ - @posts.each do |post|
13
+ %entry
14
+ %title= html_escape post.title
15
+ %link{href: url_for(post, absolute: true)}
16
+ %id= url_for(post, absolute: true)
17
+ %published= post.published_at
18
+ %updated= post.published_at
19
+ %author
20
+ %name= settings.author_name
21
+ %content{type: 'html'}
22
+ :cdata
23
+ #{post.to_html}
@@ -0,0 +1 @@
1
+ @import 'schnitzelpress';
@@ -0,0 +1,6 @@
1
+ %section.posts
2
+ - @posts.each do |post|
3
+ = partial post
4
+
5
+ %footer
6
+ %a.button{ href: "/?page=#{params[:page].to_i + 1}"} View Older Posts
@@ -0,0 +1,25 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title= [@page_title, settings.blog_title].compact.join(" | ")
5
+ %meta{ :"http-equiv" => "content-type", content: "text/html; charset=UTF-8" }
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
+ %body
10
+ .container
11
+ %header
12
+ .site-title
13
+ %a{href: '/'}= settings.blog_title
14
+ - if @show_description
15
+ ~ markdown settings.blog_description
16
+
17
+ = yield
18
+
19
+ %footer
20
+ ~ markdown settings.footer
21
+
22
+ - if production? && settings.google_analytics_id.present?
23
+ = partial 'google_analytics'
24
+ - if production? && settings.gauges_id.present?
25
+ = partial 'gauges'
@@ -0,0 +1,11 @@
1
+ - if posts.any?
2
+ %h2= title
3
+ %ul.admin-post-list
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
+ = post.title || ("%s..." % html_escape(post.body.first(50)))
@@ -0,0 +1,17 @@
1
+ #disqus_thread
2
+
3
+ :javascript
4
+ /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
5
+ var disqus_shortname = '#{settings.disqus_name}';
6
+ var disqus_developer = #{production? ? 0 : 1};
7
+ var disqus_identifier = '#{disqus_identifier}';
8
+
9
+ /* * * DON'T EDIT BELOW THIS LINE * * */
10
+ (function() {
11
+ var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
12
+ dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
13
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
14
+ })();
15
+
16
+ %noscript
17
+ Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a>
@@ -0,0 +1,29 @@
1
+ - field_options = options.slice(:id, :name, :placeholder)
2
+
3
+ .input{ class: [options[:type], options[:class]] }
4
+ %label{ for: options[:id] } #{options[:label]}:
5
+ - case options[:type].to_sym
6
+ - when :textarea
7
+ %textarea{ id: options[:id], name: options[:name], rows: 20, placeholder: options[:placeholder] }= html_escape(options[:value])
8
+
9
+ - when :radio
10
+ - options[:options].each do |o|
11
+ .option
12
+ %input{ field_options.merge(type: 'radio', value: o, checked: options[:value] == o) }= o
13
+
14
+ - when :dropdown
15
+ %select{ field_options }
16
+ - options[:options].each do |val, text|
17
+ %option{value: val, selected: options[:value] == val}= text
18
+
19
+ - when :datetime
20
+ %input{ field_options.merge(value: options[:value].to_formatted_s(:db)) }
21
+
22
+ - else # normal inputs
23
+ %input{ field_options.merge(value: options[:value]) }
24
+
25
+ - if options[:errors]
26
+ .error= options[:errors].join(", ")
27
+
28
+ - if options[:hint]
29
+ .hint= options[:hint]
@@ -0,0 +1,12 @@
1
+ :javascript
2
+ var _gauges = _gauges || [];
3
+ (function() {
4
+ var t = document.createElement('script');
5
+ t.type = 'text/javascript';
6
+ t.async = true;
7
+ t.id = 'gauges-tracker';
8
+ t.setAttribute('data-site-id', '#{settings.gauges_id}');
9
+ t.src = '//secure.gaug.es/track.js';
10
+ var s = document.getElementsByTagName('script')[0];
11
+ s.parentNode.insertBefore(t, s);
12
+ })();