radiant-filter_toolbars-extension 0.5.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.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ #Filter Toolbars
2
+
3
+ Adds a textile or markdown WYSIWYG filter to the admin textareas using Control.TextArea [http://livepipe.net/control/textarea]
4
+
5
+ If the Clipped asset manager extension is installed then the image button will launch the assets manager.
6
+
7
+
8
+ ## TODO
9
+
10
+ - Disable toolbar when selecting <none>
11
+ - Refactor duplicated filter funtions
12
+ - Implement SmartyPants
13
+
14
+
15
+ ## Installation
16
+
17
+ This has only been tested on Radiant v1.0.0 +
18
+
19
+ Install as a gem :
20
+
21
+ ```
22
+ gem install radiant-filter_toolbars-extension
23
+ ```
24
+
25
+ Include the gem in your environment.rb :
26
+
27
+ ```
28
+ config.gem 'radiant-filter_toolbars-extension', :version => '~>1.0.0'
29
+ ```
30
+
31
+ Run the update task :
32
+
33
+ ```
34
+ rake radiant:extensions:filter_toolbars:update
35
+ ```
data/Rakefile ADDED
@@ -0,0 +1,109 @@
1
+ # Determine where the RSpec plugin is by loading the boot
2
+ unless defined? RADIANT_ROOT
3
+ ENV["RAILS_ENV"] = "test"
4
+ case
5
+ when ENV["RADIANT_ENV_FILE"]
6
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
7
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
8
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
9
+ else
10
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
11
+ end
12
+ end
13
+
14
+ require 'rake'
15
+ require 'rake/rdoctask'
16
+ require 'rake/testtask'
17
+
18
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
19
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
20
+ require 'spec/rake/spectask'
21
+ require 'cucumber'
22
+ require 'cucumber/rake/task'
23
+
24
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
25
+ Object.send(:remove_const, :RADIANT_ROOT)
26
+
27
+ extension_root = File.expand_path(File.dirname(__FILE__))
28
+
29
+ task :default => [:spec, :features]
30
+ task :stats => "spec:statsetup"
31
+
32
+ desc "Run all specs in spec directory"
33
+ Spec::Rake::SpecTask.new(:spec) do |t|
34
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ end
37
+
38
+ task :features => 'spec:integration'
39
+
40
+ namespace :spec do
41
+ desc "Run all specs in spec directory with RCov"
42
+ Spec::Rake::SpecTask.new(:rcov) do |t|
43
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
44
+ t.spec_files = FileList['spec/**/*_spec.rb']
45
+ t.rcov = true
46
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
47
+ end
48
+
49
+ desc "Print Specdoc for all specs"
50
+ Spec::Rake::SpecTask.new(:doc) do |t|
51
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
52
+ t.spec_files = FileList['spec/**/*_spec.rb']
53
+ end
54
+
55
+ [:models, :controllers, :views, :helpers].each do |sub|
56
+ desc "Run the specs under spec/#{sub}"
57
+ Spec::Rake::SpecTask.new(sub) do |t|
58
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
59
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
60
+ end
61
+ end
62
+
63
+ desc "Run the Cucumber features"
64
+ Cucumber::Rake::Task.new(:integration) do |t|
65
+ t.fork = true
66
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
67
+ # t.feature_pattern = "#{extension_root}/features/**/*.feature"
68
+ t.profile = "default"
69
+ end
70
+
71
+ # Setup specs for stats
72
+ task :statsetup do
73
+ require 'code_statistics'
74
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
75
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
76
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
77
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
78
+ ::CodeStatistics::TEST_TYPES << "Model specs"
79
+ ::CodeStatistics::TEST_TYPES << "View specs"
80
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
81
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
82
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
83
+ end
84
+
85
+ namespace :db do
86
+ namespace :fixtures do
87
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
88
+ task :load => :environment do
89
+ require 'active_record/fixtures'
90
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
91
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
92
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ desc 'Generate documentation for the filter_toolbars extension.'
100
+ Rake::RDocTask.new(:rdoc) do |rdoc|
101
+ rdoc.rdoc_dir = 'rdoc'
102
+ rdoc.title = 'FilterToolbarsExtension'
103
+ rdoc.options << '--line-numbers' << '--inline-source'
104
+ rdoc.rdoc_files.include('README')
105
+ rdoc.rdoc_files.include('lib/**/*.rb')
106
+ end
107
+
108
+ # Load any custom rakefiles for extension
109
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --format progress features --tags ~@proposed,~@in_progress
@@ -0,0 +1,11 @@
1
+ # Sets up the Rails environment for Cucumber
2
+ ENV["RAILS_ENV"] = "test"
3
+ # Extension root
4
+ extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
5
+ require extension_env+'.rb'
6
+
7
+ Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step unless step =~ /datasets_loader\.rb$/}
8
+
9
+ Cucumber::Rails::World.class_eval do
10
+ dataset :filter_toolbars
11
+ end
@@ -0,0 +1,22 @@
1
+ module NavigationHelpers
2
+
3
+ # Extend the standard PathMatchers with your own paths
4
+ # to be used in your features.
5
+ #
6
+ # The keys and values here may be used in your standard web steps
7
+ # Using:
8
+ #
9
+ # When I go to the "filter_toolbars" admin page
10
+ #
11
+ # would direct the request to the path you provide in the value:
12
+ #
13
+ # admin_filter_toolbars_path
14
+ #
15
+ PathMatchers = {} unless defined?(PathMatchers)
16
+ PathMatchers.merge!({
17
+ # /filter_toolbars/i => 'admin_filter_toolbars_path'
18
+ })
19
+
20
+ end
21
+
22
+ World(NavigationHelpers)
@@ -0,0 +1,9 @@
1
+ class FilterToolbarsExtension < Radiant::Extension
2
+ version RadiantFilterToolbarsExtension::VERSION
3
+ description RadiantFilterToolbarsExtension::DESCRIPTION
4
+ url RadiantFilterToolbarsExtension::HOMEPAGE
5
+
6
+ def activate
7
+ Admin::PagesController.send :include, RadiantFilterToolbarsExtension::Controllers::Admin::PagesController
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module RadiantFilterToolbarsExtension
2
+ VERSION = '0.5.0'
3
+ SUMMARY = 'WYSIWYG Toolbar for Radiant CMS'
4
+ DESCRIPTION = 'Adds a WYSIWYG toolbar for the textile and markdown filters.'
5
+ HOMEPAGE = 'https://github.com/jsntv200/radiant-filter_toolbars-extension'
6
+ end
@@ -0,0 +1,24 @@
1
+ module RadiantFilterToolbarsExtension
2
+ module Controllers
3
+ module Admin
4
+ module PagesController
5
+ def self.included(base)
6
+ base.class_eval do
7
+ before_filter :filter_toolbar_assets, :only => [:new, :edit]
8
+
9
+ private
10
+
11
+ def filter_toolbar_assets
12
+ @javascripts << 'admin/filter_toolbars/control/livepipe'
13
+ @javascripts << 'admin/filter_toolbars/control/textarea'
14
+ @javascripts << 'admin/filter_toolbars/filter_toolbars'
15
+ @javascripts << 'admin/filter_toolbars/filters/textile'
16
+ @javascripts << 'admin/filter_toolbars/filters/markdown'
17
+ @stylesheets << 'admin/filter_toolbars'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ namespace :radiant do
2
+ namespace :extensions do
3
+ namespace :filter_toolbars do
4
+
5
+ desc "Runs the migration of the Filter Toolbars extension"
6
+ task :migrate => :environment do
7
+ require 'radiant/extension_migrator'
8
+ if ENV["VERSION"]
9
+ FilterToolbarsExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ Rake::Task['db:schema:dump'].invoke
11
+ else
12
+ FilterToolbarsExtension.migrator.migrate
13
+ Rake::Task['db:schema:dump'].invoke
14
+ end
15
+ end
16
+
17
+ desc "Copies public assets of the Filter Toolbars to the instance public/ directory."
18
+ task :update => :environment do
19
+ is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
20
+ puts "Copying assets from FilterToolbarsExtension"
21
+ Dir[FilterToolbarsExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
22
+ path = file.sub(FilterToolbarsExtension.root, '')
23
+ directory = File.dirname(path)
24
+ mkdir_p RAILS_ROOT + directory, :verbose => false
25
+ cp file, RAILS_ROOT + path, :verbose => false
26
+ end
27
+ unless FilterToolbarsExtension.root.starts_with? RAILS_ROOT # don't need to copy vendored tasks
28
+ puts "Copying rake tasks from FilterToolbarsExtension"
29
+ local_tasks_path = File.join(RAILS_ROOT, %w(lib tasks))
30
+ mkdir_p local_tasks_path, :verbose => false
31
+ Dir[File.join FilterToolbarsExtension.root, %w(lib tasks *.rake)].each do |file|
32
+ cp file, local_tasks_path, :verbose => false
33
+ end
34
+ end
35
+ end
36
+
37
+ desc "Syncs all available translations for this ext to the English ext master"
38
+ task :sync => :environment do
39
+ # The main translation root, basically where English is kept
40
+ language_root = FilterToolbarsExtension.root + "/config/locales"
41
+ words = TranslationSupport.get_translation_keys(language_root)
42
+
43
+ Dir["#{language_root}/*.yml"].each do |filename|
44
+ next if filename.match('_available_tags')
45
+ basename = File.basename(filename, '.yml')
46
+ puts "Syncing #{basename}"
47
+ (comments, other) = TranslationSupport.read_file(filename, basename)
48
+ words.each { |k,v| other[k] ||= words[k] } # Initializing hash variable as empty if it does not exist
49
+ other.delete_if { |k,v| !words[k] } # Remove if not defined in en.yml
50
+ TranslationSupport.write_file(filename, basename, comments, other)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,181 @@
1
+ /**
2
+ * @author Ryan Johnson <http://syntacticx.com/>
3
+ * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
4
+ * @package LivePipe UI
5
+ * @license MIT
6
+ * @url http://livepipe.net/core
7
+ * @require prototype.js
8
+ */
9
+
10
+ if(typeof(Control) == 'undefined')
11
+ Control = {};
12
+
13
+ var $proc = function(proc){
14
+ return typeof(proc) == 'function' ? proc : function(){return proc};
15
+ };
16
+
17
+ var $value = function(value){
18
+ return typeof(value) == 'function' ? value() : value;
19
+ };
20
+
21
+ Object.Event = {
22
+ extend: function(object){
23
+ object._objectEventSetup = function(event_name){
24
+ this._observers = this._observers || {};
25
+ this._observers[event_name] = this._observers[event_name] || [];
26
+ };
27
+ object.observe = function(event_name,observer){
28
+ if(typeof(event_name) == 'string' && typeof(observer) != 'undefined'){
29
+ this._objectEventSetup(event_name);
30
+ if(!this._observers[event_name].include(observer))
31
+ this._observers[event_name].push(observer);
32
+ }else
33
+ for(var e in event_name)
34
+ this.observe(e,event_name[e]);
35
+ };
36
+ object.stopObserving = function(event_name,observer){
37
+ this._objectEventSetup(event_name);
38
+ if(event_name && observer)
39
+ this._observers[event_name] = this._observers[event_name].without(observer);
40
+ else if(event_name)
41
+ this._observers[event_name] = [];
42
+ else
43
+ this._observers = {};
44
+ };
45
+ object.observeOnce = function(event_name,outer_observer){
46
+ var inner_observer = function(){
47
+ outer_observer.apply(this,arguments);
48
+ this.stopObserving(event_name,inner_observer);
49
+ }.bind(this);
50
+ this._objectEventSetup(event_name);
51
+ this._observers[event_name].push(inner_observer);
52
+ };
53
+ object.notify = function(event_name){
54
+ this._objectEventSetup(event_name);
55
+ var collected_return_values = [];
56
+ var args = $A(arguments).slice(1);
57
+ try{
58
+ for(var i = 0; i < this._observers[event_name].length; ++i)
59
+ collected_return_values.push(this._observers[event_name][i].apply(this,args) || null);
60
+ }catch(e){
61
+ if(e == $break)
62
+ return false;
63
+ else
64
+ throw e;
65
+ }
66
+ return collected_return_values;
67
+ };
68
+ if(object.prototype){
69
+ object.prototype._objectEventSetup = object._objectEventSetup;
70
+ object.prototype.observe = object.observe;
71
+ object.prototype.stopObserving = object.stopObserving;
72
+ object.prototype.observeOnce = object.observeOnce;
73
+ object.prototype.notify = function(event_name){
74
+ if(object.notify){
75
+ var args = $A(arguments).slice(1);
76
+ args.unshift(this);
77
+ args.unshift(event_name);
78
+ object.notify.apply(object,args);
79
+ }
80
+ this._objectEventSetup(event_name);
81
+ var args = $A(arguments).slice(1);
82
+ var collected_return_values = [];
83
+ try{
84
+ if(this.options && this.options[event_name] && typeof(this.options[event_name]) == 'function')
85
+ collected_return_values.push(this.options[event_name].apply(this,args) || null);
86
+ var callbacks_copy = this._observers[event_name]; // since original array will be modified after observeOnce calls
87
+ for(var i = 0; i < callbacks_copy.length; ++i)
88
+ collected_return_values.push(callbacks_copy[i].apply(this,args) || null);
89
+ }catch(e){
90
+ if(e == $break)
91
+ return false;
92
+ else
93
+ throw e;
94
+ }
95
+ return collected_return_values;
96
+ };
97
+ }
98
+ }
99
+ };
100
+
101
+ /* Begin Core Extensions */
102
+
103
+ //Element.observeOnce
104
+ Element.addMethods({
105
+ observeOnce: function(element,event_name,outer_callback){
106
+ var inner_callback = function(){
107
+ outer_callback.apply(this,arguments);
108
+ Element.stopObserving(element,event_name,inner_callback);
109
+ };
110
+ Element.observe(element,event_name,inner_callback);
111
+ }
112
+ });
113
+
114
+ //mouse:wheel
115
+ (function(){
116
+ function wheel(event){
117
+ var delta, element, custom_event;
118
+ // normalize the delta
119
+ if (event.wheelDelta) { // IE & Opera
120
+ delta = event.wheelDelta / 120;
121
+ } else if (event.detail) { // W3C
122
+ delta =- event.detail / 3;
123
+ }
124
+ if (!delta) { return; }
125
+ element = Event.extend(event).target;
126
+ element = Element.extend(element.nodeType === Node.TEXT_NODE ? element.parentNode : element);
127
+ custom_event = element.fire('mouse:wheel',{ delta: delta });
128
+ if (custom_event.stopped) {
129
+ Event.stop(event);
130
+ return false;
131
+ }
132
+ }
133
+ document.observe('mousewheel',wheel);
134
+ document.observe('DOMMouseScroll',wheel);
135
+ })();
136
+
137
+ /* End Core Extensions */
138
+
139
+ //from PrototypeUI
140
+ var IframeShim = Class.create({
141
+ initialize: function() {
142
+ this.element = new Element('iframe',{
143
+ style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none',
144
+ src: 'javascript:void(0);',
145
+ frameborder: 0
146
+ });
147
+ $(document.body).insert(this.element);
148
+ },
149
+ hide: function() {
150
+ this.element.hide();
151
+ return this;
152
+ },
153
+ show: function() {
154
+ this.element.show();
155
+ return this;
156
+ },
157
+ positionUnder: function(element) {
158
+ var element = $(element);
159
+ var offset = element.cumulativeOffset();
160
+ var dimensions = element.getDimensions();
161
+ this.element.setStyle({
162
+ left: offset[0] + 'px',
163
+ top: offset[1] + 'px',
164
+ width: dimensions.width + 'px',
165
+ height: dimensions.height + 'px',
166
+ zIndex: element.getStyle('zIndex') - 1
167
+ }).show();
168
+ return this;
169
+ },
170
+ setBounds: function(bounds) {
171
+ for(prop in bounds)
172
+ bounds[prop] += 'px';
173
+ this.element.setStyle(bounds);
174
+ return this;
175
+ },
176
+ destroy: function() {
177
+ if(this.element)
178
+ this.element.remove();
179
+ return this;
180
+ }
181
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @author Ryan Johnson <http://syntacticx.com/>
3
+ * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
4
+ * @package LivePipe UI
5
+ * @license MIT
6
+ * @url http://livepipe.net/control/textarea
7
+ * @require prototype.js, livepipe.js
8
+ */
9
+
10
+ /*global window, document, Prototype, Class, $, $A, Control */
11
+
12
+ if(typeof(Prototype) == "undefined") {
13
+ throw "Control.TextArea requires Prototype to be loaded."; }
14
+ if(typeof(Object.Event) == "undefined") {
15
+ throw "Control.TextArea requires Object.Event to be loaded."; }
16
+
17
+ Control.TextArea = Class.create({
18
+ initialize: function(textarea){
19
+ this.onChangeTimeout = false;
20
+ this.element = $(textarea);
21
+ $(this.element).observe('keyup',this.doOnChange.bindAsEventListener(this));
22
+ $(this.element).observe('paste',this.doOnChange.bindAsEventListener(this));
23
+ $(this.element).observe('input',this.doOnChange.bindAsEventListener(this));
24
+ if(!!document.selection){
25
+ $(this.element).observe('mouseup',this.saveRange.bindAsEventListener(this));
26
+ $(this.element).observe('keyup',this.saveRange.bindAsEventListener(this));
27
+ }
28
+ },
29
+ doOnChange: function(event){
30
+ if(this.onChangeTimeout) {
31
+ window.clearTimeout(this.onChangeTimeout); }
32
+ this.onChangeTimeout = window.setTimeout(function(){
33
+ this.notify('change',this.getValue());
34
+ }.bind(this),Control.TextArea.onChangeTimeoutLength);
35
+ },
36
+ saveRange: function(){
37
+ this.range = document.selection.createRange();
38
+ },
39
+ getValue: function(){
40
+ return this.element.value;
41
+ },
42
+ getSelection: function(){
43
+ if(!!document.selection) {
44
+ return document.selection.createRange().text; }
45
+ else if(!!this.element.setSelectionRange) {
46
+ return this.element.value.substring(this.element.selectionStart,this.element.selectionEnd); }
47
+ else {
48
+ return false; }
49
+ },
50
+ replaceSelection: function(text){
51
+ var scroll_top = this.element.scrollTop;
52
+ if(!!document.selection){
53
+ this.element.focus();
54
+ var range = (this.range) ? this.range : document.selection.createRange();
55
+ range.text = text;
56
+ range.select();
57
+ }else if(!!this.element.setSelectionRange){
58
+ var selection_start = this.element.selectionStart;
59
+ this.element.value = this.element.value.substring(0,selection_start) + text + this.element.value.substring(this.element.selectionEnd);
60
+ this.element.setSelectionRange(selection_start + text.length,selection_start + text.length);
61
+ }
62
+ this.doOnChange();
63
+ this.element.focus();
64
+ this.element.scrollTop = scroll_top;
65
+ },
66
+ wrapSelection: function(before,after){
67
+ var sel = this.getSelection();
68
+ // Remove the wrapping if the selection has the same before/after
69
+ if (sel.indexOf(before) === 0 &&
70
+ sel.lastIndexOf(after) === (sel.length - after.length)) {
71
+ this.replaceSelection(sel.substring(before.length,
72
+ sel.length - after.length));
73
+ } else { this.replaceSelection(before + sel + after); }
74
+ },
75
+ insertBeforeSelection: function(text){
76
+ this.replaceSelection(text + this.getSelection());
77
+ },
78
+ insertAfterSelection: function(text){
79
+ this.replaceSelection(this.getSelection() + text);
80
+ },
81
+ collectFromEachSelectedLine: function(callback,before,after){
82
+ this.replaceSelection((before || '') + $A(this.getSelection().split("\n")).collect(callback).join("\n") + (after || ''));
83
+ },
84
+ insertBeforeEachSelectedLine: function(text,before,after){
85
+ this.collectFromEachSelectedLine(function(line){
86
+ },before,after);
87
+ }
88
+ });
89
+ Object.extend(Control.TextArea,{
90
+ onChangeTimeoutLength: 500
91
+ });
92
+ Object.Event.extend(Control.TextArea);
93
+
94
+ Control.TextArea.ToolBar = Class.create( {
95
+ initialize: function(textarea,toolbar){
96
+ this.textarea = textarea;
97
+ if(toolbar) {
98
+ this.container = $(toolbar); }
99
+ else{
100
+ this.container = $(document.createElement('ul'));
101
+ this.textarea.element.parentNode.insertBefore(this.container,this.textarea.element);
102
+ }
103
+ },
104
+ attachButton: function(node,callback){
105
+ node.onclick = function(){return false;};
106
+ $(node).observe('click',callback.bindAsEventListener(this.textarea));
107
+ },
108
+ addButton: function(link_text,callback,attrs){
109
+ var li = document.createElement('li');
110
+ var a = document.createElement('a');
111
+ a.href = '#';
112
+ this.attachButton(a,callback);
113
+ li.appendChild(a);
114
+ Object.extend(a,attrs || {});
115
+ if(link_text){
116
+ var span = document.createElement('span');
117
+ span.innerHTML = link_text;
118
+ a.appendChild(span);
119
+ }
120
+ this.container.appendChild(li);
121
+ }
122
+ });
123
+
@@ -0,0 +1,81 @@
1
+ var FilterToolBars = {
2
+ className: 'filter_toolbar',
3
+ textarea: null,
4
+ buttons: [
5
+ { title: 'Bold', slug: 'bold' },
6
+ { title: 'Italics', slug: 'italics' },
7
+ { title: 'Heading 1', slug: 'h1' },
8
+ { title: 'Heading 2', slug: 'h2' },
9
+ { title: 'Heading 3', slug: 'h3' },
10
+ { title: 'Heading 4', slug: 'h4' },
11
+ { title: 'Ordered List', slug: 'ordered' },
12
+ { title: 'Unordered List', slug: 'unordered' },
13
+ { title: 'Link', slug: 'link' },
14
+ { title: 'Image', slug: 'image', attrs: {href:'#attach_asset'}},
15
+ { title: 'Block Quote', slug: 'quote' },
16
+ { title: 'Help', slug: 'help' }
17
+ ]
18
+ }
19
+
20
+ FilterToolBars.Filters = {
21
+ image: function() {
22
+ return false;
23
+ },
24
+
25
+ help: function() {
26
+ page_part = FilterToolBars.textarea.element.up('.page').readAttribute('data-caption');
27
+ loadFilterReference(page_part);
28
+ }
29
+ }
30
+
31
+ FilterToolBars.AttachBehavior = Behavior.create({
32
+ initialize: function(options) {
33
+ this.textarea = new Control.TextArea(this.element);
34
+ this.toolbar = new Control.TextArea.ToolBar(this.textarea);
35
+ this.toolbar.container.id = this.element.id + '_toolbar';
36
+ this.toolbar.container.className = FilterToolBars.className;
37
+ this.attach();
38
+ },
39
+
40
+ attach: function() {
41
+ FilterToolBars.buttons.each( function(button) {
42
+ var attributes = Object.extend({ className: 'button_' + button.slug, title: button.title }, button.attrs || {})
43
+ this.toolbar.addButton(button.title, function() { this.click(button.slug) }.bind(this), attributes);
44
+ }.bind(this));
45
+ },
46
+
47
+ click: function(tag) {
48
+ FilterToolBars.textarea = this.textarea;
49
+ FilterToolBars[this.filter()][tag]();
50
+ },
51
+
52
+ filter: function() {
53
+ return this.element.up('.part').down('select').getValue();
54
+ },
55
+
56
+ show: function() {
57
+ this.toolbar.show();
58
+ },
59
+
60
+ hide: function() {
61
+ this.toolbar.hide();
62
+ }
63
+ });
64
+
65
+ FilterToolBars.ChangeBehavior = Behavior.create({
66
+ initialize: function(options) {
67
+ this.change();
68
+ this.element.observe('change', this.change.bind(this));
69
+ },
70
+
71
+ change: function() {
72
+ FilterToolBars.filter = this.element.getValue()
73
+ }
74
+ });
75
+
76
+ Event.addBehavior({
77
+ '.part select' : FilterToolBars.ChangeBehavior(),
78
+ '.part .textarea' : FilterToolBars.AttachBehavior(),
79
+ 'a.button_image' : Popup.TriggerBehavior()
80
+ });
81
+
@@ -0,0 +1,57 @@
1
+ FilterToolBars.Markdown = Object.extend({
2
+ bold: function() {
3
+ FilterToolBars.textarea.wrapSelection('**', '**');
4
+ },
5
+
6
+ italics: function() {
7
+ FilterToolBars.textarea.wrapSelection('*', '*');
8
+ },
9
+
10
+ h1: function() {
11
+ FilterToolBars.textarea.insertBeforeSelection('# ');
12
+ },
13
+
14
+ h2: function() {
15
+ FilterToolBars.textarea.insertBeforeSelection('## ');
16
+ },
17
+
18
+ h3: function() {
19
+ FilterToolBars.textarea.insertBeforeSelection('### ');
20
+ },
21
+
22
+ h4: function() {
23
+ FilterToolBars.textarea.insertBeforeSelection('#### ');
24
+ },
25
+
26
+ ordered: function() {
27
+ var i = 0;
28
+
29
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
30
+ if (!line.match(/^\s+$/)) {
31
+ ++i;
32
+ return event.shiftKey ? line.replace(/^\d+\.\s/,'') : (line.match(/\d+\.\s/) ? '' : i + '. ') + line;
33
+ }
34
+ });
35
+ },
36
+
37
+ unordered: function() {
38
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
39
+ return event.shiftKey ? (line.match(/^\*{2,}/) ? line.replace(/^\*/,'') : line.replace(/^\*\s/,'')) : (line.match(/\*+\s/) ? '*' : '* ') + line;
40
+ });
41
+ },
42
+
43
+ link: function() {
44
+ var selection = this.getSelection() || 'Link Text',
45
+ response = prompt('Enter Full URL: (http:// or mailto:)', '');
46
+
47
+ if (response && response.match(/^((f|ht)tps?|mailto)/)) {
48
+ FilterToolBars.textarea.replaceSelection('[' + selection + '](' + response.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://') + ')');
49
+ }
50
+ },
51
+
52
+ quote: function() {
53
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
54
+ return event.shiftKey ? line.replace(/^\> /,'') : '> ' + line;
55
+ });
56
+ }
57
+ }, FilterToolBars.Filters);
@@ -0,0 +1,52 @@
1
+ FilterToolBars.Textile = Object.extend({
2
+ bold: function() {
3
+ FilterToolBars.textarea.wrapSelection('*', '*');
4
+ },
5
+
6
+ italics: function() {
7
+ FilterToolBars.textarea.wrapSelection('_', '_');
8
+ },
9
+
10
+ h1: function() {
11
+ FilterToolBars.textarea.insertBeforeSelection('h1. ');
12
+ },
13
+
14
+ h2: function() {
15
+ FilterToolBars.textarea.insertBeforeSelection('h2. ');
16
+ },
17
+
18
+ h3: function() {
19
+ FilterToolBars.textarea.insertBeforeSelection('h3. ');
20
+ },
21
+
22
+ h4: function() {
23
+ FilterToolBars.textarea.insertBeforeSelection('h4. ');
24
+ },
25
+
26
+ ordered: function() {
27
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
28
+ return event.shiftKey ? (line.match(/^\#{2,}/) ? line.replace(/^\#/,'') : line.replace(/^\#\s/,'')) : (line.match(/\#+\s/) ? '#' : '# ') + line;
29
+ });
30
+ },
31
+
32
+ unordered: function() {
33
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
34
+ return event.shiftKey ? (line.match(/^\*{2,}/) ? line.replace(/^\*/,'') : line.replace(/^\*\s/,'')) : (line.match(/\*+\s/) ? '*' : '* ') + line;
35
+ });
36
+ },
37
+
38
+ link: function() {
39
+ var selection = FilterToolBars.textarea.getSelection() || 'Link Text',
40
+ response = prompt('Enter Full URL: (http:// or mailto:)', '');
41
+
42
+ if (response && response.match(/^((f|ht)tps?|mailto)/)) {
43
+ FilterToolBars.textarea.replaceSelection('"' + selection + '":' + response.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://'));
44
+ }
45
+ },
46
+
47
+ quote: function() {
48
+ FilterToolBars.textarea.collectFromEachSelectedLine( function(line) {
49
+ return event.shiftKey ? line.replace(/^bq. /,'') : 'bq. ' + line;
50
+ });
51
+ }
52
+ }, FilterToolBars.Filters);
@@ -0,0 +1,62 @@
1
+ .textarea {
2
+ padding:2px;
3
+ }
4
+
5
+ .filter_toolbar {
6
+ position:relative;
7
+ margin:0;
8
+ padding:0 2px;
9
+ border:1px solid #ccc;
10
+ background-color:#F6F6F6;
11
+ width:100%;
12
+ height:26px;
13
+ border-bottom: 0;
14
+
15
+ li {
16
+ list-style:none;
17
+ float:left;
18
+
19
+ span {
20
+ display:none;
21
+ }
22
+
23
+ a {
24
+ margin: 2px;
25
+ width:20px;
26
+ height:20px;
27
+ float:left;
28
+ display:block;
29
+ background-image:url('/images/admin/filter_toolbars/icons.gif');
30
+ border:1px solid #f6f6f6;
31
+
32
+ &:hover {
33
+ border-color:#0A246A;
34
+ background-color: #B2BBD0;
35
+ }
36
+ }
37
+
38
+ .button_help {
39
+ position:absolute;
40
+ top:0;
41
+ right:0;
42
+
43
+ &:hover {
44
+ border-left-color:#5f9846;
45
+ border-right-color:#5f9846;
46
+ }
47
+ }
48
+
49
+ .button_bold { background-position:0 0 }
50
+ .button_italics { background-position:-60px 0 }
51
+ .button_h1 { background-position: -660px 0 }
52
+ .button_h2 { background-position: -680px 0 }
53
+ .button_h3 { background-position: -700px 0 }
54
+ .button_h4 { background-position: -720px 0 }
55
+ .button_ordered { background-position:-80px 0 }
56
+ .button_unordered { background-position:-20px 0 }
57
+ .button_link { background-position:-500px 0 }
58
+ .button_image { background-position:-380px 0 }
59
+ .button_quote { background-position:-220px 0 }
60
+ .button_help { background-position:-340px 0 }
61
+ }
62
+ }
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "radiant_filter_toolbars_extension"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.platform = Gem::Platform::RUBY
7
+ s.name = "radiant-filter_toolbars-extension"
8
+ s.version = RadiantFilterToolbarsExtension::VERSION
9
+ s.summary = RadiantFilterToolbarsExtension::SUMMARY
10
+ s.description = RadiantFilterToolbarsExtension::DESCRIPTION
11
+ s.homepage = RadiantFilterToolbarsExtension::HOMEPAGE
12
+ s.authors = ["Jason Taylor"]
13
+ s.email = ["jsntv200@gmail.com"]
14
+
15
+ ignores = if File.exist?('.gitignore')
16
+ File.read('.gitignore').split("\n").inject([]) {|a,p| a + Dir[p] }
17
+ else
18
+ []
19
+ end
20
+
21
+ s.files = Dir['**/*'] - ignores
22
+ s.test_files = Dir['test/**/*','spec/**/*','features/**/*'] - ignores
23
+ s.require_paths = ["lib"]
24
+
25
+ s.post_install_message = %{
26
+ Add this to your radiant project with:
27
+ config.gem 'radiant-filter_toolbars-extension', :version => '~>#{RadiantFilterToolbarsExtension::VERSION}'
28
+ }
29
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,36 @@
1
+ unless defined? RADIANT_ROOT
2
+ ENV["RAILS_ENV"] = "test"
3
+ case
4
+ when ENV["RADIANT_ENV_FILE"]
5
+ require ENV["RADIANT_ENV_FILE"]
6
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
7
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
8
+ else
9
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
10
+ end
11
+ end
12
+ require "#{RADIANT_ROOT}/spec/spec_helper"
13
+
14
+ Dataset::Resolver.default << (File.dirname(__FILE__) + "/datasets")
15
+
16
+ if File.directory?(File.dirname(__FILE__) + "/matchers")
17
+ Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
18
+ end
19
+
20
+ Spec::Runner.configure do |config|
21
+ # config.use_transactional_fixtures = true
22
+ # config.use_instantiated_fixtures = false
23
+ # config.fixture_path = RAILS_ROOT + '/spec/fixtures'
24
+
25
+ # You can declare fixtures for each behaviour like this:
26
+ # describe "...." do
27
+ # fixtures :table_a, :table_b
28
+ #
29
+ # Alternatively, if you prefer to declare them only once, you can
30
+ # do so here, like so ...
31
+ #
32
+ # config.global_fixtures = :table_a, :table_b
33
+ #
34
+ # If you declare global fixtures, be aware that they will be declared
35
+ # for all of your examples, even those that don't use them.
36
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radiant-filter_toolbars-extension
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - Jason Taylor
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-10 00:00:00 +10:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Adds a WYSIWYG toolbar for the textile and markdown filters.
23
+ email:
24
+ - jsntv200@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - cucumber.yml
33
+ - features/support/env.rb
34
+ - features/support/paths.rb
35
+ - filter_toolbars_extension.rb
36
+ - lib/radiant_filter_toolbars_extension/controllers/admin/pages_controller.rb
37
+ - lib/radiant_filter_toolbars_extension.rb
38
+ - lib/tasks/filter_toolbars_extension_tasks.rake
39
+ - public/images/admin/filter_toolbars/icons.gif
40
+ - public/javascripts/admin/filter_toolbars/control/livepipe.js
41
+ - public/javascripts/admin/filter_toolbars/control/textarea.js
42
+ - public/javascripts/admin/filter_toolbars/filter_toolbars.js
43
+ - public/javascripts/admin/filter_toolbars/filters/markdown.js
44
+ - public/javascripts/admin/filter_toolbars/filters/textile.js
45
+ - public/stylesheets/sass/admin/filter_toolbars.scss
46
+ - radiant-filter_toolbars-extension.gemspec
47
+ - Rakefile
48
+ - README.md
49
+ - spec/spec.opts
50
+ - spec/spec_helper.rb
51
+ has_rdoc: true
52
+ homepage: https://github.com/jsntv200/radiant-filter_toolbars-extension
53
+ licenses: []
54
+
55
+ post_install_message: "\n Add this to your radiant project with:\n config.gem 'radiant-filter_toolbars-extension', :version => '~>0.5.0'\n "
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.5.0
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: WYSIWYG Toolbar for Radiant CMS
85
+ test_files:
86
+ - spec/spec.opts
87
+ - spec/spec_helper.rb
88
+ - features/support/env.rb
89
+ - features/support/paths.rb