radiant-filter_toolbars-extension 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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