serif 0.2.2 → 0.2.3
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/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 = '';
|
|
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
|