schreihals 0.0.3 → 0.0.4

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 (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