textile_editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
@@ -0,0 +1,2 @@
1
+ *swp
2
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ group :test do
4
+ gem 'test-unit'
5
+ end
@@ -0,0 +1,10 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ test-unit (2.1.1)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ test-unit
@@ -0,0 +1,41 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+
9
+ Jeweler::Tasks.new do |s|
10
+ s.name = 'textile-editor'
11
+ s.author = "Ben Mills"
12
+ s.email = "ben@unfiniti.com"
13
+ s.summary = "Provide textile toolbar to textareas"
14
+ s.homepage = "http://unfiniti.com"
15
+
16
+ s.test_files = Dir['test/**/*']
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not installed."
20
+ exit 1
21
+ end
22
+
23
+
24
+ desc 'Default: run unit tests.'
25
+ task :default => :test
26
+
27
+ desc 'Test the textile_editor plugin.'
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'lib'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = true
32
+ end
33
+
34
+ desc 'Generate documentation for the textile_editor_helper plugin.'
35
+ Rake::RDocTask.new(:rdoc) do |rdoc|
36
+ rdoc.rdoc_dir = 'rdoc'
37
+ rdoc.title = 'TextileEditor'
38
+ rdoc.options << '--line-numbers' << '--inline-source'
39
+ rdoc.rdoc_files.include('README')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,187 @@
1
+ module ActionView
2
+ module Helpers
3
+
4
+ class FormBuilder
5
+ def textile_editor(method, options = {})
6
+ @template.textile_editor(@object_name, method, options.merge(:object => @object))
7
+ end
8
+ end
9
+
10
+ module PrototypeHelper
11
+ end
12
+
13
+ module FormHelper
14
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
15
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
16
+ # hash with +options+ and places the textile toolbar above it
17
+ #
18
+ # ==== Examples
19
+ # textile_editor(:post, :body, :cols => 20, :rows => 40)
20
+ # # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
21
+ # # #{@post.body}
22
+ # # </textarea>
23
+ #
24
+ # textile_editor(:comment, :text, :size => "20x30")
25
+ # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
26
+ # # #{@comment.text}
27
+ # # </textarea>
28
+ #
29
+ # textile_editor(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
30
+ # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
31
+ # # #{@application.notes}
32
+ # # </textarea>
33
+ #
34
+ # textile_editor(:entry, :body, :size => "20x20", :disabled => 'disabled')
35
+ # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
36
+ # # #{@entry.body}
37
+ # # </textarea>
38
+ def textile_editor(object_name, method, options = {})
39
+ editor_id = options[:id] || '%s_%s' % [object_name, method]
40
+ mode = options.delete(:simple) ? 'simple' : 'extended'
41
+ (@textile_editor_ids ||= []) << [editor_id.to_s, mode.to_s]
42
+
43
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
44
+ end
45
+
46
+ def textile_editor_options(options={})
47
+ (@textile_editor_options ||= { :framework => :prototype }).merge! options
48
+ end
49
+
50
+ def textile_editor_support
51
+ output = []
52
+ output << stylesheet_link_tag('textile-editor')
53
+ output << javascript_include_tag('textile-editor')
54
+ output.join("\n").html_safe
55
+ end
56
+
57
+ # registers a new button for the Textile Editor toolbar
58
+ # Parameters:
59
+ # * +text+: text to display (contents of button tag, so HTML is valid as well)
60
+ # * +options+: options Hash as supported by +content_tag+ helper in Rails
61
+ #
62
+ # Example:
63
+ # The following example adds a button labeled 'Greeting' which triggers an
64
+ # alert:
65
+ #
66
+ # <% textile_editor_button 'Greeting', :onclick => "alert('Hello!')" %>
67
+ #
68
+ # *Note*: this method must be called before +textile_editor_initialize+
69
+ def textile_editor_button(text, options={})
70
+ return textile_editor_button_separator if text == :separator
71
+ button = content_tag(:button, text, options)
72
+ button = "TextileEditor.buttons.push(\"%s\");" % escape_javascript(button)
73
+ (@textile_editor_buttons ||= []) << button
74
+ end
75
+
76
+ def textile_editor_button_separator(options={})
77
+ button = "TextileEditor.buttons.push(new TextileEditorButtonSeparator('%s'));" % (options[:simple] || '')
78
+ (@textile_editor_buttons ||= []) << button
79
+ end
80
+
81
+ def textile_extract_dom_ids(*dom_ids)
82
+ hash = dom_ids.last.is_a?(Hash) ? dom_ids.pop : {}
83
+ hash.inject(dom_ids) do |ids, (object, fields)|
84
+ ids + Array(fields).map { |field| "%s_%s" % [object, field] }
85
+ end
86
+ end
87
+
88
+ # adds the necessary javascript include tags, stylesheet tags,
89
+ # and load event with necessary javascript to active textile editor(s)
90
+ # sample output:
91
+ # <link href="/stylesheets/textile-editor.css" media="screen" rel="Stylesheet" type="text/css" />
92
+ # <script src="/javascripts/textile-editor.js" type="text/javascript"></script>
93
+ # <script type="text/javascript">
94
+ # document.observe('dom:loaded', function() {
95
+ # TextileEditor.initialize('article_body', 'extended');
96
+ # TextileEditor.initialize('article_body_excerpt', 'simple');
97
+ # });
98
+ # </script>
99
+ #
100
+ # Note: in the case of this helper being called via AJAX, the output will be reduced:
101
+ # <script type="text/javascript">
102
+ # TextileEditor.initialize('article_body', 'extended');
103
+ # TextileEditor.initialize('article_body_excerpt', 'simple');
104
+ # </script>
105
+ #
106
+ # This means that the support files must be loaded outside of the AJAX request, either
107
+ # via a call to this helper or the textile_editor_support() helper
108
+ def textile_editor_initialize(*dom_ids)
109
+ options = textile_editor_options.dup
110
+
111
+ # extract options from last argument if it's a hash
112
+ if dom_ids.last.is_a?(Hash)
113
+ hash = dom_ids.last.dup
114
+ options.merge! hash
115
+ dom_ids.last.delete :framework
116
+ end
117
+
118
+ editor_ids = (@textile_editor_ids || []) + textile_extract_dom_ids(*dom_ids)
119
+ editor_buttons = (@textile_editor_buttons || [])
120
+ output = []
121
+ output << textile_editor_support unless request.xhr?
122
+ output << '<script type="text/javascript">'
123
+ output << '/* <![CDATA[ */'
124
+
125
+ if !request.xhr?
126
+ case options[:framework]
127
+ when :prototype
128
+ output << %{document.observe('dom:loaded', function() \{}
129
+ when :jquery
130
+ output << %{$(document).ready(function() \{}
131
+ end
132
+ end
133
+
134
+ # output << %q{TextileEditor.framework = '%s';} % options[:framework].to_s
135
+ output << editor_buttons.join("\n") if editor_buttons.any?
136
+ editor_ids.each do |editor_id, mode|
137
+ output << %q{TextileEditor.initialize('%s', '%s');} % [editor_id, mode || 'extended']
138
+ end
139
+ output << '});' unless request.xhr?
140
+
141
+ output << '/* ]]> */'
142
+ output << '</script>'
143
+ output.join("\n").html_safe
144
+ end
145
+ end
146
+
147
+ module FormTagHelper
148
+ # Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions
149
+ # and includes the textile toolbar above it.
150
+ #
151
+ # ==== Options
152
+ # * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
153
+ # * <tt>:rows</tt> - Specify the number of rows in the textarea
154
+ # * <tt>:cols</tt> - Specify the number of columns in the textarea
155
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
156
+ # * Any other key creates standard HTML attributes for the tag.
157
+ #
158
+ # ==== Examples
159
+ # textile_editor_tag 'post'
160
+ # # => <textarea id="post" name="post"></textarea>
161
+ #
162
+ # textile_editor_tag 'bio', @user.bio
163
+ # # => <textarea id="bio" name="bio">This is my biography.</textarea>
164
+ #
165
+ # textile_editor_tag 'body', nil, :rows => 10, :cols => 25
166
+ # # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
167
+ #
168
+ # textile_editor_tag 'body', nil, :size => "25x10"
169
+ # # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
170
+ #
171
+ # textile_editor_tag 'description', "Description goes here.", :disabled => true
172
+ # # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
173
+ #
174
+ # textile_editor_tag 'comment', nil, :class => 'comment_input'
175
+ # # => <textarea class="comment_input" id="comment" name="comment"></textarea>
176
+ def textile_editor_tag(name, content = nil, options = {})
177
+ editor_id = options[:id] || name
178
+ mode = options.delete(:simple) ? 'simple' : 'extended'
179
+ (@textile_editor_ids ||= []) << [editor_id.to_s, mode.to_s]
180
+
181
+ text_area_tag(name, content, options)
182
+ end
183
+ end
184
+
185
+ end
186
+ end
187
+
@@ -0,0 +1,16 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'yaml'
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+
7
+ require 'action_controller'
8
+ require 'action_controller/cgi_ext'
9
+ require 'action_controller/test_process'
10
+
11
+ # Show backtraces for deprecated behavior for quicker cleanup.
12
+ ActiveSupport::Deprecation.debug = true
13
+
14
+ ActionController::Base.logger = nil
15
+ # ActionController::Base.ignore_missing_templates = false
16
+ ActionController::Routing::Routes.reload rescue nil
@@ -0,0 +1,195 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ require File.dirname(__FILE__) + '/../lib/textile_editor'
3
+ require 'ostruct'
4
+
5
+ class TextileEditorHelperTest < Test::Unit::TestCase
6
+ include ActionView::Helpers::TextHelper
7
+ include ActionView::Helpers::AssetTagHelper
8
+ include ActionView::Helpers::TagHelper
9
+ include ActionView::Helpers::FormHelper
10
+ include ActionView::Helpers::JavaScriptHelper
11
+ # include TextileEditorHelper
12
+
13
+ def setup
14
+ @controller = Class.new do
15
+ attr_reader :url_for_options
16
+ def url_for(options, *parameters_for_method_reference)
17
+ @url_for_options = options
18
+ "http://www.example.com"
19
+ end
20
+
21
+ def request; @request ||= ActionController::TestRequest.new; end
22
+ def response; @response ||= ActionController::TestResponse.new; end
23
+ end
24
+ @controller = @controller.new
25
+ @article = OpenStruct.new(:body => nil)
26
+ end
27
+
28
+ # support methods
29
+ def request
30
+ @controller.request
31
+ end
32
+
33
+ def create_simple_editor(object, field, options={})
34
+ output = textile_editor(object, field, options.merge(:simple => true))
35
+ assert_equal text_area(object, field, options), output
36
+ end
37
+
38
+ def create_extended_editor(object, field, options={})
39
+ output = textile_editor(object, field, options)
40
+ assert_equal text_area(object, field, options), output
41
+ end
42
+
43
+ def framework_initialize_output(framework)
44
+ case framework
45
+ when :prototype
46
+ %{document.observe('dom:loaded', function() \{}
47
+ when :jquery
48
+ %{$(document).ready(function() \{}
49
+ end
50
+ end
51
+
52
+ def pre_initialize_output(framework)
53
+ %{<link href="/stylesheets/textile-editor.css" media="screen" rel="stylesheet" type="text/css" />
54
+ <script src="/javascripts/textile-editor.js" type="text/javascript"></script>
55
+ <script type="text/javascript">
56
+ /* <![CDATA[ */
57
+ } +
58
+ framework_initialize_output(framework)
59
+ end
60
+
61
+ def post_initialize_output
62
+ %{\});
63
+ /* ]]> */
64
+ </script>
65
+ }
66
+ end
67
+
68
+ def expected_initialize_output(framework, editors, button_data=nil)
69
+ expected = [pre_initialize_output(framework)]
70
+ expected << button_data unless button_data.nil?
71
+ expected << editors.map do |editor|
72
+ "TextileEditor.initialize('%s', '%s');" % editor
73
+ end
74
+ expected << post_initialize_output
75
+ expected.join("\n").split("\n").map { |e| e.lstrip }.join("\n").chomp
76
+ end
77
+
78
+ # tests
79
+ def test_textile_editor
80
+ assert_nil @textile_editor_ids
81
+ create_extended_editor('article', 'body')
82
+ assert_equal [['article_body', 'extended']], @textile_editor_ids
83
+ end
84
+
85
+ def test_textile_editor_simple_mode
86
+ assert_nil @textile_editor_ids
87
+ create_simple_editor('article', 'body')
88
+ assert_equal [['article_body', 'simple']], @textile_editor_ids
89
+ end
90
+
91
+ def test_textile_editor_with_custom_id
92
+ assert_nil @textile_editor_ids
93
+ create_extended_editor('article', 'body', :id => 'my_custom_id')
94
+ assert_equal [['my_custom_id', 'extended']], @textile_editor_ids
95
+ end
96
+
97
+ def test_textile_editor_simple_mode_with_custom_id
98
+ assert_nil @textile_editor_ids
99
+ create_simple_editor('article', 'body', :id => 'my_custom_id')
100
+ assert_equal [['my_custom_id', 'simple']], @textile_editor_ids
101
+ end
102
+
103
+ def test_textile_editor_initialize
104
+ create_extended_editor('article', 'body')
105
+ output = textile_editor_initialize()
106
+ assert_equal expected_initialize_output(:prototype, [
107
+ ['article_body', 'extended']
108
+ ]), output
109
+
110
+ create_simple_editor('article', 'body_excerpt')
111
+ output = textile_editor_initialize()
112
+ assert_equal expected_initialize_output(:prototype, [
113
+ ['article_body', 'extended'],
114
+ ['article_body_excerpt', 'simple']
115
+ ]), output
116
+
117
+ output = textile_editor_initialize(:framework => :jquery)
118
+ assert_equal expected_initialize_output(:jquery, [
119
+ ['article_body', 'extended'],
120
+ ['article_body_excerpt', 'simple']
121
+ ]), output
122
+
123
+ # test using custom default options
124
+ textile_editor_options :framework => :jquery
125
+ output = textile_editor_initialize()
126
+ assert_equal expected_initialize_output(:jquery, [
127
+ ['article_body', 'extended'],
128
+ ['article_body_excerpt', 'simple']
129
+ ]), output
130
+ end
131
+
132
+ def test_textile_editor_inititalize_with_arbitrary_ids
133
+ output = textile_editor_initialize(:story_comment, :story_body)
134
+ assert_equal expected_initialize_output(:prototype, [
135
+ ['story_comment', 'extended'],
136
+ ['story_body', 'extended']
137
+ ]), output
138
+ end
139
+
140
+ def test_textile_editor_initialize_with_custom_buttons
141
+ b = '<button id="test_button" onclick="alert(\'Hello!\')" title="Hello world">Hello</button>'
142
+ button_data = ["TextileEditor.buttons.push(\"%s\");" % escape_javascript(b)]
143
+ actual = textile_editor_button('Hello',
144
+ :id => 'test_button',
145
+ :onclick => "alert('Hello!')",
146
+ :title => 'Hello world'
147
+ )
148
+
149
+ assert_equal button_data, actual
150
+
151
+ create_extended_editor('article', 'body')
152
+ output = textile_editor_initialize()
153
+ assert_equal expected_initialize_output(:prototype, [
154
+ ['article_body', 'extended']
155
+ ], button_data), output
156
+ end
157
+
158
+ def test_textile_extract_dom_ids_works_with_arrayed_hash
159
+ hash_with_array = { :recipe => [ :instructions, :introduction ] }
160
+ assert_equal [ 'recipe_instructions', 'recipe_introduction' ], textile_extract_dom_ids(hash_with_array)
161
+ end
162
+
163
+ def test_textile_extract_dom_ids_works_with_hash
164
+ hash_with_symbol = { :story => :title }
165
+ assert_equal [ 'story_title' ], textile_extract_dom_ids(hash_with_symbol)
166
+ end
167
+
168
+ def test_textile_extract_dom_ids_works_with_ids
169
+ straight_id = 'article_page'
170
+ assert_equal [ 'article_page' ], textile_extract_dom_ids(straight_id)
171
+ end
172
+
173
+ def test_textile_extract_dom_ids_works_with_mixed_params
174
+ paramd = %w(article_page)
175
+ paramd += [{
176
+ :recipe => [ :instructions, :introduction ],
177
+ :story => :title
178
+ }]
179
+ assert_equal %w(article_page recipe_instructions recipe_introduction story_title).sort,
180
+ textile_extract_dom_ids(*paramd).sort { |a,b| a.to_s <=> b.to_s }
181
+ end
182
+
183
+ def test_textile_editor_button
184
+ b = '<button id="test_button" onclick="alert(\'Hello!\')" title="Hello world">Hello</button>'
185
+ expected = ['TextileEditor.buttons.push("%s");' % escape_javascript(b)]
186
+
187
+ actual = textile_editor_button('Hello',
188
+ :id => 'test_button',
189
+ :onclick => "alert('Hello!')",
190
+ :title => 'Hello world'
191
+ )
192
+
193
+ assert_equal expected, actual
194
+ end
195
+ end
@@ -0,0 +1,41 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{textile_editor}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Mills"]
12
+ s.date = %q{2010-11-15}
13
+ s.email = %q{ben@unfiniti.com}
14
+ s.files = [
15
+ ".bundle/config",
16
+ ".gitignore",
17
+ "Gemfile",
18
+ "Gemfile.lock",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "lib/textile_editor.rb",
22
+ "textile_editor.gemspec",
23
+ "test/abstract_unit.rb",
24
+ "test/textile_editor_test.rb"
25
+ ]
26
+ s.homepage = %q{http://unfiniti.com}
27
+ s.rdoc_options = ["--charset=UTF-8"]
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.3.7}
30
+ s.summary = %q{Provide textile toolbar to textareas}
31
+ s.test_files = [
32
+ "test/abstract_unit.rb",
33
+ "test/textile_editor_test.rb"
34
+ ]
35
+
36
+ if s.respond_to? :specification_version then
37
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
+ s.specification_version = 3
39
+ end
40
+ end
41
+
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: textile_editor
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Ben Mills
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-15 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: ben@unfiniti.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - .bundle/config
31
+ - .gitignore
32
+ - Gemfile
33
+ - Gemfile.lock
34
+ - Rakefile
35
+ - VERSION
36
+ - lib/textile_editor.rb
37
+ - textile_editor.gemspec
38
+ - test/abstract_unit.rb
39
+ - test/textile_editor_test.rb
40
+ has_rdoc: true
41
+ homepage: http://unfiniti.com
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.7
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Provide textile toolbar to textareas
72
+ test_files:
73
+ - test/abstract_unit.rb
74
+ - test/textile_editor_test.rb