write 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Timeago is a jQuery plugin that makes it easy to support automatically
3
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
4
+ *
5
+ * @name timeago
6
+ * @version 0.11.4
7
+ * @requires jQuery v1.2.3+
8
+ * @author Ryan McGeary
9
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
10
+ *
11
+ * For usage and examples, visit:
12
+ * http://timeago.yarp.com/
13
+ *
14
+ * Copyright (c) 2008-2012, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
15
+ */
16
+ (function($) {
17
+ $.timeago = function(timestamp) {
18
+ if (timestamp instanceof Date) {
19
+ return inWords(timestamp);
20
+ } else if (typeof timestamp === "string") {
21
+ return inWords($.timeago.parse(timestamp));
22
+ } else if (typeof timestamp === "number") {
23
+ return inWords(new Date(timestamp));
24
+ } else {
25
+ return inWords($.timeago.datetime(timestamp));
26
+ }
27
+ };
28
+ var $t = $.timeago;
29
+
30
+ $.extend($.timeago, {
31
+ settings: {
32
+ refreshMillis: 60000,
33
+ allowFuture: false,
34
+ strings: {
35
+ prefixAgo: null,
36
+ prefixFromNow: null,
37
+ suffixAgo: "ago",
38
+ suffixFromNow: "from now",
39
+ seconds: "less than a minute",
40
+ minute: "about a minute",
41
+ minutes: "%d minutes",
42
+ hour: "about an hour",
43
+ hours: "about %d hours",
44
+ day: "a day",
45
+ days: "%d days",
46
+ month: "about a month",
47
+ months: "%d months",
48
+ year: "about a year",
49
+ years: "%d years",
50
+ wordSeparator: " ",
51
+ numbers: []
52
+ }
53
+ },
54
+ inWords: function(distanceMillis) {
55
+ var $l = this.settings.strings;
56
+ var prefix = $l.prefixAgo;
57
+ var suffix = $l.suffixAgo;
58
+ if (this.settings.allowFuture) {
59
+ if (distanceMillis < 0) {
60
+ prefix = $l.prefixFromNow;
61
+ suffix = $l.suffixFromNow;
62
+ }
63
+ }
64
+
65
+ var seconds = Math.abs(distanceMillis) / 1000;
66
+ var minutes = seconds / 60;
67
+ var hours = minutes / 60;
68
+ var days = hours / 24;
69
+ var years = days / 365;
70
+
71
+ function substitute(stringOrFunction, number) {
72
+ var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
73
+ var value = ($l.numbers && $l.numbers[number]) || number;
74
+ return string.replace(/%d/i, value);
75
+ }
76
+
77
+ var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
78
+ seconds < 90 && substitute($l.minute, 1) ||
79
+ minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
80
+ minutes < 90 && substitute($l.hour, 1) ||
81
+ hours < 24 && substitute($l.hours, Math.round(hours)) ||
82
+ hours < 42 && substitute($l.day, 1) ||
83
+ days < 30 && substitute($l.days, Math.round(days)) ||
84
+ days < 45 && substitute($l.month, 1) ||
85
+ days < 365 && substitute($l.months, Math.round(days / 30)) ||
86
+ years < 1.5 && substitute($l.year, 1) ||
87
+ substitute($l.years, Math.round(years));
88
+
89
+ var separator = $l.wordSeparator === undefined ? " " : $l.wordSeparator;
90
+ return $.trim([prefix, words, suffix].join(separator));
91
+ },
92
+ parse: function(iso8601) {
93
+ var s = $.trim(iso8601);
94
+ s = s.replace(/\.\d+/,""); // remove milliseconds
95
+ s = s.replace(/-/,"/").replace(/-/,"/");
96
+ s = s.replace(/T/," ").replace(/Z/," UTC");
97
+ s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
98
+ return new Date(s);
99
+ },
100
+ datetime: function(elem) {
101
+ var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
102
+ return $t.parse(iso8601);
103
+ },
104
+ isTime: function(elem) {
105
+ // jQuery's `is()` doesn't play well with HTML5 in IE
106
+ return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
107
+ }
108
+ });
109
+
110
+ $.fn.timeago = function() {
111
+ var self = this;
112
+ self.each(refresh);
113
+
114
+ var $s = $t.settings;
115
+ if ($s.refreshMillis > 0) {
116
+ setInterval(function() { self.each(refresh); }, $s.refreshMillis);
117
+ }
118
+ return self;
119
+ };
120
+
121
+ function refresh() {
122
+ var data = prepareData(this);
123
+ if (!isNaN(data.datetime)) {
124
+ $(this).text(inWords(data.datetime));
125
+ }
126
+ return this;
127
+ }
128
+
129
+ function prepareData(element) {
130
+ element = $(element);
131
+ if (!element.data("timeago")) {
132
+ element.data("timeago", { datetime: $t.datetime(element) });
133
+ var text = $.trim(element.text());
134
+ if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
135
+ element.attr("title", text);
136
+ }
137
+ }
138
+ return element.data("timeago");
139
+ }
140
+
141
+ function inWords(date) {
142
+ return $t.inWords(distance(date));
143
+ }
144
+
145
+ function distance(date) {
146
+ return (new Date().getTime() - date.getTime());
147
+ }
148
+
149
+ // fix for IE6 suckage
150
+ document.createElement("abbr");
151
+ document.createElement("time");
152
+ }(jQuery));
@@ -3,11 +3,20 @@ require_dependency "write/application_controller"
3
3
  module Write
4
4
  class PostsController < ApplicationController
5
5
  layout Write.layout
6
+ include ApplicationHelper
6
7
  def index
8
+ GG.fetch if GG.empty?
7
9
  @posts = GG.all
8
10
  end
9
11
  def show
10
12
  @post = GG.find params[:code]
11
13
  end
14
+ def refresh
15
+ if write_admin?
16
+ GG.clear!
17
+ GG.fetch
18
+ end
19
+ redirect_to :action => :index
20
+ end
12
21
  end
13
22
  end
@@ -1,4 +1,7 @@
1
1
  module Write
2
2
  module ApplicationHelper
3
+ def write_admin?
4
+ current_user && Write.admin?(current_user.github)
5
+ end
3
6
  end
4
7
  end
@@ -0,0 +1,69 @@
1
+ module Write
2
+ class GG
3
+ def self.log message
4
+ Rails.logger.info "******WRITE******: #{message}"
5
+ end
6
+
7
+ def self.url path
8
+ "https://api.github.com" + path
9
+ end
10
+
11
+ def self.fetch
12
+ posts = []
13
+ Write.accounts.each do |username, password|
14
+ JSON.parse(open(url "/users/#{username}/gists").read).each do |p|
15
+ next unless p["files"]["write.md"]
16
+ post = new(p)
17
+ posts.push post
18
+ end
19
+ end
20
+ Rails.cache.write "write.posts", posts
21
+ end
22
+
23
+ def self.all
24
+ Rails.cache.read "write.posts"
25
+ end
26
+
27
+ def self.empty?
28
+ all.length == 0
29
+ end
30
+
31
+ def self.clear!
32
+ log "Clearing all cached posts"
33
+ Rails.cache.write "write.posts", []
34
+ end
35
+
36
+ def self.find code
37
+ all.find { |p| p.code == code }
38
+ end
39
+
40
+ attr_reader :code
41
+
42
+ def initialize attributes
43
+ @attributes = attributes
44
+ @code = attributes["description"].downcase.gsub(/[^a-z0-9]/, "-").squeeze
45
+ @raw_url = files["write.md"]["raw_url"]
46
+ GG.log "Caching post #{@code}"
47
+ @raw_data = open(@raw_url).read
48
+ Rails.cache.write "write.post-#{id}", @raw_data
49
+ end
50
+
51
+ def id
52
+ @attributes["id"]
53
+ end
54
+
55
+ def year
56
+ @attributes["created_at"].to_date.year
57
+ end
58
+
59
+ def content
60
+ Rails.cache.read "write.post-#{id}"
61
+ end
62
+
63
+ def method_missing method, *args, &block
64
+ attribute = @attributes[method.to_s]
65
+ return attribute if attribute
66
+ super
67
+ end
68
+ end
69
+ end
@@ -6,11 +6,17 @@
6
6
 
7
7
  <% @posts.each do |p| %>
8
8
  <div class="post-container">
9
- <%= link_to p.description, short_post_path(p.code), class: "post-title" %>
9
+ <%= link_to p.description, short_post_path(p.year, p.code), class: "post-title" %>
10
10
  <%= content_tag :div, time_ago_in_words(p.updated_at) + " ago", class: "post-time" %>
11
11
  <div class="post-content">
12
12
  <%= truncate_html(Write::MD.render(p.content), :length => 256).html_safe %>
13
- <%= link_to "Read", short_post_path(p.code), class: "post-continue" %>
13
+ <%= link_to "Read", short_post_path(p.year, p.code), class: "post-continue" %>
14
14
  </div>
15
15
  </div>
16
16
  <% end %>
17
+
18
+ <% if write_admin? %>
19
+ <div class="write-admin">
20
+ <%= link_to "Refresh all posts", posts_refresh_path %>
21
+ </div>
22
+ <% end %>
@@ -2,13 +2,15 @@
2
2
 
3
3
  <%= stylesheet_link_tag "write/application", :media => "all" %>
4
4
 
5
+ <%= content_tag :div, link_to(Write.tagline, root_path), class: "blog-title" %>
6
+
5
7
  <div id="post-view" data-comments-url="<%= @post.comments_url %>">
6
8
  <%= content_tag :div, @post.description, class: "post-title" %>
7
9
  <%= content_tag :div, time_ago_in_words(@post.updated_at) + " ago", class: "post-time" %>
8
10
 
9
11
  <%= content_tag :div, Write::MD.render(@post.content).html_safe, class: "post-content" %>
10
-
11
- <div id="comments">Loading ...</div>
12
+ <%= content_tag :div, link_to("Write a comment (requires a github account)", @post.html_url, :class => "btn"), class: "github-link" %>
13
+ <div id="comments"></div>
12
14
  </div>
13
15
 
14
- <%= javascript_include_tag "write/comments" %>
16
+ <%= javascript_include_tag "write/application" %>
@@ -1,5 +1,6 @@
1
1
  Write::Engine.routes.draw do
2
2
  resources :posts
3
- get ":code" => "posts#show", :as => :short_post
3
+ get ":year/:code" => "posts#show", :as => :short_post
4
+ get "refresh(/:code)" => "posts#refresh", :as => :posts_refresh
4
5
  root to: "posts#index"
5
6
  end
@@ -8,7 +8,12 @@ module Write
8
8
  mattr_accessor :config
9
9
 
10
10
  def self.accounts
11
- config[:accounts] || { "github" => "changeme" }
11
+ accounts = config[:accounts].presence || { "github" => "changeme" }
12
+ accounts.is_a?(Hash) ? accounts.keys : accounts
13
+ end
14
+
15
+ def self.admin? account
16
+ accounts.include? account
12
17
  end
13
18
 
14
19
  def self.title
@@ -28,7 +33,9 @@ module Write
28
33
  Pygmentize.process(code, language)
29
34
  end
30
35
  end
31
- MD = Redcarpet::Markdown.new(PygmentizeHTML, fenced_code_blocks: true, autolink: true, space_after_headers: true, strikethrough: true, superscript: true)
36
+ MD = Redcarpet::Markdown.new(PygmentizeHTML, :fenced_code_blocks => true, :autolink => true,
37
+ :lax_spacing => true, :space_after_headers => true,
38
+ :strikethrough => true, :superscript => true)
32
39
  end
33
40
 
34
41
  Write.config = {}
@@ -1,8 +1,13 @@
1
1
  module Write
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Write
4
+ initializer 'write.action_controller' do |app|
5
+ ActiveSupport.on_load :action_controller do
6
+ helper Write::ApplicationHelper
7
+ end
8
+ end
4
9
  config.after_initialize do
5
- GG.fetch if Write.accounts.present?
10
+ GG.clear!
6
11
  end
7
12
  end
8
13
  end
@@ -1,3 +1,3 @@
1
1
  module Write
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: write
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-13 00:00:00.000000000 Z
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -166,14 +166,15 @@ files:
166
166
  - app/controllers/write/application_controller.rb
167
167
  - app/controllers/write/posts_controller.rb
168
168
  - app/helpers/write/application_helper.rb
169
- - app/models/gg.rb
170
- - app/views/layouts/write/application.html.erb
169
+ - app/models/write/gg.rb
171
170
  - app/views/write/posts/index.html.erb
172
171
  - app/views/write/posts/show.html.erb
173
172
  - app/assets/stylesheets/write/application.css
174
173
  - app/assets/stylesheets/write/pygments.css
175
174
  - app/assets/javascripts/write/application.js
176
175
  - app/assets/javascripts/write/comments.js.coffee
176
+ - app/assets/javascripts/write/markdown.js
177
+ - app/assets/javascripts/write/timeago.js
177
178
  - config/routes.rb
178
179
  - lib/write/version.rb
179
180
  - lib/write/engine.rb
@@ -1,50 +0,0 @@
1
- class GG
2
- def self.url path
3
- "https://api.github.com" + path
4
- end
5
-
6
- def self.fetch
7
- posts = []
8
- Write.accounts.each do |username, password|
9
- JSON.parse(open(url "/users/#{username}/gists").read).each do |p|
10
- next unless p["files"]["write.md"]
11
- post = new(p)
12
- posts.push post
13
- end
14
- end
15
- Rails.cache.write "write.posts", posts
16
- end
17
-
18
- def self.all
19
- Rails.cache.read "write.posts"
20
- end
21
-
22
- def self.find code
23
- all.find { |p| p.code == code }
24
- end
25
-
26
- attr_reader :code
27
-
28
- def initialize attributes
29
- @attributes = attributes
30
- @code = attributes["description"].downcase.gsub(/[^a-z0-9]/, "-").squeeze
31
- @raw_url = files["write.md"]["raw_url"]
32
- puts "*** Write *** Caching post #{@code}"
33
- @raw_data = open(@raw_url).read
34
- Rails.cache.write "write.post-#{id}", @raw_data
35
- end
36
-
37
- def id
38
- @attributes["id"]
39
- end
40
-
41
- def content
42
- Rails.cache.read "write.post-#{id}"
43
- end
44
-
45
- def method_missing method, *args, &block
46
- attribute = @attributes[method.to_s]
47
- return attribute if attribute
48
- super
49
- end
50
- end
@@ -1,14 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Write</title>
5
- <%= stylesheet_link_tag "write/application", :media => "all" %>
6
- <%= javascript_include_tag "write/application" %>
7
- <%= csrf_meta_tags %>
8
- </head>
9
- <body>
10
-
11
- <%= yield %>
12
-
13
- </body>
14
- </html>