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.
- data/README.md +36 -3
- data/app/assets/javascripts/write/comments.js.coffee +2 -2
- data/app/assets/javascripts/write/markdown.js +1616 -0
- data/app/assets/javascripts/write/timeago.js +152 -0
- data/app/controllers/write/posts_controller.rb +9 -0
- data/app/helpers/write/application_helper.rb +3 -0
- data/app/models/write/gg.rb +69 -0
- data/app/views/write/posts/index.html.erb +8 -2
- data/app/views/write/posts/show.html.erb +5 -3
- data/config/routes.rb +2 -1
- data/lib/write.rb +9 -2
- data/lib/write/engine.rb +6 -1
- data/lib/write/version.rb +1 -1
- metadata +5 -4
- data/app/models/gg.rb +0 -50
- data/app/views/layouts/write/application.html.erb +0 -14
@@ -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
|
@@ -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"
|
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/
|
16
|
+
<%= javascript_include_tag "write/application" %>
|
data/config/routes.rb
CHANGED
data/lib/write.rb
CHANGED
@@ -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
|
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 = {}
|
data/lib/write/engine.rb
CHANGED
@@ -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.
|
10
|
+
GG.clear!
|
6
11
|
end
|
7
12
|
end
|
8
13
|
end
|
data/lib/write/version.rb
CHANGED
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
|
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-
|
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
|
data/app/models/gg.rb
DELETED
@@ -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>
|