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.
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/.travis.yml +9 -0
- data/.watchr +15 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +5 -0
- data/Rakefile +7 -0
- data/bin/schnitzelpress +5 -0
- data/lib/public/.gitkeep +0 -0
- data/lib/public/favicon.ico +0 -0
- data/lib/schnitzelpress.rb +30 -0
- data/lib/schnitzelpress/actions/admin.rb +49 -0
- data/lib/schnitzelpress/actions/auth.rb +29 -0
- data/lib/schnitzelpress/actions/blog.rb +70 -0
- data/lib/schnitzelpress/app.rb +45 -0
- data/lib/schnitzelpress/cli.rb +28 -0
- data/lib/schnitzelpress/helpers.rb +72 -0
- data/lib/schnitzelpress/post.rb +168 -0
- data/lib/schnitzelpress/rake.rb +23 -0
- data/lib/schnitzelpress/static.rb +17 -0
- data/lib/schnitzelpress/version.rb +3 -0
- data/lib/templates/new_blog/.gitignore +5 -0
- data/lib/templates/new_blog/Gemfile +19 -0
- data/lib/templates/new_blog/Rakefile +2 -0
- data/lib/templates/new_blog/app.rb.tt +28 -0
- data/lib/templates/new_blog/config.ru.tt +2 -0
- data/lib/templates/new_blog/public/.gitkeep +0 -0
- data/lib/views/404.haml +4 -0
- data/lib/views/admin/admin.haml +12 -0
- data/lib/views/admin/edit.haml +3 -0
- data/lib/views/admin/new.haml +3 -0
- data/lib/views/atom.haml +23 -0
- data/lib/views/blog.scss +1 -0
- data/lib/views/index.haml +6 -0
- data/lib/views/layout.haml +25 -0
- data/lib/views/partials/_admin_post_list.haml +11 -0
- data/lib/views/partials/_disqus.haml +17 -0
- data/lib/views/partials/_form_field.haml +29 -0
- data/lib/views/partials/_gauges.haml +12 -0
- data/lib/views/partials/_google_analytics.haml +10 -0
- data/lib/views/partials/_post.haml +44 -0
- data/lib/views/partials/_post_form.haml +18 -0
- data/lib/views/post.haml +7 -0
- data/lib/views/schnitzelpress.scss +106 -0
- data/schnitzelpress.gemspec +60 -0
- data/spec/app_spec.rb +56 -0
- data/spec/factories.rb +28 -0
- data/spec/post_spec.rb +78 -0
- data/spec/spec_helper.rb +36 -0
- 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,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,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
|
File without changes
|
data/lib/views/404.haml
ADDED
@@ -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"
|
data/lib/views/atom.haml
ADDED
@@ -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}
|
data/lib/views/blog.scss
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
@import 'schnitzelpress';
|
@@ -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,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
|
+
})();
|