spontaneous 0.2.0.beta7 → 0.2.0.beta8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37102d6cbb9bfed6dc5d0360590d78f569ca3194
4
- data.tar.gz: ae60e964be4ae4a4f9fbfd94154c28c1766be9ce
3
+ metadata.gz: 1fbce2b0e756da55c854fb535ed8454a1aed9aed
4
+ data.tar.gz: 9a3d4faf5726570997d13c3786246768c3c3495e
5
5
  SHA512:
6
- metadata.gz: a05914ff42c526f549a6e0367d9df545e700b233eb0312bb570448eeb0e48434764c7c6f8b83088c34680ce2bc51bb2e9f27665165947ce3b1546532dcf6a044
7
- data.tar.gz: b0ca442ffa7eef22df25a7df9813f6d8c1263ec359fffff1eb4b6e3ad6cc6c73bb4a5d9f2265e949778fd07b639891fb2635dc076fd6bdcbf444cf32514fcc87
6
+ metadata.gz: 71decdd38247bc06e094eec89575a899b8e95a4156f4a8b431548b58a591f3721ee585b134a420faa773beed01be88c9ca70470db1d425d61bd5fdba8d7167d3
7
+ data.tar.gz: 08ddbb0a24ccf19d7b2a76ab72e0d677ade2106d259deb00a3c9884aaa84454ac1073ee29d56eebc78fdca3e4886898c1477f792deb49ad66e5dffb7b823bd42
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.2.0.beta8, released 2014-10-29
2
+
3
+ #### Fixes
4
+
5
+ - Include `-webkit` prefixed versions of all flexbox properties to fix display in Safari
6
+ - Fix image drag & drop in Chrome -- revoking blob URLs immediately now results in a broken image
7
+ - Remove all references to `Page#path` in initialization to avoid trying to resolve the full path before the pages' parents have been assigned
8
+ - Explicitly copy compiled assets to the new published revision using a new core publish step rather than hijack the asset compilation/resolution step. This ensures that dependencies are copied on a second publish.
9
+ - Fix layout of box tabs by using appropriate mixins to apply `-webkit` prefixed styles
10
+
1
11
  ## 0.2.0.beta7, released 2014-09-03
2
12
 
3
13
  #### Fixes
@@ -659,17 +659,17 @@
659
659
  height: 100%;
660
660
 
661
661
  .slot-tabs {
662
+ @include display-box;
663
+ @include box-orient(row);
662
664
  min-height: 32px;
663
665
  vertical-align: top;
664
666
  @include vertical-gradient(#666666, #4d4d4d);
665
667
  padding-left: $container-padding;
666
668
  padding-right: $container-padding;
667
- display: flex;
668
- flex-direction: row;
669
669
  li {
670
670
  @include box-sizing;
671
671
  @include interface;
672
- flex: 1 1 auto;
672
+ @include box-flex(1);
673
673
  font-size: 11px;
674
674
  color: #dddddd;
675
675
  display: block;
@@ -1669,7 +1669,7 @@
1669
1669
 
1670
1670
  @media all and (max-width: 1000px) {
1671
1671
  .fields-preview-text li {
1672
- flex-direction: column;
1672
+ @include box-orient(column);
1673
1673
  }
1674
1674
  }
1675
1675
  @import "top.css.scss";
@@ -115,6 +115,7 @@ $target-name-size: 14px;
115
115
  // display: -webkit-box;
116
116
  // display: -moz-box;
117
117
  display: flex;
118
+ display: -webkit-flex;
118
119
  }
119
120
  @mixin box-sizing($model: border-box) {
120
121
  -webkit-box-sizing: $model;
@@ -124,15 +125,19 @@ $target-name-size: 14px;
124
125
 
125
126
  @mixin box-orient($direction: row) {
126
127
  flex-direction: $direction;
128
+ -webkit-flex-direction: $direction;
127
129
  }
128
130
  @mixin box-pack($packing: center) {
129
131
  justify-content: $packing;
132
+ -webkit-justify-content: $packing;
130
133
  }
131
134
  @mixin box-align($aligning: start) {
132
135
  align-items: $aligning;
136
+ -webkit-align-items: $aligning;
133
137
  }
134
138
  @mixin box-flex($flex: 1) {
135
- flex-grow: $flex;
139
+ flex-grow: $flex;
140
+ -webkit-flex-grow: $flex;
136
141
  }
137
142
 
138
143
  @mixin no-select {
@@ -413,6 +413,7 @@ $dialogue-title-height: 32px;
413
413
  background-color: rgba(33, 33, 33, 0.9);
414
414
  color: #aaaaaa;
415
415
  font-size: 0.7em;
416
+ z-index: 1;
416
417
  .lock-state {
417
418
  overflow: auto;
418
419
  padding: $unit/2;
@@ -4,6 +4,10 @@ Spontaneous.Field.File = (function($, S) {
4
4
  var FileField = new JS.Class(Spontaneous.Field.String, {
5
5
  selected_files: false,
6
6
 
7
+ initialize: function(dialogue, conflict) {
8
+ this.blobs = [];
9
+ this.callSuper();
10
+ },
7
11
  currentValue: function() {
8
12
  var pending, v = this.get('value');
9
13
  if ((pending = v.__pending__)) {
@@ -111,10 +115,16 @@ Spontaneous.Field.File = (function($, S) {
111
115
  dialogue.open();
112
116
  },
113
117
  unload: function() {
114
- this.callSuper();
115
118
  this.input = null;
116
119
  this._progress_bar = null;
120
+ this.freeBlobs();
117
121
  Spontaneous.UploadManager.unregister(this);
122
+ this.callSuper();
123
+ },
124
+ freeBlobs: function() {
125
+ this.blobs.forEach(function(url) {
126
+ window.URL.revokeObjectURL(url);
127
+ });
118
128
  },
119
129
  upload_complete: function(values) {
120
130
  this.set('value', values.processed_value);
@@ -146,6 +156,12 @@ Spontaneous.Field.File = (function($, S) {
146
156
  return true;
147
157
  },
148
158
 
159
+ createObjectURL: function(file) {
160
+ var url = window.URL.createObjectURL(file);
161
+ this.blobs.push(url);
162
+ return url;
163
+ },
164
+
149
165
  edit: function() {
150
166
  var self = this;
151
167
  var wrap = dom.div('.file-field', {'style':'position:relative;'});
@@ -164,10 +180,9 @@ Spontaneous.Field.File = (function($, S) {
164
180
 
165
181
  var files_selected = function(files) {
166
182
  if (files.length > 0) {
167
- var file = files[0], url = window.URL.createObjectURL(file);
183
+ var file = files[0], url = this.createObjectURL(file);
168
184
  this.selected_files = files;
169
185
  this._edited_value = url;
170
- window.URL.revokeObjectURL(url);
171
186
  set_info(File.filename(file), file.fileSize);
172
187
  }
173
188
  }.bind(this);
@@ -160,7 +160,7 @@ Spontaneous.Field.Image = (function($, S) {
160
160
  if (files.length > 0) {
161
161
  this.select_files(files);
162
162
  var file = files[0],
163
- url = window.URL.createObjectURL(file)
163
+ url = this.createObjectURL(file)
164
164
  , image = this.image;
165
165
  this._edited_value = url;
166
166
  image.__start_upload = true;
@@ -177,8 +177,6 @@ Spontaneous.Field.Image = (function($, S) {
177
177
  }
178
178
  }.bind(this));
179
179
  image.attr('src', url);
180
- // see http://www.htmlfivewow.com/slide25
181
- window.URL.revokeObjectURL(url);
182
180
  }
183
181
  return false;
184
182
  }.bind(this);
@@ -306,13 +304,12 @@ Spontaneous.Field.Image = (function($, S) {
306
304
 
307
305
  var files_selected = function(files) {
308
306
  if (files.length > 0) {
309
- var file = files[0], url = window.URL.createObjectURL(file);
307
+ var file = files[0], url = this.createObjectURL(file);
310
308
  img.attr('src', url).removeClass('empty');
311
309
  this.select_files(files);
312
310
  img.attr('src', url);
313
311
  this._edited_value = url;
314
312
  this.image.attr('src', url);
315
- window.URL.revokeObjectURL(url);
316
313
  set_info(File.filename(file), file.fileSize, null, null);
317
314
  }
318
315
  }.bind(this);
@@ -1,12 +1,21 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  Sequel.migration do
4
+ no_transaction
4
5
  up do
5
- [:content, :spontaneous_content_archive, :spontaneous_content_history].each do |table|
6
- alter_table table do
7
- add_column :content_hash, :varchar, size: 32
8
- add_column :published_content_hash, :varchar, size: 32
9
- add_column :content_hash_changed, :boolean, default: true
6
+ [:content, :spontaneous_content_history, :spontaneous_content_archive].each do |table|
7
+ begin
8
+ alter_table table do
9
+ add_column :content_hash, :varchar, size: 32
10
+ add_column :published_content_hash, :varchar, size: 32
11
+ add_column :content_hash_changed, :boolean, default: true
12
+ end
13
+ rescue ::Sequel::DatabaseError => e
14
+ # because syncing ignores the content_archive table
15
+ # if we run this migration, then sync, then re-run it
16
+ # (in the case where the production db is behind the dev db)
17
+ # this one table will throw an error
18
+ raise unless table == :spontaneous_content_archive
10
19
  end
11
20
  end
12
21
  alter_table :content do
@@ -15,32 +24,34 @@ Sequel.migration do
15
24
 
16
25
  # testing environment
17
26
  if defined?(Content)
27
+ transaction do
18
28
 
19
- self.logger = nil
29
+ self.logger = nil
20
30
 
21
- state = Spontaneous::State.first
31
+ state = Spontaneous::State.first
22
32
 
23
- published_revision = state.nil? ? nil : state[:published_revision]
33
+ published_revision = state.nil? ? nil : state[:published_revision]
24
34
 
25
- content_hash = published_content_hash = nil
35
+ content_hash = published_content_hash = nil
26
36
 
27
- model = Spontaneous::Model(:content)
37
+ model = Spontaneous::Model(:content)
28
38
 
29
- update_content_hashes = Proc.new do |content|
30
- published_content_hash = nil
31
- content_hash = content.calculate_content_hash!
32
- if published_revision
33
- model.mapper.scope(published_revision, false) do
34
- published = model[content.id]
35
- published_content_hash = published.calculate_content_hash! if published
39
+ update_content_hashes = Proc.new do |content|
40
+ published_content_hash = nil
41
+ content_hash = content.calculate_content_hash!
42
+ if published_revision
43
+ model.mapper.scope(published_revision, false) do
44
+ published = model[content.id]
45
+ published_content_hash = published.calculate_content_hash! if published
46
+ end
36
47
  end
48
+ p [content.id, content.class, content.path, content_hash, published_content_hash] #if published_content_hash != content_hash
49
+ model.dataset.unfiltered.where(id: content.id).update(content_hash: content_hash, published_content_hash: published_content_hash, content_hash_changed: (content_hash != published_content_hash))
37
50
  end
38
- p [content.id, content.class, content.path, content_hash, published_content_hash] #if published_content_hash != content_hash
39
- model.dataset.unfiltered.where(id: content.id).update(content_hash: content_hash, published_content_hash: published_content_hash, content_hash_changed: (content_hash != published_content_hash))
40
- end
41
51
 
42
- Content::Piece.dataset.order(Sequel.desc(:depth)).each(&update_content_hashes)
43
- Content::Page.dataset.order(Sequel.desc(:depth)).each(&update_content_hashes)
52
+ Content::Piece.dataset.order(Sequel.desc(:depth)).each(&update_content_hashes)
53
+ Content::Page.dataset.order(Sequel.desc(:depth)).each(&update_content_hashes)
54
+ end
44
55
  end
45
56
  end
46
57
 
@@ -1,12 +1,23 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  Sequel.migration do
4
+ no_transaction
4
5
  up do
5
6
  [:content, :spontaneous_content_archive, :spontaneous_content_history].each do |table|
6
- alter_table table do
7
- add_column :content_hash_changed_at, :timestamp
7
+ begin
8
+ alter_table table do
9
+ add_column :content_hash_changed_at, :timestamp
10
+ end
11
+ rescue ::Sequel::DatabaseError => e
12
+ # because syncing ignores the content_archive table
13
+ # if we run this migration, then sync, then re-run it
14
+ # (in the case where the production db is behind the dev db)
15
+ # this one table will throw an error
16
+ raise unless table == :spontaneous_content_archive
17
+ end
18
+ transaction do
19
+ self[table].update(content_hash_changed_at: :modified_at)
8
20
  end
9
- self[table].update(content_hash_changed_at: :modified_at)
10
21
  end
11
22
  end
12
23
 
@@ -5,12 +5,16 @@ module Spontaneous::Asset
5
5
  module Environment
6
6
  def self.new(context)
7
7
  if context.publishing?
8
- Publish.new(context.site, context.revision, context.development?)
8
+ publishing(context.site, context.revision, context.development?)
9
9
  else
10
- Preview.new(context.site)
10
+ preview(context.site)
11
11
  end
12
12
  end
13
13
 
14
+ def self.publishing(site, revision, development)
15
+ Publish.new(site, revision, development)
16
+ end
17
+
14
18
  def self.preview(site = Spontaneous::Site.instance)
15
19
  Preview.new(site)
16
20
  end
@@ -125,11 +129,19 @@ module Spontaneous::Asset
125
129
 
126
130
  def to_url(asset, body = false)
127
131
  return asset if asset.is_a?(String)
132
+ query = {}
133
+ query['body'] = 1 if body
134
+ query[asset.digest] = nil if dynamic_fingerprint?
128
135
  path = asset.logical_path
129
- path = "#{path}?body=1" if body
136
+ path = "#{path}?#{Rack::Utils.build_query(query)}" unless query.empty?
130
137
  "/" << asset_mount_point << "/" << path
131
138
  end
132
139
 
140
+ # include the asset fingerprint as a query param for cache busting
141
+ def dynamic_fingerprint?
142
+ true
143
+ end
144
+
133
145
  def asset_mount_point
134
146
  "assets"
135
147
  end
@@ -175,6 +187,11 @@ module Spontaneous::Asset
175
187
  @development || false
176
188
  end
177
189
 
190
+ # include the asset fingerprint as a query param for cache busting
191
+ def dynamic_fingerprint?
192
+ false
193
+ end
194
+
178
195
  # A proxy to the sprockets manifest that compiles assets on the first run
179
196
  # then re-uses them on the second
180
197
  class Manifest
@@ -202,29 +219,10 @@ module Spontaneous::Asset
202
219
  unless (args.all? { |key| assets.key?(key) })
203
220
  compile!(*args)
204
221
  end
205
- copy_assets_to_revision(args)
206
222
  end
207
223
 
208
224
  def compile!(*args)
209
225
  @manifest.compile(*args)
210
- copy_assets_to_revision(args)
211
- end
212
-
213
- def copy_assets_to_revision(logical_paths)
214
- assets = @manifest.assets
215
- paths = logical_paths.map { |a| assets[a] }.compact
216
- source, dest = shared_asset_dir, revision_asset_dir
217
- paths.each do |asset|
218
- copy_asset_to_revision(source, dest, asset)
219
- end
220
- end
221
-
222
- def copy_asset_to_revision(source, dest, asset)
223
- to = dest + asset
224
- return if to.exist?
225
- from = source + asset
226
- to.dirname.mkpath
227
- FileUtils.cp(from, to)
228
226
  end
229
227
 
230
228
  def asset_compilation_dir
@@ -70,7 +70,14 @@ module Spontaneous::Model
70
70
  # => [#<ContentClass...>, #<ContentClass...>]
71
71
  #
72
72
  def to_proc
73
- Proc.new { |obj| self === obj }
73
+ Proc.new { |obj| self.===(obj) }
74
+ end
75
+
76
+ # Expands type testing to include peeking inside PagePiece instances
77
+ def ===(other)
78
+ return true if super
79
+ return (self == other.content_class) if other.respond_to?(:content_class)
80
+ false
74
81
  end
75
82
  end
76
83
 
@@ -148,6 +155,10 @@ module Spontaneous::Model
148
155
 
149
156
  alias_method :is_page?, :page?
150
157
 
158
+ def content_class
159
+ self
160
+ end
161
+
151
162
  # Do not overwrite this method directly.
152
163
  # If you want a page to render the content of another configure
153
164
  # the type using Content::render
@@ -30,11 +30,6 @@ module Spontaneous::Model::Page
30
30
 
31
31
  private :__create_private_root=, :__create_private_root?
32
32
 
33
- def after_initialize
34
- super
35
- set_generated_slug
36
- end
37
-
38
33
  def before_create
39
34
  place_in_page_tree
40
35
  set_slug_from_dynamic_value
@@ -46,11 +41,6 @@ module Spontaneous::Model::Page
46
41
  fix_generated_slug_conflicts
47
42
  end
48
43
 
49
- def set_generated_slug
50
- return unless slug.nil?
51
- self.slug = generate_default_slug
52
- end
53
-
54
44
  def fix_generated_slug_conflicts
55
45
  o = s = slug || generate_default_slug
56
46
  n = 0
@@ -183,9 +173,13 @@ module Spontaneous::Model::Page
183
173
  end
184
174
  end
185
175
 
176
+ def initialized_slug
177
+ return slug unless slug.nil?
178
+ self.slug = generate_default_slug
179
+ end
186
180
 
187
181
  def calculate_path
188
- calculate_path_with_slug(slug)
182
+ calculate_path_with_slug(initialized_slug)
189
183
  end
190
184
 
191
185
  def calculate_path_with_slug(slug)
@@ -32,6 +32,12 @@ module Spontaneous
32
32
  target
33
33
  end
34
34
 
35
+ # Used by Spontaneous::Model::Core::=== to look inside PagePiece objects
36
+ # and test against the class of the target, not of the proxy
37
+ def content_class
38
+ target.class
39
+ end
40
+
35
41
  def id
36
42
  target.id
37
43
  end
@@ -132,6 +132,7 @@ module Spontaneous::Publishing
132
132
  :create_revision_directory,
133
133
  :render_revision,
134
134
  :generate_search_indexes,
135
+ :copy_assets,
135
136
  :copy_static_files,
136
137
  :generate_rackup_file,
137
138
  :activate_revision,
@@ -148,6 +149,7 @@ require 'spontaneous/publishing/steps/create_revision_directory'
148
149
  require 'spontaneous/publishing/steps/render_revision'
149
150
  require 'spontaneous/publishing/steps/generate_search_indexes'
150
151
  require 'spontaneous/publishing/steps/copy_static_files'
152
+ require 'spontaneous/publishing/steps/copy_assets'
151
153
  require 'spontaneous/publishing/steps/generate_rackup_file'
152
154
  require 'spontaneous/publishing/steps/activate_revision'
153
155
  require 'spontaneous/publishing/steps/write_revision_file'
@@ -0,0 +1,77 @@
1
+ module Spontaneous::Publishing::Steps
2
+ class CopyAssets < BaseStep
3
+
4
+ def count
5
+ assets.length
6
+ end
7
+
8
+ def call
9
+ @progress.stage("copying assets")
10
+ ensure_asset_dir
11
+ assets.each do |logical_path, asset|
12
+ copy_asset(asset)
13
+ @progress.step(1, "'#{logical_path}' => '#{asset}'")
14
+ end
15
+ end
16
+
17
+ def rollback
18
+ FileUtils.rm_r(revision_asset) if File.exists?(revision_asset)
19
+ end
20
+
21
+ def ensure_asset_dir
22
+ dir = revision_asset
23
+ end
24
+
25
+ def copy_asset(asset)
26
+ ['', '.gz'].each do |suffix|
27
+ copy_asset_file(asset + suffix)
28
+ end
29
+ end
30
+
31
+ def copy_asset_file(asset)
32
+ source = File.join(manifest.asset_compilation_dir, asset)
33
+ if File.exist?(source)
34
+ dest = ensure_dir File.join(revision_asset, asset)
35
+ link_file(source, dest)
36
+ end
37
+ end
38
+
39
+ def link_file(source, dest)
40
+ src_dev = File::stat(source).dev
41
+ dst_dev = File::stat(File.dirname(dest)).dev
42
+ if (src_dev == dst_dev)
43
+ FileUtils.ln(source, dest, :force => true)
44
+ else
45
+ FileUtils.cp(source, dest)
46
+ end
47
+ end
48
+
49
+ def revision_asset
50
+ @asset_dest ||= Pathname.new(Spontaneous.revision_dir(revision) / 'assets').tap do |path|
51
+ FileUtils.mkdir_p(path) unless File.exists?(path)
52
+ end
53
+ end
54
+
55
+ def ensure_dir(path)
56
+ dir = File.dirname(path)
57
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
58
+ path
59
+ end
60
+
61
+ def assets
62
+ manifest.assets
63
+ end
64
+
65
+ def manifest
66
+ environment.manifest
67
+ end
68
+
69
+ def environment
70
+ @environment ||= Spontaneous::Asset::Environment.publishing(site, revision, development?)
71
+ end
72
+
73
+ def development?
74
+ Spontaneous.development?
75
+ end
76
+ end
77
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Spontaneous
4
- VERSION = "0.2.0.beta7"
4
+ VERSION = "0.2.0.beta8"
5
5
  GEM = false
6
6
  end
@@ -0,0 +1,6 @@
1
+ $width: 23px;
2
+
3
+ .something {
4
+ width: $width;
5
+ background-image: asset_url('i/sine_waves.png');
6
+ }
@@ -1110,7 +1110,7 @@ describe "Back" do
1110
1110
  it "render SASS templates" do
1111
1111
  get "/assets/css/sass_template.css"
1112
1112
  assert last_response.ok?, "Should return 200 but got #{last_response.status}"
1113
- last_response.body.must_match /color: #ffeeff/
1113
+ last_response.body.must_match /color: #fef/
1114
1114
  end
1115
1115
 
1116
1116
  it "compile CoffeeScript" do
@@ -53,10 +53,10 @@ describe "AssetBundler" do
53
53
  compiled_css_path = Dir["#{@site.root}/public/@spontaneous/assets/*.css"].first
54
54
  css = File.read(compiled_css_path)
55
55
  css.must_match /\.simple \{\s+color: #aaa/
56
- css.must_match /\.basic \{\s+color: #aabbcc/
57
- css.must_match /\.complex \{\s+color: #ddeeff;\s*width: #{Date.today.day}px/
56
+ css.must_match /\.basic \{\s+color: #abc/
57
+ css.must_match /\.complex \{\s+color: #def;\s*width: #{Date.today.day}px/
58
58
  css.must_match /\.subdir\.simple \{\s+color: #000;/
59
- css.must_match /\.subdir\.complex \{\s+color: #aaaaaa;\s+height: #{Date.today.day}px/
59
+ css.must_match /\.subdir\.complex \{\s+color: #aaa;\s+height: #{Date.today.day}px/
60
60
  end
61
61
 
62
62
  it "produce compressed CSS by default" do
@@ -65,10 +65,10 @@ describe "AssetBundler" do
65
65
  compiled_css_path = Dir["#{@site.root}/public/@spontaneous/assets/spontaneous*.css"].first
66
66
  css = File.read(compiled_css_path)
67
67
  css.must_match /\.simple\{color:#aaa\}/
68
- css.must_match /\.basic\{color:#aabbcc\}/
69
- css.must_match /\.complex\{color:#ddeeff;width:#{Date.today.day}px\}/
68
+ css.must_match /\.basic\{color:#abc\}/
69
+ css.must_match /\.complex\{color:#def;width:#{Date.today.day}px\}/
70
70
  css.must_match /\.subdir\.simple\{color:#000\}/
71
- css.must_match /\.subdir\.complex\{color:#aaaaaa;height:#{Date.today.day}px\}/
71
+ css.must_match /\.subdir\.complex\{color:#aaa;height:#{Date.today.day}px\}/
72
72
  end
73
73
  end
74
74
 
@@ -6,9 +6,7 @@ require 'ostruct'
6
6
  describe "Assets" do
7
7
  include RackTestMethods
8
8
 
9
- def app
10
- Spontaneous::Rack::Back.application(site)
11
- end
9
+ let(:app) {Spontaneous::Rack::Back.application(site)}
12
10
 
13
11
  module LiveSimulation
14
12
  # simulate a production + publishing environment
@@ -59,6 +57,13 @@ describe "Assets" do
59
57
  end
60
58
  end
61
59
 
60
+ def asset_digest(asset_relative_path)
61
+ digest = context.asset_environment.environment.digest
62
+ digest.update(File.read(File.join(fixture_root, asset_relative_path)))
63
+ digest.hexdigest
64
+ end
65
+
66
+ let(:y_png_digest) { asset_digest('public2/i/y.png') }
62
67
 
63
68
  start do
64
69
  fixture_root = File.expand_path("../../fixtures/assets", __FILE__)
@@ -136,16 +141,32 @@ describe "Assets" do
136
141
  describe "development" do
137
142
  let(:context) { development_context }
138
143
 
144
+ let(:a_js_digest) { asset_digest('public1/js/a.js') }
145
+ let(:b_js_digest) { asset_digest('public2/js/b.js') }
146
+ let(:c_js_digest) { asset_digest('public2/js/c.js') }
147
+ let(:x_js_digest) { asset_digest('public1/x.js') }
148
+ # these are compiled so fairly complex to calculate their digests
149
+ # not impossible, but annoying
150
+ let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
151
+ let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
152
+ let(:all_js_digest) { 'd406fc3c21d90828a2f0a718c89e8d99' }
153
+
154
+ let(:a_css_digest) { '7b04d295476986c24d8c77245943e5b9' }
155
+ let(:b_css_digest) { '266643993e14da14f2473d45f003bd2c' }
156
+ let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
157
+ let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
158
+ let(:all_css_digest) { 'cf61c624b91b9ea126804291ac55bd5d' }
159
+
139
160
  it "includes all js dependencies" do
140
161
  result = context.scripts('js/all', 'js/m', 'js/c', 'x')
141
162
  result.must_equal [
142
- '<script type="text/javascript" src="/assets/js/a.js?body=1"></script>',
143
- '<script type="text/javascript" src="/assets/js/b.js?body=1"></script>',
144
- '<script type="text/javascript" src="/assets/js/n.js?body=1"></script>',
145
- '<script type="text/javascript" src="/assets/js/all.js?body=1"></script>',
146
- '<script type="text/javascript" src="/assets/js/m.js?body=1"></script>',
147
- '<script type="text/javascript" src="/assets/js/c.js?body=1"></script>',
148
- '<script type="text/javascript" src="/assets/x.js?body=1"></script>'
163
+ %|<script type="text/javascript" src="/assets/js/a.js?body=1&#{a_js_digest}"></script>|,
164
+ %|<script type="text/javascript" src="/assets/js/b.js?body=1&#{b_js_digest}"></script>|,
165
+ %|<script type="text/javascript" src="/assets/js/n.js?body=1&#{n_js_digest}"></script>|,
166
+ %|<script type="text/javascript" src="/assets/js/all.js?body=1&#{all_js_digest}"></script>|,
167
+ %|<script type="text/javascript" src="/assets/js/m.js?body=1&#{m_js_digest}"></script>|,
168
+ %|<script type="text/javascript" src="/assets/js/c.js?body=1&#{c_js_digest}"></script>|,
169
+ %|<script type="text/javascript" src="/assets/x.js?body=1&#{x_js_digest}"></script>|
149
170
  ].join("\n")
150
171
  end
151
172
 
@@ -158,11 +179,11 @@ describe "Assets" do
158
179
  it "includes all css dependencies" do
159
180
  result = context.stylesheets('css/all', 'css/c', 'x')
160
181
  result.must_equal [
161
- '<link rel="stylesheet" href="/assets/css/b.css?body=1" />',
162
- '<link rel="stylesheet" href="/assets/css/a.css?body=1" />',
163
- '<link rel="stylesheet" href="/assets/css/all.css?body=1" />',
164
- '<link rel="stylesheet" href="/assets/css/c.css?body=1" />',
165
- '<link rel="stylesheet" href="/assets/x.css?body=1" />'
182
+ %|<link rel="stylesheet" href="/assets/css/b.css?body=1&#{b_css_digest}" />|,
183
+ %|<link rel="stylesheet" href="/assets/css/a.css?body=1&#{a_css_digest}" />|,
184
+ %|<link rel="stylesheet" href="/assets/css/all.css?body=1&#{all_css_digest}" />|,
185
+ %|<link rel="stylesheet" href="/assets/css/c.css?body=1&#{c_css_digest}" />|,
186
+ %|<link rel="stylesheet" href="/assets/x.css?body=1&#{x_css_digest}" />|
166
187
  ].join("\n")
167
188
  end
168
189
 
@@ -183,14 +204,24 @@ describe "Assets" do
183
204
  let(:app) { Spontaneous::Rack::Back.application(site) }
184
205
  let(:context) { preview_context }
185
206
 
207
+ let(:c_js_digest) { 'f669550dd7e10e9646ad781f44756950' }
208
+ let(:x_js_digest) { '6b4c9176b2838a4949a18284543fc19c' }
209
+ let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
210
+ let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
211
+ let(:all_js_digest) { 'cd1f681752f5038421be0bc5ea0e855d' }
212
+
213
+ let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
214
+ let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
215
+ let(:all_css_digest) { 'bb2c289a27b3d5d4467dde6d60722fd3' }
216
+
186
217
  describe "javascript" do
187
- it "include scripts as separate files" do
218
+ it "include scripts as separate files with finger prints" do
188
219
  result = context.scripts('js/all', 'js/m.js', 'js/c.js', 'x')
189
220
  result.must_equal [
190
- '<script type="text/javascript" src="/assets/js/all.js"></script>',
191
- '<script type="text/javascript" src="/assets/js/m.js"></script>',
192
- '<script type="text/javascript" src="/assets/js/c.js"></script>',
193
- '<script type="text/javascript" src="/assets/x.js"></script>'
221
+ %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
222
+ %|<script type="text/javascript" src="/assets/js/m.js?#{m_js_digest}"></script>|,
223
+ %|<script type="text/javascript" src="/assets/js/c.js?#{c_js_digest}"></script>|,
224
+ %|<script type="text/javascript" src="/assets/x.js?#{x_js_digest}"></script>|
194
225
  ].join("\n")
195
226
  end
196
227
 
@@ -199,15 +230,15 @@ describe "Assets" do
199
230
  it "handles urls passed as an array" do
200
231
  result = context.scripts(['js/all', 'js/m.js'])
201
232
  result.must_equal [
202
- '<script type="text/javascript" src="/assets/js/all.js"></script>',
203
- '<script type="text/javascript" src="/assets/js/m.js"></script>'
233
+ %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
234
+ %|<script type="text/javascript" src="/assets/js/m.js?#{m_js_digest}"></script>|
204
235
  ].join("\n")
205
236
  end
206
237
 
207
238
  it "should ignore missing files" do
208
239
  result = context.scripts('js/all', 'js/missing')
209
240
  result.must_equal [
210
- '<script type="text/javascript" src="/assets/js/all.js"></script>',
241
+ %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
211
242
  '<script type="text/javascript" src="js/missing.js"></script>'
212
243
  ].join("\n")
213
244
  end
@@ -244,7 +275,7 @@ describe "Assets" do
244
275
  context = preview_context
245
276
  result = context.scripts('js/all', '//use.typekit.com/abcde', 'http://cdn.google.com/jquery.js', 'https://cdn.google.com/jquery.js')
246
277
  result.must_equal [
247
- '<script type="text/javascript" src="/assets/js/all.js"></script>',
278
+ %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
248
279
  '<script type="text/javascript" src="//use.typekit.com/abcde"></script>',
249
280
  '<script type="text/javascript" src="http://cdn.google.com/jquery.js"></script>',
250
281
  '<script type="text/javascript" src="https://cdn.google.com/jquery.js"></script>'
@@ -256,18 +287,18 @@ describe "Assets" do
256
287
  it "include css files as separate links" do
257
288
  result = context.stylesheets('css/all', 'css/c', 'x')
258
289
  result.must_equal [
259
- '<link rel="stylesheet" href="/assets/css/all.css" />',
260
- '<link rel="stylesheet" href="/assets/css/c.css" />',
261
- '<link rel="stylesheet" href="/assets/x.css" />'
290
+ %|<link rel="stylesheet" href="/assets/css/all.css?#{all_css_digest}" />|,
291
+ %|<link rel="stylesheet" href="/assets/css/c.css?#{c_css_digest}" />|,
292
+ %|<link rel="stylesheet" href="/assets/x.css?#{x_css_digest}" />|
262
293
  ].join("\n")
263
294
  end
264
295
 
265
296
  it "allows passing scripts as an array" do
266
297
  result = context.stylesheets(['css/all', 'css/c', 'x'])
267
298
  result.must_equal [
268
- '<link rel="stylesheet" href="/assets/css/all.css" />',
269
- '<link rel="stylesheet" href="/assets/css/c.css" />',
270
- '<link rel="stylesheet" href="/assets/x.css" />'
299
+ %|<link rel="stylesheet" href="/assets/css/all.css?#{all_css_digest}" />|,
300
+ %|<link rel="stylesheet" href="/assets/css/c.css?#{c_css_digest}" />|,
301
+ %|<link rel="stylesheet" href="/assets/x.css?#{x_css_digest}" />|
271
302
  ].join("\n")
272
303
  end
273
304
 
@@ -327,11 +358,11 @@ describe "Assets" do
327
358
 
328
359
  it "should allow for embedding asset images into templates" do
329
360
  result = renderer.render_string("${ asset_path 'i/y.png' }", @page.output(:html))
330
- result.must_equal "/assets/i/y.png"
361
+ result.must_equal "/assets/i/y.png?#{y_png_digest}"
331
362
  end
332
363
  it "should allow for embedding asset urls into templates" do
333
364
  result = renderer.render_string("${ asset_url 'i/y.png' }", @page.output(:html))
334
- result.must_equal "url(/assets/i/y.png)"
365
+ result.must_equal "url(/assets/i/y.png?#{y_png_digest})"
335
366
  end
336
367
  end
337
368
  end
@@ -340,11 +371,17 @@ describe "Assets" do
340
371
  let(:app) { Spontaneous::Rack::Front.application(site) }
341
372
  let(:context) { live_context }
342
373
  let(:revision) { site.revision(context.revision) }
374
+ let(:progress) { Spontaneous::Publishing::Progress::Silent.new }
375
+
376
+ def publish_assets(revision)
377
+ context.asset_environment.manifest.compile!
378
+ Spontaneous::Publishing::Steps::CopyAssets.new(site, revision, [], progress).call
379
+ end
343
380
 
344
381
  before do
345
382
  FileUtils.rm_f(Spontaneous.revision_dir) if File.exist?(Spontaneous.revision_dir)
346
383
  system "ln -nfs #{revision.root} #{Spontaneous.revision_dir}"
347
- # FileUtils.ln_s(revision.root, Spontaneous.revision_dir)
384
+ publish_assets(context.revision)
348
385
  end
349
386
 
350
387
  after do
@@ -352,14 +389,14 @@ describe "Assets" do
352
389
  end
353
390
 
354
391
  describe "javascript" do
355
- let(:all_sha) { "b1d3d85feff4d68e16d2b4da97717aa0" }
356
- let(:x_sha) { "cf638f60cff3ce84f156cb4621a914b4" }
392
+ let(:all_sha) { "ed62549e8edc1f61a1e27136602f01d9" }
393
+ let(:x_sha) { "66e92be1e412458f6ff02f4c5dd9beb1" }
357
394
  it "bundles & fingerprints local scripts" do
358
395
  result = context.scripts('js/all', 'js/m.js', 'js/c.js', 'x')
359
396
  result.must_equal [
360
397
  %(<script type="text/javascript" src="/assets/js/all-#{all_sha}.js"></script>),
361
- '<script type="text/javascript" src="/assets/js/m-424bd92768589875beac31e333399631.js"></script>',
362
- '<script type="text/javascript" src="/assets/js/c-ac0d40982cc84fc656234ef2a57e09e8.js"></script>',
398
+ '<script type="text/javascript" src="/assets/js/m-a5be7324bc314d5cf470a59c3732ef10.js"></script>',
399
+ '<script type="text/javascript" src="/assets/js/c-c24bcbb4f9647b078cc919746aa7fc3a.js"></script>',
363
400
  %(<script type="text/javascript" src="/assets/x-#{x_sha}.js"></script>)
364
401
  ].join("\n")
365
402
  end
@@ -413,6 +450,7 @@ describe "Assets" do
413
450
  context = live_context
414
451
  def context.revision; 100 end
415
452
  revision = site.revision(context.revision)
453
+ publish_assets(context.revision)
416
454
  manifest = Spontaneous::JSON.parse File.read(site.path("assets/tmp") + "manifest.json")
417
455
  compiled = manifest[:assets][:"js/all.js"]
418
456
  ::File.open(site.path("assets/tmp")+compiled, 'w') do |file|
@@ -426,14 +464,14 @@ describe "Assets" do
426
464
  end
427
465
 
428
466
  describe "css" do
429
- let(:all_sha) { "2468ffc5102b6bfcaf69a4fc8db59fdd" }
430
- let(:x_sha) { "ae3ee1dc79a34d24e28456118c1b9623" }
467
+ let(:all_sha) { "2e17f25ddeba996223a6cd1e28e7a319" }
468
+ let(:x_sha) { "2560aec2891794825eba770bf84823fb" }
431
469
 
432
470
  it "bundles & fingerprints local stylesheets" do
433
471
  result = context.stylesheets('css/all', 'css/a.css', 'x')
434
472
  result.must_equal [
435
473
  %(<link rel="stylesheet" href="/assets/css/all-#{all_sha}.css" />),
436
- '<link rel="stylesheet" href="/assets/css/a-35b26d0cd9c7ebff494c8627e0d4ed14.css" />',
474
+ '<link rel="stylesheet" href="/assets/css/a-0164c6d5b696ec2f2c5e70cade040da8.css" />',
437
475
  %(<link rel="stylesheet" href="/assets/x-#{x_sha}.css" />)
438
476
  ].join("\n")
439
477
  end
@@ -457,7 +495,7 @@ describe "Assets" do
457
495
  ].join("\n")
458
496
  end
459
497
 
460
- it "makes bundled scripts available under /assets" do
498
+ it "makes bundled stylesheets available under /assets" do
461
499
  path = context.stylesheet_urls('css/all').first
462
500
  get path
463
501
  asset_path = revision.path(path)
@@ -499,17 +537,15 @@ describe "Assets" do
499
537
  end
500
538
  end
501
539
 
502
- let(:y_sha) { "e2b6a69468b467c7414ae0e12124a66e" }
503
-
504
540
  describe "images" do
505
541
  it "bundles images and links using fingerprinted asset url" do
506
542
  path = context.stylesheet_urls('css/image1').first
507
543
  get path
508
544
  assert last_response.ok?, "Recieved #{last_response.status} not 200"
509
545
  result = last_response.body
510
- result.must_match %r{background:url\(/assets/i/y-#{y_sha}\.png\)}
546
+ result.must_match %r{background:url\(/assets/i/y-#{y_png_digest}\.png\)}
511
547
 
512
- asset_path = revision.path("/assets/i/y-#{y_sha}.png")
548
+ asset_path = revision.path("/assets/i/y-#{y_png_digest}.png")
513
549
  assert asset_path.exist?
514
550
  end
515
551
 
@@ -526,8 +562,8 @@ describe "Assets" do
526
562
  get path
527
563
  assert last_response.ok?, "Recieved #{last_response.status} not 200"
528
564
  result = last_response.body
529
- result.must_match %r{background:url\(/assets/i/y-#{y_sha}\.png\?query=true#hash\)}
530
- asset_path = revision.path("/assets/i/y-#{y_sha}.png")
565
+ result.must_match %r{background:url\(/assets/i/y-#{y_png_digest}\.png\?query=true#hash\)}
566
+ asset_path = revision.path("/assets/i/y-#{y_png_digest}.png")
531
567
  assert asset_path.exist?
532
568
  end
533
569
  end
@@ -537,11 +573,11 @@ describe "Assets" do
537
573
 
538
574
  it "should allow for embedding asset images into templates" do
539
575
  result = renderer.render_string("${ asset_path 'i/y.png' }", @page.output(:html))
540
- result.must_equal "/assets/i/y-#{y_sha}.png"
576
+ result.must_equal "/assets/i/y-#{y_png_digest}.png"
541
577
  end
542
578
  it "should allow for embedding asset urls into templates" do
543
579
  result = renderer.render_string("${ asset_url 'i/y.png' }", @page.output(:html))
544
- result.must_equal "url(/assets/i/y-#{y_sha}.png)"
580
+ result.must_equal "url(/assets/i/y-#{y_png_digest}.png)"
545
581
  end
546
582
  end
547
583
  end
@@ -344,5 +344,15 @@ describe "Content" do
344
344
  it "provides a to_proc method that makes filtering by class easy" do
345
345
  @instances.select(&C).map(&:class).must_equal [C, C]
346
346
  end
347
+
348
+ it "can filter pagepieces that point to an object of the given type" do
349
+ pps = @instances.map { |i| Spontaneous::PagePiece.new(nil, i, nil)}
350
+ pps.select(&P).map { |i| i.to_page.class }.must_equal [P, P]
351
+ end
352
+
353
+ it "can filter content instances out a list POROs" do
354
+ pps = [Object.new, Object.new, @instances.first]
355
+ pps.select(&P).map { |i| i.to_page.class }.must_equal [P]
356
+ end
347
357
  end
348
358
  end
@@ -469,6 +469,80 @@ describe "Publishing Pipeline" do
469
469
  end
470
470
  end
471
471
 
472
+ describe "CopyAssets" do
473
+ let(:step) { Spontaneous::Publishing::Steps::CopyAssets }
474
+ let(:application_path) { Pathname.new(File.expand_path("../../fixtures/example_application", __FILE__)) }
475
+ let(:fixtures_path) { application_path + "assets" }
476
+ let(:revision_root) { @site.revision_dir(revision) }
477
+ let(:environment) { Spontaneous::Asset::Environment.publishing(@site, revision, false) }
478
+ let(:manifest) { environment.manifest }
479
+ let(:assets) { environment.manifest.assets }
480
+
481
+ def assert_assets(revision)
482
+ revision_root = @site.revision_dir(revision)
483
+ assets.length.must_equal 3
484
+ assets.each do |logical_path, asset|
485
+ assert File.exist?(File.join(revision_root, 'assets', asset)), "asset '#{asset}' does not exist"
486
+ end
487
+ end
488
+
489
+ before do
490
+ FileUtils.cp_r(fixtures_path, @site.root)
491
+ File.exist?(@site.root / 'assets/css/site.css.scss').must_equal true
492
+ manifest.compile!('css/site.css', 'i/xes.png')
493
+ end
494
+
495
+ it "has the right shortcut name" do
496
+ step.to_sym.must_equal :copy_assets
497
+ end
498
+
499
+ it "sets the progress stage to 'copying files'" do
500
+ progress = mock
501
+ progress.stubs(:step)
502
+ progress.expects(:stage).with("copying assets").once
503
+ run_step(progress)
504
+ end
505
+
506
+ it "steps the progress once" do
507
+ progress = mock
508
+ progress.stubs(:stage)
509
+ progress.expects(:step).with(1, instance_of(String)).times(3)
510
+ run_step(progress)
511
+ end
512
+
513
+ it "gives its step count as the number of facets" do
514
+ step.count(@site, revision, nil).must_equal 3
515
+ end
516
+
517
+ it "copies compiled assets to the revision's asset dir" do
518
+ run_step
519
+ assert_assets(revision)
520
+ end
521
+
522
+ it "copies compressed assets to the revision's asset dir" do
523
+ asset = assets.values.first
524
+ compressed = asset + '.gz'
525
+ FileUtils.cp(manifest.asset_compilation_dir + asset, manifest.asset_compilation_dir + compressed)
526
+ run_step
527
+ assert File.exist?(File.join(revision_root, 'assets', compressed)), "#{compressed} should exist"
528
+ end
529
+
530
+ it "deletes the copied files on rollback" do
531
+ instance = run_step
532
+ instance.rollback
533
+ refute File.exist?(File.join(revision_root, "assets"))
534
+ end
535
+
536
+ it "runs rollback after throwing an exception" do
537
+ instance = mock
538
+ step.expects(:new).returns(instance)
539
+ instance.expects(:call).raises(Exception)
540
+ instance.expects(:rollback)
541
+ lambda{ run_step }.must_raise(Exception)
542
+ end
543
+
544
+ end
545
+
472
546
  describe "GenerateRackupFile" do
473
547
  let(:step) { Spontaneous::Publishing::Steps::GenerateRackupFile }
474
548
  let(:rackup_path) { @site.revision_dir(revision) / "config.ru" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spontaneous
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta7
4
+ version: 0.2.0.beta8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garry Hill
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-03 00:00:00.000000000 Z
11
+ date: 2014-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -887,6 +887,7 @@ files:
887
887
  - lib/spontaneous/publishing/steps/activate_revision.rb
888
888
  - lib/spontaneous/publishing/steps/archive_old_revisions.rb
889
889
  - lib/spontaneous/publishing/steps/base_step.rb
890
+ - lib/spontaneous/publishing/steps/copy_assets.rb
890
891
  - lib/spontaneous/publishing/steps/copy_static_files.rb
891
892
  - lib/spontaneous/publishing/steps/create_revision_directory.rb
892
893
  - lib/spontaneous/publishing/steps/generate_rackup_file.rb
@@ -1030,6 +1031,9 @@ files:
1030
1031
  - test/fixtures/example_application/Gemfile
1031
1032
  - test/fixtures/example_application/Gemfile.lock
1032
1033
  - test/fixtures/example_application/Rakefile
1034
+ - test/fixtures/example_application/assets/css/site.css.scss
1035
+ - test/fixtures/example_application/assets/i/sine_waves.png
1036
+ - test/fixtures/example_application/assets/i/xes.png
1033
1037
  - test/fixtures/example_application/config/back.rb
1034
1038
  - test/fixtures/example_application/config/back.ru
1035
1039
  - test/fixtures/example_application/config/back.yml
@@ -1369,6 +1373,9 @@ test_files:
1369
1373
  - test/fixtures/example_application/Gemfile
1370
1374
  - test/fixtures/example_application/Gemfile.lock
1371
1375
  - test/fixtures/example_application/Rakefile
1376
+ - test/fixtures/example_application/assets/css/site.css.scss
1377
+ - test/fixtures/example_application/assets/i/sine_waves.png
1378
+ - test/fixtures/example_application/assets/i/xes.png
1372
1379
  - test/fixtures/example_application/config/back.rb
1373
1380
  - test/fixtures/example_application/config/back.ru
1374
1381
  - test/fixtures/example_application/config/back.yml