write 0.0.1 → 0.2.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.
@@ -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>