aslakjo-aslakjo-comatose 2.0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG +195 -0
  2. data/INSTALL +20 -0
  3. data/LICENSE +20 -0
  4. data/MANIFEST +91 -0
  5. data/README.markdown +159 -0
  6. data/Rakefile +176 -0
  7. data/SPECS +61 -0
  8. data/about.yml +7 -0
  9. data/bin/comatose +112 -0
  10. data/comatose.gemspec +113 -0
  11. data/generators/comatose_migration/USAGE +15 -0
  12. data/generators/comatose_migration/comatose_migration_generator.rb +74 -0
  13. data/generators/comatose_migration/templates/migration.rb +35 -0
  14. data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
  15. data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
  16. data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
  17. data/init.rb +2 -0
  18. data/install.rb +18 -0
  19. data/lib/acts_as_versioned.rb +543 -0
  20. data/lib/comatose.rb +33 -0
  21. data/lib/comatose/comatose_drop.rb +79 -0
  22. data/lib/comatose/configuration.rb +69 -0
  23. data/lib/comatose/page_wrapper.rb +119 -0
  24. data/lib/comatose/processing_context.rb +69 -0
  25. data/lib/comatose/tasks/admin.rb +60 -0
  26. data/lib/comatose/tasks/data.rb +82 -0
  27. data/lib/comatose/tasks/setup.rb +52 -0
  28. data/lib/comatose/version.rb +4 -0
  29. data/lib/comatose_admin_controller.rb +395 -0
  30. data/lib/comatose_admin_helper.rb +37 -0
  31. data/lib/comatose_controller.rb +138 -0
  32. data/lib/comatose_helper.rb +3 -0
  33. data/lib/comatose_page.rb +141 -0
  34. data/lib/liquid.rb +52 -0
  35. data/lib/liquid/block.rb +96 -0
  36. data/lib/liquid/context.rb +190 -0
  37. data/lib/liquid/document.rb +17 -0
  38. data/lib/liquid/drop.rb +48 -0
  39. data/lib/liquid/errors.rb +7 -0
  40. data/lib/liquid/extensions.rb +53 -0
  41. data/lib/liquid/file_system.rb +62 -0
  42. data/lib/liquid/htmltags.rb +64 -0
  43. data/lib/liquid/standardfilters.rb +111 -0
  44. data/lib/liquid/standardtags.rb +399 -0
  45. data/lib/liquid/strainer.rb +42 -0
  46. data/lib/liquid/tag.rb +25 -0
  47. data/lib/liquid/template.rb +88 -0
  48. data/lib/liquid/variable.rb +39 -0
  49. data/lib/redcloth.rb +1129 -0
  50. data/lib/support/class_options.rb +36 -0
  51. data/lib/support/inline_rendering.rb +48 -0
  52. data/lib/support/route_mapper.rb +50 -0
  53. data/lib/text_filters.rb +140 -0
  54. data/lib/text_filters/markdown.rb +14 -0
  55. data/lib/text_filters/markdown_smartypants.rb +15 -0
  56. data/lib/text_filters/none.rb +8 -0
  57. data/lib/text_filters/rdoc.rb +13 -0
  58. data/lib/text_filters/simple.rb +8 -0
  59. data/lib/text_filters/textile.rb +15 -0
  60. data/rails/init.rb +3 -0
  61. data/resources/layouts/comatose_admin_template.html.erb +28 -0
  62. data/resources/public/images/collapsed.gif +0 -0
  63. data/resources/public/images/expanded.gif +0 -0
  64. data/resources/public/images/no-children.gif +0 -0
  65. data/resources/public/images/page.gif +0 -0
  66. data/resources/public/images/spinner.gif +0 -0
  67. data/resources/public/images/title-hover-bg.gif +0 -0
  68. data/resources/public/javascripts/comatose_admin.js +401 -0
  69. data/resources/public/stylesheets/comatose_admin.css +404 -0
  70. data/tasks/comatose.rake +9 -0
  71. data/test/behaviors.rb +106 -0
  72. data/test/fixtures/comatose_pages.yml +96 -0
  73. data/test/functional/comatose_admin_controller_test.rb +114 -0
  74. data/test/functional/comatose_controller_test.rb +44 -0
  75. data/test/javascripts/test.html +26 -0
  76. data/test/javascripts/test_runner.js +307 -0
  77. data/test/test_helper.rb +55 -0
  78. data/test/unit/class_options_test.rb +52 -0
  79. data/test/unit/comatose_page_test.rb +136 -0
  80. data/test/unit/processing_context_test.rb +108 -0
  81. data/test/unit/text_filters_test.rb +52 -0
  82. data/views/comatose_admin/_form.html.erb +96 -0
  83. data/views/comatose_admin/_page_list_item.html.erb +60 -0
  84. data/views/comatose_admin/delete.html.erb +18 -0
  85. data/views/comatose_admin/edit.html.erb +5 -0
  86. data/views/comatose_admin/index.html.erb +29 -0
  87. data/views/comatose_admin/new.html.erb +5 -0
  88. data/views/comatose_admin/reorder.html.erb +30 -0
  89. data/views/comatose_admin/versions.html.erb +40 -0
  90. data/views/layouts/comatose_admin.html.erb +837 -0
  91. data/views/layouts/comatose_admin_customize.html.erb +28 -0
  92. data/views/layouts/comatose_content.html.erb +17 -0
  93. metadata +148 -0
@@ -0,0 +1,36 @@
1
+ class Class
2
+ def define_option(name, default=nil)
3
+ sym = name.to_sym
4
+ cattr_reader(sym)
5
+ cattr_writer(sym)
6
+ send("#{name.to_s}=", default)
7
+ end
8
+
9
+ def blockable_attr_accessor(sym)
10
+ module_eval(<<-EVAL, __FILE__, __LINE__)
11
+ def #{sym}(&block)
12
+ if block_given?
13
+ @#{sym} = block
14
+ else
15
+ @#{sym}
16
+ end
17
+ end
18
+ def #{sym}=(value)
19
+ @#{sym} = value
20
+ end
21
+ EVAL
22
+ end
23
+ end
24
+
25
+ class Module
26
+ def attr_accessor_with_default(sym, default = nil, &block)
27
+ raise 'Default value or block required' unless !default.nil? || block
28
+ define_method(sym, block_given? ? block : Proc.new { default })
29
+ module_eval(<<-EVAL, __FILE__, __LINE__)
30
+ def #{sym}=(value)
31
+ class << self; attr_reader :#{sym} end
32
+ @#{sym} = value
33
+ end
34
+ EVAL
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ # Extends the view to support rendering inline comatose pages...
2
+ ActionView::Base.class_eval do
3
+ alias_method :render_otherwise, :render
4
+
5
+ def render(options = {}, old_local_assigns = {}, &block) #:nodoc:
6
+ if options.is_a?(Hash) && page_name = options.delete(:comatose)
7
+ render_comatose(page_name, options[:params] || options)
8
+ else
9
+ render_otherwise(options, old_local_assigns, &block)
10
+ end
11
+ end
12
+
13
+ def render_comatose(page_path, params = {})
14
+ params = {
15
+ :silent => false,
16
+ :use_cache => true,
17
+ :locals => {}
18
+ }.merge(params)
19
+ if params[:use_cache] and params[:locals].empty?
20
+ render_cached_comatose_page(page_path, params)
21
+ else
22
+ render_comatose_page(page_path, params)
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def render_cached_comatose_page(page_path, params)
29
+ key = page_path.gsub(/\//, '+')
30
+ unless html = controller.read_fragment(key)
31
+ html = render_comatose_page( page_path, params )
32
+ controller.write_fragment(key, html) unless Comatose.config.disable_caching
33
+ end
34
+ html
35
+ end
36
+
37
+ def render_comatose_page(page_path, params)
38
+ if page = ComatosePage.find_by_path(page_path)
39
+ # Add the request params to the context...
40
+ params[:locals]['params'] = controller.params
41
+ html = page.to_html( params[:locals] )
42
+ else
43
+ html = params[:silent] ? '' : "<p><tt>#{page_path}</tt> not found</p>"
44
+ end
45
+ end
46
+
47
+ end
48
+
@@ -0,0 +1,50 @@
1
+ # For use with 'Edge Rails'
2
+ class ActionController::Routing::RouteSet::Mapper
3
+
4
+ # For mounting a page to a path
5
+ def comatose_root( path, options={} )
6
+ opts = {
7
+ :index => '',
8
+ :layout => 'comatose_content.html.erb',
9
+ :use_cache => 'true',
10
+ :cache_path => nil,
11
+ :named_route=> nil
12
+ }.merge(options)
13
+ # Ensure the controller is aware of the mount point...
14
+ Comatose.add_mount_point(path, opts)
15
+ # Add the route...
16
+ opts[:controller] = 'comatose'
17
+ opts[:action] ='show'
18
+ route_name = opts.delete(:named_route)
19
+ unless route_name.nil?
20
+ named_route( route_name, "#{path}/*page", opts )
21
+ else
22
+ if opts[:index] == '' # if it maps to the root site URI, name it comatose_root
23
+ named_route( 'comatose_root', "#{path}/*page", opts )
24
+ else
25
+ connect( "#{path}/*page", opts )
26
+ end
27
+ end
28
+ end
29
+
30
+ # For mounting the admin
31
+ def comatose_admin( path='comatose_admin', options={} )
32
+ opts = {
33
+ :controller => 'comatose_admin',
34
+ :named_route => 'comatose_admin'
35
+ }.merge(options)
36
+ route_name = opts.delete(:named_route)
37
+ named_route( route_name, "#{path}/:action/:id", opts )
38
+ end
39
+
40
+ def method_missing( name, *args, &proc )
41
+ if name.to_s.starts_with?( 'comatose_' )
42
+ opts = (args.last.is_a?(Hash)) ? args.pop : {}
43
+ opts[:named_route] = name.to_s #[9..-1]
44
+ comatose_root( *(args << opts) )
45
+ else
46
+ super unless args.length >= 1 && proc.nil?
47
+ @set.add_named_route(name, *args)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,140 @@
1
+ require 'support/class_options'
2
+
3
+ class TextFilters
4
+
5
+ define_option :default_processor, :liquid
6
+ define_option :default_filter, "Textile"
7
+ define_option :logger, nil
8
+
9
+ @registered_filters = {}
10
+ @registered_titles = {}
11
+
12
+ attr_reader :name
13
+ attr_reader :title
14
+ def initialize(name, title)
15
+ @name = name
16
+ @title = title
17
+ end
18
+
19
+ # The default create_link method...
20
+ # Override for your specific filter, if needed, in the #define block
21
+ def create_link(title, url)
22
+ %Q|<a href="#{url}">#{title}</a>|
23
+ end
24
+
25
+ # Process the text with using the specified context
26
+ def process_text(text, context, processor=nil)
27
+ case (processor || TextFilters.default_processor)
28
+ when :erb then process_with_erb(text, context)
29
+ when :liquid then process_with_liquid(text, context)
30
+ else raise "Unknown Text Processor '#{processor.to_s}'"
31
+ end
32
+ end
33
+
34
+ class << self
35
+ private :new
36
+ attr_reader :registered_filters
37
+ attr_reader :registered_titles
38
+
39
+ # Use this to create and register your TextFilters
40
+ def define(name, title, &block)
41
+ begin
42
+ p = new(name, title)
43
+ p.instance_eval(&block)
44
+ if p.respond_to? :render_text
45
+ registered_titles[title] = name
46
+ registered_filters[name] = p
47
+ else
48
+ raise "#render_text isn't implemented in this class"
49
+ end
50
+ rescue LoadError
51
+ TextFilters.logger.debug "Filter '#{name}' was not included: #{$!}" unless TextFilters.logger.nil?
52
+ rescue
53
+ TextFilters.logger.debug "Filter '#{name}' was not included: #{$!}" unless TextFilters.logger.nil?
54
+ end
55
+ end
56
+
57
+ def get_filter(name)
58
+ name = TextFilters.default_filter if name.nil?
59
+ name = registered_titles[name] if name.is_a? String
60
+ filter = registered_filters[name]
61
+ raise "No filter found for '#{name}' in registered_titles: #{registered_titles.inspect} or registered_filters: #{registered_filters.inspect}" unless filter
62
+ filter
63
+ end
64
+
65
+ def [](name)
66
+ get_filter(name)
67
+ end
68
+
69
+ def all
70
+ registered_filters
71
+ end
72
+
73
+ def all_titles
74
+ registered_titles.keys
75
+ end
76
+
77
+ def render_text(text, name=nil)
78
+ get_filter(name).render_text(text)
79
+ end
80
+
81
+ def process_text(text, context=nil, name=nil, processor=nil)
82
+ get_filter(name).process_text(text, context, processor)
83
+ end
84
+
85
+ # Call
86
+ def transform(text, context=nil, name=nil, processor=nil)
87
+ render_text( process_text(text, context, name, processor), name )
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ # This is an instance method so that it won't puke on requiring
94
+ # a non-existant library until it's being registered -- the only
95
+ # place I can really capture the LoadError
96
+ def require(name)
97
+ Kernel.require name
98
+ end
99
+
100
+ # Use ERB to process text...
101
+ def process_with_erb(text, context)
102
+ begin
103
+ ERB.new( text ).result( context.get_binding )
104
+ rescue
105
+ raise "ERB Error: #{$!}"
106
+ end
107
+ end
108
+
109
+ # Use Liquid to process text...
110
+ def process_with_liquid(text, context={})
111
+ begin
112
+ context = context.stringify_keys if context.respond_to? :stringify_keys
113
+ Liquid::Template.parse(text).render(context)
114
+ rescue
115
+ raise "Liquid Error: #{$!}"
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ class Hash
122
+ # Having the method_missing catchall in conjunction with get_binding
123
+ # allows us to use the hash as a Context for ERB
124
+ def method_missing(meth, *arg)
125
+ if self.has_key? meth.to_s
126
+ self[meth.to_s]
127
+ else
128
+ super
129
+ end
130
+ end
131
+ # Gets the binding object for use with ERB
132
+ def get_binding
133
+ binding
134
+ end
135
+ end
136
+
137
+
138
+ Dir[File.join(File.dirname(__FILE__), 'text_filters', '*.rb')].each do |path|
139
+ require "text_filters/#{File.basename(path)}"
140
+ end
@@ -0,0 +1,14 @@
1
+ #
2
+ # MARKDOWN
3
+ #
4
+ TextFilters.define :markdown, "Markdown" do
5
+ require 'bluecloth'
6
+
7
+ def render_text(text)
8
+ BlueCloth.new(text).to_html
9
+ end
10
+
11
+ def create_link(title, url)
12
+ "[#{title}](#{url})"
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ #
2
+ # MARKDOWN + SMARTYPANTS
3
+ #
4
+ TextFilters.define :markdown_smartypants, "Markdown + SmartyPants" do
5
+ require 'bluecloth'
6
+ require 'rubypants'
7
+
8
+ def render_text(text)
9
+ RubyPants.new( BlueCloth.new(text).to_html ).to_html
10
+ end
11
+
12
+ def create_link(title, url)
13
+ "[#{title}](#{url})"
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # NONE
3
+ #
4
+ TextFilters.define :none, "[No Filter]" do
5
+ def render_text(text)
6
+ text
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ #
2
+ # RDOC
3
+ #
4
+ TextFilters.define :rdoc, "RDoc" do
5
+ require 'rdoc/markup/simple_markup'
6
+ require 'rdoc/markup/simple_markup/to_html'
7
+
8
+ def render_text(text)
9
+ p = SM::SimpleMarkup.new
10
+ h = SM::ToHtml.new
11
+ p.convert(text, h)
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # SIMPLE
3
+ #
4
+ TextFilters.define :simple, "Simple" do
5
+ def render_text(text)
6
+ text.gsub("\n", '<br/>')
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ #
2
+ # TEXTILE
3
+ #
4
+ TextFilters.define :textile, "Textile" do
5
+ require 'redcloth'
6
+
7
+ def render_text(text)
8
+ RedCloth.new(text).to_html(:refs_markdown, :textile, :markdown)
9
+ end
10
+
11
+ def create_link(title, url)
12
+ %Q|"#{title}":#{url}|
13
+ end
14
+
15
+ end
@@ -0,0 +1,3 @@
1
+ # Init for gem version of Comatose
2
+
3
+ require 'comatose'
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html class="noscript" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=<%%= Comatose.config.content_type %>"/>
6
+ <title>Comatose Admin</title>
7
+ <%= style_contents %>
8
+ <%%= javascript_include_tag :defaults %>
9
+ <%= script_contents %>
10
+ </head>
11
+ <body>
12
+ <div id="page-container">
13
+ <div id="header">
14
+ <h1><%%= link_to Comatose.config.admin_title, :controller=>controller.controller_name, :action=>'index' %></h1>
15
+ <div id="flash">
16
+ <span id="flash-content"><%%= flash[:notice] %></span>
17
+ </div>
18
+ <h5><%%= Comatose.config.admin_sub_title %></h5>
19
+ </div>
20
+ <div id="content">
21
+ <%%= yield %>
22
+ </div>
23
+ <div id="footer">
24
+ Powered by <a href="http://comatose.rubyforge.org" target="_new_window">Comatose <%%= Comatose::VERSION_STRING %></a>, created by <a href="http://www.mattmccray.com" target="_new_window">M@ McCray</a>.
25
+ </div>
26
+ </div>
27
+ </body>
28
+ </html>
@@ -0,0 +1,401 @@
1
+ // CSS Browser Selector v0.2.3b (M@: added noscript support)
2
+ // Documentation: http://rafael.adm.br/css_browser_selector
3
+ // License: http://creativecommons.org/licenses/by/2.5/
4
+ // Author: Rafael Lima (http://rafael.adm.br)
5
+ // Contributors: http://rafael.adm.br/css_browser_selector#contributors
6
+ var css_browser_selector = function() {
7
+ var
8
+ ua = navigator.userAgent.toLowerCase(),
9
+ is = function(t){ return ua.indexOf(t) != -1; },
10
+ h = document.getElementsByTagName('html')[0],
11
+ b = (!(/opera|webtv/i.test(ua)) && /msie (\d)/.test(ua)) ? ((is('mac') ? 'ieMac ' : '') + 'ie ie' + RegExp.$1)
12
+ : is('gecko/') ? 'gecko' : is('opera') ? 'opera' : is('konqueror') ? 'konqueror' : is('applewebkit/') ? 'webkit safari' : is('mozilla/') ? 'gecko' : '',
13
+ os = (is('x11') || is('linux')) ? ' linux' : is('mac') ? ' mac' : is('win') ? ' win' : '';
14
+ var c = b+os+' js';
15
+ h.className = h.className.replace('noscript', '') + h.className?' '+c:c;
16
+ }();
17
+
18
+ // List View Functions
19
+ var ComatoseList = {
20
+ save_node_state: true,
21
+ state_store: 'cookie', // Only 'cookie' for now
22
+ state_key: 'ComatoseTreeState',
23
+
24
+ init: function() {
25
+ var items = ComatoseList._read_state();
26
+ items.each(function(node){
27
+ ComatoseList.expand_node(node.replace('page_controller_', ''))
28
+ });
29
+ },
30
+
31
+ toggle_tree_nodes : function(img, id) {
32
+ if(/expanded/.test(img.src)) {
33
+ $('page_list_'+ id).addClassName('collapsed');
34
+ img.src = img.src.replace(/expanded/, 'collapsed')
35
+ if(ComatoseList.save_node_state) {
36
+ var items = ComatoseList._read_state();
37
+ items = items.select(function(id){ return id != img.id; })
38
+ ComatoseList._write_state(items);
39
+ }
40
+ } else {
41
+ $('page_list_'+ id).removeClassName('collapsed');
42
+ img.src = img.src.replace(/collapsed/, 'expanded')
43
+ if(ComatoseList.save_node_state) {
44
+ var items = ComatoseList._read_state();
45
+ items.push(img.id);
46
+ ComatoseList._write_state(items);
47
+ }
48
+ }
49
+ },
50
+
51
+ expand_node: function(id) {
52
+ $('page_list_'+ id).removeClassName('collapsed');
53
+ $('page_controller_'+ id).src = $('page_controller_'+ id).src.replace(/collapsed/, 'expanded')
54
+ },
55
+
56
+ collapse_node: function(id) {
57
+ $('page_list_'+ id).addClassName('collapsed');
58
+ $('page_controller_'+ id).src = $('page_controller_'+ id).src.replace(/expanded/, 'collapsed')
59
+ },
60
+
61
+ item_hover : function(node, state, is_delete) {
62
+ if( state == 'over') {
63
+ $(node).addClassName( (is_delete) ? 'hover-delete' : 'hover' );
64
+ } else {
65
+ $(node).removeClassName( (is_delete) ? 'hover-delete' : 'hover' );
66
+ }
67
+ },
68
+
69
+ toggle_reorder: function(node, anc, id) {
70
+ if( $(node).hasClassName('do-reorder') ) {
71
+ $(node).removeClassName( 'do-reorder' );
72
+ $(anc).removeClassName('reordering');
73
+ $(anc).innerHTML = "reorder children";
74
+ } else {
75
+ $(node).addClassName( 'do-reorder' );
76
+ $(anc).addClassName('reordering');
77
+ $(anc).innerHTML = "finished reordering";
78
+ // Make sure the children are visible...
79
+ ComatoseList.expand_node(id);
80
+ }
81
+ },
82
+
83
+ _write_state: function(items) {
84
+ var cookie = {}; var options = {}; var expiration = new Date();
85
+ cookie[ ComatoseList.state_key ] = items.join(',');
86
+ expiration.setDate(expiration.getDate()+30)
87
+ options['expires'] = expiration;
88
+ Cookie.write( cookie, options );
89
+ },
90
+
91
+ _read_state: function() {
92
+ var state = Cookie.read( ComatoseList.state_key );
93
+ return (state != "" && state != null) ? state.split(',') : [];
94
+ }
95
+ }
96
+
97
+ // Edit Form Functions
98
+ var ComatoseEditForm = {
99
+
100
+ default_data: {},
101
+ last_preview: {},
102
+ last_title_slug: '',
103
+ mode : null,
104
+ liquid_horiz: true,
105
+ width_offset: 325,
106
+
107
+ // Initialize the page...
108
+ init : function(mode) {
109
+ this.mode = mode;
110
+ this.default_data = Form.serialize(document.forms[0]);
111
+ if(mode == 'new') {
112
+ this.last_title_slug = $('page_title').value.toSlug();
113
+ Event.observe('page_title', 'blur', ComatoseEditForm.title_updated_aggressive);
114
+ } else {
115
+ Event.observe('page_title', 'blur', ComatoseEditForm.title_updated);
116
+ }
117
+ $('page_title').focus();
118
+ Hide.these(
119
+ 'preview-area',
120
+ 'slug_row',
121
+ 'parent_row',
122
+ 'keywords_row',
123
+ 'filter_row',
124
+ 'created_row'
125
+ );
126
+ $('page_title').select();
127
+ // Create the horizontal liquidity of the fields
128
+ if(this.liquid_horiz) {
129
+ xOffset = this.width_offset;
130
+ new Layout.LiquidHoriz((xOffset + 50), 'page_title');
131
+ new Layout.LiquidHoriz(xOffset, 'page_slug','page_keywords','page_parent','page_body');
132
+ }
133
+ },
134
+ // For use when updating an existing page...
135
+ title_updated : function() {
136
+ slug = $('page_slug');
137
+ if(slug.value == "") {
138
+ title = $('page_title');
139
+ slug.value = title.value.toSlug();
140
+ }
141
+ },
142
+ // For use when creating a new page...
143
+ title_updated_aggressive : function() {
144
+ slug = $('page_slug');
145
+ title = $('page_title');
146
+ if(slug.value == "" || slug.value == this.last_title ) {
147
+ slug.value = title.value.toSlug();
148
+ }
149
+ this.last_title = slug.value;
150
+ },
151
+ // Todo: Make the meta fields remember their visibility?
152
+ toggle_extra_fields : function(anchor) {
153
+ if(anchor.innerHTML == "More...") {
154
+ Show.these(
155
+ 'slug_row',
156
+ 'keywords_row',
157
+ 'parent_row',
158
+ 'filter_row',
159
+ 'created_row'
160
+ );
161
+ anchor.innerHTML = 'Less...';
162
+ } else {
163
+ Hide.these(
164
+ 'slug_row',
165
+ 'keywords_row',
166
+ 'parent_row',
167
+ 'filter_row',
168
+ 'created_row'
169
+ );
170
+ anchor.innerHTML = 'More...';
171
+ }
172
+ },
173
+ // Uses server to create preview of content...
174
+ preview_content : function(preview_url) {
175
+ $('preview-area').show();
176
+ var params = Form.serialize(document.forms[0]);
177
+ if( params != this.last_preview ) {
178
+ $('preview-panel').innerHTML = "<span style='color:blue;'>Loading Preview...</span>";
179
+ new Ajax.Updater(
180
+ 'preview-panel',
181
+ preview_url,
182
+ { parameters: params }
183
+ );
184
+ }
185
+ this.last_preview = params;
186
+ },
187
+ cancel : function(url) {
188
+ var current_data = Form.serialize(document.forms[0]);
189
+ var data_changed = (this.default_data != current_data)
190
+ if(data_changed) {
191
+ if( confirm('Changes detected. You will lose all the updates you have made if you proceed...') ) {
192
+ location.href = url;
193
+ }
194
+ } else {
195
+ location.href = url;
196
+ }
197
+
198
+ }
199
+ }
200
+
201
+ var Hide = {
202
+ these : function() {
203
+ for (var i = 0; i < arguments.length; i++) {
204
+ try {
205
+ $(arguments[i]).hide();
206
+ } catch (e) {}
207
+ }
208
+ }
209
+ }
210
+
211
+ var Show = {
212
+ these : function() {
213
+ for (var i = 0; i < arguments.length; i++) {
214
+ try {
215
+ $(arguments[i]).show();
216
+ } catch (e) {}
217
+ }
218
+ }
219
+ }
220
+
221
+ // Layout namespace
222
+ var Layout = {};
223
+
224
+ // This class allows dom objects to stretch with the browser
225
+ // (for when a good, cross-browser, CSS approach can't be found)
226
+ Layout.LiquidBase = Class.create();
227
+ // Base class for all Liquid* layouts...
228
+ Object.extend(Layout.LiquidBase.prototype, {
229
+ enabled: true,
230
+ elems: [],
231
+ offset: null,
232
+ // Constructor is (offset, **array_of_elements)
233
+ initialize: function() {
234
+ args = $A(arguments)
235
+ this.offset = args.shift();
236
+ this.elems = args.select( function(elem){ return ($(elem) != null) } );
237
+ if( this.elems.length > 0 ) {
238
+ this.on_resize(); // Initial size
239
+ Event.observe(window, 'resize', this.on_resize.bind(this) );
240
+ Event.observe(window, 'load', this.on_resize.bind(this) );
241
+ }
242
+ },
243
+ resize_in: function(timeout) {
244
+ setTimeout( this.on_resize.bind(this), timeout );
245
+ },
246
+ on_resize: function() {
247
+ // Need to override!
248
+ alert('Override on_resize, please!');
249
+ }
250
+ });
251
+
252
+
253
+ // Liquid vertical layout
254
+ Layout.LiquidVert = Class.create();
255
+ Object.extend(Layout.LiquidVert.prototype, Object.extend(Layout.LiquidBase.prototype, {
256
+ on_resize: function() {
257
+ if( this.offset != null && this.enabled ) {
258
+ var new_height = ((window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight) - this.offset) +"px";
259
+ this.elems.each(function(e){ $(e).style.height = new_height; })
260
+ }
261
+ }
262
+ }) );
263
+
264
+
265
+ // Liquid horizontal layout
266
+ Layout.LiquidHoriz = Class.create();
267
+ Object.extend(Layout.LiquidHoriz.prototype, Object.extend(Layout.LiquidBase.prototype, {
268
+ on_resize: function() {
269
+ if( this.offset != null && this.enabled ) {
270
+ var new_width = ((window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) - this.offset) +"px";
271
+ this.elems.each( function(e){ $(e).style.width = new_width; })
272
+ }
273
+ }
274
+ }) );
275
+
276
+ // String Extensions... Yes, these are from Radiant! ;-)
277
+ Object.extend(String.prototype, {
278
+ upcase: function() {
279
+ return this.toUpperCase();
280
+ },
281
+ downcase: function() {
282
+ return this.toLowerCase();
283
+ },
284
+ strip: function() {
285
+ return this.replace(/^\s+/, '').replace(/\s+$/, '');
286
+ },
287
+ toInteger: function() {
288
+ return parseInt(this);
289
+ },
290
+ toSlug: function() {
291
+ // M@: Modified from Radiant's version, removes multple --'s next to each other
292
+ // This is the same RegExp as the one on the page model...
293
+ return this.strip().downcase().replace(/[^-a-z0-9~\s\.:;+=_]/g, '').replace(/[\s\.:;=_+]+/g, '-').replace(/[\-]{2,}/g, '-');
294
+ }
295
+ });
296
+
297
+ // Run a spinner when an AJAX request in running...
298
+ var ComatoseAJAXSpinner = {
299
+ busy : function () {
300
+ if($('spinner') && Ajax.activeRequestCount > 0) {
301
+ Effect.Appear('spinner',{duration:0.5,queue:'end'});
302
+ }
303
+ },
304
+
305
+ notBusy: function() {
306
+ if($('spinner') && Ajax.activeRequestCount == 0) {
307
+ Effect.Fade('spinner',{duration:0.5,queue:'end'});
308
+ }
309
+ }
310
+ }
311
+ // Register it with Prototype...
312
+ Ajax.Responders.register({
313
+ onCreate: ComatoseAJAXSpinner.busy,
314
+ onComplete: ComatoseAJAXSpinner.notBusy
315
+ });
316
+
317
+
318
+ if(!window.Cookie)
319
+ (function (){
320
+ // From Mephisto!
321
+ window.Cookie = {
322
+ version: '0.7',
323
+ cookies: {},
324
+ _each: function(iterator) {
325
+ $H(this.cookies).each(iterator);
326
+ },
327
+
328
+ getAll: function() {
329
+ this.cookies = {};
330
+ $A(document.cookie.split('; ')).each(function(cookie) {
331
+ var seperator = cookie.indexOf('=');
332
+ this.cookies[cookie.substring(0, seperator)] =
333
+ unescape(cookie.substring(seperator + 1, cookie.length));
334
+ }.bind(this));
335
+ return this.cookies;
336
+ },
337
+
338
+ read: function() {
339
+ var cookies = $A(arguments), results = [];
340
+ this.getAll();
341
+ cookies.each(function(name) {
342
+ if (this.cookies[name]) results.push(this.cookies[name]);
343
+ else results.push(null);
344
+ }.bind(this));
345
+ return results.length > 1 ? results : results[0];
346
+ },
347
+
348
+ write: function(cookies, options) {
349
+ if (cookies.constructor == Object && cookies.name) cookies = [cookies];
350
+ if (cookies.constructor == Array) {
351
+ $A(cookies).each(function(cookie) {
352
+ this._write(cookie.name, cookie.value, cookie.expires,
353
+ cookie.path, cookie.domain);
354
+ }.bind(this));
355
+ } else {
356
+ options = options || {expires: false, path: '', domain: ''};
357
+ for (name in cookies){
358
+ this._write(name, cookies[name],
359
+ options.expires, options.path, options.domain);
360
+ }
361
+ }
362
+ },
363
+
364
+ _write: function(name, value, expires, path, domain) {
365
+ if (name.indexOf('=') != -1) return;
366
+ var cookieString = name + '=' + escape(value);
367
+ if (expires) cookieString += '; expires=' + expires.toGMTString();
368
+ if (path) cookieString += '; path=' + path;
369
+ if (domain) cookieString += '; domain=' + domain;
370
+ document.cookie = cookieString;
371
+ },
372
+
373
+ erase: function(cookies) {
374
+ var cookiesToErase = {};
375
+ $A(arguments).each(function(cookie) {
376
+ cookiesToErase[cookie] = '';
377
+ });
378
+
379
+ this.write(cookiesToErase, {expires: (new Date((new Date()).getTime() - 1e11))});
380
+ this.getAll();
381
+ },
382
+
383
+ eraseAll: function() {
384
+ this.erase.apply(this, $H(this.getAll()).keys());
385
+ }
386
+ };
387
+
388
+ Object.extend(Cookie, {
389
+ get: Cookie.read,
390
+ set: Cookie.write,
391
+
392
+ add: Cookie.read,
393
+ remove: Cookie.erase,
394
+ removeAll: Cookie.eraseAll,
395
+
396
+ wipe: Cookie.erase,
397
+ wipeAll: Cookie.eraseAll,
398
+ destroy: Cookie.erase,
399
+ destroyAll: Cookie.eraseAll
400
+ });
401
+ })();