Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Version 0.2 (exported from SVN)

  • Loading branch information...
commit 5556795464d8821ccfad13de907388d400aa52be 1 parent 273848f
@chrisparrish chrisparrish authored
Showing with 568 additions and 6,016 deletions.
  1. +66 −27 README
  2. +2 −2 app/controllers/admin/css_controller.rb
  3. +2 −2 app/controllers/admin/js_controller.rb
  4. +29 −6 app/controllers/admin/text_asset_controller.rb
  5. +25 −20 app/controllers/text_asset_site_controller.rb
  6. +0 −2  app/helpers/admin/css_helper.rb
  7. +0 −2  app/helpers/admin/js_helper.rb
  8. +12 −0 app/helpers/admin/text_asset_helper.rb
  9. +0 −2  app/helpers/text_asset_site_helper.rb
  10. +0 −11 app/models/css_asset.rb
  11. +1 −0  app/models/javascript.rb
  12. +0 −11 app/models/js_asset.rb
  13. +1 −0  app/models/stylesheet.rb
  14. +3 −2 app/models/text_asset.rb
  15. +4 −1 app/models/text_asset_response_cache.rb
  16. +0 −36 app/views/admin/css/edit.html.erb
  17. +0 −36 app/views/admin/css/index.html.erb
  18. +0 −17 app/views/admin/css/remove.html.erb
  19. +0 −37 app/views/admin/js/edit.html.erb
  20. +0 −36 app/views/admin/js/index.html.erb
  21. +0 −17 app/views/admin/js/remove.html.erb
  22. +30 −0 app/views/admin/text_asset/edit.html.erb
  23. +40 −0 app/views/admin/text_asset/index.html.erb
  24. +17 −0 app/views/admin/text_asset/remove.html.erb
  25. +22 −6 custom_settings.rb
  26. +5 −3 db/migrate/001_create_text_assets.rb
  27. +0 −119 lib/cssmin.rb
  28. +0 −233 lib/jsmin.rb
  29. +25 −11 lib/styles_n_scripts/config.rb
  30. +119 −67 spec/controllers/admin/css_and_js_controllers_common_spec.rb
  31. +0 −3  spec/controllers/admin/css_controller_spec.rb
  32. +0 −3  spec/controllers/admin/js_controller_spec.rb
  33. +53 −44 spec/controllers/text_asset_site_controller_spec.rb
  34. +0 −11 spec/helpers/admin/css_helper_spec.rb
  35. +0 −11 spec/helpers/admin/js_helper_spec.rb
  36. +0 −11 spec/helpers/text_asset_site_helper_spec.rb
  37. +39 −19 spec/lib/config_spec.rb
  38. +5 −11 spec/models/css_asset_and_js_asset_common_spec.rb
  39. +0 −34 spec/models/css_asset_minify_spec.rb
  40. +0 −25 spec/models/js_asset_minify_spec.rb
  41. +6 −2 spec/models/text_asset_response_cache_spec.rb
  42. +0 −4,184 spec/samples/prototype.js
  43. +0 −264 spec/samples/prototype.minified.js
  44. +0 −624 spec/samples/radiant.css
  45. +0 −1  spec/samples/radiant.minified.css
  46. +0 −12 spec/samples/test.css
  47. +0 −1  spec/samples/test.minified.css
  48. +23 −15 spec/scenarios/javascripts_scenario.rb
  49. +23 −15 spec/scenarios/stylesheets_scenario.rb
  50. +16 −20 styles_n_scripts_extension.rb
View
93 README
@@ -1,52 +1,91 @@
= Styles 'n Scripts Extension
-The files extension is a proof-of-concept suggested by John Long
-as a means of separating javascripts & stylesheets from other
-site content stored in pages.
+The Styles 'n Scripts extension is a proof-of-concept suggested by John Long
+as a means of separating javascripts & stylesheets from other site content
+stored in pages.
+
+Even though I call it "proof-of-concept," it's already been through one
+iteration with John and is pretty thoroughly tested so don't think that it's not
+usable yet. (The versioning is more to denote the other features that I still
+want to put in it).
WHY CHANGE THINGS?
==================
-As John sees it, the pages tab is really for the main content.
-(Think of it as the list of available destinations for your users)
-Sure, they need stylesheets and such but those are supporting files
-that they need to view your pages.
+As John sees it, the pages tab is really for storing the main content. (Think of
+it as the list of available destinations for your users). Sure, they need
+stylesheets and javascripts but those are supporting files (much like images).
+They augment your pages.
There are a number of interesting benefits gained by this approach:
- * These files really deserve designer-level permissions no user-
- level. Well, they just got their own tabs.
+ * These files really deserve designer-level permissions -- not user-level.
+ Well, they just got their own tabs with the appropriate permissions.
- * These files should be cached differently. Rather than the 5
- minute page cache, we now have essentially infinite caching.
+ * These files should be cached differently. Rather than the 5 minute
+ page cache, we now have essentially infinite caching. Even more importantly
+ because there's no dynamic content, we can tie the Last-Modified header to
+ the updated_at datestamp and allow your server to quit repeatedly serving
+ these assets.
- * This frees up pages to offer fields like <meta keywords...> that
- make absolutely no sense for, say, a stylesheet page.
+ * This frees up pages to offer fields (like meta keywords, meta description,
+ etc.) and focus on behaviors that make absolutely no sense for, say, a
+ stylesheet page (I mean, do javascripts really need a layout?).
- * This opens the door for minification and obfuscation of scripts
- and stylesheets (I've incorporated JSMin and a stylesheet minifier
- in the initial release. Future versions may include Dean Edwards Packer or
- other more advanced compressors).
+ * Declutter the pages tree view so that it truly only shows what your clients
+ see -- the things they'd aim their browser at.
+
+ * This opens the door for validation, minification and obfuscation of scripts
+ and stylesheets (I'm thinking that these belong in a separate extension but
+ they're *much* easier to build now that CSS and JS are a distinct type of
+ object).
* This lays a conceptual foundation for other, non-text assets like images,
flash, and the like.
+INSTALLATION
+============
+1. Copy this extension into your existing Radiant project (place it at:
+ [your project location]/vendor/extensions/styles_n_scripts
+
+2. Incorporate the database migrations into your existing database using:
+ rake db:migrate:extensions
+
+ Or, if you prefer to be more specific to this extension:
+ rake radiant:extensions:styles_n_scripts:migrate
+
+3. Copy the needed images into Radiant's /public/images directory using:
+ rake radiant:extensions:styles_n_scripts:update
+
+4. (Optional) Configure your stylesheet and javascript directories. By default
+ The Styles 'n Scripts serves your stylesheets and javascripts out of the
+ /css and /js folders respectively. You can change these locations via the
+ custom_settings.rb file if you wish (just make sure you aren't competing
+ with Radiant's /public files or your own pages.
+
+5. (Optional) Configure your stylesheet and javascript content (MIME) types.
+ Again there are defaults but you can change them via the custom_settings.rb
+ file.
+
-
TO-DO
=====
-Create a way for users to upload stylesheets and javascripts (maybe even many in
-a zipped file) and unpack them into the db.
+Create a way for users to upload stylesheets and javascripts (maybe even upload
+many via a zipped file) and unpack them into the db.
-Improve caching (I'd like to see a scenario where files are cached on save (or
-even just Rails page caching. Maintaining a server-ready cache that bypasses
-ruby wouldn't be all that hard for these items.).
+Improve caching. I'd like to see a scenario where files are cached-on-save.
+Rails page caching might be a great choice. (Maintaining the cache -- sweepers,
+etc. -- wouldn't be all that hard for these items.).
Create <r:stylesheet> (or maybe <r:text_asset>) tag to let users reference
-stylesheets from pages, snippets, and layouts.
+stylesheets from pages, snippets, and layouts. Is this really helpful?
+
+Create a <r:include_stylesheet> tag for stylesheets and one for scripts (similar
+to the <r:content> tag for pages) to merge stylesheets/scripts into one big file
+to reduce the number of requests and speed serving.
-Create a tagging mechanism for combining stylesheets or scripts into a single
-file to reduce the number of requests and speed serving.
+Add file minification to strip out comments and whitespace (and maybe obfuscate)
+javascripts and stylesheets. (I already have this started).
-Figure out what the core team needs to get this puppy baked in with Radiant!
+Figure out what the core team needs to get this puppy baked into Radiant!
View
4 app/controllers/admin/css_controller.rb
@@ -1,8 +1,8 @@
class Admin::CssController < Admin::TextAssetController
- model_class CssAsset
+ model_class Stylesheet
only_allow_access_to :index, :new, :edit, :remove,
:when => [:developer, :admin],
:denied_url => { :controller => 'page', :action => 'index' },
- :denied_message => 'You must have developer privileges to perform this action.'
+ :denied_message => 'You must have developer or administrator privileges to perform this action.'
end
View
4 app/controllers/admin/js_controller.rb
@@ -1,8 +1,8 @@
class Admin::JsController < Admin::TextAssetController
- model_class JsAsset
+ model_class Javascript
only_allow_access_to :index, :new, :edit, :remove,
:when => [:developer, :admin],
:denied_url => { :controller => 'page', :action => 'index' },
- :denied_message => 'You must have developer privileges to perform this action.'
+ :denied_message => 'You must have developer or administrator privileges to perform this action.'
end
View
35 app/controllers/admin/text_asset_controller.rb
@@ -1,22 +1,45 @@
class Admin::TextAssetController < Admin::AbstractModelController
- def initialize
+ def initialize()
super
+ # overwrite @cache from super with our special cache
@cache = TextAssetResponseCache.instance
end
+
+ def index
+ # we CANNOT use the plural 'self.models' as this creates a @stylesheets
+ # instance variable which conflictss with Radiants admin @stylesheets
+ @text_assets = self.model = model_class.find(:all)
+ # force child controllers to render template in the admin/text_asset directory
+ render :template => "admin/text_asset/index", :object => @model_name = model_name
+ end
+
+
def new
- self.model = model_class.new
- render :action => "edit" if handle_new_or_edit_post
+ @text_asset = self.model = model_class.new
+ # force child controllers to render template in the admin/text_asset directory
+ render :template => "admin/text_asset/edit" if handle_new_or_edit_post
end
+
+ def edit
+ @text_asset = self.model = model_class.find_by_id(params[:id])
+ # force child controllers to render template in the admin/text_asset directory
+ render :template => "admin/text_asset/edit" if handle_new_or_edit_post
+ end
+
+
def remove
- self.model = model_class.find(params[:id])
+ @text_asset = self.model = model_class.find(params[:id])
if request.post?
model.destroy
announce_removed
- clear_model_cache
- redirect_to model_index_url # <-- Added this line to clear cache on remove
+ clear_model_cache # <-- Added this line to clear cache on remove
+ redirect_to model_index_url
+ else
+ # force child controllers to render template in the admin/text_asset directory
+ render :template => "admin/text_asset/remove"
end
end
View
45 app/controllers/text_asset_site_controller.rb
@@ -24,26 +24,31 @@ def show_text_asset
end
- def show_uncached_text_asset(filename, asset_class, url)
- case asset_class
- when 'css_asset'
- text_asset = CssAsset.find_by_filename(filename)
- mime_type = StylesNScripts::Config[:css_mime_type]
- when 'js_asset'
- text_asset = JsAsset.find_by_filename(filename)
- mime_type = StylesNScripts::Config[:js_mime_type]
+ private
+
+ def show_uncached_text_asset(filename, asset_class, url)
+ case asset_class
+ when 'stylesheet'
+ @text_asset = Stylesheet.find_by_filename(filename)
+ mime_type = StylesNScripts::Config[:stylesheet_mime_type]
+ when 'javascript'
+ @text_asset = Javascript.find_by_filename(filename)
+ mime_type = StylesNScripts::Config[:javascript_mime_type]
+ end
+ unless @text_asset.nil?
+ response.headers['Content-Type'] = mime_type
+ # set the last modified date based on updated_at time for the asset
+ # we can do this as long as there is no dynamic content in the assets
+ response.headers['Last-Modified'] = @text_asset.updated_at
+ response.body = @text_asset.content
+
+ # for text_assets, we cache no matter what (there's no status setting for them)
+ text_asset_cache.cache_response(url, response) if request.get?
+ @performed_render = true
+ else
+ params[:url] = url
+ show_page
+ end
end
- unless text_asset.nil?
- response.headers['Content-Type'] = mime_type
- response.body = text_asset.content
-
- # for text_assets, we cache no matter what (there's no status setting for them)
- @text_asset_cache.cache_response(url, response) if request.get?
- @performed_render = true
- else
- params[:url] = url
- show_page
- end
- end
end
View
2  app/helpers/admin/css_helper.rb
@@ -1,2 +0,0 @@
-module Admin::CssHelper
-end
View
2  app/helpers/admin/js_helper.rb
@@ -1,2 +0,0 @@
-module Admin::JsHelper
-end
View
12 app/helpers/admin/text_asset_helper.rb
@@ -0,0 +1,12 @@
+module Admin::TextAssetHelper
+
+ def model_name
+ (@model_name || @text_asset.class).to_s.underscore
+ end
+
+
+ def proper_model_name
+ model_name.humanize.titlecase
+ end
+
+end
View
2  app/helpers/text_asset_site_helper.rb
@@ -1,2 +0,0 @@
-module TextAssetSiteHelper
-end
View
11 app/models/css_asset.rb
@@ -1,11 +0,0 @@
-class CssAsset < TextAsset
-
- def content
- if self.minify?
- CSSMin.minify(self.raw_content)
- else
- super
- end
- end
-
-end
View
1  app/models/javascript.rb
@@ -0,0 +1 @@
+class Javascript < TextAsset; end
View
11 app/models/js_asset.rb
@@ -1,11 +0,0 @@
-class JsAsset < TextAsset
-
- def content
- if self.minify?
- JSMin.minify(self.raw_content)
- else
- super
- end
- end
-
-end
View
1  app/models/stylesheet.rb
@@ -0,0 +1 @@
+class Stylesheet < TextAsset; end
View
5 app/models/text_asset.rb
@@ -7,8 +7,9 @@ class TextAsset < ActiveRecord::Base
# the following regexp uses \A and \Z rather than ^ and $ to enforce no "\n" characters
validates_format_of :filename, :with => %r{\A[-_.A-Za-z0-9]*\Z}, :message => 'invalid format'
- def content
- self.raw_content
+ def url
+ StylesNScripts::Config["#{self.class.to_s.underscore}_directory"] +
+ "/" + self.filename
end
end
View
5 app/models/text_asset_response_cache.rb
@@ -1,12 +1,15 @@
class TextAssetResponseCache < ResponseCache
+
def initialize(options={})
@@defaults[:directory] = "#{RAILS_ROOT}/#{StylesNScripts::Config[:response_cache_directory]}"
@@defaults[:expire_time] = 1.year
super(options)
end
+
def self.instance
- # can't use @@instance as this class is inherited
+ # can't use @@instance as this class is inherited so use: @tarc_instance
@@tarc_instance ||= new
end
+
end
View
36 app/views/admin/css/edit.html.erb
@@ -1,36 +0,0 @@
-<% if @css_asset.new_record? -%>
-<h1>New Stylesheet</h1>
-<% else -%>
-<h1>Edit Stylesheet</h1>
-<% end -%>
-
-<form method="post" action="">
- <%= hidden_field "css_asset", "lock_version" %>
- <div class="form-area">
- <p class="title">
- <label for="css_asset_filename">Filename</label>
- <%= text_field "css_asset", "filename", :class => 'textbox', :maxlength => 100 %>
- </p>
-
- <p class="content">
- <label for="css_asset_raw_content">Body</label>
- <%= text_area "css_asset", "raw_content", :class => "textarea", :style => "width: 100%" %>
- </p>
-
- <div class="row">
- <p>
- <%= check_box("css_asset", "minify") %>
- <label for="css_asset_minify">Minify Output?</label>
- </p>
- </div>
-
- <span class="clear">&nbsp;</span>
-
- <%= updated_stamp @css_asset %>
- </div>
- <p class="buttons">
- <%= submit_tag(@css_asset.new_record? ? 'Create Stylesheet' : 'Save Changes', :class => 'button') %> <%= save_model_and_continue_editing_button(@css_asset) %> or <%= link_to "Cancel", css_asset_index_url %>
- </p>
-</form>
-
-<%= focus 'css_asset_filename' %>
View
36 app/views/admin/css/index.html.erb
@@ -1,36 +0,0 @@
-<h1>Stylesheets</h1>
-
-<p>Cascading Stylesheets (CSS) are text assets used by your web pages, layouts, and snippets to stylize your content.</p>
-
-<table id="css_assets" class="index" cellpadding="0" cellspacing="0" border="0">
- <thead>
- <tr>
- <th class="css_asset">Stylesheet</th>
- <th class="modify">Modify</th>
- </tr>
- </thead>
- <tbody>
-<% unless @css_assets.empty? -%>
-<% for css_asset in @css_assets -%>
- <tr class="node level-1">
- <td class="css_asset">
- <%= image "stylesheet", :class => "icon", :alt => 'stylesheet-icon', :title => '' %>
- <span><%= link_to css_asset.filename, css_asset_edit_url(:id => css_asset) %></span>
- </td>
- <td class="remove"><%= link_to image('remove', :alt => 'Remove Stylesheet'), css_asset_remove_url(:id => css_asset) %></td>
- </tr>
-<% end -%>
-<% else -%>
- <tr>
- <td colspan="2" class="note">No Stylesheets</td>
- </tr>
-<% end -%>
- </tbody>
-</table>
-<script type="text/javascript">
-// <![CDATA[
- new RuledTable('css_assets');
-// ]]>
-</script>
-
-<p><%= link_to image('new-stylesheet', :alt => 'New Css'), css_asset_new_url %></p>
View
17 app/views/admin/css/remove.html.erb
@@ -1,17 +0,0 @@
-<h1>Remove Stylesheet</h1>
-<p>Are you sure you want to <strong class="warning">permanently remove</strong> the following css?</p>
-
-<table id="css_assets" class="index" cellpadding="0" cellspacing="0" border="0">
- <tbody>
- <tr class="node level-1" onmouseover="Element.addClassName(this, 'highlight');" onmouseout="Element.removeClassName(this, 'highlight');">
- <td class="css_asset">
- <%= image "stylesheet", :class => "icon", :alt => 'stylesheet-icon', :title => '' %>
- <%= @css_asset.filename %>
- </td>
- </tr>
- </tbody>
-</table>
-
-<form method="post" action="">
- <p class="buttons"><%= submit_tag "Delete Stylesheet", :class => 'button' %> or <%= link_to 'Cancel', css_asset_index_url %></p>
-</form>
View
37 app/views/admin/js/edit.html.erb
@@ -1,37 +0,0 @@
-<% if @js_asset.new_record? -%>
-<h1>New Javascript</h1>
-<% else -%>
-<h1>Edit Javascript</h1>
-<% end -%>
-
-<form method="post" action="">
- <%= hidden_field "js_asset", "lock_version" %>
- <div class="form-area">
- <p class="title">
- <label for="js_asset_filename">Filename</label>
- <%= text_field "js_asset", "filename", :class => 'textbox', :maxlength => 100 %>
- </p>
-
- <p class="content">
- <label for="js_asset_raw_content">Body</label>
- <%= text_area "js_asset", "raw_content", :class => "textarea", :style => "width: 100%" %>
- </p>
-
- <div class="row">
- <p>
- <%= check_box("js_asset", "minify") %>
- <label for="js_asset_minify">Minify Output?</label>
- </p>
- </div>
-
- <span class="clear">&nbsp;</span>
-
- <%= updated_stamp @js_asset %>
- </div>
-
- <p class="buttons">
- <%= submit_tag(@js_asset.new_record? ? 'Create Javascript' : 'Save Changes', :class => 'button') %> <%= save_model_and_continue_editing_button(@js_asset) %> or <%= link_to "Cancel", js_asset_index_url %>
- </p>
-</form>
-
-<%= focus 'js_asset_filename' %>
View
36 app/views/admin/js/index.html.erb
@@ -1,36 +0,0 @@
-<h1>Javascripts</h1>
-
-<p>Javascripts are text assets used by your web pages, layouts, and snippets to add dynamic behaviors.</p>
-
-<table id="js_assets" class="index" cellpadding="0" cellspacing="0" border="0">
- <thead>
- <tr>
- <th class="js_asset">Javascript</th>
- <th class="modify">Modify</th>
- </tr>
- </thead>
- <tbody>
-<% unless @js_assets.empty? -%>
-<% for js_asset in @js_assets -%>
- <tr class="node level-1">
- <td class="js_asset">
- <%= image "javascript", :class => "icon", :alt => 'javascript-icon', :title => '' %>
- <span><%= link_to js_asset.filename, js_asset_edit_url(:id => js_asset) %></span>
- </td>
- <td class="remove"><%= link_to image('remove', :alt => 'Remove Javascript'), js_asset_remove_url(:id => js_asset) %></td>
- </tr>
-<% end -%>
-<% else -%>
- <tr>
- <td colspan="2" class="note">No Javascripts</td>
- </tr>
-<% end -%>
- </tbody>
-</table>
-<script type="text/javascript">
-// <![CDATA[
- new RuledTable('js_assets');
-// ]]>
-</script>
-
-<p><%= link_to image('new-javascript', :alt => 'New Css'), js_asset_new_url %></p>
View
17 app/views/admin/js/remove.html.erb
@@ -1,17 +0,0 @@
-<h1>Remove Javascript</h1>
-<p>Are you sure you want to <strong class="warning">permanently remove</strong> the following js?</p>
-
-<table id="js_assets" class="index" cellpadding="0" cellspacing="0" border="0">
- <tbody>
- <tr class="node level-1" onmouseover="Element.addClassName(this, 'highlight');" onmouseout="Element.removeClassName(this, 'highlight');">
- <td class="js_asset">
- <%= image "javascript", :class => "icon", :alt => 'javascript-icon', :title => '' %>
- <%= @js_asset.filename %>
- </td>
- </tr>
- </tbody>
-</table>
-
-<form method="post" action="">
- <p class="buttons"><%= submit_tag "Delete Javascript", :class => 'button' %> or <%= link_to 'Cancel', js_asset_index_url %></p>
-</form>
View
30 app/views/admin/text_asset/edit.html.erb
@@ -0,0 +1,30 @@
+<% if @text_asset.new_record? -%>
+<h1>New <%= proper_model_name %></h1>
+<% else -%>
+<h1>Edit <%= proper_model_name %></h1>
+<% end -%>
+
+<form method="post" action="">
+ <%= hidden_field(model_name, "lock_version") %>
+ <div class="form-area">
+ <p class="title">
+ <label for="<%= model_name %>_filename">Filename</label>
+ <%= text_field(model_name, "filename", :class => 'textbox', :maxlength => 100) %>
+ </p>
+
+ <p class="content">
+ <label for="<%= model_name %>_content">Body</label>
+ <%= text_area(model_name, "content", :class => "textarea", :style => "width: 100%") %>
+ </p>
+
+ <span class="clear">&nbsp;</span>
+
+ <%= updated_stamp @text_asset %>
+ </div>
+
+ <p class="buttons">
+ <%= submit_tag(@text_asset.new_record? ? "Create #{proper_model_name}" : 'Save Changes', :class => 'button') %> <%= save_model_and_continue_editing_button(@text_asset) %> or <%= link_to("Cancel", send("#{model_name}_index_url") ) %>
+ </p>
+</form>
+
+<%= focus("#{model_name}_filename") %>
View
40 app/views/admin/text_asset/index.html.erb
@@ -0,0 +1,40 @@
+<% if model_name == 'stylesheet' -%>
+<h1>Stylesheets</h1>
+<p>Cascading Stylesheets (CSS files) can be used to alter the look &amp; feel of your pages, layouts, and/or snippets.</p>
+<% elsif model_name == 'javascript' -%>
+<h1>Javascripts</h1>
+<p>Javascripts add dynamic behaviors that can be used within your pages, layouts, and/or snippets.</p>
+<% end -%>
+
+<table id="stylesheets" class="index" cellpadding="0" cellspacing="0" border="0">
+ <thead>
+ <tr>
+ <th class="<%= model_name %>"><%= proper_model_name %></th>
+ <th class="modify">Modify</th>
+ </tr>
+ </thead>
+ <tbody>
+<% unless @text_assets.empty? -%>
+<% for text_asset in @text_assets -%>
+ <tr class="node level-1">
+ <td class="snippet">
+ <%= image model_name, :class => "icon", :alt => "#{model_name}-icon", :title => text_asset.url %>
+ <span><%= link_to text_asset.filename, send("#{model_name}_edit_url", :id => text_asset), :title => text_asset.url %></span>
+ </td>
+ <td class="remove"><%= link_to image('remove', :alt => "Remove #{proper_model_name}"), send("#{model_name}_remove_url", :id => text_asset) %></td>
+ </tr>
+<% end -%>
+<% else -%>
+ <tr>
+ <td colspan="2" class="note">No <%= proper_model_name.pluralize %></td>
+ </tr>
+<% end -%>
+ </tbody>
+</table>
+<script type="text/javascript">
+// <![CDATA[
+ new RuledTable('<%= model_name %>');
+// ]]>
+</script>
+
+<p><%= link_to image("new-#{model_name}", :alt => "New #{proper_model_name}"), send("#{model_name}_new_url") %></p>
View
17 app/views/admin/text_asset/remove.html.erb
@@ -0,0 +1,17 @@
+<h1>Remove <%= proper_model_name %></h1>
+<p>Are you sure you want to <strong class="warning">permanently remove</strong> the following <%= model_name %>?</p>
+
+<table id="<%= model_name %>" class="index" cellpadding="0" cellspacing="0" border="0">
+ <tbody>
+ <tr class="node level-1" onmouseover="Element.addClassName(this, 'highlight');" onmouseout="Element.removeClassName(this, 'highlight');">
+ <td class="<%= model_name %>">
+ <%= image(model_name, :class => "icon", :alt => "#{model_name}-icon", :title => '') %>
+ <%= @text_asset.filename %>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<form method="post" action="">
+ <p class="buttons"><%= submit_tag("Delete #{proper_model_name}", :class => 'button') %> or <%= link_to('Cancel', send("#{model_name}_index_url") ) %></p>
+</form>
View
28 custom_settings.rb
@@ -1,11 +1,13 @@
# Initializes settings for the extension. By uncommenting lines below, you may
-# customize these settings.
+# customize these settings. This file gets executed once at load time so these
+# values are designed to stick until you reboot Radiant.
#
# Ideally, along with some updating to Radiant, managing these settings would
# move to their own location in the Radiant Admin UI (probably with admin-only
# permissions).
module CustomSettings
+ # always start by initializing the defauls so we know what to expect...
StylesNScripts::Config.restore_defaults
# Uncomment the items below to customize the locations from which stylesheets
@@ -18,10 +20,10 @@ module CustomSettings
#
# Currently, you may use complex paths like:
# 'assets/text/css'
- # but be warned, we may or may not support this in the future.
-# StylesNScripts::Config[:css_directory] = 'my_awesome_styles'
-# StylesNScripts::Config[:js_directory] = 'my_nifty_scripts')
+# StylesNScripts::Config[:stylesheet_directory] = 'my/stylesheets/go/here'
+# StylesNScripts::Config[:javascript_directory] = 'all_my_scripts'
+
# Uncomment the items below to customize the MIME types for stylesheets and
@@ -29,6 +31,20 @@ module CustomSettings
# * text/css
# * text/javascript
-# StylesNScripts::Config[:css_mime_type] = "It's a Stylesheet File!"
-# StylesNScripts::Config[:js_mime_type] = "It's a Javascript File!"
+# StylesNScripts::Config[:stylesheet_mime_type] = "text/foo"
+# StylesNScripts::Config[:javascript_mime_type] = "application/bar"
+
+
+ # Uncomment the item below to customize where stylesheets and javascripts are
+ # cached. (This extension caches those files in a different location than
+ # where Radiant's ResponseCache caches pages.
+ #
+ # Our cache (the TextAssetResponseCache) will periodically blow away this
+ # entire directory so make sure you don't pick some conflicting name. For
+ # instance, choosing 'public' as your cache folder would be *bad*
+ #
+ # The default is:
+ # * text_asset_cache
+
+# StylesNScripts::Config[:response_cache_directory] = "my_cache_directory"
end
View
8 db/migrate/001_create_text_assets.rb
@@ -1,16 +1,18 @@
class CreateTextAssets < ActiveRecord::Migration
+
def self.up
create_table :text_assets do |t|
t.string :class_name, :limit => 25
t.string :filename, :limit => 100
- t.text :raw_content
- t.integer :created_by, :updated_by, :lock_version
- t.boolean :minify, :default => false
+ t.text :content
t.timestamps
+ t.integer :created_by_id, :updated_by_id, :lock_version
end
end
+
def self.down
drop_table :text_assets
end
+
end
View
119 lib/cssmin.rb
@@ -1,119 +0,0 @@
-#--
-# Copyright (c) 2008 Ryan Grove <ryan@wonko.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of this project nor the names of its contributors may be
-# used to endorse or promote products derived from this software without
-# specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#++
-
-# = CSSMin
-#
-# Minifies CSS using a fast, safe routine adapted from Julien Lecomte's YUI
-# Compressor, which was in turn adapted from Isaac Schlueter's cssmin PHP
-# script.
-#
-# Author:: Ryan Grove (mailto:ryan@wonko.com)
-# Version:: 1.0.0 (2008-03-22)
-# Copyright:: Copyright (c) 2008 Ryan Grove. All rights reserved.
-# License:: New BSD License (http://opensource.org/licenses/bsd-license.php)
-# Website:: http://github.com/rgrove/cssmin/
-#
-# == Example
-#
-# require 'rubygems'
-# require 'cssmin'
-#
-# File.open('example.css', 'r') {|file| puts CSSMin.minify(file) }
-#
-module CSSMin
-
- # Reads CSS from +input+ (which can be a String or an IO object) and
- # returns a String containing minified CSS.
- def self.minify(input)
- css = input.is_a?(IO) ? input.read : input.to_s
-
- # Remove comments.
- css.gsub!(/\/\*[\s\S]*?\*\//, '')
-
- # Compress all runs of whitespace to a single space to make things easier
- # to work with.
- css.gsub!(/\s+/, ' ')
-
- # Replace box model hacks with placeholders.
- css.gsub!(/"\\"\}\\""/, '___BMH___')
-
- # Remove unnecessary spaces, but be careful not to turn "p :link {...}"
- # into "p:link{...}".
- css.gsub!(/(?:^|\})[^\{:]+\s+:+[^\{]*\{/) do |match|
- match.gsub(':', '___PSEUDOCLASSCOLON___')
- end
- css.gsub!(/\s+([!\{\};:>+\(\)\],])/, '\1')
- css.gsub!('___PSEUDOCLASSCOLON___', ':')
- css.gsub!(/([!\{\}:;>+\(\[,])\s+/, '\1')
-
- # Add missing semicolons.
- css.gsub!(/([^;\}])\}/, '\1;}')
-
- # Replace 0(%, em, ex, px, in, cm, mm, pt, pc) with just 0.
- css.gsub!(/([\s:])([+-]?0)(?:%|em|ex|px|in|cm|mm|pt|pc)/i, '\1\2')
-
- # Replace 0 0 0 0; with 0.
- css.gsub!(/:(?:0 )+0;/, ':0;')
-
- # Replace background-position:0; with background-position:0 0;
- css.gsub!('background-position:0;', 'background-position:0 0;')
-
- # Replace 0.6 with .6, but only when preceded by : or a space.
- css.gsub!(/(:|\s)0+\.(\d+)/, '\1.\2')
-
- # Convert rgb color values to hex values.
- css.gsub!(/rgb\s*\(\s*([0-9,\s]+)\s*\)/) do |match|
- '#' << $1.scan(/\d+/).map{|n| n.to_i.to_s(16).rjust(2, '0') }.join
- end
-
- # Compress color hex values, making sure not to touch values used in IE
- # filters, since they would break.
- css.gsub!(/([^"'=\s])\s*#([0-9a-f])\2([0-9a-f])\3([0-9a-f])\4/i, '\1#\2\3\4')
-
- # Remove empty rules.
- css.gsub!(/[^\}]+\{;\}\n/, '')
-
- # Re-insert box model hacks.
- css.gsub!('___BMH___', '"\"}\""')
-
-# # Replace 6.0 with 6, but only when preceded by : or a space.
- css.gsub!(/(:|\s)(\d+)\.0+(?=[^\d])/, '\1\2')
-
-# # Replace : 2em 2em 2em 2em; with :2em;
- css.gsub!(/(?!:)((?:\d+|\d+\.[1-9]+|\.[1-9]+)(?:%|em|ex|px|in|cm|mm|pt|pc))\s+\1\s+\1\s+\1(?=;|\})/, '\1')
-
-# # Replace : 2em 3px 2em 3px; with :2em 3px;
- css.gsub!(/(?!:)((?:\d+|\d+\.[1-9]+|\.[1-9]+)+(?:%|em|ex|px|in|cm|mm|pt|pc)\s+(?:\d+|\d+\.[1-9]+|\.[1-9]+)+(?:%|em|ex|px|in|cm|mm|pt|pc))\s+\1(?=;|\})/, '\1')
-
-# # Remove any semicolons before braces ';}' => '}'
- css.gsub!(/;(\})/, '\1')
-
- css.strip
- end
-
-end
View
233 lib/jsmin.rb
@@ -1,233 +0,0 @@
-#--
-# jsmin.rb - Ruby implementation of Douglas Crockford's JSMin.
-#
-# This is a port of jsmin.c, and is distributed under the same terms, which are
-# as follows:
-#
-# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# The Software shall be used for Good, not Evil.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#++
-
-require 'strscan'
-
-# = JSMin
-#
-# Ruby implementation of Douglas Crockford's JavaScript minifier, JSMin.
-#
-# Author:: Ryan Grove (mailto:ryan@wonko.com)
-# Version:: 1.0.0 (2008-03-22)
-# Copyright:: Copyright (c) 2008 Ryan Grove. All rights reserved.
-# Website:: http://github.com/rgrove/jsmin/
-#
-# == Example
-#
-# require 'rubygems'
-# require 'jsmin'
-#
-# File.open('example.js', 'r') {|file| puts JSMin.minify(file) }
-#
-module JSMin
- ORD_LF = "\n"[0].freeze
- ORD_SPACE = ' '[0].freeze
-
- class << self
-
- # Reads JavaScript from +input+ (which can be a String or an IO object) and
- # returns a String containing minified JS.
- def minify(input)
- @js = StringScanner.new(input.is_a?(IO) ? input.read : input.to_s)
-
- @a = "\n"
- @b = nil
- @lookahead = nil
- @output = ''
-
- action_get
-
- while !@a.nil? do
- case @a
- when ' '
- if alphanum?(@b)
- action_output
- else
- action_copy
- end
-
- when "\n"
- if @b == ' '
- action_get
- elsif @b =~ /[{\[\(+-]/
- action_output
- else
- if alphanum?(@b)
- action_output
- else
- action_copy
- end
- end
-
- else
- if @b == ' '
- if alphanum?(@a)
- action_output
- else
- action_get
- end
- elsif @b == "\n"
- if @a =~ /[}\]\)\\"+-]/
- action_output
- else
- if alphanum?(@a)
- action_output
- else
- action_get
- end
- end
- else
- action_output
- end
- end
- end
-
- @output
- end
-
- private
-
- # Corresponds to action(1) in jsmin.c.
- def action_output
- @output << @a
- action_copy
- end
-
- # Corresponds to action(2) in jsmin.c.
- def action_copy
- @a = @b
-
- if @a == '\'' || @a == '"'
- loop do
- @output << @a
- @a = get
-
- break if @a == @b
-
- if @a[0] <= ORD_LF
- raise "JSMin parse error: unterminated string literal: #{@a}"
- end
-
- if @a == '\\'
- @output << @a
- @a = get
-
- if @a[0] <= ORD_LF
- raise "JSMin parse error: unterminated string literal: #{@a}"
- end
- end
- end
- end
-
- action_get
- end
-
- # Corresponds to action(3) in jsmin.c.
- def action_get
- @b = nextchar
-
- if @b == '/' && (@a == "\n" || @a =~ /[\(,=:\[!&|?{};]/)
- @output << @a
- @output << @b
-
- loop do
- @a = get
-
- if @a == '/'
- break
- elsif @a == '\\'
- @output << @a
- @a = get
- elsif @a[0] <= ORD_LF
- raise "JSMin parse error: unterminated regular expression " +
- "literal: #{@a}"
- end
-
- @output << @a
- end
-
- @b = nextchar
- end
- end
-
- # Returns true if +c+ is a letter, digit, underscore, dollar sign,
- # backslash, or non-ASCII character.
- def alphanum?(c)
- c.is_a?(String) && !c.empty? && (c[0] > 126 || c =~ /[0-9a-z_$\\]/i)
- end
-
- # Returns the next character from the input. If the character is a control
- # character, it will be translated to a space or linefeed.
- def get
- c = @lookahead.nil? ? @js.getch : @lookahead
- @lookahead = nil
-
- return c if c.nil? || c == "\n" || c[0] >= ORD_SPACE
- return "\n" if c == "\r"
- return ' '
- end
-
- # Gets the next character, excluding comments.
- def nextchar
- c = get
- return c unless c == '/'
-
- case peek
- when '/'
- loop do
- c = get
- return c if c[0] <= ORD_LF
- end
-
- when '*'
- get
- loop do
- case get
- when '*'
- if peek == '/'
- get
- return ' '
- end
-
- when nil
- raise 'JSMin parse error: unterminated comment'
- end
- end
-
- else
- return c
- end
- end
-
- # Gets the next character without getting it.
- def peek
- @lookahead = get
- end
- end
-end
View
36 lib/styles_n_scripts/config.rb
@@ -1,17 +1,19 @@
module StylesNScripts
+
class Config
# stores the default values for the settings
# note: key names *must* be declared as strings -- *not* as symbols
- @defaults = { 'css_directory' => 'css',
- 'js_directory' => 'js',
- 'css_mime_type' => 'text/css',
- 'js_mime_type' => 'text/javascript',
+ @defaults = { 'stylesheet_directory' => 'css',
+ 'javascript_directory' => 'js',
+ 'stylesheet_mime_type' => 'text/css',
+ 'javascript_mime_type' => 'text/javascript',
'response_cache_directory' => 'text_asset_cache'
}
@@live_config = {}
+
class << self
# Getter for the config values. Includes a set of conditionals to
@@ -32,6 +34,7 @@ def [](key)
@@live_config[key]
end
+
# Setter for Config key/value pairs. Keys must be limited to valid
# settings for the extension.
def []=(key, value)
@@ -40,9 +43,9 @@ def []=(key, value)
case key
when 'response_cache_directory'
- validate_cache_directory(value, key)
+ value = validate_cache_directory(value, key)
when /_directory$/
- validate_directory(value, key)
+ value = validate_directory(value, key)
when /_mime_type$/
validate_mime_type(value, key)
end
@@ -50,6 +53,7 @@ def []=(key, value)
@@live_config[key] = Radiant::Config[key] = value
end
+
def restore_defaults
@defaults.each do |key, value|
Radiant::Config[key] = value
@@ -57,25 +61,35 @@ def restore_defaults
@@live_config = {}
end
+
private
-
+
# determines whether 'key' matches one of the keys in @defaults (
def validate_key(key)
raise("invalid setting name: #{key.inspect}") unless @defaults.has_key?(key)
end
-
+
+
def validate_cache_directory(directory, directory_label)
- raise("invalid #{directory_label} value: #{directory.inspect}") unless directory =~ %r{\A[-_A-Za-z0-9]*\Z}
+ raise("invalid #{directory_label} value: #{directory.inspect}") unless directory =~ %r{\A/?[-_A-Za-z0-9]*/?\Z}
+ # return value with leading/trailing slashes removed
+ directory.gsub(/^\//, '').gsub(/\/$/, '')
end
+
def validate_directory(directory, directory_label)
- raise("invalid #{directory_label} value: #{directory.inspect}") unless directory =~ %r{\A[-_A-Za-z0-9]+(/[-_A-Za-z0-9]+)*\Z}
+ raise("invalid #{directory_label} value: #{directory.inspect}") unless directory =~ %r{\A/?[-_A-Za-z0-9]+(/[-_A-Za-z0-9]+)*/?\Z}
+ # return value with leading/trailing slashes removed
+ directory.gsub(/^\//, '').gsub(/\/$/, '')
end
-
+
+
def validate_mime_type(mime_type, mime_type_label)
raise("invalid #{mime_type_label} value: #{mime_type.inspect}") unless mime_type =~ %r{\A[-.A-Za-z0-9]+(/[-.A-Za-z0-9]+)*\Z}
end
end
+
end
+
end
View
186 spec/controllers/admin/css_and_js_controllers_common_spec.rb
@@ -1,17 +1,17 @@
require File.dirname(__FILE__) + '/../../spec_helper'
[ { :controller => Admin::CssController,
- :controller_path => 'admin/css',
- :model => CssAsset,
- :name => 'css_asset',
- :symbol => :css_asset,
+ :controller_path => 'admin/text_asset',
+ :model => Stylesheet,
+ :name => 'stylesheet',
+ :symbol => :stylesheet,
:main_scenario => :main_css },
{ :controller => Admin::JsController,
- :controller_path => 'admin/js',
- :model => JsAsset,
- :name => 'js_asset',
- :symbol => :js_asset,
+ :controller_path => 'admin/text_asset',
+ :model => Javascript,
+ :name => 'javascript',
+ :symbol => :javascript,
:main_scenario => :main_js }
].each do |current_asset|
@@ -21,13 +21,16 @@
# for some *strange* reason, which ever loads 2nd -- javascripts or
# stylesheets -- trumps the previous. Everything works fine *except* for
# calls to text_assets(:symbolic_name). Don't ask me why but it assumes
- # that this method should always return a CssAsset or JsAsset (which-
+ # that this method should always return a Stylesheet or Javascript (which-
# ever is loaded last).
- scenario :users, :stylesheets if current_asset[:name] == 'css_asset'
- scenario :users, :javascripts if current_asset[:name] == 'js_asset'
+ scenario :users, :stylesheets if current_asset[:name] == 'stylesheet'
+ scenario :users, :javascripts if current_asset[:name] == 'javascript'
+ test_helper :caching
+
before :each do
login_as :developer
+ @cache = @controller.cache = FakeResponseCache.new
end
@@ -35,115 +38,136 @@
controller.should be_kind_of(Admin::AbstractModelController)
end
- it "should handle current_asset[:model]s" do
+
+ it "should have a model_class of #{current_asset[:name]}" do
controller.class.model_class.should == current_asset[:model]
end
+
[:index, :new, :edit, :remove].each do |action|
it "should require login to access the #{action} action" do
logout
lambda { get action }.should require_login
end
+
it "should allow access to developers" do
lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:allow => [users(:developer)])
end
+
it "should allow access to admins" do
lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:allow => [users(:admin)])
end
+
it "should deny non-developers and non-admins" do
- lambda { get action }.should restrict_access(:deny => [users(:non_admin), users(:existing)])
+ lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:deny => [users(:non_admin), users(:existing)])
end
end
+
describe "index action" do
before :each do
get :index
end
+
it "should be successful" do
response.should be_success
end
+
it "should render the index template" do
response.should render_template("#{current_asset[:controller_path]}/index")
end
+
it "should load an array of models" do
- plural_symbol = current_asset[:symbol].to_s.pluralize.intern
- assigns[plural_symbol].should be_kind_of(Array)
- assigns[plural_symbol].all? { |i| i.kind_of?(current_asset[:model]) }.should be_true
+ assigns[:text_assets].should be_kind_of(Array)
+ assigns[:text_assets].all? { |i| i.kind_of?(current_asset[:model]) }.should be_true
end
end
+
describe "new action" do
describe "via GET" do
before :each do
get :new
end
+
it "should be successful" do
response.should be_success
end
+
it "should render the edit template" do
response.should render_template("#{current_asset[:controller_path]}/edit")
end
- it "should load a new model" do
- assigns[current_asset[:symbol]].should_not be_nil
- assigns[current_asset[:symbol]].should be_kind_of(current_asset[:model])
- assigns[current_asset[:symbol]].should be_new_record
+
+ it "should load a new #{current_asset[:name]}" do
+ assigns[:text_asset].should_not be_nil
+ assigns[:text_asset].should be_kind_of(current_asset[:model])
+ assigns[:text_asset].should be_new_record
end
end
+
describe "via POST" do
- describe "when the model validates" do
+ describe "when the #{current_asset[:name]} validates" do
before :each do
post :new,
current_asset[:symbol] => send("#{current_asset[:name]}_params")
@text_asset = current_asset[:model].find_by_filename('Test')
end
+
it "should redirect to the index" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_index_url"))
end
- it "should create the model" do
- assigns[current_asset[:symbol]].should_not be_new_record
+
+ it "should create the #{current_asset[:name]}" do
+ assigns[:text_asset].should_not be_new_record
end
+
it "should add a flash notice" do
flash[:notice].should_not be_nil
flash[:notice].should =~ /saved/
end
end
-
- describe "when the model fails validation" do
+
+
+ describe "when the #{current_asset[:name]} fails validation" do
before :each do
post :new,
current_asset[:symbol] => send("#{current_asset[:name]}_params",
:filename => nil)
@text_asset = current_asset[:model].find_by_filename('Test')
end
-
+
+
it "should render the edit template" do
response.should render_template("#{current_asset[:controller_path]}/edit")
end
-
- it "should not create the model" do
- assigns[current_asset[:symbol]].should be_new_record
+
+
+ it "should not create the #{current_asset[:name]}" do
+ assigns[:text_asset].should be_new_record
end
-
+
+
it "should add a flash error" do
flash[:error].should_not be_nil
flash[:error].should =~ /error/
end
end
-
+
+
describe "when 'Save and Continue Editing' was clicked" do
before :each do
post :new,
@@ -152,7 +176,8 @@
:continue => 'Save and Continue Editing'
@text_asset = current_asset[:model].find_by_filename('Test')
end
-
+
+
it "should redirect to the edit action" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_edit_url", :id => @text_asset))
@@ -160,6 +185,7 @@
end
end
+
end
@@ -168,75 +194,89 @@
before :each do
get :edit, :id => text_asset_id(current_asset[:main_scenario])
end
-
+
+
it "should be successful" do
response.should be_success
end
-
+
+
it "should render the edit template" do
response.should render_template("#{current_asset[:controller_path]}/edit")
end
-
- it "should load the existing model" do
- assigns[current_asset[:symbol]].should_not be_nil
- assigns[current_asset[:symbol]].should be_kind_of(current_asset[:model])
- assigns[current_asset[:symbol]].should == text_assets(current_asset[:main_scenario])
+
+
+ it "should load the existing #{current_asset[:name]}" do
+ assigns[:text_asset].should_not be_nil
+ assigns[:text_asset].should be_kind_of(current_asset[:model])
+ assigns[:text_asset].should == text_assets(current_asset[:main_scenario])
end
end
-
+
+
describe "via POST" do
- describe "when the model validates" do
+ describe "when the #{current_asset[:name]} validates" do
before :each do
post :edit,
:id => text_asset_id(current_asset[:main_scenario]),
current_asset[:symbol] => send("#{current_asset[:name]}_params")
end
+
it "should redirect to the index" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_index_url"))
end
- it "should save the model" do
- assigns[current_asset[:symbol]].should be_valid
+
+ it "should save the #{current_asset[:name]}" do
+ assigns[:text_asset].should be_valid
end
+
it "should add a flash notice" do
flash[:notice].should_not be_nil
flash[:notice].should =~ /saved/
end
-
- # it "should clear the model cache" do
- # @cache.should be_cleared
- # end
+
+
+ it "should clear the TextAssetResponseCache" do
+ @cache.should be_cleared
+ end
end
-
- describe "when the model fails validation" do
+
+
+ describe "when the #{current_asset[:name]} fails validation" do
before :each do
post :edit,
:id => text_asset_id(current_asset[:main_scenario]),
current_asset[:symbol] => send("#{current_asset[:name]}_params",
:filename => nil)
end
-
+
+
it "should render the edit template" do
response.should render_template("#{current_asset[:controller_path]}/edit")
end
-
- it "should not save the model" do
- assigns[current_asset[:symbol]].should_not be_valid
+
+
+ it "should not save the #{current_asset[:name]}" do
+ assigns[:text_asset].should_not be_valid
end
-
+
+
it "should add a flash error" do
flash[:error].should_not be_nil
flash[:error].should =~ /error/
end
-
- # it "should not clear the model cache" do
- # @cache.should_not be_cleared
- # end
+
+
+ it "should not clear the TextAssetResponseCache" do
+ @cache.should_not be_cleared
+ end
end
-
+
+
describe "when 'Save and Continue Editing' was clicked" do
before :each do
post :edit,
@@ -245,11 +285,18 @@
:continue => 'Save and Continue Editing'
end
+
it "should redirect to the edit action" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_edit_url",
:id => text_asset_id(current_asset[:main_scenario])))
end
+
+
+ it "should clear the TextAssetResponseCache" do
+ @cache.should be_cleared
+ end
+
end
end
@@ -270,8 +317,8 @@
response.should render_template("#{current_asset[:controller_path]}/remove")
end
- it "should load the specified model" do
- assigns[current_asset[:symbol]].should == text_assets(current_asset[:main_scenario])
+ it "should load the specified #{current_asset[:name]}" do
+ assigns[:text_asset].should == text_assets(current_asset[:main_scenario])
end
end
@@ -280,7 +327,7 @@
post :remove, :id => text_asset_id(current_asset[:main_scenario])
end
- it "should destroy the model" do
+ it "should destroy the #{current_asset[:name]}" do
current_asset[:model].find_by_filename(main_scenario_filename(current_asset[:main_scenario])).should be_nil
end
@@ -293,15 +340,20 @@
flash[:notice].should_not be_nil
flash[:notice].should =~ /deleted/
end
+
+ it "should clear the TextAssetResponseCache" do
+ @cache.should be_cleared
+ end
end
end
-
end
end
-# reverse calculates the filename created by the scenario (main_js -> main.js)
-def main_scenario_filename(main_scenario_symbolic_name)
- main_scenario_symbolic_name.to_s.gsub('_', '.')
-end
+private
+
+ # reverse calculates the filename created by the scenario (main_js -> main.js)
+ def main_scenario_filename(main_scenario_symbolic_name)
+ main_scenario_symbolic_name.to_s.gsub('_', '.')
+ end
View
3  spec/controllers/admin/css_controller_spec.rb
@@ -1,3 +0,0 @@
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-describe Admin::CssController do; end
View
3  spec/controllers/admin/js_controller_spec.rb
@@ -1,3 +0,0 @@
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-describe Admin::JsController do; end
View
97 spec/controllers/text_asset_site_controller_spec.rb
@@ -2,7 +2,7 @@
describe TextAssetSiteController, "routes text_asset requests" do
# Pages scenario is used for two reasons. First, we test for conditions where
- # pages have been created that conflict with css_ or js_directory
+ # pages have been created that conflict with css_ or javascript_directory
# values. Secondly, at least one page must exist when SiteController goes
# to find an uncached page or else it redirects the user to the login screen.
scenario :javascripts, :stylesheets, :pages
@@ -24,22 +24,22 @@
controller.text_asset_cache.should be_kind_of(TextAssetResponseCache)
end
- [ { :name => 'css',
- :asset_class => 'css_asset',
- :default_directory => 'css',
+ [ { :asset_class => 'stylesheet',
+ :directory_key => 'stylesheet_directory',
+ :directory_value => 'css',
:main_scenario => 'main.css' },
- { :name => 'js',
- :asset_class => 'js_asset',
- :default_directory => 'js',
+ { :asset_class => 'javascript',
+ :directory_key => 'javascript_directory',
+ :directory_value => 'js',
:main_scenario => 'main.js' }
].each do |current_asset|
describe current_asset[:asset_class] do
- it "should route urls based on a simple, customized setting for :#{current_asset[:name]}_directory" do
+ it "should route urls based on a simple, customized setting for :#{current_asset[:directory_key]}" do
# change the css_ or js_direectory and recreate routes to use them
- StylesNScripts::Config["#{current_asset[:name]}_directory"] = 'foo'
+ StylesNScripts::Config[current_asset[:directory_key]] = 'foo'
ActionController::Routing::Routes.reload!
params_from(:get, "/foo/#{current_asset[:main_scenario]}").should ==
@@ -49,10 +49,10 @@
:directory => 'foo',
:asset_class => current_asset[:asset_class] }
end
-
- it "should route urls based on a multi-level, customized setting for :#{current_asset[:name]}_directory" do
+
+ it "should route urls based on a multi-level, customized setting for :#{current_asset[:directory_key]}" do
# change the css_ or js_direectory and recreate routes to use them
- StylesNScripts::Config["#{current_asset[:name]}_directory"] = 'foo/bar/baz'
+ StylesNScripts::Config[current_asset[:directory_key]] = 'foo/bar/baz'
ActionController::Routing::Routes.reload!
params_from(:get, "/foo/bar/baz/#{current_asset[:main_scenario]}").should ==
@@ -62,20 +62,20 @@
:directory => 'foo/bar/baz',
:asset_class => current_asset[:asset_class] }
end
-
- it "should route urls based on the default when no custom settings for :#{current_asset[:name]}_directory" do
- params_from(:get, "/#{current_asset[:default_directory]}/#{current_asset[:main_scenario]}").should ==
+
+ it "should route urls based on the default when no custom settings for :#{current_asset[:directory_key]}" do
+ params_from(:get, "/#{current_asset[:directory_value]}/#{current_asset[:main_scenario]}").should ==
{ :controller => 'text_asset_site',
:action => 'show_text_asset',
:filename => [current_asset[:main_scenario]],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class] }
end
-
+
it "should find and render an existing asset" do
get :show_text_asset,
:filename => [current_asset[:main_scenario]],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
# For SOME reason, the response.header does not include a 'status' key so it is
# not possible to check for success.
@@ -83,12 +83,21 @@
# response.should be_success
response.body.should == "Main #{current_asset[:asset_class]} content"
end
-
+
+ it "should render an existing asset with a Last-Modified header matching the file's updated_at date/time" do
+ get :show_text_asset,
+ :filename => [current_asset[:main_scenario]],
+ :directory => current_asset[:directory_value],
+ :asset_class => current_asset[:asset_class]
+ response.headers['Last-Modified'].should ==
+ text_assets(current_asset[:main_scenario].symbolize).updated_at
+ end
+
it "should find and render an existing asset on the default dev site" do
request.host = "dev.site.com"
get :show_text_asset,
:filename => [current_asset[:main_scenario]],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
# For SOME reason, the response.header does not include a 'status' key so it is
# not possible to check for success.
@@ -99,69 +108,69 @@
it "should render a 404 page for a non-existing asset" do
get :show_text_asset,
:filename => ['non-existent.file'],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
response.should render_template('site/not_found')
response.headers["Status"].should == "404 Not Found"
end
-
- it "should render a 404 page for #{current_asset[:name]}_directory (/#{current_asset[:default_directory]}/)" do
+
+ it "should render a 404 page for #{current_asset[:directory_key]} (/#{current_asset[:directory_value]}/)" do
get :show_text_asset,
:filename => [],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
response.should render_template('site/not_found')
response.headers["Status"].should == "404 Not Found"
end
- it "should render a 404 page if url includes a deeper path than :#{current_asset[:name]}_directory" do
+ it "should render a 404 page if url includes a deeper path than :#{current_asset[:directory_key]}" do
get :show_text_asset,
:filename => ['bogus', 'extra', 'path', 'segments', [current_asset[:main_scenario]]],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
response.headers["Status"].should == "404 Not Found"
response.should render_template('site/not_found')
end
-
-
+
+
# This is sort of dumb. Really, users should not go anywhere near creating
# a page with the same name as the css directory. If they do, here's what
# should happen.
- it "should render a page that is competing with :#{current_asset[:name]}_directory" do
- create_page current_asset[:default_directory]
+ it "should render a page that is competing with :#{current_asset[:directory_key]}" do
+ create_page current_asset[:directory_value]
get :show_text_asset,
:filename => [],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
response.should be_success
- response.body.should == "#{current_asset[:default_directory]} body."
+ response.body.should == "#{current_asset[:directory_value]} body."
end
-
+
# Nor should they be putting child pages inside that competing page...
- it "should render a page inside :#{current_asset[:name]}_directory (immediate child of /#{current_asset[:default_directory]}/)" do
- create_page current_asset[:default_directory] do
+ it "should render a page inside :#{current_asset[:directory_key]} (immediate child of /#{current_asset[:directory_value]}/)" do
+ create_page current_asset[:directory_value] do
create_page 'page-inside'
end
get :show_text_asset,
:filename => ['page-inside'],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
# WHY DOES THIS NEXT LINE FAIL????? AAAARRRRGGGHHH!!
response.should be_success
response.body.should == 'page-inside body.'
end
-
+
# Or grandchildren -- but we'll test for 'em anyway.
- it "should render a page inside :#{current_asset[:name]}_directory (grandchild of /#{current_asset[:default_directory]}/)" do
- create_page current_asset[:default_directory] do
+ it "should render a page inside :#{current_asset[:directory_key]} (grandchild of /#{current_asset[:directory_value]}/)" do
+ create_page current_asset[:directory_value] do
create_page 'page-inside' do
create_page 'another-page'
end
end
-
+
get :show_text_asset,
:filename => ['page-inside', 'another-page'],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
response.should be_success
response.body.should == 'another-page body.'
@@ -170,15 +179,15 @@
# But, if there's a text_asset and page -- both with the same url, then
# the asset wins.
it "should render the text_asset and not the page if both have the same url" do
- create_page current_asset[:default_directory] do
+ create_page current_asset[:directory_value] do
create_page 'abc.123'
end
send("create_#{current_asset[:asset_class]}", 'abc.123')
-
+
get :show_text_asset,
:filename => ['abc.123'],
- :directory => current_asset[:default_directory],
+ :directory => current_asset[:directory_value],
:asset_class => current_asset[:asset_class]
# response.should be_success
response.body.should == 'dummy content'
View
11 spec/helpers/admin/css_helper_spec.rb
@@ -1,11 +0,0 @@
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-describe Admin::CssHelper do
-
- #Delete this example and add some real ones or delete this file
- it "should include the Admin::CssHelper" do
- included_modules = self.metaclass.send :included_modules
- included_modules.should include(Admin::CssHelper)
- end
-
-end
View
11 spec/helpers/admin/js_helper_spec.rb
@@ -1,11 +0,0 @@
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-describe Admin::JsHelper do
-
- #Delete this example and add some real ones or delete this file
- it "should include the Admin::JsHelper" do
- included_modules = self.metaclass.send :included_modules
- included_modules.should include(Admin::JsHelper)
- end
-
-end
View
11 spec/helpers/text_asset_site_helper_spec.rb
@@ -1,11 +0,0 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-
-describe TextAssetSiteHelper do
-
- #Delete this example and add some real ones or delete this file
- it "should include the TextAssetSiteHelper" do
- included_modules = self.metaclass.send :included_modules
- included_modules.should include(TextAssetSiteHelper)
- end
-
-end
View
58 spec/lib/config_spec.rb
@@ -14,10 +14,10 @@
end
- [ :css_directory,
- :js_directory,
- :css_mime_type,
- :js_mime_type,
+ [ :stylesheet_directory,
+ :javascript_directory,
+ :stylesheet_mime_type,
+ :javascript_mime_type,
:response_cache_directory
].each do |setting_name|
describe setting_name do
@@ -26,7 +26,8 @@
StylesNScripts::Config[setting_name] = 'a-test-value'
StylesNScripts::Config[setting_name].should eql('a-test-value')
end
-
+
+
it "should accept declarations where the key name is a string" do
StylesNScripts::Config[setting_name.to_s] = 'a-test-value'
StylesNScripts::Config[setting_name.to_s].should eql('a-test-value')
@@ -36,10 +37,10 @@
end
- { :css_directory => 'css',
- :js_directory => 'js',
- :css_mime_type => 'text/css',
- :js_mime_type => 'text/javascript',
+ { :stylesheet_directory => 'css',
+ :javascript_directory => 'js',
+ :stylesheet_mime_type => 'text/css',
+ :javascript_mime_type => 'text/javascript',
:response_cache_directory => 'text_asset_cache'
}.each do |setting_name, default_value|
describe setting_name do
@@ -49,8 +50,8 @@
# we can then call StylesNScripts::Config.restore_defaults to clear our
# cache of values
end
-
-
+
+
it "should return default value after calling the #restore_defaults method" do
StylesNScripts::Config[setting_name] = 'a-test-value'
StylesNScripts::Config.restore_defaults
@@ -62,9 +63,10 @@
# the following specs apply both to css and js directories...
- [:css_directory, :js_directory].each do |setting_name|
+ [:stylesheet_directory, :javascript_directory].each do |setting_name|
describe setting_name do
+
# declare an array of values with alphanumeric-characters, iterate through each and test
['abc', 'ABC', 'aBc', 'AbC123'].each do |alpha_num_value|
it "should allow alphanumeric chars (like: #{alpha_num_value.inspect})" do
@@ -72,7 +74,8 @@
StylesNScripts::Config[setting_name].should eql(alpha_num_value)
end
end
-
+
+
# declare an array of values with slashes, iterate through each and test
['aBc-123', 'AbC_123', 'a-B_c-1_23'].each do |hyphen_underscore_value|
it "should allow hyphens, and/or underscores (like: #{hyphen_underscore_value.inspect})" do
@@ -80,7 +83,8 @@
StylesNScripts::Config[setting_name].should eql(hyphen_underscore_value)
end
end
-
+
+
# declare an array of values with slashes, iterate through each and test
['aBc/123', 'Ab-C/1_23', 'a/B_c-1/2_3'].each do |slash_value|
it "should allow slashes (like: #{slash_value.inspect})" do
@@ -89,6 +93,19 @@
end
end
+
+ it 'should change an acceptable value ending in a "/" to one without' do
+ StylesNScripts::Config[setting_name] = "abc/123/"
+ StylesNScripts::Config[setting_name].should eql("abc/123")
+ end
+
+
+ it 'should change an acceptable value starting with a "/" to one without' do
+ StylesNScripts::Config[setting_name] = "/abc/123"
+ StylesNScripts::Config[setting_name].should eql("abc/123")
+ end
+
+
# declare an array of invalid characters, inject them into valid values and test
%w[! @ # $ % ^ & * ( ) { } < > + = ? , : ; ' " \\ \t \ \n \r \[ \]].each do |invalid_char|
it "should reject invalid characters (like:#{invalid_char.inspect})" do
@@ -99,8 +116,9 @@
end
end
- # declare an array of values with bad slashes, iterate through each and test
- ['/abc/123', 'abc/123/', '/abc/123/','abc//123'].each do |slash_value|
+
+ # declare an array of values with bad/multiple slashes, iterate through each and test
+ ['//abc/123', 'abc/123//', '/abc///123/','abc\123'].each do |slash_value|
it "should reject invalid use of slashes (like: #{slash_value.inspect})" do
lambda{StylesNScripts::Config[setting_name] = slash_value}.should raise_error(
RuntimeError,
@@ -109,16 +127,14 @@
end
end
-
end
end
# the following specs apply both to css and js mime_types...
- [:css_mime_type, :js_mime_type].each do |setting_name|
+ [:stylesheet_mime_type, :javascript_mime_type].each do |setting_name|
describe setting_name do
-
['text/javascript', 'text/javascript1.0', 'text/x-javascript', 'text/css',
'application/x-ecmascript'].each do |mime_example|
it "should allow valid mime-types (like: #{mime_example.inspect})" do
@@ -127,6 +143,7 @@
end
end
+
# declare an array of invalid characters, inject them into valid values and test
%w[! @ # $ % ^ & * ( ) { } < > + = ? , : ; ' " _ \\ \t \ \n \r \[ \]].each do |invalid_char|
it "should reject invalid characters (like: #{invalid_char.inspect})" do
@@ -137,6 +154,7 @@
end
end
+
it 'should reject mime-types ending in a "/"' do
lambda{StylesNScripts::Config[setting_name] = "text/"}.should raise_error(
RuntimeError,
@@ -158,6 +176,7 @@
end
end
+
# declare an array of values with slashes, iterate through each and test
['aBc-123', 'AbC_123', 'a-B_c-1_23'].each do |hyphen_underscore_value|
it "should allow hyphens, and/or underscores (like: #{hyphen_underscore_value.inspect})" do
@@ -166,6 +185,7 @@
end
end
+
# declare an array of invalid characters, inject them into valid values and test
%w[! @ # $ % ^ & * ( ) { } < > + = ? , : ; ' " \\ \t \ \n \r \[ \] /].each do |invalid_char|
it "should reject invalid characters (like:#{invalid_char.inspect})" do
View
16 spec/models/css_asset_and_js_asset_common_spec.rb
@@ -1,6 +1,6 @@
require File.dirname(__FILE__) + '/../spec_helper'