schreihals 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +1 -1
  2. data/.rspec +1 -0
  3. data/.travis.yml +9 -0
  4. data/.watchr +6 -5
  5. data/LICENSE +2 -2
  6. data/README.md +2 -54
  7. data/Rakefile +4 -9
  8. data/lib/schreihals.rb +10 -9
  9. data/lib/schreihals/actions/admin.rb +49 -0
  10. data/lib/schreihals/actions/auth.rb +29 -0
  11. data/lib/schreihals/actions/blog.rb +68 -0
  12. data/lib/schreihals/app.rb +15 -43
  13. data/lib/schreihals/cli.rb +0 -14
  14. data/lib/schreihals/helpers.rb +38 -5
  15. data/lib/schreihals/post.rb +126 -44
  16. data/lib/schreihals/rake.rb +21 -0
  17. data/lib/schreihals/version.rb +1 -1
  18. data/lib/templates/new_blog/Gemfile +17 -1
  19. data/lib/templates/new_blog/Rakefile +2 -0
  20. data/lib/templates/new_blog/app.rb.tt +28 -0
  21. data/lib/templates/new_blog/config.ru.tt +2 -14
  22. data/{example/public/media → lib/templates/new_blog/public}/.gitkeep +0 -0
  23. data/lib/views/admin/admin.haml +12 -0
  24. data/lib/views/admin/edit.haml +3 -0
  25. data/lib/views/admin/new.haml +3 -0
  26. data/lib/views/atom.haml +12 -10
  27. data/lib/views/index.haml +3 -0
  28. data/lib/views/layout.haml +4 -3
  29. data/lib/views/partials/_admin_post_list.haml +11 -0
  30. data/lib/views/partials/_form_field.haml +27 -0
  31. data/lib/views/partials/_post.haml +16 -12
  32. data/lib/views/partials/_post_form.haml +12 -0
  33. data/lib/views/schreihals.scss +61 -25
  34. data/schreihals.gemspec +27 -11
  35. data/spec/app_spec.rb +56 -0
  36. data/spec/factories.rb +15 -0
  37. data/spec/post_spec.rb +60 -0
  38. data/spec/spec_helper.rb +35 -0
  39. data/{test → test.old}/app_test.rb +0 -0
  40. data/{test → test.old}/files/simple_document.md +0 -0
  41. data/test.old/post_test.rb +62 -0
  42. data/{test → test.old}/posts/2011-12-23-first-post.md +0 -0
  43. data/{test → test.old}/posts/2011-12-24-second-post.md +0 -0
  44. data/{test → test.old}/posts/static-page.md +0 -0
  45. data/{test → test.old}/test_helper.rb +0 -0
  46. metadata +158 -87
  47. data/example/Gemfile +0 -5
  48. data/example/config.ru +0 -8
  49. data/example/posts/2010-06-17-pythton-is-great.md +0 -9
  50. data/example/posts/2011-12-22-ruby-is-great.md +0 -16
  51. data/lib/schreihals/actions.rb +0 -45
  52. data/lib/schreihals/document.rb +0 -52
  53. data/lib/templates/first-post.md.tt +0 -1
  54. data/lib/templates/new-post.md.tt +0 -13
  55. data/lib/templates/new_blog/posts/.gitkeep +0 -0
  56. data/lib/templates/new_blog/public/media/.gitkeep +0 -0
  57. data/test/document_test.rb +0 -41
@@ -1,74 +1,156 @@
1
1
  require 'tilt'
2
+ require 'coderay'
2
3
 
3
4
  module Schreihals
4
- class Post < Document
5
- def initialize(*args)
6
- super
7
- self.attributes = {
8
- 'disqus' => true,
9
- 'status' => 'published',
10
- 'summary' => nil,
11
- 'link' => nil,
12
- 'read_more' => nil,
13
- 'date' => nil,
14
- 'title' => nil,
15
- 'slug' => nil,
16
- 'disqus_identifier' => file_name
17
- }.merge(attributes)
18
-
19
- # extract date and slug from file name, if possible
20
- if file_name_without_extension =~ /^(\d{4}-\d{1,2}-\d{1,2})-?(.+)$/
21
- attributes['date'] ||= Date.parse($1)
22
- attributes['slug'] ||= $2
23
- else
24
- attributes['slug'] ||= file_name_without_extension
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
+
16
+ # basic data
17
+ field :title, type: String
18
+ field :body, type: String
19
+ field :slugs, type: Array, default: []
20
+
21
+ # optional fields
22
+ field :summary, type: String
23
+ field :link, type: String
24
+ field :read_more, type: String
25
+
26
+ # times & status
27
+ field :published_at, type: DateTime
28
+ field :status, type: Symbol, default: :draft
29
+
30
+ # flags
31
+ field :disqus, type: Boolean, default: false
32
+
33
+ # extra
34
+ field :body_html, type: String
35
+
36
+ validates_presence_of :title, :body, :status, :slug
37
+ validates_inclusion_of :status, in: [:draft, :published]
38
+
39
+ scope :published, where(:status => :published)
40
+ scope :drafts, where(:status => :draft)
41
+ scope :pages, where(:published_at.exists => false)
42
+ scope :posts, where(:published_at.exists => true)
43
+ scope :article_posts, -> { posts.where(:link => nil) }
44
+ scope :link_posts, -> { posts.where(:link.ne => nil) }
45
+ scope :for_year, ->(year) { d = Date.new(year) ; where(published_at: (d.beginning_of_year)..(d.end_of_year)) }
46
+ scope :for_month, ->(year, month) { d = Date.new(year,month) ; where(published_at: (d.beginning_of_month)..(d.end_of_month)) }
47
+ scope :latest, -> { published.posts.desc(:published_at) }
48
+
49
+ before_validation :nil_if_blank
50
+ before_validation :set_default_slug
51
+ before_validation :set_published_at
52
+ before_save :update_body_html
53
+
54
+ def disqus_identifier
55
+ slug
56
+ end
57
+
58
+ def slug
59
+ slugs.try(:last)
60
+ end
61
+
62
+ def previous_slugs
63
+ slugs[0..-2]
64
+ end
65
+
66
+ def slug=(v)
67
+ unless v.blank?
68
+ slugs.delete(v)
69
+ slugs << v
25
70
  end
26
71
  end
27
72
 
28
- def year
29
- date.year
73
+ def set_default_slug
74
+ if slug.blank?
75
+ self.slug = title.parameterize
76
+ end
30
77
  end
31
78
 
32
- def month
33
- date.month
79
+ def set_published_at
80
+ if published_at.nil? && status_changed? && status == :published
81
+ self.published_at = Time.now
82
+ end
34
83
  end
35
84
 
36
- def day
37
- date.day
85
+ def nil_if_blank
86
+ attributes.keys.each do |attr|
87
+ self[attr].strip! if self[attr].is_a?(String)
88
+ self[attr] = nil if self[attr] == ""
89
+ end
38
90
  end
39
91
 
40
- def to_url
41
- date.present? ? "/#{year}/#{month}/#{day}/#{slug}/" : "/#{slug}/"
92
+ def update_body_html
93
+ self.body_html = render
42
94
  end
43
95
 
44
- def disqus?
45
- disqus && published?
96
+ def to_html
97
+ if body_html.nil?
98
+ update_body_html
99
+ save
100
+ end
101
+
102
+ body_html
46
103
  end
47
104
 
48
- def published?
49
- status == 'published'
105
+ def render
106
+ @@markdown ||= Redcarpet::Markdown.new(MarkdownRenderer,
107
+ autolink: true, space_after_headers: true, fenced_code_blocks: true)
108
+
109
+ @@markdown.render(body)
50
110
  end
51
111
 
52
112
  def post?
53
- date.present?
113
+ published_at.present?
54
114
  end
55
115
 
56
116
  def page?
57
117
  !post?
58
118
  end
59
119
 
60
- class << self
61
- def latest(options = {})
62
- options = {published_only: false}.merge(options)
120
+ def published?
121
+ status == :published
122
+ end
63
123
 
64
- posts = documents.select(&:date)
65
- posts = posts.select(&:published?) if options[:published_only]
66
- posts.sort_by(&:date).reverse.first(10)
67
- end
124
+ def draft?
125
+ status == :draft
126
+ end
68
127
 
69
- def with_slug(slug)
70
- documents.detect { |p| p.slug == slug }
71
- end
128
+ def link_post?
129
+ link.present?
130
+ end
131
+
132
+ def article_post?
133
+ link.nil?
134
+ end
135
+
136
+ def year
137
+ published_at.year
138
+ end
139
+
140
+ def month
141
+ published_at.month
142
+ end
143
+
144
+ def day
145
+ published_at.day
146
+ end
147
+
148
+ def to_url
149
+ published_at.present? ? "/#{year}/#{month}/#{day}/#{slug}/" : "/#{slug}/"
150
+ end
151
+
152
+ def disqus?
153
+ disqus && published?
72
154
  end
73
155
  end
74
156
  end
@@ -0,0 +1,21 @@
1
+ require 'schreihals'
2
+
3
+ desc 'Run the Schreihals 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=\"#{Schreihals.mongo_uri}\" heroku mongo:pull"
17
+ end
18
+
19
+ task :push do
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Schreihals
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -1,3 +1,19 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'schreihals', '~> 0.0.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 schreihals. I told you you were about
10
+ # to live dangerously!
11
+ #
12
+ gem 'schnitzelstyle', git: 'git://github.com/hmans/schnitzelstyle.git'
13
+ gem 'schreihals', git: 'git://github.com/hmans/schreihals.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 'schreihals', :path => '../schreihals'
@@ -0,0 +1,2 @@
1
+ require File.expand_path("../app.rb", __FILE__)
2
+ require 'schreihals/rake'
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler.require
5
+
6
+ Schreihals.mongo_uri =
7
+ ENV['MONGOLAB_URI'] ||
8
+ ENV['MONGOHQ_URL'] ||
9
+ ENV['MONGO_URL'] ||
10
+ 'mongodb://localhost/your_new_blog' # used for local development
11
+
12
+ class App < Schreihals::App
13
+ configure do
14
+ set :blog_title, "<%= @name %>"
15
+ set :blog_description, "A new blog powered by Schreihals."
16
+ set :author_name, "Your Name"
17
+ set :footer, "powered by [Schreihals](http://schreihals.info)"
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
@@ -1,14 +1,2 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
3
- require 'schreihals'
4
-
5
- class MyBlog < Schreihals::App
6
- set :blog_title, "<%= @name %>"
7
- set :blog_url, "http://<%= @name %>.info"
8
- set :blog_description, ""
9
- set :author_name, "Your Name"
10
- # set :disqus_name, ""
11
- # set :google_analytics_id, ""
12
- end
13
-
14
- run MyBlog
1
+ require File.expand_path("../app.rb", __FILE__)
2
+ run App
@@ -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
@@ -1,21 +1,23 @@
1
1
  !!! XML
2
2
  %feed{xmlns: 'http://www.w3.org/2005/Atom'}
3
3
  %title= settings.blog_title
4
- %link{href: url_for(',', absolute: true)}
5
- %link{href: url_for('/atom.xml', absolute: true), rel: 'self'}
6
- %id= "http://hmans.net/"
4
+ %link{href: url_for('/', absolute: true)}
5
+ %id= base_url
7
6
  - if @posts.any?
8
- %updated= @posts.first.date
7
+ %updated= @posts.first.published_at
8
+
9
9
  %author
10
10
  %name= settings.author_name
11
+
11
12
  - @posts.each do |post|
12
13
  %entry
13
- %title= post.title
14
- %link{rel: 'alternate', href: url_for(post, absolute: true)}
15
- %id urn:uuid:#{post.disqus_identifier}
16
- %published= post.date
17
- %updated= post.date
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
18
19
  %author
19
20
  %name= settings.author_name
20
21
  %content{type: 'html'}
21
- ~ post.to_html
22
+ :cdata
23
+ #{post.to_html}
@@ -1,3 +1,6 @@
1
1
  %section.posts
2
2
  - @posts.each do |post|
3
3
  = partial post
4
+
5
+ %footer
6
+ %a.button{ href: "/?page=#{params[:page].to_i + 1}"} View Older Posts
@@ -2,14 +2,15 @@
2
2
  %html
3
3
  %head
4
4
  %title= [@page_title, settings.blog_title].compact.join(" | ")
5
+ %meta{ :"http-equiv" => "content-type", content: "text/html; charset=UTF-8" }
5
6
  %meta{ name: "viewport", content: "width=device-width, initial-scale=1.0" }
6
7
  %link{ href: '/blog.css', media: "screen", rel: "stylesheet", type: "text/css" }
7
- %link{ href: '/atom.xml', title: "Subscribe via Atom Feed", rel: 'alternate', type: 'application/atom+xml' }
8
+ %link{ href: settings.feed_url, title: "Subscribe via Atom Feed", rel: 'alternate', type: 'application/atom+xml' }
8
9
  %body
9
10
  .container
10
11
  %header
11
- %h1
12
- = link_to settings.blog_title, '/'
12
+ .site-title
13
+ %a{href: '/'}= settings.blog_title
13
14
  - if @show_description
14
15
  ~ markdown settings.blog_description
15
16
 
@@ -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
@@ -0,0 +1,27 @@
1
+ .input{ class: options[:type] }
2
+ %label{ for: options[:id] } #{options[:label]}:
3
+ - case options[:type].to_sym
4
+ - when :textarea
5
+ %textarea{ id: options[:id], name: options[:name], rows: 20 }= html_escape(options[:value])
6
+
7
+ - when :radio
8
+ - options[:options].each do |o|
9
+ .option
10
+ %input{ type: 'radio', id: options[:id], name: options[:name], value: o, checked: options[:value] == o }= o
11
+
12
+ - when :dropdown
13
+ %select{ id: options[:id], name: options[:name]}
14
+ - options[:options].each do |val, text|
15
+ %option{value: val, selected: options[:value] == val}= text
16
+
17
+ - when :datetime
18
+ %input{ id: options[:id], name: options[:name], value: options[:value].to_formatted_s(:db) }
19
+
20
+ - else # normal inputs
21
+ %input{ id: options[:id], name: options[:name], value: options[:value] }
22
+
23
+ - if options[:errors]
24
+ %span.error= options[:errors].join(", ")
25
+
26
+ - if options[:extra]
27
+ %span.extra= options[:extra]
@@ -1,28 +1,27 @@
1
1
  - complete ||= false
2
- - show_title ||= post.title.present?
2
+ - show_title ||= post.article_post? && post.title.present?
3
+ - show_link ||= post.link_post?
3
4
  - show_summary ||= post.summary.present?
4
5
  - show_body ||= complete || !show_summary
5
6
  - show_read_more ||= !complete && post.summary.present?
6
- - show_permalink ||= post.post? && !show_read_more
7
+ - show_permalink ||= admin_logged_in? || (post.post? && !show_read_more)
7
8
  - show_twitter ||= complete && post.post? && settings.twitter_id.present?
8
9
 
9
- %article.post{class: post.status}
10
+ %article.post{class: [post.status, post.link_post? ? 'link' : 'article']}
10
11
  %header
11
12
  - if show_title
12
- %h2
13
- %a{href: post.link.present? ? post.link : post.to_url}
14
- = post.title
15
- = "→" if post.link.present?
16
- .info
17
- - if post.date.present?
18
- #{post.date}
13
+ %h1
14
+ %a{href: post.to_url}= post.title
15
+ - if show_link
16
+ %a{href: post.link}= post.title
17
+
19
18
 
20
19
  - if show_summary
21
20
  .summary
22
21
  ~ markdown post.summary
23
22
  - if show_read_more
24
23
  %p
25
- = link_to (post.read_more || settings.read_more), post
24
+ %a{href: url_for(post)}= post.read_more.presence || settings.read_more
26
25
 
27
26
 
28
27
  - if show_body
@@ -30,7 +29,12 @@
30
29
 
31
30
  %footer
32
31
  - if show_permalink
33
- %p.permalink= link_to '∞', post
32
+ %p.permalink
33
+ %a{href: url_for(post)}= post.published_at.try(:to_date) || "∞"
34
+ - if admin_logged_in?
35
+ &middot;
36
+ %a{href: "/admin/edit/#{post.id}"} edit
37
+
34
38
  - if show_twitter
35
39
  .social_media_buttons
36
40
  - if show_twitter