blabs 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +15 -0
  3. data/app/assets/images/uploadify-cancel.png +0 -0
  4. data/app/assets/images/uploadify.swf +0 -0
  5. data/app/assets/javascripts/blabs.js +79 -0
  6. data/app/assets/javascripts/jquery.uploadify.min.js +16 -0
  7. data/app/assets/stylesheets/blabs/blog.sass +6 -0
  8. data/app/assets/stylesheets/uploadify.css.scss +92 -0
  9. data/app/controllers/blabs/blog_posts_controller.rb +131 -0
  10. data/app/controllers/blabs/images_controller.rb +35 -0
  11. data/app/controllers/blabs/published_blog_posts_controller.rb +30 -0
  12. data/app/controllers/blabs_controller.rb +31 -0
  13. data/app/helpers/blabs/blog_posts_helper.rb +114 -0
  14. data/app/models/blabs/blog_post.rb +70 -0
  15. data/app/models/blabs/image.rb +4 -0
  16. data/app/models/blabs/tag.rb +9 -0
  17. data/app/models/blabs/tagging.rb +9 -0
  18. data/app/uploaders/image_uploader.rb +65 -0
  19. data/app/views/blabs/blog_posts/_form.html.haml +54 -0
  20. data/app/views/blabs/blog_posts/_image.html.haml +5 -0
  21. data/app/views/blabs/blog_posts/_image_panel.html.haml +32 -0
  22. data/app/views/blabs/blog_posts/edit.html.haml +7 -0
  23. data/app/views/blabs/blog_posts/feed.atom.builder +15 -0
  24. data/app/views/blabs/blog_posts/index.html.haml +27 -0
  25. data/app/views/blabs/blog_posts/new.html.haml +7 -0
  26. data/app/views/blabs/blog_posts/show.html.haml +16 -0
  27. data/app/views/layouts/blabs.html.erb +21 -0
  28. data/config/routes.rb +16 -0
  29. data/db/migrate/20130802113537_create_blabs_tables.rb +23 -0
  30. data/db/migrate/20130909092406_add_extract_to_blog_posts.rb +5 -0
  31. data/db/migrate/20131209144117_create_tags.rb +16 -0
  32. data/lib/blabs.rb +53 -0
  33. data/lib/blabs/engine.rb +13 -0
  34. data/lib/blabs/version.rb +3 -0
  35. data/lib/generators/blabs_generator.rb +8 -0
  36. data/lib/tasks/blabs_tasks.rake +4 -0
  37. data/spec/controllers/blabs/blog_posts_controller_spec.rb +50 -0
  38. data/spec/controllers/blabs/published_blog_posts_controller_spec.rb +32 -0
  39. data/spec/dummy/README.rdoc +28 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +15 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/models/account.rb +2 -0
  46. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  47. data/spec/dummy/bin/bundle +3 -0
  48. data/spec/dummy/bin/rails +4 -0
  49. data/spec/dummy/bin/rake +4 -0
  50. data/spec/dummy/config.ru +4 -0
  51. data/spec/dummy/config/application.rb +23 -0
  52. data/spec/dummy/config/boot.rb +5 -0
  53. data/spec/dummy/config/database.yml +25 -0
  54. data/spec/dummy/config/environment.rb +5 -0
  55. data/spec/dummy/config/environments/development.rb +29 -0
  56. data/spec/dummy/config/environments/production.rb +80 -0
  57. data/spec/dummy/config/environments/test.rb +36 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/spec/dummy/config/initializers/inflections.rb +16 -0
  61. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  62. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +56 -0
  67. data/spec/dummy/db/migrate/20130808145755_create_accounts.rb +9 -0
  68. data/spec/dummy/db/schema.rb +60 -0
  69. data/spec/dummy/public/404.html +58 -0
  70. data/spec/dummy/public/422.html +58 -0
  71. data/spec/dummy/public/500.html +57 -0
  72. data/spec/dummy/public/favicon.ico +0 -0
  73. data/spec/helpers/blabs/blog_posts_helper_spec.rb +50 -0
  74. data/spec/lib/blabs_spec.rb +8 -0
  75. data/spec/models/blabs/blog_post_spec.rb +76 -0
  76. data/spec/spec_helper.rb +19 -0
  77. metadata +356 -0
@@ -0,0 +1,114 @@
1
+ # encoding: UTF-8
2
+
3
+ module Blabs::BlogPostsHelper
4
+ EXTRACT_LENGTH = 600
5
+
6
+ def title(title)
7
+ content_for(:title, title)
8
+ content_tag(:h1, title)
9
+ end
10
+
11
+ def field_error(form, object, field)
12
+ if object.errors[field] && object.errors[field].size > 0
13
+ content_tag 'div', object.errors[field].first,
14
+ class: 'i i-info form-error'
15
+ end
16
+ end
17
+
18
+ def admin_controls(blog_post)
19
+ edit_link = link_to("Edit", edit_blabs_blog_post_path(blog_post))
20
+ publish_link = publish_or_unpublish_link(blog_post)
21
+ delete_link = link_to("Delete", blog_post, method: :delete, data: { confirm: "Are you sure?" })
22
+ raw [edit_link, publish_link, delete_link].join(" • ")
23
+ end
24
+
25
+ def formatted_body(text)
26
+ options = { autolink: true, lax_spacing: true, space_after_headers: true}
27
+ renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
28
+ auto_link_username(renderer.render(text)).html_safe
29
+ end
30
+
31
+ def validate_layout
32
+ if !content_for?(:title) && !@blog_post.meta_title.blank?
33
+ raise "You need to put a title yield in your layouts title tag.\n
34
+ e.g. <title>My Blog - <%= yield(:title) %> </title>\n"
35
+ end
36
+
37
+ if !content_for?(:meta_tags)
38
+ raise "You need to put a meta_tags yield in your layouts head tag.\n
39
+ e.g. <%= yield(:meta_tags) %> </head>\n"
40
+ end
41
+
42
+ end
43
+
44
+ def pretty_date(date)
45
+ date.strftime("#{date.day.ordinalize} %B %Y") unless date.blank?
46
+ end
47
+
48
+ def extract(blog_post)
49
+ if blog_post.extract.present?
50
+ extract = blog_post.extract + " " + link_to("Read more", pretty_blog_post_path(blog_post), :class=>"more")
51
+ formatted_body(extract)
52
+ else
53
+ truncate_html(formatted_body(blog_post.body), length: EXTRACT_LENGTH,
54
+ omission: "... #{link_to("Read more", pretty_blog_post_path(blog_post), :class=>"more")}",
55
+ escape: false)
56
+ end
57
+ end
58
+
59
+ def show_meta_tags
60
+ content_for(:meta_tags) do
61
+ concat twitter_meta_tag("card", "summary")
62
+ concat twitter_meta_tag("site", Blabs.twitter_site_username)
63
+ concat twitter_meta_tag("creator", @blog_post.try(:author_twitter_username))
64
+ concat twitter_meta_tag("title", @blog_post.title)
65
+
66
+ sample = truncate(strip_tags(formatted_body(@blog_post.body)), length: EXTRACT_LENGTH)
67
+
68
+ concat twitter_meta_tag("description", sample.strip)
69
+ concat twitter_meta_tag("image", @blog_post.post_image_url)
70
+
71
+ concat open_graph_meta_tag("title", @blog_post.title)
72
+ concat open_graph_meta_tag("description", sample.strip)
73
+ concat open_graph_meta_tag("image", @blog_post.post_image_url)
74
+ concat open_graph_meta_tag("url", pretty_blog_post_url(@blog_post))
75
+ end
76
+ end
77
+
78
+ def index_meta_tags
79
+ content_for(:meta_tags) do
80
+ concat auto_discovery_link_tag :atom, "/blog/feed"
81
+ concat auto_discovery_link_tag :rss, "/blog/feed.rss"
82
+ end
83
+ end
84
+
85
+ def auto_link_username(body)
86
+ body.gsub(/@(\w+)/, %Q{<a href="https://twitter.com/\\1">@\\1</a>})
87
+ end
88
+
89
+ def display_author(blog_post)
90
+ if blog_post.author_twitter_username.blank?
91
+ blog_post.author
92
+ else
93
+ link_to(blog_post.author, "https://twitter.com/#{blog_post.author_twitter_username.delete("@")}")
94
+ end
95
+ end
96
+
97
+ private
98
+ def publish_or_unpublish_link(blog_post)
99
+ if blog_post.published?
100
+ link_to("Unpublish", blabs_published_blog_posts_path(id: blog_post), method: :delete)
101
+ else
102
+ link_to("Publish", blabs_published_blog_posts_path(id: blog_post), method: :post)
103
+ end
104
+ end
105
+
106
+ def twitter_meta_tag(label, value)
107
+ tag(:meta, name: "twitter:#{label}", content: value)
108
+ end
109
+
110
+ def open_graph_meta_tag(label, value)
111
+ tag(:meta, property: "og:#{label}", content: value)
112
+ end
113
+
114
+ end
@@ -0,0 +1,70 @@
1
+ module Blabs
2
+ class BlogPost < ActiveRecord::Base
3
+ self.table_name_prefix = 'blabs_'
4
+
5
+ validates :title, presence: true
6
+ validates :body, presence: true
7
+ validates :author, presence: true
8
+ validates :path, presence: true, uniqueness: true
9
+
10
+ serialize :extra_attributes, Hash
11
+
12
+ before_validation :set_path
13
+ before_validation :set_meta_title
14
+
15
+ has_many :taggings, class_name: "Blabs::Tagging", foreign_key: "blabs_blog_post_id"
16
+ has_many :tags, class_name: "Blabs::Tag", through: :taggings, foreign_key: "blabs_tag_id"
17
+
18
+ def self.tagged_with(name)
19
+ Tag.find_by_name!(name).blog_posts
20
+ end
21
+
22
+ def to_param
23
+ path
24
+ end
25
+
26
+ def publish!
27
+ update_attributes(published: true, published_at: Time.now) unless published?
28
+ end
29
+
30
+ def unpublish!
31
+ update_attributes(published: false, published_at: nil) if published?
32
+ end
33
+
34
+ def toggle_publish!(value)
35
+ value ? publish! : unpublish!
36
+ end
37
+
38
+ def post_image_url
39
+ (Blabs.post_image_url_attribute && extra_attributes[Blabs.post_image_url_attribute]) || begin
40
+ find_link_regex = /\[([^\[]+)\]\(([^\)]+)\)/
41
+ body.match(find_link_regex) && $2
42
+ end
43
+ end
44
+
45
+ def author_twitter_username
46
+ username = self[:author_twitter_username]
47
+ username.blank? ? "" : "@#{username}".squeeze("@")
48
+ end
49
+
50
+ def tag_list=(names)
51
+ self.tags = names.split(",").map do |n|
52
+ Blabs::Tag.where(name: n.strip).first_or_create!
53
+ end
54
+ end
55
+
56
+ def tag_list
57
+ self.tags.collect {|t| t.name }.join(", ")
58
+ end
59
+
60
+ private
61
+
62
+ def set_path
63
+ self.path = self.title.parameterize if self.path.blank? && self.title.present?
64
+ end
65
+
66
+ def set_meta_title
67
+ self.meta_title = self.title if self.meta_title.blank?
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,4 @@
1
+ class Blabs::Image < ActiveRecord::Base
2
+ mount_uploader :image, ImageUploader
3
+ self.table_name_prefix = 'blabs_'
4
+ end
@@ -0,0 +1,9 @@
1
+ module Blabs
2
+ class Tag < ActiveRecord::Base
3
+ self.table_name_prefix = 'blabs_'
4
+
5
+ has_many :taggings, class_name: "Blabs::Tagging", foreign_key: "blabs_tag_id"
6
+ has_many :blog_posts, through: :taggings, class_name: "Blabs::BlogPost"
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Blabs
2
+ class Tagging < ActiveRecord::Base
3
+ self.table_name_prefix = 'blabs_'
4
+
5
+ belongs_to :tag, class_name: "Blabs::Tag", foreign_key: "blabs_tag_id"
6
+ belongs_to :blog_post, class_name: "Blabs::BlogPost", foreign_key: "blabs_blog_post_id"
7
+
8
+ end
9
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ require 'carrierwave/processing/mime_types'
3
+
4
+ class ImageUploader < CarrierWave::Uploader::Base
5
+
6
+ # Include RMagick or MiniMagick support:
7
+ # include CarrierWave::RMagick
8
+ include CarrierWave::MiniMagick
9
+ include CarrierWave::MimeTypes
10
+
11
+ # Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
12
+ # include Sprockets::Helpers::RailsHelper
13
+ # include Sprockets::Helpers::IsolatedHelper
14
+
15
+ # Choose what kind of storage to use for this uploader:
16
+ if Rails.env.production?
17
+ storage :fog
18
+ fog_credentials Blabs.fog_credentials
19
+ fog_directory Blabs.fog_directory
20
+ else
21
+ storage :file
22
+ end
23
+
24
+
25
+ # Override the directory where uploaded files will be stored.
26
+ # This is a sensible default for uploaders that are meant to be mounted:
27
+ def store_dir
28
+ "uploads/#{model.class.to_s.underscore}/#{model.id}"
29
+ end
30
+
31
+ # Provide a default URL as a default if there hasn't been a file uploaded:
32
+ # def default_url
33
+ # # For Rails 3.1+ asset pipeline compatibility:
34
+ # # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
35
+ #
36
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
37
+ # end
38
+
39
+ # Process files as they are uploaded:
40
+ # process :scale => [200, 300]
41
+ #
42
+ # def scale(width, height)
43
+ # # do something
44
+ # end
45
+
46
+ process :set_content_type
47
+
48
+ # Create different versions of your uploaded files:
49
+ version :thumb do
50
+ process :resize_to_fit => [60, 60]
51
+ end
52
+
53
+ # Add a white list of extensions which are allowed to be uploaded.
54
+ # For images you might use something like this:
55
+ # def extension_white_list
56
+ # %w(jpg jpeg gif png)
57
+ # end
58
+
59
+ # Override the filename of the uploaded files:
60
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
61
+ # def filename
62
+ # "something.jpg" if original_filename
63
+ # end
64
+
65
+ end
@@ -0,0 +1,54 @@
1
+ =form_for(@blog_post) do |f|
2
+ .col-lg-8
3
+ .form-group
4
+ = f.label :title
5
+ = f.text_field :title, class: "form-control", placeholder: "Give your blog post an interesting title"
6
+ = field_error(f, @blog_post, :title)
7
+ .row
8
+ .col-lg-6
9
+ .form-group
10
+ = f.label :author
11
+ = f.text_field :author, class: "form-control", placeholder: "You need to put your name in"
12
+ = field_error(f, @blog_post, :author)
13
+ .col-lg-6
14
+ .form-group
15
+ = f.label :author_twitter_username
16
+ = f.text_field :author_twitter_username, class: "form-control", placeholder: "Your twitter name (for the twitter summary card)"
17
+ = field_error(f, @blog_post, :author_twitter_username)
18
+ .form-group
19
+ = f.label :body
20
+ %p.pull-right= link_to("Markdown syntax (new window)", "http://daringfireball.net/projects/markdown/syntax", target: :window, tabIndex: -1)
21
+ = f.text_area :body, class: "form-control", placeholder: "Use markdown to write your blog post", rows: 25
22
+ = field_error(f, @blog_post, :body)
23
+ .form-group
24
+ = f.label :extract
25
+ %p.pull-right Leave blank to use the default extract
26
+ = f.text_area :extract, class: "form-control", placeholder: "Use markdown to write your extract", rows: 2
27
+ = field_error(f, @blog_post, :extract)
28
+ .form-group
29
+ = f.submit "Save (unpublished)", class: "btn btn-default"
30
+ = f.submit "Save and publish", class: "btn btn-default btn-success", name: "publish"
31
+ = f.submit "Preview", class: "btn btn-default btn-info", name: "preview"
32
+ or
33
+ = link_to "cancel", "/blog", data: { confirm: "Are you sure?" }
34
+ .col-lg-4.panel.panel-info
35
+ .panel-heading For SEO nerds
36
+ .well.well-sm If you don't put anything in here, a sensible default will be applied
37
+ .form-group
38
+ = f.label :meta_title
39
+ = f.text_field :meta_title, class: "form-control", placeholder: "Meta title - for you SEO nerds (will default to the title)"
40
+ = field_error(f, @blog_post, :meta_title)
41
+ .form-group
42
+ = f.label :path
43
+ = f.text_field :path, class: "form-control", placeholder: "The url - (will be defaulted to something sensible)"
44
+ = field_error(f, @blog_post, :path)
45
+ .form-group
46
+ = f.label :tag_list
47
+ = f.text_field :tag_list, class: "form-control", placeholder: "Comma delimited list of tags"
48
+ - if Blabs.extra_attributes.any?
49
+ .col-lg-4.panel.panel-info
50
+ - Blabs.extra_attributes.each do |attribute|
51
+ .form-group
52
+ = label_tag "blabs_blog_post[extra_attributes][#{attribute}]", attribute.to_s.humanize
53
+ = text_field_tag "blabs_blog_post[extra_attributes][#{attribute}]", @blog_post.extra_attributes[attribute], class: "form-control"
54
+ =render "image_panel"
@@ -0,0 +1,5 @@
1
+ %li.list-group-item.image[image]
2
+ = link_to(image_tag(image.image_url(:thumb)), image.image_url) if image.image?
3
+ = link_to("Add", image.image_url, class: "add-to-blog")
4
+ |
5
+ = link_to("Delete", blabs_image_path(image), :method => :delete, data: { confirm: "Are you sure?" }, :remote => :true)
@@ -0,0 +1,32 @@
1
+ .col-lg-4.panel.panel-success
2
+ .panel-heading Images
3
+ .well.well-sm Upload an image, and then add it to the post with the add button
4
+ = fields_for Blabs::Image.new do |f|
5
+ = f.file_field :image, rel: blabs_images_path
6
+ %ul#images.list-group
7
+ = render partial: 'image', collection: @images
8
+
9
+ - key = Rails.application.config.session_options[:key]
10
+ = content_for :scripts do
11
+ :javascript
12
+ var upload_params = {};
13
+ var csrf_param = $('meta[name=csrf-param]').attr('content');
14
+ var csrf_token = $('meta[name=csrf-token]').attr('content');
15
+ upload_params[csrf_param] = encodeURI(encodeURIComponent(csrf_token));
16
+ upload_params['#{ key }'] = '#{ cookies[key] }';
17
+
18
+ var url = $('input#blabs_image_image').attr('rel');
19
+ $('input#blabs_image_image').uploadify({
20
+ 'swf' : '#{ image_path("uploadify.swf") }',
21
+ 'buttonText': "Upload image",
22
+ 'uploader' : url,
23
+ 'fileObjName' : 'blabs_image[image]',
24
+ 'fileTypeExts' : '*.png;*.jpg;*.gif',
25
+ 'multi' : true,
26
+ 'formData' : upload_params,
27
+ 'auto' : true,
28
+ 'onUploadSuccess' : function(file, data, response) {
29
+ var newImage = $('#images').prepend(data);
30
+ $(document).trigger('new_image', $(newImage).attr("id"));
31
+ }
32
+ });
@@ -0,0 +1,7 @@
1
+ %ul.breadcrumb
2
+ %li= link_to("All posts", "/blog")
3
+ %li.active= "Edit #{@blog_post.title}"
4
+
5
+ .content
6
+ =title("Edit #{@blog_post.title}")
7
+ =render partial: 'form'
@@ -0,0 +1,15 @@
1
+ atom_feed do |feed|
2
+ feed.title @title
3
+ feed.updated @updated
4
+
5
+ @posts.each do |post|
6
+ feed.entry post do |entry|
7
+ entry.title post.title
8
+ entry.content formatted_body(post.body), type: 'html'
9
+ entry.updated(post.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ"))
10
+ entry.author do |author|
11
+ author.name post.author
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ - index_meta_tags
2
+
3
+ .blabs-blog
4
+ %nav.blabs-navigation
5
+ - if blabs_admin_required && @blog_posts.any?
6
+ =link_to("New Blog Post", new_blabs_blog_post_path)
7
+ -if @blog_posts.none?
8
+ %article
9
+ %header
10
+ %h2.title You don't have any blog posts.
11
+ %p.authorship #{link_to("Why don't you create the first!", new_blabs_blog_post_path)}
12
+
13
+ -else
14
+ -@blog_posts.each do |blog_post|
15
+ %article
16
+ %header
17
+ %h2.title=link_to(blog_post.title, pretty_blog_post_path(blog_post))
18
+ %p.authorship #{display_author(blog_post)} • #{pretty_date(blog_post.published_at) || pretty_date(blog_post.created_at)}
19
+ %hr
20
+ .content.post-body
21
+ - if @blog_posts.size == 1
22
+ = formatted_body(blog_post.body)
23
+ - else
24
+ = extract(blog_post)
25
+ - if blabs_admin_required
26
+ .blabs-admin-controls
27
+ = admin_controls(blog_post)
@@ -0,0 +1,7 @@
1
+ %ul.breadcrumb
2
+ %li= link_to("All posts", "/blog")
3
+ %li.active= "New blog post"
4
+
5
+ .content
6
+ =title("New blog post")
7
+ =render partial: 'form'
@@ -0,0 +1,16 @@
1
+ - content_for(:title, @blog_post.meta_title)
2
+ - show_meta_tags
3
+ - validate_layout
4
+ .blabs-blog
5
+ %nav.blabs-navigation
6
+ =link_to("< Back", "/blog")
7
+ %article
8
+ %header
9
+ %h1.title= @blog_post.title
10
+ %p.authorship #{display_author(@blog_post)} • #{pretty_date(@blog_post.published_at) || pretty_date(@blog_post.created_at)}
11
+
12
+ .content.post-body
13
+ = formatted_body(@blog_post.body)
14
+ - if blabs_admin_required
15
+ .blabs-admin-controls
16
+ = admin_controls(@blog_post)