monologue 0.1.0

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 (63) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +73 -0
  3. data/Rakefile +32 -0
  4. data/app/assets/javascripts/monologue/admin/application.js +13 -0
  5. data/app/assets/javascripts/monologue/admin/tinymce-config.js +21 -0
  6. data/app/assets/javascripts/monologue/blog/application.js +9 -0
  7. data/app/assets/stylesheets/monologue/admin/application.css +10 -0
  8. data/app/assets/stylesheets/monologue/blog/application.css +13 -0
  9. data/app/assets/stylesheets/monologue/blog/custom.css +1 -0
  10. data/app/assets/stylesheets/monologue/blog/fonts.css +1 -0
  11. data/app/assets/stylesheets/monologue/blog/monologue.css +37 -0
  12. data/app/assets/stylesheets/monologue/blog/skeleton/base.css +343 -0
  13. data/app/assets/stylesheets/monologue/blog/skeleton/layout.css +58 -0
  14. data/app/assets/stylesheets/monologue/blog/skeleton/skeleton.css +242 -0
  15. data/app/controllers/monologue/admin/base_controller.rb +12 -0
  16. data/app/controllers/monologue/admin/posts_controller.rb +54 -0
  17. data/app/controllers/monologue/admin/sessions_controller.rb +22 -0
  18. data/app/controllers/monologue/application_controller.rb +22 -0
  19. data/app/controllers/monologue/posts_controller.rb +25 -0
  20. data/app/form_builders/monologue_admin_form_builder.rb +61 -0
  21. data/app/helpers/monologue/admin/admin_helper.rb +4 -0
  22. data/app/helpers/monologue/application_helper.rb +8 -0
  23. data/app/helpers/monologue/posts_helper.rb +4 -0
  24. data/app/helpers/monologue/sessions_helper.rb +6 -0
  25. data/app/models/monologue/post.rb +40 -0
  26. data/app/models/monologue/posts_revision.rb +34 -0
  27. data/app/models/monologue/user.rb +6 -0
  28. data/app/sweepers/monologue/posts_sweeper.rb +28 -0
  29. data/app/views/layouts/monologue/_google_analytics.html.erb +15 -0
  30. data/app/views/layouts/monologue/admin.html.erb +21 -0
  31. data/app/views/layouts/monologue/admin/_nav_bar.html.erb +24 -0
  32. data/app/views/layouts/monologue/application.html.erb +60 -0
  33. data/app/views/monologue/admin/posts/_form.html.erb +16 -0
  34. data/app/views/monologue/admin/posts/edit.html.erb +4 -0
  35. data/app/views/monologue/admin/posts/index.html.erb +20 -0
  36. data/app/views/monologue/admin/posts/new.html.erb +4 -0
  37. data/app/views/monologue/admin/sessions/new.html.erb +13 -0
  38. data/app/views/monologue/posts/404.html.erb +9 -0
  39. data/app/views/monologue/posts/_pagination.html.erb +9 -0
  40. data/app/views/monologue/posts/_social_sharing.html.erb +42 -0
  41. data/app/views/monologue/posts/feed.rss.builder +19 -0
  42. data/app/views/monologue/posts/index.html.erb +18 -0
  43. data/app/views/monologue/posts/show.html.erb +38 -0
  44. data/config/locales/en.yml +106 -0
  45. data/config/locales/fr.yml +106 -0
  46. data/config/routes.rb +15 -0
  47. data/db/migrate/20120114001001_create_monologue_users.rb +11 -0
  48. data/db/migrate/20120120193858_create_monologue_posts_revisions.rb +18 -0
  49. data/db/migrate/20120120193907_create_monologue_posts.rb +10 -0
  50. data/db/seeds.rb +1 -0
  51. data/lib/monologue.rb +17 -0
  52. data/lib/monologue/engine.rb +11 -0
  53. data/lib/monologue/version.rb +3 -0
  54. data/lib/tasks/monologue_tasks.rake +4 -0
  55. data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings-white.png +0 -0
  56. data/vendor/assets/images/monologue/bootstrap/glyphicons-halflings.png +0 -0
  57. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker-fr.js +9 -0
  58. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap-datepicker.js +338 -0
  59. data/vendor/assets/javascripts/monologue/bootstrap/bootstrap.min.js +1 -0
  60. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-datepicker.css +130 -0
  61. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap-responsive.min.css +3 -0
  62. data/vendor/assets/stylesheets/monologue/bootstrap/bootstrap.min.css +610 -0
  63. metadata +285 -0
@@ -0,0 +1,4 @@
1
+ module Monologue
2
+ module Admin::AdminHelper
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ module Monologue
2
+ module ApplicationHelper
3
+ def monologue_admin_form_for(object, options = {}, &block)
4
+ options[:builder] = MonologueAdminFormBuilder
5
+ form_for(object, options, &block)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ module Monologue
2
+ module PostsHelper
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Monologue
2
+ module Admin
3
+ module SessionsHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ module Monologue
2
+ class Post < ActiveRecord::Base
3
+ has_many :posts_revisions, :dependent => :destroy
4
+
5
+ accepts_nested_attributes_for :posts_revisions
6
+ attr_accessible :posts_revisions_attributes
7
+ attr_accessible :published
8
+
9
+
10
+
11
+ scope :default, includes(:posts_revisions).where("posts_revision_id = monologue_posts_revisions.id").order("published_at DESC")
12
+ scope :published, default.where(:published => true)
13
+
14
+ validates :posts_revision_id, :uniqueness => true
15
+
16
+ def just_the_revision_one_before
17
+ self.posts_revisions.where("post_id = ?", self.id).order("monologue_posts_revisions.updated_at DESC").offset(1).limit(1).first
18
+ end
19
+
20
+ def latest_revision
21
+ self.posts_revisions.where("post_id = ?", self.id).order("monologue_posts_revisions.updated_at DESC").limit(1).first
22
+ end
23
+
24
+ def self.page p
25
+ per_page = Monologue.posts_per_page || 10
26
+ set_total_pages(per_page)
27
+ p = (p.nil? ? 0 : p.to_i - 1)
28
+ offset = (p==0 ? 0 : p * per_page)
29
+ self.limit(per_page).offset(offset)
30
+ end
31
+
32
+ def self.total_pages
33
+ @number_of_pages
34
+ end
35
+
36
+ def self.set_total_pages per_page
37
+ @number_of_pages = self.count / per_page + ( self.count % per_page == 0 ? 0 : 1 )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Monologue
2
+ class PostsRevision < ActiveRecord::Base
3
+ attr_accessible :title, :content, :url, :published_at
4
+
5
+ before_validation :generate_url
6
+
7
+
8
+ after_save :latest_revision_is_current
9
+
10
+ belongs_to :post
11
+ belongs_to :user
12
+
13
+ validates :title, :presence => true
14
+ validates :content, :presence => true
15
+ validates :url, :presence => true
16
+ validates :user_id, :presence => true
17
+ # validates :post_id, :presence => true # TODO: do something about this validation on the first creation of a POST
18
+ validates :published_at, :presence => true
19
+
20
+ def latest_revision_is_current
21
+ post = Monologue::Post.find(self.post_id)
22
+ post.posts_revision_id = self.id
23
+ post.save!
24
+ end
25
+
26
+ private
27
+
28
+ def generate_url
29
+ year = self.published_at.class == ActiveSupport::TimeWithZone ? self.published_at.year : DateTime.now.year
30
+ self.title = "" if self.title.nil?
31
+ self.url = "#{Monologue::Engine.routes.url_helpers.root_path}#{year}/#{self.title.parameterize}" if self.url.nil? || self.url.strip == ""
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ class Monologue::User < ActiveRecord::Base
2
+ has_many :posts
3
+ attr_accessible :name, :email, :password, :password_confirmation
4
+ has_secure_password
5
+ validates_presence_of :password, :on => :create
6
+ end
@@ -0,0 +1,28 @@
1
+ class Monologue::PostsSweeper < ActionController::Caching::Sweeper
2
+ observe Monologue::Post
3
+
4
+
5
+ def sweep(post)
6
+ root_path = Monologue::Engine.routes.url_helpers.root_path if root_path.nil? # TODO: why do I have to do this to make tests pass? There must be something much more clean to make tests pass
7
+ page_cache_directory = Rails.public_path if page_cache_directory.nil? # TODO: we should not need this either...
8
+ if post.posts_revisions.count > 0
9
+ current_post_path = "#{page_cache_directory}#{post.just_the_revision_one_before.url}.html" unless post.just_the_revision_one_before.nil?
10
+ current_post_path = "#{page_cache_directory}#{post.posts_revisions.last.url}.html" if post.posts_revisions.count == 1
11
+ File.delete current_post_path if File.exists? current_post_path
12
+ end
13
+
14
+ feed_file_path = "#{page_cache_directory}#{root_path}feed.rss"
15
+ File.delete feed_file_path if File.exists? feed_file_path
16
+
17
+ root_file_path = "#{page_cache_directory}#{root_path.chomp("/")}.html"
18
+ root_file_path = "#{page_cache_directory}/index.html" if root_path.chomp("/") == "" # TODO: add test for that? It would need another dummy app mounted at root...?
19
+ File.delete root_file_path if File.exists? root_file_path
20
+
21
+ FileUtils.rm_rf "#{page_cache_directory}/page" # remove pages
22
+ end
23
+
24
+ alias_method :after_create, :sweep
25
+ alias_method :after_update, :sweep
26
+ alias_method :after_destroy, :sweep
27
+
28
+ end
@@ -0,0 +1,15 @@
1
+ <% if defined? Monologue.google_analytics_id %>
2
+ <script type="text/javascript">
3
+
4
+ var _gaq = _gaq || [];
5
+ _gaq.push(['_setAccount', '<%= Monologue.google_analytics_id %>']);
6
+ _gaq.push(['_trackPageview']);
7
+
8
+ (function() {
9
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
10
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
11
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
12
+ })();
13
+
14
+ </script>
15
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Monologue</title>
5
+ <%= stylesheet_link_tag "monologue/admin/application" %>
6
+ <%= javascript_include_tag "monologue/admin/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+ <% if current_user %>
11
+ <%= render "layouts/monologue/admin/nav_bar" %>
12
+ <% end %>
13
+ <div class="container">
14
+ <% flash.each do |name, msg| %>
15
+ <%= content_tag :div, msg, :id => "flash_#{name}", :class => (name == :notice ? "alert alert-info" : "alert alert-error") %>
16
+ <% end %>
17
+
18
+ <%= yield %>
19
+ </div>
20
+ </body>
21
+ </html>
@@ -0,0 +1,24 @@
1
+ <div class="navbar">
2
+ <div class="navbar-inner">
3
+ <div class="container">
4
+ <%= link_to "Monologue", admin_path, :class => "brand" %>
5
+ <ul class="nav pull-left">
6
+
7
+ <li class="<%= request.fullpath === new_admin_post_path ? "active":"" %>">
8
+ <a href="<%= new_admin_post_path %>"><%=t(".add_a_monologue")%></a>
9
+ </li>
10
+ <li class="<%= request.fullpath === admin_posts_path || request.fullpath === admin_path ? "active":"" %>">
11
+ <a href="<%= admin_posts_path %>"><%=t(".list_monologues")%></a>
12
+ </li>
13
+ <li>
14
+ <a href="#"><%=t(".comments")%></a>
15
+ </li>
16
+ </ul>
17
+ <ul class="nav pull-right">
18
+ <li><a href="#"><%=t(".logged_in_as")%> <%= current_user.email %>.</a></li>
19
+ <li><%= link_to t(".log_out"), admin_logout_path %></li>
20
+ </ul>
21
+
22
+ </div>
23
+ </div>
24
+ </div>
@@ -0,0 +1,60 @@
1
+ <!DOCTYPE html>
2
+ <!--[if lt IE 7 ]><html class="ie ie6" lang="en"> <![endif]-->
3
+ <!--[if IE 7 ]><html class="ie ie7" lang="en"> <![endif]-->
4
+ <!--[if IE 8 ]><html class="ie ie8" lang="en"> <![endif]-->
5
+ <!--[if (gte IE 9)|!(IE)]><!--><html lang="en"> <!--<![endif]-->
6
+ <head>
7
+ <title>
8
+ <%= content_for?(:title) ? ((yield :title) + " | #{Monologue.site_name}") : Monologue.site_name %>
9
+ </title>
10
+ <meta charset="utf-8" />
11
+ <meta name="description" content="<%= content_for?(:meta_description) ? yield(:meta_description) : Monologue.meta_description %>" />
12
+ <meta name="keyword" content="<%=Monologue.meta_keyword%>">
13
+
14
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
16
+
17
+ <%= stylesheet_link_tag "monologue/blog/application" %>
18
+ <%= javascript_include_tag "monologue/blog/application" %>
19
+
20
+ <!-- Mobile Specific Metas
21
+ ================================================== -->
22
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
23
+
24
+ <link href="/feed" rel="alternate" title="RSS" type="application/rss+xml" >
25
+ <%= render "layouts/monologue/google_analytics" %>
26
+ </head>
27
+ <body>
28
+ <div class="container">
29
+ <header class="main-header sixteen columns clearfix">
30
+ <h1><a href="<%= root_path %>"><%=Monologue.site_name%></a></h1>
31
+ <h2><%=Monologue.site_subtitle%></h2>
32
+ </header>
33
+
34
+ <div class="content sixteen columns">
35
+ <%= yield %>
36
+ </div>
37
+ </div>
38
+ <footer>
39
+ <%= yield :footer %>
40
+ </footer>
41
+
42
+ <script type="text/javascript">
43
+ /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
44
+ var disqus_shortname = '<%= Monologue.disqus_shortname%>'; // required: replace example with your forum shortname
45
+
46
+ <% if Rails.env.development? %>
47
+ var disqus_developer = 1; // developer mode is on
48
+ <% end %>
49
+
50
+
51
+ /* * * DON'T EDIT BELOW THIS LINE * * */
52
+ (function () {
53
+ var s = document.createElement('script'); s.async = true;
54
+ s.type = 'text/javascript';
55
+ s.src = 'http://' + disqus_shortname + '.disqus.com/count.js';
56
+ (document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
57
+ }());
58
+ </script>
59
+ </body>
60
+ </html>
@@ -0,0 +1,16 @@
1
+ <%= post.error_messages %>
2
+ <%= post.fields_for @revision do |revision| %>
3
+ <%= revision.text_field :title, :label => t(".title"), :class => "span6" %>
4
+ <%= revision.text_area :content, :label => t(".content"), :class => "span12 ckeditor" %>
5
+ <%= revision.text_field :url, :label => raw("#{t(".url.before_generated_url")} '#{monologue.root_path}#{DateTime.now.year}/#{t(".url.generated_title")}'#{t(".url.after_generated_url")}") %>
6
+ <%= revision.text_field :published_at, :label => t(".published_at"), :data => {:datepicker => "datepicker"}, :value => revision.object.published_at.nil? ? "" : revision.object.published_at.strftime("%Y-%m-%d") %>
7
+ <% end %>
8
+ <%= post.check_box :published, :label => t(".published") %>
9
+
10
+ <%= post.submit t(".save"), :class => "btn btn-large btn-primary" %> <a href="<%= @revision.url %>" target="_blank" class="btn btn-large"><%= t(".preview") %></a>
11
+
12
+ <script>
13
+ if($.fn.datepicker.defaults_<%= I18n.locale %>){
14
+ $.fn.datepicker.defaults = $.fn.datepicker.defaults_<%= I18n.locale %>;
15
+ }
16
+ </script>
@@ -0,0 +1,4 @@
1
+ <h1><%= t(".header") %> "<%= @post.posts_revisions.last.title %>"</h1>
2
+ <%= monologue_admin_form_for ["admin", @post], :method => :put do |post| %>
3
+ <%= render :partial => 'form', :locals => {:post => post} %>
4
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <table class="table table-striped table-bordered">
2
+ <thead>
3
+ <tr>
4
+ <th><%= t(".title")%></th>
5
+ <th><%= t(".status")%></th>
6
+ <th></th>
7
+ <th></th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <% @posts.each do |post|%>
12
+ <tr>
13
+ <td><%= link_to post.posts_revisions.last.title, edit_admin_post_path(post) %></td>
14
+ <td><span class="label label-<%= post.published ? "success" : "important" %>"><%= post.published ? t(".published") : t(".not_published") %></span></td>
15
+ <td><%= link_to t(".edit"), edit_admin_post_path(post), :class => "btn btn-small" %></td>
16
+ <td><%= link_to t(".delete"), ["admin", post], :confirm => 'Are you sure?', :method => :delete, :class => "btn btn-small btn-danger" %></td>
17
+ </tr>
18
+ <% end %>
19
+ </tbody>
20
+ </table>
@@ -0,0 +1,4 @@
1
+ <h1><%= t(".header") %></h1>
2
+ <%= monologue_admin_form_for ["admin", @post] do |post| %>
3
+ <%= render :partial => 'form', :locals => {:post => post} %>
4
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <h1><%= t(".title") %></h1>
2
+
3
+ <%= form_tag admin_sessions_path do %>
4
+ <div class="field">
5
+ <%= label_tag :email, t(".email") %>
6
+ <%= text_field_tag :email, params[:email] %>
7
+ </div>
8
+ <div class="field">
9
+ <%= label_tag :password, t(".password") %>
10
+ <%= password_field_tag :password %>
11
+ </div>
12
+ <div class="actions"><%= submit_tag t(".button"), :class => "btn btn-large btn-primary" %></div>
13
+ <% end %>
@@ -0,0 +1,9 @@
1
+
2
+ <% content_for :title do %>
3
+ <%= t(".title") %>
4
+ <% end %>
5
+
6
+ <article>
7
+ <header><h1><%= t(".title") %></h1></header>
8
+ <p><%= t(".message") %></p>
9
+ </article>
@@ -0,0 +1,9 @@
1
+ <div id="pagination">
2
+ <% if @posts.total_pages > 1 && @posts.total_pages != @page.to_i %>
3
+ <%= link_to t(".older_posts"), posts_page_path(@page.to_i+1), :class => "older_posts" %>
4
+ <% end %>
5
+
6
+ <% if @posts.total_pages > 1 && @page.to_i > 1 %>
7
+ <%= link_to t(".newer_posts"), posts_page_path(@page.to_i-1), :class => "newer_posts" %>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1,42 @@
1
+ <footer class="monologue-social-sharing well">
2
+ <h2><%= t(".tagline") %></h2>
3
+ <!-- FACEBOOK LIKE -->
4
+
5
+ <div id="fb-root"></div>
6
+ <script>(function(d, s, id) {
7
+ var js, fjs = d.getElementsByTagName(s)[0];
8
+ if (d.getElementById(id)) return;
9
+ js = d.createElement(s); js.id = id;
10
+ js.src = "//connect.facebook.net/<%= Monologue.facebook_like_locale || "en_US" %>/all.js#xfbml=1&appId=288133081204194";
11
+ fjs.parentNode.insertBefore(js, fjs);
12
+ }(document, 'script', 'facebook-jssdk'));</script>
13
+ <div class="fb-like" data-layout="box_count" data-show-faces="true" data-font="verdana"></div>
14
+ <!-- /FACEBOOK LIKE -->
15
+
16
+ <!-- TWITTER -->
17
+ <a href="https://twitter.com/share" class="twitter-share-button"
18
+ data-lang="<%= Monologue.twitter_locale || "en" %>"
19
+ data-url="<%= request.url %>"
20
+ data-via="<%=Monologue.twitter_username%>"
21
+ data-text="<%=@revision.title%>"
22
+ data-count="vertical">Tweet</a>
23
+
24
+ <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
25
+ <!-- /TWITTER -->
26
+
27
+ <!-- GOOGLE +1 -->
28
+ <!-- Placez cette balise là où vous souhaitez positionner le bouton +1. -->
29
+ <g:plusone size="tall"></g:plusone>
30
+
31
+ <!-- Placez cet appel d'affichage à l'endroit approprié. -->
32
+ <script type="text/javascript">
33
+ window.___gcfg = {lang: '<%=Monologue.google_plusone_locale || "en" %>'};
34
+
35
+ (function() {
36
+ var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
37
+ po.src = 'https://apis.google.com/js/plusone.js';
38
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
39
+ })();
40
+ </script>
41
+ <!-- /GOOGLE +1 -->
42
+ </footer>
@@ -0,0 +1,19 @@
1
+ xml.instruct! :xml, :version => "1.0"
2
+ xml.rss :version => "2.0" do
3
+ xml.channel do
4
+ xml.title Monologue.site_name
5
+ xml.description Monologue.meta_description
6
+ xml.link root_url
7
+
8
+ for post in @posts
9
+ revision = post.posts_revisions.last
10
+ xml.item do
11
+ xml.title revision.title
12
+ xml.description raw(revision.content)
13
+ xml.pubDate revision.published_at.to_s(:rfc822)
14
+ xml.link Monologue.site_url + revision.url
15
+ xml.guid Monologue.site_url + revision.url
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ <% @posts.each do |post| %>
2
+ <% revision = post.posts_revisions.first %>
3
+
4
+ <section>
5
+ <header>
6
+ <h1><%= link_to revision.title, revision.url %></h1>
7
+ <time datetime="<%= revision.published_at %>">
8
+ <%= revision.published_at.to_date.to_formatted_s(:long_ordinal) %>
9
+ </time>&nbsp;&nbsp;|&nbsp;&nbsp;<%= revision.user.name %>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="<%= revision.url + "#disqus_thread" %>"></a>
10
+ </header>
11
+
12
+ <p><%= raw truncate(strip_tags(revision.content), :length => 1000) %></p>
13
+
14
+ <%= link_to t(".readmore"), revision.url, :class => "btn alignleft" %>
15
+ </section>
16
+ <% end %>
17
+
18
+ <%= render "pagination" %>