serif 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +17 -17
- data/README.md +31 -3
- data/bin/serif +6 -0
- data/lib/serif/admin_server.rb +30 -8
- data/lib/serif/config.rb +4 -0
- data/lib/serif/content_file.rb +7 -22
- data/lib/serif/post.rb +0 -6
- data/lib/serif/server.rb +3 -1
- data/lib/serif/site.rb +57 -1
- data/lib/serif.rb +2 -0
- data/serif.gemspec +1 -1
- data/statics/assets/js/attachment.js +91 -0
- data/statics/assets/js/jquery.drop.js +33 -0
- data/statics/assets/js/jquery.insert.js +39 -0
- data/statics/skeleton/_config.yml +39 -1
- data/statics/templates/admin/edit_draft.liquid +9 -10
- data/statics/templates/admin/edit_post.liquid +6 -10
- data/statics/templates/admin/layout.liquid +47 -3
- data/statics/templates/admin/new_draft.liquid +5 -9
- data/test/content_file_spec.rb +39 -0
- data/test/file_digest_tag_spec.rb +4 -0
- data/test/filters_spec.rb +4 -0
- data/test/post_spec.rb +6 -0
- data/test/site_dir/_site/drafts/another-sample-draft/cdc8037ed098e34a19fc6671ab652f908dd94009ff642d4e5ee80fa566fa.html +13 -0
- data/test/site_dir/_site/drafts/sample-draft/f828e5f22d76b04c586e90680ac814e6b233d3d380f6be6975beba75081b.html +13 -0
- data/test/site_dir/_site/test-blog/post-to-be-published-on-generate.html +1 -0
- data/test/site_dir/_site/test-blog/sample-post.html +1 -0
- data/test/site_dir/_site/test-blog/second-post.html +1 -0
- data/test/site_dir/_templates/post.html +1 -0
- data/test/site_dir/_trash/1361047797-autopublish-draft +5 -0
- data/test/site_dir/_trash/{1360450928-autopublish-draft → 1361047797-test-draft} +1 -1
- data/test/site_generation_spec.rb +36 -0
- data/test/site_spec.rb +8 -0
- metadata +11 -4
- data/test/site_dir/_trash/1360450928-test-draft +0 -3
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
serif (0.2.
|
4
|
+
serif (0.2.3)
|
5
5
|
liquid (~> 2.4)
|
6
6
|
pygments.rb (~> 0.3)
|
7
7
|
rack (~> 1.0)
|
@@ -14,39 +14,39 @@ PATH
|
|
14
14
|
GEM
|
15
15
|
remote: http://rubygems.org/
|
16
16
|
specs:
|
17
|
-
diff-lcs (1.1.
|
17
|
+
diff-lcs (1.1.3)
|
18
18
|
liquid (2.4.1)
|
19
|
-
multi_json (1.
|
19
|
+
multi_json (1.6.1)
|
20
20
|
posix-spawn (0.3.6)
|
21
21
|
pygments.rb (0.3.7)
|
22
22
|
posix-spawn (~> 0.3.6)
|
23
23
|
yajl-ruby (~> 1.1.0)
|
24
|
-
rack (1.
|
24
|
+
rack (1.5.2)
|
25
25
|
rack-protection (1.3.2)
|
26
26
|
rack
|
27
27
|
rack-rewrite (1.3.3)
|
28
|
-
rake (0.9.
|
28
|
+
rake (0.9.6)
|
29
29
|
redcarpet (2.2.2)
|
30
30
|
redhead (0.0.8)
|
31
|
-
rspec (2.
|
32
|
-
rspec-core (~> 2.
|
33
|
-
rspec-expectations (~> 2.
|
34
|
-
rspec-mocks (~> 2.
|
35
|
-
rspec-core (2.
|
36
|
-
rspec-expectations (2.
|
37
|
-
diff-lcs (~> 1.1.
|
38
|
-
rspec-mocks (2.
|
31
|
+
rspec (2.12.0)
|
32
|
+
rspec-core (~> 2.12.0)
|
33
|
+
rspec-expectations (~> 2.12.0)
|
34
|
+
rspec-mocks (~> 2.12.0)
|
35
|
+
rspec-core (2.12.2)
|
36
|
+
rspec-expectations (2.12.1)
|
37
|
+
diff-lcs (~> 1.1.3)
|
38
|
+
rspec-mocks (2.12.2)
|
39
39
|
simplecov (0.7.1)
|
40
40
|
multi_json (~> 1.0)
|
41
41
|
simplecov-html (~> 0.7.1)
|
42
42
|
simplecov-html (0.7.1)
|
43
|
-
sinatra (1.3.
|
44
|
-
rack (~> 1.
|
45
|
-
rack-protection (~> 1.
|
43
|
+
sinatra (1.3.4)
|
44
|
+
rack (~> 1.4)
|
45
|
+
rack-protection (~> 1.3)
|
46
46
|
tilt (~> 1.3, >= 1.3.3)
|
47
47
|
slop (3.4.3)
|
48
48
|
tilt (1.3.3)
|
49
|
-
timecop (0.5.
|
49
|
+
timecop (0.5.9.2)
|
50
50
|
yajl-ruby (1.1.0)
|
51
51
|
|
52
52
|
PLATFORMS
|
data/README.md
CHANGED
@@ -6,7 +6,15 @@ Serif is a file-based blogging engine intended for simple sites. It compiles Mar
|
|
6
6
|
|
7
7
|
# Changes and what's new
|
8
8
|
|
9
|
-
|
9
|
+
## Latest release
|
10
|
+
|
11
|
+
* Support drag-and-drop image uploading in the admin interface, with customisable paths. (#18)
|
12
|
+
* Generate private preview files for drafts, and generate the site on every draft change. (#19, #24)
|
13
|
+
* `serif dev` server serves 404s on missing files instead of 500 exceptions. (#22)
|
14
|
+
* Warn about _config.yml auth details after `serif new` skeleton (#23)
|
15
|
+
* Smarter onbeforeunload warnings that only fire if changes have been made. (#17)
|
16
|
+
|
17
|
+
See `CHANGELOG` for more.
|
10
18
|
|
11
19
|
# Contents of this README
|
12
20
|
|
@@ -210,6 +218,7 @@ admin:
|
|
210
218
|
username: username
|
211
219
|
password: password
|
212
220
|
permalink: /blog/:year/:month/:title
|
221
|
+
images_upload_path: /images/:timestamp_:name
|
213
222
|
```
|
214
223
|
|
215
224
|
If a permalink setting is not given in the configuration, the default is `/:title`. There are the following options available for permalinks:
|
@@ -221,9 +230,28 @@ Placeholder | Value
|
|
221
230
|
`:month` | Month as given in the filename, e.g., "01"
|
222
231
|
`:day` | Day as given in the filename, e.g., "28"
|
223
232
|
|
233
|
+
### Admin drag-and-drop upload path
|
234
|
+
|
235
|
+
The `images_upload_path` configuration setting is an _absolute path_ and will be relative to the base directory of your site, used in the admin interface to control where files are sent. The default value is `/images/:timestamp_:name`. Similar to permalinks, the following placeholders are available:
|
236
|
+
|
237
|
+
Placeholder | Value
|
238
|
+
----------- |:-----
|
239
|
+
`:slug` | URL "slug" at the time of upload, e.g., "your-post-title"
|
240
|
+
`:year` | Year at the time of upload, e.g., "2013"
|
241
|
+
`:month` | Month at the time of upload, e.g., "02"
|
242
|
+
`:day` | Day at the time of upload, e.g., "16"
|
243
|
+
`:name` | Original filename string of the image being uploaded
|
244
|
+
`:timestamp`| Unix timestamp, e.g., "1361057832685"
|
245
|
+
|
246
|
+
Any slashes in `images_upload_path` are converted to directories.
|
247
|
+
|
224
248
|
## Other files
|
225
249
|
|
226
|
-
Any other file in the directory's root will be copied over exactly as-is, with two caveats
|
250
|
+
Any other file in the directory's root will be copied over exactly as-is, with two caveats.
|
251
|
+
|
252
|
+
First, `images/` is used for the drag-and-drop file uploads from the admin interface. Files are named with `<unix_timestamp>.<extension>`.
|
253
|
+
|
254
|
+
Second, for any file ending in `.html` or `.xml`:
|
227
255
|
|
228
256
|
1. These files are assumed to contain [Liquid markup](http://liquidmarkup.org/) and will be processed as such.
|
229
257
|
2. Any header data will not be included in the processed output.
|
@@ -382,7 +410,7 @@ In addition to those mentioned above, such as the archive page variables, there
|
|
382
410
|
|
383
411
|
## Post template variables
|
384
412
|
|
385
|
-
These are available on individual post pages.
|
413
|
+
These are available on individual post pages, in `_template/post.html`.
|
386
414
|
|
387
415
|
* `{{ post }}` --- the post being processed. Allows access to variables like `post.url`, `post.title`, `post.slug`, `post.created` and `post.content`.
|
388
416
|
* `{{ prev_post }}` --- the post published chronologically before `post`.
|
data/bin/serif
CHANGED
@@ -50,6 +50,12 @@ def produce_skeleton(dir)
|
|
50
50
|
files.each do |f|
|
51
51
|
FileUtils.cp_r(f, dir, verbose: true)
|
52
52
|
end
|
53
|
+
|
54
|
+
puts
|
55
|
+
puts "*** NOTE ***"
|
56
|
+
puts
|
57
|
+
puts "You should now edit the username and password in _config.yml"
|
58
|
+
puts
|
53
59
|
end
|
54
60
|
|
55
61
|
commands = Slop::Commands.new do
|
data/lib/serif/admin_server.rb
CHANGED
@@ -42,7 +42,7 @@ class AdminServer
|
|
42
42
|
get "/admin/new/draft" do
|
43
43
|
content = Draft.new(site)
|
44
44
|
autofocus = "slug"
|
45
|
-
liquid :new_draft, locals: { post: content, autofocus: autofocus }
|
45
|
+
liquid :new_draft, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), post: content, autofocus: autofocus }
|
46
46
|
end
|
47
47
|
|
48
48
|
post "/admin/new/draft" do
|
@@ -61,12 +61,13 @@ class AdminServer
|
|
61
61
|
autofocus = "title" unless params[:title]
|
62
62
|
autofocus = "slug" unless params[:slug]
|
63
63
|
|
64
|
-
liquid :new_draft, locals: { error_message: error_message, post: content, autofocus: autofocus }
|
64
|
+
liquid :new_draft, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), error_message: error_message, post: content, autofocus: autofocus }
|
65
65
|
else
|
66
66
|
if Draft.exist?(site, params[:slug])
|
67
|
-
liquid :new_draft, locals: { error_message: error_message, post: content, autofocus: autofocus }
|
67
|
+
liquid :new_draft, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), error_message: error_message, post: content, autofocus: autofocus }
|
68
68
|
else
|
69
69
|
content.save(params[:markdown])
|
70
|
+
site.generate
|
70
71
|
redirect to("/admin")
|
71
72
|
end
|
72
73
|
end
|
@@ -106,7 +107,7 @@ class AdminServer
|
|
106
107
|
error_message = "You must pick a URL to use"
|
107
108
|
end
|
108
109
|
|
109
|
-
liquid :edit_draft, locals: { error_message: error_message, post: content }
|
110
|
+
liquid :edit_draft, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), error_message: error_message, post: content, private_url: site.private_url(content) }
|
110
111
|
else
|
111
112
|
content.save(params[:markdown])
|
112
113
|
|
@@ -114,9 +115,10 @@ class AdminServer
|
|
114
115
|
# a directory-change-level event.
|
115
116
|
if params[:publish] == "yes"
|
116
117
|
content.publish!
|
117
|
-
site.generate
|
118
118
|
end
|
119
119
|
|
120
|
+
site.generate
|
121
|
+
|
120
122
|
redirect to("/admin")
|
121
123
|
end
|
122
124
|
end
|
@@ -133,7 +135,7 @@ class AdminServer
|
|
133
135
|
error_message = "Content must not be blank." if params[:markdown].empty?
|
134
136
|
error_message = "Title must not be blank." if params[:title].empty?
|
135
137
|
|
136
|
-
liquid :edit_post, locals: { error_message: error_message, post: content }
|
138
|
+
liquid :edit_post, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), error_message: error_message, post: content }
|
137
139
|
else
|
138
140
|
content.save(params[:markdown])
|
139
141
|
site.generate
|
@@ -147,10 +149,10 @@ class AdminServer
|
|
147
149
|
|
148
150
|
if params[:type] == "posts"
|
149
151
|
content = site.posts.find { |p| p.slug == params[:slug] }
|
150
|
-
liquid :edit_post, locals: { post: content, autofocus: "markdown" }
|
152
|
+
liquid :edit_post, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), post: content, autofocus: "markdown" }
|
151
153
|
elsif params[:type] == "drafts"
|
152
154
|
content = Draft.from_slug(site, params[:slug])
|
153
|
-
liquid :edit_draft, locals: { post: content, autofocus: "markdown" }
|
155
|
+
liquid :edit_draft, locals: { images_path: site.config.image_upload_path.gsub(/"/, '\"'), post: content, autofocus: "markdown", private_url: site.private_url(content) }
|
154
156
|
else
|
155
157
|
response.status = 404
|
156
158
|
return "Nope"
|
@@ -171,6 +173,26 @@ class AdminServer
|
|
171
173
|
Redcarpet::Markdown.new(Serif::MarkupRenderer, fenced_code_blocks: true).render(content).strip
|
172
174
|
end
|
173
175
|
end
|
176
|
+
|
177
|
+
post "/admin/attachment" do
|
178
|
+
attachment = params["attachment"]
|
179
|
+
filename = attachment["final_name"]
|
180
|
+
file = attachment["file"]
|
181
|
+
uid = attachment["uid"]
|
182
|
+
|
183
|
+
tempfile = file[:tempfile]
|
184
|
+
|
185
|
+
FileUtils.mkdir_p(File.join(site.directory, File.dirname(filename)))
|
186
|
+
FileUtils.mkdir_p(File.dirname(site.site_path(filename)))
|
187
|
+
|
188
|
+
# move to the source directory
|
189
|
+
FileUtils.mv(tempfile.path, File.join(site.directory, filename))
|
190
|
+
|
191
|
+
# copy to production to avoid the need to generate right now
|
192
|
+
FileUtils.copy(File.join(site.directory, filename), site.site_path(filename))
|
193
|
+
|
194
|
+
"File uploaded"
|
195
|
+
end
|
174
196
|
end
|
175
197
|
|
176
198
|
def initialize(source_directory)
|
data/lib/serif/config.rb
CHANGED
data/lib/serif/content_file.rb
CHANGED
@@ -51,10 +51,6 @@ class ContentFile
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
def modified
|
55
|
-
File.mtime(@path)
|
56
|
-
end
|
57
|
-
|
58
54
|
def draft?
|
59
55
|
!published?
|
60
56
|
end
|
@@ -104,15 +100,17 @@ class ContentFile
|
|
104
100
|
converted_headers
|
105
101
|
end
|
106
102
|
|
107
|
-
def self.rename(original_slug, new_slug)
|
108
|
-
raise if File.exist?("#{dirname}/#{new_slug}")
|
109
|
-
File.rename("#{dirname}/#{original_slug}", "#{dirname}/#{new_slug}")
|
110
|
-
end
|
111
|
-
|
112
103
|
def save(markdown = nil)
|
113
104
|
markdown ||= content if !new?
|
114
105
|
|
115
106
|
save_path = path || "#{self.class.dirname}/#{@slug}"
|
107
|
+
|
108
|
+
if new?
|
109
|
+
set_publish_time(Time.now)
|
110
|
+
else
|
111
|
+
set_updated_time(Time.now)
|
112
|
+
end
|
113
|
+
|
116
114
|
File.open(save_path, "w") do |f|
|
117
115
|
f.puts %Q{#{raw_headers}
|
118
116
|
|
@@ -124,24 +122,11 @@ class ContentFile
|
|
124
122
|
|
125
123
|
true # always return true for now
|
126
124
|
end
|
127
|
-
|
128
|
-
def [](header)
|
129
|
-
h = headers[header]
|
130
|
-
if h
|
131
|
-
h
|
132
|
-
else
|
133
|
-
raise "no such header #{header}"
|
134
|
-
end
|
135
|
-
end
|
136
125
|
|
137
126
|
def inspect
|
138
127
|
%Q{<#{self.class} #{headers.inspect}>}
|
139
128
|
end
|
140
129
|
|
141
|
-
def self.all
|
142
|
-
Post.all + Draft.all
|
143
|
-
end
|
144
|
-
|
145
130
|
protected
|
146
131
|
|
147
132
|
def set_publish_time(time)
|
data/lib/serif/post.rb
CHANGED
@@ -36,12 +36,6 @@ class Post < ContentFile
|
|
36
36
|
all(site).find { |p| p.slug == slug }
|
37
37
|
end
|
38
38
|
|
39
|
-
def save(markdown)
|
40
|
-
# update the timestamp if the content has actually changed
|
41
|
-
set_updated_time(Time.now) unless markdown.strip == content.strip
|
42
|
-
super
|
43
|
-
end
|
44
|
-
|
45
39
|
def to_liquid
|
46
40
|
h = {
|
47
41
|
"title" => title,
|
data/lib/serif/server.rb
CHANGED
@@ -7,6 +7,8 @@ class DevelopmentServer
|
|
7
7
|
class DevApp < Sinatra::Base
|
8
8
|
set :public_folder, Dir.pwd
|
9
9
|
|
10
|
+
not_found { "Resource not found" }
|
11
|
+
|
10
12
|
get "/" do
|
11
13
|
File.read(File.expand_path("_site/index.html"))
|
12
14
|
end
|
@@ -22,7 +24,7 @@ class DevelopmentServer
|
|
22
24
|
# make a naive assumption that there's a 404 file at 404.html
|
23
25
|
file ||= Dir[File.expand_path("_site/404.html")].first
|
24
26
|
|
25
|
-
File.read(file)
|
27
|
+
file ? File.read(file) : 404
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
data/lib/serif/site.rb
CHANGED
@@ -26,6 +26,7 @@ module Filters
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def encode_uri_component(string)
|
29
|
+
return "" unless string
|
29
30
|
CGI.escape(string)
|
30
31
|
end
|
31
32
|
|
@@ -42,7 +43,7 @@ class FileDigest < Liquid::Tag
|
|
42
43
|
DIGEST_CACHE = {}
|
43
44
|
|
44
45
|
# file_digest "file.css" [prefix:.]
|
45
|
-
Syntax = /^\s*(\S+)\s*(?:(prefix\s*:\s*\S+)\s*)
|
46
|
+
Syntax = /^\s*(\S+)\s*(?:(prefix\s*:\s*\S+)\s*)?$/
|
46
47
|
|
47
48
|
def initialize(tag_name, markup, tokens)
|
48
49
|
super
|
@@ -120,6 +121,18 @@ class Site
|
|
120
121
|
most_recent ? most_recent.updated : Time.now
|
121
122
|
end
|
122
123
|
|
124
|
+
# Gives the URL absolute path to a private draft preview.
|
125
|
+
#
|
126
|
+
# If the draft has no such preview available, returns nil.
|
127
|
+
def private_url(draft)
|
128
|
+
private_draft_pattern = site_path("/drafts/#{draft.slug}/*")
|
129
|
+
file = Dir[private_draft_pattern].first
|
130
|
+
|
131
|
+
return nil unless file
|
132
|
+
|
133
|
+
"/drafts/#{draft.slug}/#{File.basename(file, ".html")}"
|
134
|
+
end
|
135
|
+
|
123
136
|
def bypass?(filename)
|
124
137
|
!%w[.html .xml].include?(File.extname(filename))
|
125
138
|
end
|
@@ -296,6 +309,8 @@ class Site
|
|
296
309
|
next_post = post
|
297
310
|
end
|
298
311
|
|
312
|
+
generate_draft_previews(default_layout)
|
313
|
+
|
299
314
|
generate_archives(default_layout)
|
300
315
|
|
301
316
|
if Dir.exist?("_site")
|
@@ -308,6 +323,47 @@ class Site
|
|
308
323
|
|
309
324
|
private
|
310
325
|
|
326
|
+
# generates draft preview files for any unpublished drafts.
|
327
|
+
#
|
328
|
+
# uses the same template as live posts.
|
329
|
+
def generate_draft_previews(layout)
|
330
|
+
drafts = self.drafts
|
331
|
+
|
332
|
+
template = Liquid::Template.parse(File.read("_templates/post.html"))
|
333
|
+
|
334
|
+
# publish each draft under a randomly generated name, or use the
|
335
|
+
# existing file if one is present.
|
336
|
+
drafts.each do |draft|
|
337
|
+
url = private_url(draft)
|
338
|
+
if url
|
339
|
+
# take our existing URL like /drafts/foo/<random> (without .html)
|
340
|
+
# and give the filename
|
341
|
+
file = File.basename(url)
|
342
|
+
else
|
343
|
+
# create a new name
|
344
|
+
file = SecureRandom.hex(30)
|
345
|
+
end
|
346
|
+
|
347
|
+
# convert the name into a relative path
|
348
|
+
file = "drafts/#{draft.slug}/#{file}"
|
349
|
+
|
350
|
+
# the absolute path in the site's tmp path, where we create the file
|
351
|
+
# ready to be deployed.
|
352
|
+
live_preview_file = tmp_path(file)
|
353
|
+
FileUtils.mkdir_p(File.dirname(live_preview_file))
|
354
|
+
|
355
|
+
puts "#{url ? "Updating" : "Creating"} draft preview: #{file}"
|
356
|
+
|
357
|
+
File.open(live_preview_file + ".html", "w") do |f|
|
358
|
+
f.puts layout.render!(
|
359
|
+
"draft_preview" => true,
|
360
|
+
"page" => { "title" => [ "Draft Preview", draft.title ] },
|
361
|
+
"content" => template.render!("site" => self, "post" => draft)
|
362
|
+
)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
311
367
|
# goes through all draft posts that have "publish: now" headers and
|
312
368
|
# calls #publish! on each one
|
313
369
|
def preprocess_autopublish_drafts
|
data/lib/serif.rb
CHANGED
data/serif.gemspec
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
// Derived by Adam Prescott from a code snippet used
|
2
|
+
// with implied permission:
|
3
|
+
//
|
4
|
+
// http://blog.alexmaccaw.com/svbtle-image-uploading
|
5
|
+
|
6
|
+
var createAttachment = function(file, element) {
|
7
|
+
var data = new FormData();
|
8
|
+
|
9
|
+
var d = new Date();
|
10
|
+
var uid = d.getTime();
|
11
|
+
|
12
|
+
var month = d.getMonth().toString();
|
13
|
+
if (month.length < 2) { month = "0" + month; }
|
14
|
+
|
15
|
+
var day = d.getDate().toString();
|
16
|
+
if (day.length < 2) { day = "0" + day; }
|
17
|
+
|
18
|
+
var processedName = file.name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/__+/g, "_");
|
19
|
+
|
20
|
+
var s = Serif.variables["imageUploadPattern"];
|
21
|
+
|
22
|
+
if (/:slug/.test(s) && !(Serif.variables["currentSlug"] && Serif.variables["currentSlug"])) {
|
23
|
+
alert("Your image upload path is set to use a slug, but no such slug exists yet.");
|
24
|
+
return null;
|
25
|
+
}
|
26
|
+
|
27
|
+
var placeholderValues = {
|
28
|
+
":slug": Serif.variables["currentSlug"],
|
29
|
+
":timestamp": uid.toString(),
|
30
|
+
":year": d.getFullYear().toString(),
|
31
|
+
":month": month,
|
32
|
+
":day": day,
|
33
|
+
":name": processedName
|
34
|
+
};
|
35
|
+
|
36
|
+
$.each(placeholderValues, function(placeholder, value) {
|
37
|
+
s = s.replace(placeholder, value);
|
38
|
+
});
|
39
|
+
|
40
|
+
var extension = file.name.substring(file.name.lastIndexOf('.') + 1);
|
41
|
+
|
42
|
+
var finalName = s;
|
43
|
+
|
44
|
+
// if it doesn't already have the extension in the name, add it,
|
45
|
+
// otherwise, correct the, e.g., _png, to .png.
|
46
|
+
//
|
47
|
+
// this just avoids _png.png noise
|
48
|
+
if (finalName.substring(finalName.length - extension.length - 1) == ("_" + extension)) {
|
49
|
+
finalName = finalName.replace(new RegExp("_" + extension + "$"), "." + extension);
|
50
|
+
} else {
|
51
|
+
finalName = finalName + "." + extension;
|
52
|
+
}
|
53
|
+
|
54
|
+
data.append('attachment[file]', file);
|
55
|
+
data.append('attachment[uid]', uid);
|
56
|
+
data.append('attachment[final_name]', finalName);
|
57
|
+
|
58
|
+
$.ajax({
|
59
|
+
url: '/admin/attachment',
|
60
|
+
data: data,
|
61
|
+
cache: false,
|
62
|
+
contentType: false,
|
63
|
+
processData: false,
|
64
|
+
type: 'POST',
|
65
|
+
}).error(function() {
|
66
|
+
console.log("error uploading image");
|
67
|
+
});
|
68
|
+
|
69
|
+
var absText = '![' + file.name + '](' + finalName + ')';
|
70
|
+
$(element).insertAtCaret(absText);
|
71
|
+
};
|
72
|
+
|
73
|
+
$(function() {
|
74
|
+
if ($("[data-attachify]").length > 0) {
|
75
|
+
$(document).dropArea();
|
76
|
+
|
77
|
+
$(document).bind("drop", function(e) {
|
78
|
+
e.preventDefault();
|
79
|
+
e = e.originalEvent;
|
80
|
+
|
81
|
+
var files = e.dataTransfer.files;
|
82
|
+
|
83
|
+
for (var i=0; i < files.length; i++) {
|
84
|
+
// Only upload images
|
85
|
+
if (/image/.test(files[i].type)) {
|
86
|
+
createAttachment(files[i], $("[data-attachify]").first());
|
87
|
+
}
|
88
|
+
};
|
89
|
+
});
|
90
|
+
}
|
91
|
+
});
|
@@ -0,0 +1,33 @@
|
|
1
|
+
// All credit to Alex MacCaw:
|
2
|
+
//
|
3
|
+
// https://gist.github.com/maccman/2907187
|
4
|
+
|
5
|
+
(function($){
|
6
|
+
function dragEnter(e) {
|
7
|
+
$(e.target).addClass("dragOver");
|
8
|
+
e.stopPropagation();
|
9
|
+
e.preventDefault();
|
10
|
+
return false;
|
11
|
+
};
|
12
|
+
|
13
|
+
function dragOver(e) {
|
14
|
+
e.originalEvent.dataTransfer.dropEffect = "copy";
|
15
|
+
e.stopPropagation();
|
16
|
+
e.preventDefault();
|
17
|
+
return false;
|
18
|
+
};
|
19
|
+
|
20
|
+
function dragLeave(e) {
|
21
|
+
$(e.target).removeClass("dragOver");
|
22
|
+
e.stopPropagation();
|
23
|
+
e.preventDefault();
|
24
|
+
return false;
|
25
|
+
};
|
26
|
+
|
27
|
+
$.fn.dropArea = function(){
|
28
|
+
this.bind("dragenter", dragEnter).
|
29
|
+
bind("dragover", dragOver).
|
30
|
+
bind("dragleave", dragLeave);
|
31
|
+
return this;
|
32
|
+
};
|
33
|
+
})(jQuery);
|
@@ -0,0 +1,39 @@
|
|
1
|
+
// All credit to Alex MacCaw
|
2
|
+
//
|
3
|
+
// https://gist.github.com/maccman/2907189
|
4
|
+
|
5
|
+
(function($){
|
6
|
+
var insertAtCaret = function(value) {
|
7
|
+
if (document.selection) { // IE
|
8
|
+
this.focus();
|
9
|
+
sel = document.selection.createRange();
|
10
|
+
sel.text = value;
|
11
|
+
this.focus();
|
12
|
+
}
|
13
|
+
else if (this.selectionStart || this.selectionStart == '0') {
|
14
|
+
var startPos = this.selectionStart;
|
15
|
+
var endPos = this.selectionEnd;
|
16
|
+
var scrollTop = this.scrollTop;
|
17
|
+
|
18
|
+
this.value = [
|
19
|
+
this.value.substring(0, startPos),
|
20
|
+
value,
|
21
|
+
this.value.substring(endPos, this.value.length)
|
22
|
+
].join('');
|
23
|
+
|
24
|
+
this.focus();
|
25
|
+
this.selectionStart = startPos + value.length;
|
26
|
+
this.selectionEnd = startPos + value.length;
|
27
|
+
this.scrollTop = scrollTop;
|
28
|
+
|
29
|
+
} else {
|
30
|
+
throw new Error('insertAtCaret not supported');
|
31
|
+
}
|
32
|
+
};
|
33
|
+
|
34
|
+
$.fn.insertAtCaret = function(value){
|
35
|
+
$(this).each(function(){
|
36
|
+
insertAtCaret.call(this, value);
|
37
|
+
})
|
38
|
+
};
|
39
|
+
})(jQuery);
|
@@ -8,7 +8,45 @@
|
|
8
8
|
# permalink: [permalink format for generated posts]
|
9
9
|
#
|
10
10
|
# See the README for information on permalink formats.
|
11
|
-
|
11
|
+
#
|
12
|
+
# Image upload path configuration, used for any images added
|
13
|
+
# through the admin drag-and-drop interface:
|
14
|
+
#
|
15
|
+
# image_upload_path: [/some/absolute/path/:with/:placeholders]
|
16
|
+
#
|
17
|
+
# This should be an ABSOLUTE PATH and will be relative to your
|
18
|
+
# base site (e.g., mysite.com). The value used will have the
|
19
|
+
# relevant extension appended AUTOMATICALLY.
|
20
|
+
#
|
21
|
+
# The default value is: /images/:timestamp_:name
|
22
|
+
#
|
23
|
+
# Possible placeholders for the image upload path:
|
24
|
+
#
|
25
|
+
# :year
|
26
|
+
# current full year, e.g., "2013"
|
27
|
+
#
|
28
|
+
# :month
|
29
|
+
# current month as a two digit value, e.g., "02"
|
30
|
+
#
|
31
|
+
# :day
|
32
|
+
# current day as a two digit value, e.g., "01"
|
33
|
+
#
|
34
|
+
# :timestamp
|
35
|
+
# unix timestamp in seconds, e.g., "1361057832685"
|
36
|
+
#
|
37
|
+
# :name
|
38
|
+
# filename of the image being uploaded
|
39
|
+
#
|
40
|
+
# :slug
|
41
|
+
# the slug of the post at the time the image is added
|
42
|
+
#
|
43
|
+
# Any slashes in this string will become directories relative to the
|
44
|
+
# base site directory.
|
45
|
+
#
|
46
|
+
# Example: image_upload_path: /images/:slug/:year_:month_:day_:timestamp
|
47
|
+
#
|
48
|
+
# This would lead to an example path of
|
49
|
+
# /images/sample-post/2013_02_16_1361057832685.png
|
12
50
|
admin:
|
13
51
|
username: changethisusername
|
14
52
|
password: changethispassword
|
@@ -14,6 +14,10 @@
|
|
14
14
|
</code>
|
15
15
|
</h2>
|
16
16
|
|
17
|
+
<h2 id="private-url">
|
18
|
+
<code><span>preview:</span> <a href="{{ private_url }}">{{ private_url | escape }}</a></code>
|
19
|
+
</h2>
|
20
|
+
|
17
21
|
<div class="post{% if post.draft %} draft{% endif %}">
|
18
22
|
<header>
|
19
23
|
<h1 id="heading" class="post">
|
@@ -28,7 +32,7 @@
|
|
28
32
|
</header>
|
29
33
|
</div>
|
30
34
|
<div id="content-areas">
|
31
|
-
<textarea spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown"{% if autofocus == "markdown" %}{{ " autofocus" }}{% endif %}>{{ post.content | strip | escape }}</textarea>
|
35
|
+
<textarea data-attachify spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown"{% if autofocus == "markdown" %}{{ " autofocus" }}{% endif %}>{{ post.content | strip | escape }}</textarea>
|
32
36
|
|
33
37
|
<article id="entry" class="post" data-state="hidden"></article>
|
34
38
|
</div>
|
@@ -51,13 +55,8 @@
|
|
51
55
|
</form>
|
52
56
|
{% endif %}
|
53
57
|
|
54
|
-
|
58
|
+
<aside>
|
59
|
+
<p>Drag and drop images into the form above to upload them.</p>
|
60
|
+
</aside>
|
55
61
|
|
56
|
-
|
57
|
-
<script>
|
58
|
-
$(function() {
|
59
|
-
window.onbeforeunload = function() {
|
60
|
-
return "You may lose unsaved changes if you leave this page.";
|
61
|
-
}
|
62
|
-
});
|
63
|
-
</script>
|
62
|
+
</div>
|
@@ -17,14 +17,14 @@
|
|
17
17
|
<div class="post draft">
|
18
18
|
<header>
|
19
19
|
<h1 id="heading" class="post">
|
20
|
-
<textarea spellcheck="false" placeholder="It was a dark and stormy night" name="title" autofocus>{{ post.title | escape }}</textarea>
|
20
|
+
<textarea data-attachify spellcheck="false" placeholder="It was a dark and stormy night" name="title" autofocus>{{ post.title | escape }}</textarea>
|
21
21
|
</h1>
|
22
22
|
|
23
23
|
<time datetime="{{ post.created | date: "%Y-%m-%d" }}">{{ post.created | date: "%B %d %Y" }}</time>
|
24
24
|
</header>
|
25
25
|
</div>
|
26
26
|
<div id="content-areas">
|
27
|
-
<textarea spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown">{{ post.content | strip | escape }}</textarea>
|
27
|
+
<textarea data-attachify spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown">{{ post.content | strip | escape }}</textarea>
|
28
28
|
|
29
29
|
<article id="entry" class="post" data-state="hidden"></article>
|
30
30
|
</div>
|
@@ -33,12 +33,8 @@
|
|
33
33
|
|
34
34
|
<input type="checkbox" id="render-preview"> <label class="preview" for="render-preview">Preview</label>
|
35
35
|
</form>
|
36
|
-
</div>
|
37
36
|
|
38
|
-
<
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
-
});
|
44
|
-
</script>
|
37
|
+
<aside>
|
38
|
+
<p>Drag and drop images into the form above to upload them.</p>
|
39
|
+
</aside>
|
40
|
+
</div>
|
@@ -43,7 +43,7 @@ body > nav ul li:last-child {
|
|
43
43
|
background: none;
|
44
44
|
}
|
45
45
|
|
46
|
-
#edit #slug {
|
46
|
+
#edit #slug, #edit #private-url {
|
47
47
|
color: #aaaaaa;
|
48
48
|
text-transform: lowercase;
|
49
49
|
margin-bottom: 0;
|
@@ -182,6 +182,12 @@ body > nav ul li:last-child {
|
|
182
182
|
min-height: 20em;
|
183
183
|
}
|
184
184
|
|
185
|
+
#edit > aside {
|
186
|
+
opacity: 0.5;
|
187
|
+
position: absolute;
|
188
|
+
bottom: -4em;
|
189
|
+
}
|
190
|
+
|
185
191
|
#edit #entry {
|
186
192
|
display: none;
|
187
193
|
width: 100%;
|
@@ -336,11 +342,43 @@ body > nav ul li:last-child {
|
|
336
342
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
|
337
343
|
<script src="/admin/js/jquery.autosize.js"></script>
|
338
344
|
<script src="/admin/js/mousetrap.min.js"></script>
|
345
|
+
<script src="/admin/js/jquery.drop.js"></script>
|
346
|
+
<script src="/admin/js/jquery.insert.js"></script>
|
347
|
+
<script src="/admin/js/attachment.js"></script>
|
339
348
|
|
340
349
|
<script>
|
350
|
+
var Serif = {
|
351
|
+
"variables": {
|
352
|
+
"imageUploadPattern": "{{ images_path }}",
|
353
|
+
"currentSlug": {% if post.slug %}"{{ post.slug }}"{% else %}null{% endif %}
|
354
|
+
}
|
355
|
+
};
|
356
|
+
</script>
|
357
|
+
|
358
|
+
<script>
|
359
|
+
function setDepartureCheck(enable) {
|
360
|
+
if (enable) {
|
361
|
+
window.onbeforeunload = function() {
|
362
|
+
return "You may lose unsaved changes if you leave this page.";
|
363
|
+
}
|
364
|
+
} else {
|
365
|
+
window.onbeforeunload = null;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
|
369
|
+
setDepartureCheck(false);
|
370
|
+
|
371
|
+
$(function() {
|
372
|
+
$("input, textarea").change(function(event) {
|
373
|
+
setDepartureCheck(true);
|
374
|
+
});
|
375
|
+
});
|
376
|
+
|
341
377
|
$("#edit").ready(function() {
|
342
378
|
$("#slug input").blur(function() {
|
343
|
-
$(this).val($(this).val().trim().replace(/\s+/g, '-').replace(/^-+|-+$/g, '').toLowerCase());
|
379
|
+
$(this).val($(this).val().trim().replace(/\s+/g, '-').replace(/"/g, "-").replace(/^-+|-+$/g, '').toLowerCase());
|
380
|
+
|
381
|
+
Serif.variables["currentSlug"] = $(this).val();
|
344
382
|
});
|
345
383
|
|
346
384
|
$("#slug input").keyup(function(event) {
|
@@ -356,6 +394,10 @@ body > nav ul li:last-child {
|
|
356
394
|
|
357
395
|
$("#confirm-delete + label").text("Delete");
|
358
396
|
|
397
|
+
$("#edit form input[type=submit]").click(function(event) {
|
398
|
+
setDepartureCheck(false);
|
399
|
+
});
|
400
|
+
|
359
401
|
$("#delete input[type=submit]").click(function(event) {
|
360
402
|
var el = $(this);
|
361
403
|
|
@@ -372,6 +414,7 @@ body > nav ul li:last-child {
|
|
372
414
|
|
373
415
|
$("#confirm-delete + label").click(function(event) {
|
374
416
|
event.preventDefault();
|
417
|
+
setDepartureCheck(false);
|
375
418
|
$(this).parent("form").submit();
|
376
419
|
});
|
377
420
|
|
@@ -425,6 +468,7 @@ body > nav ul li:last-child {
|
|
425
468
|
});
|
426
469
|
|
427
470
|
Mousetrap.bind(["ctrl+s", "command+s"], function(e) {
|
471
|
+
setDepartureCheck(false);
|
428
472
|
$("#edit form input[type=submit]").click();
|
429
473
|
$("#shortcuts").hide();
|
430
474
|
return false;
|
@@ -445,7 +489,7 @@ body > nav ul li:last-child {
|
|
445
489
|
});
|
446
490
|
|
447
491
|
Mousetrap.bind(["ctrl+.", "command+."], function(e) {
|
448
|
-
$("#delete, #save, #render-preview + label, #publish + label").fadeToggle();
|
492
|
+
$("#delete, #save, #render-preview + label, #publish + label, #edit > aside, #private-url").fadeToggle();
|
449
493
|
$("#slug, .post > header time").slideToggle();
|
450
494
|
$("#content").focus();
|
451
495
|
$("body > nav").toggleClass("hidden");
|
@@ -22,7 +22,7 @@
|
|
22
22
|
</header>
|
23
23
|
</div>
|
24
24
|
<div id="content-areas">
|
25
|
-
<textarea spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown"{% if autofocus == "markdown" %}{{ " autofocus" }}{% endif %}>{{ post.content | strip | escape }}</textarea>
|
25
|
+
<textarea data-attachify spellcheck="false" id="content" placeholder="Lorem ipsum dolor sit amet." name="markdown"{% if autofocus == "markdown" %}{{ " autofocus" }}{% endif %}>{{ post.content | strip | escape }}</textarea>
|
26
26
|
|
27
27
|
<article id="entry" class="post" data-state="hidden"></article>
|
28
28
|
</div>
|
@@ -32,12 +32,8 @@
|
|
32
32
|
<input type="checkbox" id="render-preview"> <label class="preview" for="render-preview">Preview</label>
|
33
33
|
</form>
|
34
34
|
|
35
|
-
|
35
|
+
<aside>
|
36
|
+
<p>Drag and drop images into the form above to upload them.</p>
|
37
|
+
</aside>
|
36
38
|
|
37
|
-
|
38
|
-
$(function() {
|
39
|
-
window.onbeforeunload = function() {
|
40
|
-
return "You may lose unsaved changes if you leave this page.";
|
41
|
-
}
|
42
|
-
});
|
43
|
-
</script>
|
39
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Serif::ContentFile do
|
4
|
+
subject do
|
5
|
+
Serif::Site.new(testing_dir)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#title=" do
|
9
|
+
it "sets the underlying header value to the assigned title" do
|
10
|
+
(subject.drafts + subject.posts).each do |content_file|
|
11
|
+
content_file.title = "foobar"
|
12
|
+
content_file.headers[:title].should == "foobar"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#save(markdown)" do
|
18
|
+
it "sets the underlying updated time value for posts" do
|
19
|
+
draft = Serif::Draft.new(subject)
|
20
|
+
draft.title = "Testing"
|
21
|
+
draft.slug = "hi"
|
22
|
+
|
23
|
+
begin
|
24
|
+
draft.save("# Some content")
|
25
|
+
draft.publish!
|
26
|
+
|
27
|
+
post = Serif::Post.from_slug(subject, draft.slug)
|
28
|
+
|
29
|
+
t = Time.now
|
30
|
+
Timecop.freeze(t + 30) do
|
31
|
+
post.save("# Heading content")
|
32
|
+
post.updated.to_i.should == (t + 30).to_i
|
33
|
+
end
|
34
|
+
ensure
|
35
|
+
FileUtils.rm(post.path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -30,5 +30,9 @@ describe Serif::FileDigest do
|
|
30
30
|
it "ignores trailing whitespace on the prefix" do
|
31
31
|
file_digest("test-stylesheet.css prefix:. ").render(@context).should == ".f8390232f0c354a871f9ba0ed306163c"
|
32
32
|
end
|
33
|
+
|
34
|
+
it "raises a SyntaxError on invalid syntax" do
|
35
|
+
expect { file_digest("test-stylesheet.css pefoiejw").render(@context) }.to raise_error(SyntaxError)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
end
|
data/test/filters_spec.rb
CHANGED
data/test/post_spec.rb
CHANGED
@@ -12,4 +12,10 @@ describe Serif::Post do
|
|
12
12
|
it "uses the config file's permalink value" do
|
13
13
|
@posts.all? { |p| p.url == "/test-blog/#{p.slug}" }.should be_true
|
14
14
|
end
|
15
|
+
|
16
|
+
describe "#inspect" do
|
17
|
+
it "includes headers" do
|
18
|
+
@posts.all? { |p| p.inspect.should include(p.headers.inspect) }
|
19
|
+
end
|
20
|
+
end
|
15
21
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<meta charset="UTF-8">
|
3
|
+
<title>My site: Draft Preview - another sample draft</title>
|
4
|
+
<h1>mysite.com</h1>
|
5
|
+
|
6
|
+
|
7
|
+
<h2>another sample draft</h2>
|
8
|
+
|
9
|
+
<p>another-sample-draft</p>
|
10
|
+
|
11
|
+
<p><a href="http://twitter.com/share?text=another+sample+draft&url=http%3A%2F%2Fwww.mysite.com">Submit this to Twitter.</p>
|
12
|
+
|
13
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<meta charset="UTF-8">
|
3
|
+
<title>My site: Draft Preview - Sample draft</title>
|
4
|
+
<h1>mysite.com</h1>
|
5
|
+
|
6
|
+
|
7
|
+
<h2>Sample draft</h2>
|
8
|
+
|
9
|
+
<p>Just a sample draft.</p>
|
10
|
+
|
11
|
+
<p><a href="http://twitter.com/share?text=Sample+draft&url=http%3A%2F%2Fwww.mysite.com">Submit this to Twitter.</p>
|
12
|
+
|
13
|
+
|
@@ -49,6 +49,42 @@ describe Serif::Site do
|
|
49
49
|
previous_title[/(?<=: ).+/].should == "Sample post"
|
50
50
|
end
|
51
51
|
|
52
|
+
it "sets a draft_preview flag for preview urls" do
|
53
|
+
preview_flag_pattern = /draftpreviewflagexists/
|
54
|
+
|
55
|
+
subject.generate
|
56
|
+
|
57
|
+
d = Serif::Draft.from_slug(subject, "sample-draft")
|
58
|
+
preview_contents = File.read(testing_dir("_site/#{subject.private_url(d)}.html"))
|
59
|
+
preview_contents =~ preview_flag_pattern
|
60
|
+
|
61
|
+
# does not exist on live published pages
|
62
|
+
(File.read(testing_dir("_site/test-blog/second-post.html")) =~ preview_flag_pattern).should be_false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "creates draft preview files" do
|
66
|
+
subject.generate
|
67
|
+
|
68
|
+
Dir.exist?(testing_dir("_site/drafts")).should be_true
|
69
|
+
Dir[File.join(testing_dir("_site/drafts/*"))].size.should == subject.drafts.size
|
70
|
+
|
71
|
+
Dir.exist?(testing_dir("_site/drafts/sample-draft")).should be_true
|
72
|
+
Dir[File.join(testing_dir("_site/drafts/sample-draft"), "*.html")].size.should == 1
|
73
|
+
|
74
|
+
d = Serif::Draft.from_slug(subject, "sample-draft")
|
75
|
+
subject.private_url(d).should_not be_nil
|
76
|
+
|
77
|
+
# absolute paths
|
78
|
+
(subject.private_url(d) =~ /\A\/drafts\/#{d.slug}\/.*\z/).should be_true
|
79
|
+
|
80
|
+
# 60 characters long (30 bytes as hex chars)
|
81
|
+
(subject.private_url(d) =~ /\A\/drafts\/#{d.slug}\/[a-z0-9]{60}\z/).should be_true
|
82
|
+
|
83
|
+
# does not create more than one
|
84
|
+
subject.generate
|
85
|
+
Dir[File.join(testing_dir("_site/drafts/sample-draft"), "*.html")].size.should == 1
|
86
|
+
end
|
87
|
+
|
52
88
|
context "for drafts with a publish: now header" do
|
53
89
|
before :all do
|
54
90
|
@time = Time.utc(2012, 12, 21, 15, 30, 00)
|
data/test/site_spec.rb
CHANGED
@@ -23,6 +23,14 @@ describe Serif::Site do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe "#private_url" do
|
27
|
+
it "returns nil for a draft without an existing file" do
|
28
|
+
d = double("")
|
29
|
+
d.stub(:slug) { "foo" }
|
30
|
+
subject.private_url(d).should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
26
34
|
describe "#latest_update_time" do
|
27
35
|
it "is the latest time that a post was updated" do
|
28
36
|
subject.latest_update_time.should == Serif::Post.all(subject).max_by { |p| p.updated }.updated
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serif
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
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: 2013-02-
|
12
|
+
date: 2013-02-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -235,13 +235,17 @@ files:
|
|
235
235
|
- statics/skeleton/_layouts/default.html
|
236
236
|
- statics/skeleton/archive.html
|
237
237
|
- statics/assets/js/mousetrap.min.js
|
238
|
+
- statics/assets/js/jquery.insert.js
|
239
|
+
- statics/assets/js/attachment.js
|
238
240
|
- statics/assets/js/jquery.autosize.js
|
241
|
+
- statics/assets/js/jquery.drop.js
|
239
242
|
- bin/serif
|
240
243
|
- test/test_helper.rb
|
241
244
|
- test/filters_spec.rb
|
242
245
|
- test/config_spec.rb
|
243
246
|
- test/liquid_filter_date_extension_spec.rb
|
244
247
|
- test/site_spec.rb
|
248
|
+
- test/content_file_spec.rb
|
245
249
|
- test/draft_spec.rb
|
246
250
|
- test/markup_renderer_spec.rb
|
247
251
|
- test/site_dir/index.html
|
@@ -257,6 +261,8 @@ files:
|
|
257
261
|
- test/site_dir/_site/test-archive/2013/01/index.html
|
258
262
|
- test/site_dir/_site/test-archive/2012/12/index.html
|
259
263
|
- test/site_dir/_site/test-archive/2012/11/index.html
|
264
|
+
- test/site_dir/_site/drafts/another-sample-draft/cdc8037ed098e34a19fc6671ab652f908dd94009ff642d4e5ee80fa566fa.html
|
265
|
+
- test/site_dir/_site/drafts/sample-draft/f828e5f22d76b04c586e90680ac814e6b233d3d380f6be6975beba75081b.html
|
260
266
|
- test/site_dir/_site/index.html
|
261
267
|
- test/site_dir/_site/file-digest-test.html
|
262
268
|
- test/site_dir/_site/page-alt-layout.html
|
@@ -266,8 +272,8 @@ files:
|
|
266
272
|
- test/site_dir/_site/test-blog/second-post.html
|
267
273
|
- test/site_dir/_site/test-blog/sample-post.html
|
268
274
|
- test/site_dir/_site/archive.html
|
269
|
-
- test/site_dir/_trash/
|
270
|
-
- test/site_dir/_trash/
|
275
|
+
- test/site_dir/_trash/1361047797-test-draft
|
276
|
+
- test/site_dir/_trash/1361047797-autopublish-draft
|
271
277
|
- test/site_dir/_posts/2012-01-05-sample-post
|
272
278
|
- test/site_dir/_posts/2013-01-01-second-post
|
273
279
|
- test/site_dir/_layouts/alt-layout.html
|
@@ -312,6 +318,7 @@ test_files:
|
|
312
318
|
- test/config_spec.rb
|
313
319
|
- test/liquid_filter_date_extension_spec.rb
|
314
320
|
- test/site_spec.rb
|
321
|
+
- test/content_file_spec.rb
|
315
322
|
- test/draft_spec.rb
|
316
323
|
- test/markup_renderer_spec.rb
|
317
324
|
- test/file_digest_tag_spec.rb
|