Browse files

update preview periodically

update jquery(-ui) and include autocomplete field
use combobox
fixed enter at the end of a heading
fix backspace and del handling
dialogs are created on the fly
textarea does not keep selection on modechange
validation
working on styling
load editor from any html element
save button
selection api in mode
lists
key combos are no longer captured in firefox
decoupled wysiwyg and datamodes

and of course lots of specs and fixes
hit a roadblock on trying to spec with selenium
native events are not supported in awesome
  • Loading branch information...
1 parent 7c2e68a commit f2867c25a0a34adf40dd42e970b20ac5897ba126 @johnny committed Jun 4, 2011
Showing with 4,970 additions and 7,959 deletions.
  1. +4 −0 .gitignore
  2. +5 −0 Gemfile
  3. +101 −0 Gemfile.lock
  4. +14 −0 Rakefile
  5. +143 −23 TODO
  6. +2 −3 config.rb
  7. +14 −2 join
  8. +139 −0 public/javascripts/dialog.js
  9. +60 −39 public/javascripts/joined-min.js
  10. +1,346 −373 public/javascripts/joined.js
  11. +415 −332 public/javascripts/markupEditor.js
  12. +53 −24 public/javascripts/modes/textile/bruteForceCompiler.js
  13. +195 −81 public/javascripts/modes/textile/textileMode.js
  14. +470 −48 public/javascripts/modes/wysiwygMode.js
  15. +92 −0 public/javascripts/plugins/combobox.js
  16. +87 −0 public/javascripts/plugins/enhanceTextfield.js
  17. +66 −0 public/javascripts/plugins/isValid.js
  18. +15 −0 public/javascripts/site.js
  19. +19 −0 public/javascripts/test/markupEditor.js
  20. +12 −0 public/javascripts/test/seleniumSupport.js
  21. +180 −0 public/javascripts/test/testHelper.js
  22. +31 −10 public/javascripts/test/textileCompiler.js
  23. +348 −208 public/javascripts/test/textileMode.js
  24. +321 −230 public/javascripts/test/wysiwygMode.js
  25. +13 −0 public/javascripts/util.js
  26. +0 −181 public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js
  27. +146 −0 public/javascripts/vendor/jquery-ui.js
  28. +5 −6,229 public/javascripts/vendor/jquery.js
  29. BIN public/stylesheets/images/icons/disk.png
  30. BIN public/stylesheets/images/icons/layout.png
  31. BIN public/stylesheets/images/icons/text_list_bullets.png
  32. BIN public/stylesheets/images/icons/text_list_numbers.png
  33. BIN public/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
  34. +57 −4 public/stylesheets/smoothness/{jquery-ui-1.8.9.custom.css → jquery-ui-1.8.13.custom.css}
  35. +3 −0 selenium/spec.opts
  36. +11 −0 selenium/spec_helper.rb
  37. 0 selenium/support/firefox_profile/.parentlock
  38. BIN selenium/support/firefox_profile/XUL.mfasl
  39. BIN selenium/support/firefox_profile/addons.sqlite
  40. +1 −0 selenium/support/firefox_profile/bookmarkbackups/bookmarks-2011-07-05.json
  41. BIN selenium/support/firefox_profile/cert8.db
  42. BIN selenium/support/firefox_profile/chromeappsstore.sqlite
  43. +5 −0 selenium/support/firefox_profile/compatibility.ini
  44. BIN selenium/support/firefox_profile/content-prefs.sqlite
  45. BIN selenium/support/firefox_profile/cookies.sqlite
  46. BIN selenium/support/firefox_profile/downloads.sqlite
  47. +6 −0 selenium/support/firefox_profile/extensions.ini
  48. BIN selenium/support/firefox_profile/extensions.sqlite
  49. BIN selenium/support/firefox_profile/extensions/firebug@software.joehewitt.com.xpi
  50. BIN selenium/support/firefox_profile/extensions/firequery@binaryage.com.xpi
  51. +1 −0 selenium/support/firefox_profile/firebug/annotations.json
  52. 0 selenium/support/firefox_profile/firebug/breakpoints.json
  53. BIN selenium/support/firefox_profile/formhistory.sqlite
  54. BIN selenium/support/firefox_profile/key3.db
  55. +39 −0 selenium/support/firefox_profile/localstore.rdf
  56. +62 −0 selenium/support/firefox_profile/mimeTypes.rdf
  57. BIN selenium/support/firefox_profile/permissions.sqlite
  58. BIN selenium/support/firefox_profile/places.sqlite
  59. BIN selenium/support/firefox_profile/places.sqlite-shm
  60. 0 selenium/support/firefox_profile/places.sqlite-wal
  61. +102 −0 selenium/support/firefox_profile/pluginreg.dat
  62. +59 −0 selenium/support/firefox_profile/prefs.js
  63. +1 −0 selenium/support/firefox_profile/search.json
  64. BIN selenium/support/firefox_profile/search.sqlite
  65. BIN selenium/support/firefox_profile/secmod.db
  66. +1 −0 selenium/support/firefox_profile/sessionstore.bak
  67. +1 −0 selenium/support/firefox_profile/sessionstore.js
  68. BIN selenium/support/firefox_profile/signons.sqlite
  69. BIN selenium/support/firefox_profile/startupCache/startupCache.4.little
  70. BIN selenium/support/firefox_profile/urlclassifier3.sqlite
  71. +2 −0 selenium/support/firefox_profile/urlclassifierkey3.txt
  72. +1 −0 selenium/support/firefox_profile/weave/toFetch/clients.json
  73. +1 −0 selenium/support/firefox_profile/weave/toFetch/tabs.json
  74. BIN selenium/support/firefox_profile/webappsstore.sqlite
  75. +36 −0 selenium/support/helpers.rb
  76. +24 −0 selenium/textile_spec.rb
  77. +5 −11 views/dev.html.haml
  78. +0 −24 views/devLayout.haml
  79. +11 −8 views/layout.haml
  80. +19 −0 views/multiple.html.haml
  81. +0 −14 views/partials/dialogs.haml
  82. +21 −0 views/releaseLayout.haml
  83. +5 −0 views/selenium.html.haml
  84. +150 −88 views/stylesheets/markupEditor.css.sass
  85. +12 −23 views/test.html.haml
  86. +27 −0 views/test_runner.html.haml
  87. +1 −1 views/textareas/html.erb
  88. +10 −3 views/textareas/textile.erb
View
4 .gitignore
@@ -3,6 +3,10 @@
# dirs
build/
inspiration/
+.sass-cache/
+.bundle/
+bin/
+selenium/support/firefox_profile/Cache
# tempfiles
*[~#]
View
5 Gemfile
@@ -0,0 +1,5 @@
+source "http://rubygems.org"
+gem "middleman"
+gem "closure-compiler"
+gem "selenium-webdriver"
+gem "rspec"
View
101 Gemfile.lock
@@ -0,0 +1,101 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activesupport (3.0.9)
+ childprocess (0.1.9)
+ ffi (~> 1.0.6)
+ chunky_png (1.2.0)
+ closure-compiler (1.1.1)
+ coffee-script (2.2.0)
+ coffee-script-source
+ execjs
+ coffee-script-source (1.1.1)
+ compass (0.11.2)
+ chunky_png (~> 1.1)
+ fssm (>= 0.2.7)
+ sass (~> 3.1)
+ crack (0.1.8)
+ daemons (1.1.4)
+ diff-lcs (1.1.2)
+ eventmachine (0.12.10)
+ execjs (1.2.0)
+ multi_json (~> 1.0)
+ ffi (1.0.9)
+ fssm (0.2.7)
+ haml (3.1.2)
+ http_router (0.7.10)
+ rack (>= 1.0.0)
+ url_mount (~> 0.2.1)
+ httparty (0.7.8)
+ crack (= 0.1.8)
+ i18n (0.6.0)
+ json_pure (1.5.3)
+ middleman (1.2.8)
+ coffee-script (~> 2.2.0)
+ compass (= 0.11.2)
+ haml (~> 3.1.0)
+ httparty (~> 0.7.0)
+ padrino-core (~> 0.9.23)
+ padrino-helpers (~> 0.9.23)
+ rack (~> 1.0)
+ rack-test (~> 0.5.0)
+ sass (~> 3.1.0)
+ shotgun (~> 0.8.0)
+ sinatra (~> 1.2.0)
+ thin (~> 1.2.0)
+ thor (~> 0.14.0)
+ tilt (~> 1.3.0)
+ uglifier (~> 0.5.0)
+ multi_json (1.0.3)
+ padrino-core (0.9.29)
+ activesupport (>= 3.0.0)
+ http_router (~> 0.7.8)
+ sinatra (~> 1.2.6)
+ thor (>= 0.14.3)
+ tilt (~> 1.3.0)
+ padrino-helpers (0.9.29)
+ i18n (>= 0.4.1)
+ padrino-core (= 0.9.29)
+ rack (1.3.0)
+ rack-test (0.5.7)
+ rack (>= 1.0)
+ rspec (2.6.0)
+ rspec-core (~> 2.6.0)
+ rspec-expectations (~> 2.6.0)
+ rspec-mocks (~> 2.6.0)
+ rspec-core (2.6.4)
+ rspec-expectations (2.6.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.6.0)
+ rubyzip (0.9.4)
+ sass (3.1.4)
+ selenium-webdriver (0.2.2)
+ childprocess (>= 0.1.9)
+ ffi (>= 1.0.7)
+ json_pure
+ rubyzip
+ shotgun (0.8)
+ rack (>= 1.0)
+ sinatra (1.2.6)
+ rack (~> 1.1)
+ tilt (>= 1.2.2, < 2.0)
+ thin (1.2.11)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ thor (0.14.6)
+ tilt (1.3.2)
+ uglifier (0.5.4)
+ execjs (>= 0.3.0)
+ multi_json (>= 1.0.2)
+ url_mount (0.2.1)
+ rack
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ closure-compiler
+ middleman
+ rspec
+ selenium-webdriver
View
14 Rakefile
@@ -0,0 +1,14 @@
+require 'rubygems'
+require 'rspec/core/rake_task'
+
+desc "Run specs, run a specific spec with"
+task :spec => [ "spec:default" ]
+
+SPEC_OPTS = ["--options", "./selenium/spec.opts"]
+
+namespace :spec do
+ RSpec::Core::RakeTask.new('default') do |t|
+ t.rspec_opts = SPEC_OPTS
+ t.pattern = 'selenium/*_spec.rb'
+ end
+end
View
166 TODO
@@ -1,35 +1,150 @@
-* [5/11] Further improvements
- - [X] Fix bugs in textileCompiler
- - [X] get specs running in Firefox
- - [X] W: check if selection is in preview before calculating states
- and doing things: Firefox select text -> h1 -> p -> all gone
- - [X] formatBlock should select multiple paragraphs
- - [X] refactor to use prototypes
- - [X] abstract getStates
+* [17/29] Further improvements
- [-] publish project
- [ ] documentation
- [X] better static files handling:
- [X] package files
- [X] join and compress js files
- [X] push to github
- - [ ] update preview periodically (and track changes)
- - [ ] clean pasted text
- - [ ] Better Dialogs (for inspiration see gollum)
- - [ ] dialogs should be created on the fly
- - [ ] values for the selects should be defined by a method call
+ - [X] decouple preview and data-modes
+ - [-] wysiwyg
+ - [ ] Toolbar should work only if there is a range inside the preview
+ (also the state checking)
+ - [ ] expand selection to word boundaries, just as in textile
+ - [ ] join lists on backspace/entf between lists
+ - [ ] Firefox: fix checkState for one bold word
+ - [ ] br tags should be top level, because Border expects that
+ or alternatively change behavior of the node search within Border
+ This would be the approach with a future since there might be other
+ cases of nested nodes ( b>i, i>b would be different now, but should
+ be the same)
+ - [X] handle delete key
+ - [X] selecting all and pressing bold should bolden each (part of a) paragraph in turn
+ - [X] ignore special keyCodes on deletion
+ - [X] on keyup check if the content is still in a valid block tag
+ - [X] chrome problems
+ - [X] return at the end of a heading
+ - [X] backspace into another paragraph
+ this will keep the style of the backspaced paragraph with span tags
+ - [X] firefox problems
+ - [X] selecting all and deleting/overwriting it
+ overwriting with normal keys won't work
+ - [X] respect the top blockformat on overwriting
+ - [X] return at the end of a heading
+ - [X] enter in the middle of a heading does not move the rest of the heading down
+ - [X] selecting all by hand and deleting it leaves empty tags
+ this is a result of the current implementation of checkIfDeletedAll
+ - [ ] textile
+ - [ ] replace this.tag
+ - [ ] add close editor
+ - [ ] flexible toolbar, which stays on top
+ - [ ] support haml
+ - [ ] provide support for external mode select
+ - [-] Better Dialogs (for inspiration see gollum)
+ - [ ] display errors as tooltips
+ - [X] reset errors on open
+ - [X] enhancedTextfield: type, click clear, type again (no button), click cancel (button appears and button click did not work)
+ - [X] style clearButton + autocomplete
+ - [X] use errors
+ - [X] autocomplete select should trigger change (file bug)
+ - [X] support prompts in textfields
+ - [X] add a clear button to text fields
+ - [X] dialogs should be created on the fly
+ - [X] values for the selects should be defined by a method call if functions are defined
+ - [X] fields should share the combobox
- [ ] localization
- - [ ] different toolbars for different modes.
+ - [ ] spec with selenium
+ - [ ] spec press enter
+ - [ ] in list
+ - [ ] in heading
+ - [ ] spec press shift enter
+ - [ ] in list
+ - [ ] show window in different awesome tab
+ - [ ] use existing window (not supported in ruby bindings)
+ - [ ] fire native events (not supported in awesome, try in kde or windows)
+ - [ ] clean and parse pasted html
+ - [ ] handle blocktags within lists
+ - [X] list functionality
+ - [X] wysiwyg
+ - [X] button click
+ - [X] own
+ - [X] on
+ - [X] off
+ - [X] off in the middle should seperate the list
+ - [X] align
+ - [X] bold/italic
+ - [X] conversion to textile should handle linebreaks
+ - [X] link
+ - [X] image
+ - [X] paragraph
+ - [X] backspace out of list (also at the beginning of the div) should remove list
+ and replace it with a paragraph
+ - [X] ensure <br> only inside block tags (this is now handled during conversion.
+ this way html import is easier)
+ - [X] shift + enter throws error
+ - [X] enter in list (by default)
+ - [X] fix conversion
+ - [X] refactor selection api to mode
+ - [X] fix textile compiler
+ - [X] textile
+ - [X] shift enter in list
+ - [X] enter in list
+ - [X] button click
+ - [X] own
+ - [X] align
+ - [X] bold/italic
+ - [X] link
+ - [X] image
+ - [X] paragraph (ignore it)
+ - [X] create own selenium view template
+ - [X] load firebug into selenium window
+ - [X] fix selection on end of textfield
+ - [X] should not catch strg-r
+ - [X] fix joined file
+ - [X] generalize spec helpers
+ - [X] load editor from any html element
+ - [X] optimize hiding and showing. use least possible dom calls
+ - [X] dynamically change the toolbar. Each mode should define the
+ visible fields. If save is visible is defined by the
+ presence of the save setting. The order is defined in markupEditor and not
+ changeable. It would confuse the user otherwise
+ - [X] spec
+ - [X] add save button which calls a submit callback
+ - [X] basics
+ - [X] keep styles from the original html element
+ - [X] different toolbars for different modes.
+ - [X] Fix bugs in textileCompiler
+ - [X] get specs running in Firefox
+ - [X] W: check if selection is in preview before calculating states
+ and doing things: Firefox select text -> h1 -> p -> all gone
+ - [X] formatBlock should select multiple paragraphs
+ - [X] refactor to use prototypes
+ - [X] abstract getStates
+ - [X] update preview periodically (and track changes)
+ - [X] prevent link click follow
+ - [X] preview div should always have a p-tag inside
+ - [X] on mode change clear selection (select something in ta
+ -> preview -> ta selection should be gone)
+ - [X] fix rare bug: updating preview in preview mode should not write textile into preview
+ reproduce: load dev.html -> press any key -> wait
-* [0/2] Bugs
- - [ ] Select Paragraph Box -> up and down arrows don't change the
- text, but they should
- - [ ] pressing enter at the end of a heading sets the cursor outside any paragraph
+* [1/3] Bugs
+ - [X] pressing enter at the end of a heading sets the cursor outside
+ any paragraph
+ - [ ] h1. * item1
+ does not compile right
+ - [ ] set cursor between _a and click italic two times -> not the
+ same situation as before. Such two clicks should be idempotent
-* [/] Consider
+* [0/5] Consider
+ - [ ] Partial list change should either
+ change the whole list (currently wysiwyg)
+ change part off the list (currently textile and the norm)
- [ ] turn off url display in bottom (consider accessability, gollum
has the same construct)
- [ ] double click should work: use hover to change contentEditable
value and remember selection
+ - [ ] do not focus the textarea after toolbar action, so that the current select
+ is still selected and can be changed via the arrow keys
- [ ] remove conflicting markup in textile in one line (means if you
select a string with parts of it already marked bold and click the
unselected bold button, the parts should be marked as bold as a whole)
@@ -38,10 +153,15 @@
* List of Firefox Bugs
+- Selecting everything and pressing delete removes all content within the wysiwyg area
- doubleclicking a word does not select the Word itself but some
whitespace left of it too
-- first paragraph in contentEditable cannot be changed
-- pressing enter at the end of a heading does not create a new p tag
- (webkit too)
+- pressing enter at the end of a heading lands outside any tag
- justify* does only work with contentEditable = true on body
-- double click in textarea does not select the word if there is an element with contentEditable = true on the page
+- double click in textarea does not select the word if there is an element with contentEditable = true on the page
+
+* List of Chrome Bugs
+
+- pressing enter at the end of a heading lands inside a div
+- backspacing into a different paragraph type will keep the
+ style of the backspaced paragraph with span tags
View
5 config.rb
@@ -9,9 +9,8 @@ def text_field(name, options = {})
end
end
-with_layout :devLayout do
- page "/test.html"
- page "/dev.html"
+with_layout :releaseLayout do
+ page "/index.html"
end
# Change the JS directory
View
16 join
@@ -1,10 +1,22 @@
#!/usr/bin/ruby
-files = %w{markupEditor modes/textile/textileMode modes/textile/bruteForceCompiler modes/wysiwygMode}
+files = %w{plugins/isValid markupEditor util plugins/enhanceTextfield plugins/combobox dialog modes/textile/textileMode modes/textile/bruteForceCompiler modes/wysiwygMode}
File.open('public/javascripts/joined.js','w') do |file|
files.each do |filename|
- file.puts File.open('public/javascripts/' + filename + '.js').read
+ regexp = /( # Match she-bang style C-comment
+ \/\*[!*] # Opening delimiter.
+ [^*]*\*+ # {normal*} Zero or more non-*, one or more *
+ (?: # Begin {(special normal*)*} construct.
+ [^*\/] # {special} a non-*, non-\/ following star.
+ [^*]*\*+ # More {normal*}
+ )* # Finish "Unrolling-the-Loop"
+ \/ # Closing delimiter.
+ | # Or
+ $\s*\/\/.* # One line comments
+ )/x
+
+ file.puts File.open('public/javascripts/' + filename + '.js').read #.gsub(regexp, '')
end
end
View
139 public/javascripts/dialog.js
@@ -0,0 +1,139 @@
+(function($){
+ var callback;
+ $.fn.isValid.init();
+ function initDialog(dialogNode, fields, availableButtons){
+ var fieldsLength = fields.length, $form = dialogNode.find(":first-child");
+ dialogNode.dialog({
+ autoOpen: false,
+ width: 600,
+ close: function() {
+ if(callback.close){
+ callback.close();
+ }
+ for(i = 0; i < fieldsLength; i++){
+ fields[i].val('')
+ .removeAttr('checked')
+ .removeAttr('selected');
+ }
+ },
+ open: function(){
+ for(i = 0; i < fieldsLength; i++){
+ fields[i].change();
+ }
+ fields[0][0].setSelectionRange(0,0);
+ $form.isValid('reset');
+ }
+ });
+
+ return {
+ dialog: function(task, cb){
+ if(cb){
+ callback = cb;
+ }
+ dialogNode.dialog(task);
+ },
+ find: function(query){ return dialogNode.find(query); },
+ selectButtons: function(buttonNames){
+ var buttons={},i=buttonNames.length;
+ while(i--){
+ buttons[buttonNames[i]] = availableButtons[buttonNames[i]];
+ }
+ dialogNode.dialog('option','buttons',buttons);
+ },
+ val: function(query,value){
+ this.find(query).val(value);
+ }
+ };
+ }
+
+ var t10n = {
+ linkTitle: 'Link',
+ insertImageTitle: 'Image',
+ uri: 'Link',
+ uriPrompt: 'Enter or select link',
+ title: 'Title',
+ titlePrompt: 'Enter title',
+ imageUri: 'Image Source'
+ };
+
+ function createDialog(name, fields){
+ var $dialogNode, proxy, i, fieldName, jQueryFunctions, method, args,
+ fieldsLength = fields.length,
+ $fields = [];
+
+ $dialogNode = $('<div id=\"'+ name + '-dialog\" title=\"' +
+ t10n[name + "Title"] + '\"><form>');
+ var $form = $dialogNode.find(":first-child");
+
+ for(i=0; i < fieldsLength; i++){
+ fieldName = fields[i][0];
+ jQueryFunctions = fields[i][1];
+
+ $form.append(
+ '<label for=\"' + fieldName + '\">'+ t10n[fieldName] + '</label>'
+ );
+ $fields[i] = $('<input type=\"text\" class=\"' + fieldName + '\" name=\"' + fieldName + "\">")
+ .appendTo($form);
+
+ if(jQueryFunctions){
+ for(method in jQueryFunctions){
+ if(jQueryFunctions.hasOwnProperty(method)){
+ args = jQueryFunctions[method];
+ $fields[i][method](args);
+ }
+ }
+ }
+ $fields[i].enhanceTextfield({prompt: t10n[fieldName+"Prompt"]});
+ }
+
+ submit = function() {
+ var args = [],i;
+ for(i=0; i < fieldsLength; i++){
+ args[i] = $fields[i].submit().val();
+ }
+ if($form.isValid()){
+ callback.submit.apply(this,args);
+ $dialogNode.dialog("close");
+ }
+ };
+
+ proxy = initDialog($dialogNode, $fields, {
+ Create: submit,
+ Update: submit,
+ Remove: function(){
+ callback.remove();
+ $dialogNode.dialog("close");
+ },
+ Cancel: function() {
+ $dialogNode.dialog("close");
+ }
+ });
+
+ return function(buttonNames){
+ proxy.selectButtons(buttonNames);
+ return proxy;
+ };
+ }
+
+ ME.dialog = {
+ link: createDialog('link',[
+ ['title', {
+ required: true
+ }],
+ ['uri', {
+ combobox: {key: 'uri'},
+ required: true
+ }]
+ ]),
+ insertImage: createDialog('insertImage', [
+ ['imageUri', {
+ combobox: {key: 'imageUri'},
+ required: true
+ }],
+ ['title'],
+ ['uri', {
+ combobox: {key: 'uri'}
+ }]
+ ])
+ };
+})(jQuery);
View
99 public/javascripts/joined-min.js
@@ -1,39 +1,60 @@
-ME=function(k){function d(a){k.extend(this,a)}function o(a){this.name=a}function b(a,c){this.name=a;this.options=c||[]}function h(a){var c=k('<div class="toolbar"></div>'),f=this;this.textArea=a.textArea;this.htmlDiv=a.htmlDiv;this.editor=a;this.div=c;for(item in j)j.hasOwnProperty(item)&&c.append(j[item].getButton());c.mouseup(function(c){c=c.target;if(!/(select|option)/i.test(c.nodeName)){if(/span/i.test(c.nodeName))c=c.parentNode;var e=c.className;e=e.split(" ")[0];f.runAction(e,c);a.checkState()}return!1}).change(function(a){a=
-a.target;f.runAction(a.className,a);return!1});a.container.prepend(c)}function l(a){var c=this;this.loadedModes={};this.setDataType(a.attr("class"));if(this.dataType)this.textArea=a.bind("mouseup keyup",function(){c.checkState()}),this.htmlDiv=k('<div class="preview"></div>').bind("mouseup keyup",function(){c.is("wysiwyg")&&c.checkState()}),this.container=a.wrap('<div class="markupEditor"></div>').parent().append(c.htmlDiv),this.toolbar=new h(c)}function m(a,c,f){if(c)for(element in c)c.hasOwnProperty(element)&&
-element!=="default"&&(j[element]||(j[element]=new a(element)),j[element][f]=k.extend({name:element},c["default"],c[element]))}var g={},e={},j={};d.prototype={load:function(a){this.editor=a;this.htmlDiv=a.htmlDiv;this.textArea=a.textArea;console.log("loaded Mode "+this.name)},getStates:k.noop,activate:function(){this.htmlDiv.is(":empty")?this.updatePreview():this.updateTextArea();this.afterActivation()},updatePreview:function(){console.log("updating preview in Mode "+this.name);this.htmlDiv.html(this.toHTML())},
-updateTextArea:function(){console.log("updating TA in Mode "+this.name);this.textArea.val(this.toText())},afterActivation:function(){this.textArea.show();this.htmlDiv.attr("contentEditable",!1)},buildStateObject:function(a,c){for(var f,e=a.length,b={};e--;)switch(f=a[e],f.tag?f.tag:f.nodeName.toLowerCase()){case "a":c.a=f;b.link=!0;break;case "img":c.img=f;b.insertImage=!0;break;case "i":b.italic=!0;break;case "li":break;case "ol":b.insertOrderedList=!0;b.insertUnorderedList=!1;break;case "b":b.bold=
-!0;break;case "ul":b.insertOrderedList=!1;b.insertUnorderedList=!0;break;default:b.formatBlock=f.tag?f.tag:f.nodeName.toLowerCase()}return b}};o.prototype={getButton:function(){return'<a href="#" class="'+this.name+'" ><span>'+this.name+"</span></a>"}};b.prototype={getButton:function(){var a=k('<select class="'+this.name+'"></select>'),c=this.options.length,f;a.className=this.name;for(f=0;f<c;f+=1)k("<option/>").val(this.options[f][0]).text(this.options[f][1]).appendTo(a);return a}};h.prototype={getDivSelection:function(){this.htmlDiv.focus();
-theSelection=window.getSelection();theRange=theSelection.getRangeAt(0);return theRange.toString()},getTextAreaSelection:function(a){var c=this.textArea,f=c.val();c.focus();this.scrollPosition=c.scrollTop;this.selectionStart=c[0].selectionStart;this.selectionEnd=c[0].selectionEnd;if(a){a=Math.max(f.lastIndexOf(" ",this.selectionStart),f.lastIndexOf("\n",this.selectionStart));this.selectionStart=a!==-1?a+1:0;a=f.indexOf("\n",this.selectionEnd);c=a===-1?f.slice(this.selectionStart):f.slice(this.selectionStart,
-a);a=0;do a=c.indexOf(" ",a+1);while(a!==-1&&this.selectionEnd>this.selectionStart+a);if(a===-1)a=c.length;this.selectionEnd=this.selectionStart+a}return this.selection=f.slice(this.selectionStart,this.selectionEnd)},replaceTextAreaSelection:function(a){var c=this.textArea,f=this.selectionStart;c.val(c.val().slice(0,this.selectionStart)+a+c.val().slice(this.selectionEnd,c.val().length));c[0].setSelectionRange(f,f+a.length);c.focus()},extendRightSelection:function(a){var c;a=RegExp(a.source,"g");a.lastIndex=
-this.selectionEnd;if((c=a.exec(this.textArea.val()))&&a.lastIndex==this.selectionEnd+c[0].length)return this.selectionEnd+=c[0].length,c[0]},extendLeftSelection:function(a){var c=this.textArea.val().slice(0,this.selectionStart);a=RegExp(a.source+"$");if(a=a.exec(c))return this.selectionStart-=a[0].length,a[0]},replaceDivSelection:function(){},getSelection:function(a){return this.editor.is("wysiwyg")?this.getDivSelection(a):this.getTextAreaSelection(a)},replaceSelection:function(a){this.editor.is("wysiwyg")?
-this.replaceDivSelection(a):this.replaceTextAreaSelection(a)},runAction:function(a,c){j[a][this.editor.currentMode.id].clicked(this,c);a!="changeMode"&&!this.editor.is("wysiwyg")&&this.editor.currentMode.updatePreview()},setActive:function(a){a&&this.div.children().each(function(){var c=this.className.split(" ")[0];a[c]===!0?this.className=c+" on":a[c]?this.value=a[c]:this.className=c})}};l.prototype={changeMode:function(a){if(!a||a===this.currentMode.id)return!1;a=this.getMode(a);this.commit();a.activate();
-this.currentMode=a},getDataMode:function(){return this.getMode(this.dataType)},getMode:function(a){if(this.loadedModes[a])return this.loadedModes[a];else if(e[a])return this.loadedModes[a]=e[a](this),this.loadedModes[a];else console.log("Mode "+a+" is not defined")},setDataType:function(a){var c,f=a.split(/\s+/);for(a=0;a<f.length;a+=1)if(c=f[a],c!=="wysiwyg"&&e[c])this.dataType=c},commit:function(){this.is("wysiwyg")?this.getMode(this.dataType).updateTextArea():this.currentMode.updatePreview()},
-is:function(a){return this.currentMode.id===a},checkState:function(){this.toolbar.setActive(this.currentMode.getStates())}};k.fn.initMarkupEditor=function(a){this.each(function(c,f){f=k(f);if(f.is("textarea")){var b=f,e;e={};k.extend(e,g,a);e=new l(b,e);e.currentMode=e.getDataMode();if(b.hasClass("wysiwyg"))e.currentMode=e.getMode("wysiwyg");e.currentMode.activate();e.toolbar.setActive({changeMode:e.currentMode.id})}});return this};j.changeMode=new b("changeMode");j.formatBlock=new b("formatBlock",
-[["p","Paragraph"],["h1","Heading 1"],["h2","Heading 2"],["h3","Heading 3"]]);return{addMode:function(a,c){var f=c(),h=f.buttons,g=f.selects;f.id=a;m(o,h,a);m(b,g,a);j.changeMode.options.push([a,f.name]);j.changeMode[a]={clicked:function(a,c){a.editor.changeMode(c.value)}};e[a]=function(a){var c=new d(f);c.load(a);return c};return f}}}(jQuery);
-ME.dialog=function(k){function d(d,l){fields=d.find(":input");d.dialog({autoOpen:!1,width:600,close:function(){b.close&&b.close();fields.not(":button, :submit, :reset").val("").removeAttr("checked").removeAttr("selected")}});return{dialog:function(m,g){g&&(b=g);d.dialog(m)},find:function(b){return d.find(b)},selectButtons:function(b){for(var g={},e=b.length;e--;)g[b[e]]=l[b[e]];d.dialog("option","buttons",g)},val:function(b,d){this.find(b).val(d)}}}function o(h,l){return function(m){var g,e,j=k("#"+
-h+"-dialog");l=l(j);e=l.length;submit=function(){var a=[],c;for(c=0;c<e;c++)a[c]=l[c].val();b.submit.apply(this,a);j.dialog("close")};g=d(j,{Ok:submit,Update:submit,Remove:function(){b.remove();j.dialog("close")},Cancel:function(){j.dialog("close")}});this[h]=function(a){g.selectButtons(a);return g};return this[h](m)}}var b;return{link:o("link",function(b){return[b.find("input.title"),$uri=b.find("input.uri"),b.find("select.uri").change(function(){$uri.val(k(this).val())})]}),insertImage:o("insertImage",
-function(b){return[b.find("input.imageUri"),b.find("input.title"),b.find("input.uri")]})}}(jQuery);$(document).ready(function(){$("textarea.markup").initMarkupEditor({})});
-ME.addMode("textile",function(){function k(a,c){var f=a.editor.currentMode,b=f.getParagraphs(),e=b.length;for(i=0;i<e;i++)b[i]=c(b[i]);f.setParagraphs(b)}function d(a,c){k(a,function(a){var b,e,d=[];if(/^\w+\([^)]+\)\./.test(a)){b=jQuery.trim(a.slice(a.indexOf("(")+1,a.indexOf(")"))).split(/\s+/);e=b.length;for(i=0;i<e;i++)b[i]!="right"&&b[i]!="left"&&b[i]!="center"&&d.push(b[i]);d.push(c);return a.replace(/^(\w+)[^.]+.\s+/,"$1("+d.join(" ")+"). ")}else return/^\w+\./.test(a)?a.replace(/^(\w+)\.\s*/,
-"$1("+c+"). "):"p("+c+"). "+a})}function o(a,c){match=c.exec(b);if(c.lastIndex!==0){for(;c.lastIndex<h;)match=c.exec(b);a.selectionStart=c.lastIndex-match[0].length;a.selectionEnd=c.lastIndex;return match}}var b,h,l,m,g,e={},j=jQuery;regexpes={"*":[/^(\w+\. )?\s*\*/,/\*([\.]*)$/],_:[/^(\w+\. )?\s*_/,/_([\.]*)$/]};return{name:"Textile Mode",buttons:{"default":{clicked:function(a,c){var b=a.getSelection(!0).split("\n"),e=b.length,d,n;for(d=0;d<e;d++)n=b[d],/^\s*$/.test(n)||(/ on$/.test(c.className)?
-(n=(match=n.match(regexpes[this.delimiter][0]))?(match[1]||"")+n.slice(match[0].length):this.delimiter+a.extendLeftSelection(/[ .]+/)+n,(match=n.match(regexpes[this.delimiter][1]))?n=n.slice(0,-match[0].length)+(match[1]||""):n+=a.extendRightSelection(/ +/)+this.delimiter):n=j.trim(n.replace(/^(\w+\. )?\s*(.*)/,"$1"+this.delimiter+"$2"))+this.delimiter,b[d]=n);a.replaceSelection(b.join("\n"))}},bold:{delimiter:"*"},italic:{delimiter:"_"},alignLeft:{clicked:function(a){d(a,"left")}},alignRight:{clicked:function(a){d(a,
-"right")}},alignCenter:{clicked:function(a){d(a,"center")}},link:{clicked:function(a,c){var b,d,j,n;/ on$/.test(c.className)?(b=ME.dialog.link(["Update","Remove","Cancel"]),j=e.a.attributes.href,n=o(a,RegExp('"([^"]*)":'+j,"g")),d=n[1],b.val("input.uri",j)):(b=ME.dialog.link(["Ok","Cancel"]),d=a.getSelection());/^\s*$/.test(d)||b.val(".title",d);b.dialog("open",{submit:function(c,b){a.replaceSelection('"'+c+'":'+b)},remove:function(){a.replaceSelection(n[1])},close:function(){a.editor.currentMode.updatePreview();
-a.editor.checkState()}})}},insertImage:{clicked:function(a,c){var b,d,j;if(/ on$/.test(c.className)){b=ME.dialog.insertImage(["Update","Remove","Cancel"]);j=e.img.attributes.src;o(a,RegExp("!"+j+"(\\([^\\)]*\\))?!(:[^ \n]*)?","g"));if(e.a)d=e.a.attributes.href;b.val("input.uri",d);b.val("input.imageUri",j);b.val("input.title",e.img.attributes.title)}else b=ME.dialog.insertImage(["Ok","Cancel"]),a.getSelection();b.dialog("open",{submit:function(c,b,e){b&&!/^\s*$/.test(b)&&(c=c+"("+b+")");c="!"+c+"!";
-e&&!/^\s*$/.test(e)&&(c=c+":"+e);a.replaceSelection(c)},remove:function(){a.replaceSelection("")},close:function(){a.editor.currentMode.updatePreview();a.editor.checkState()}})}}},selects:{formatBlock:{clicked:function(a,c){k(a,function(a){return/^\w+(\([\w ]+\))?\./.test(a)?a.replace(/^\w+(\([\w ]+\))?\.\s+/,c.value+"$1. "):c.value+". "+a})}}},updatePreview:function(){this.htmlDiv.html(textileCompiler.compile(this.textArea.val()))},toText:function(a){a||(a=this.htmlDiv.html());a=a.replace(/\s*<(h[1-4])>((.|[\r\n])*?)<\/\1>\s*/gi,
-"\n\n$1. $2\n\n");a=a.replace(/\s*<(p)>((.|[\r\n])*?)<\/\1>\s*/gi,"\n\n$2\n\n");a=a.replace(/\s*<(p|h[1-4]).*class=\"([^\"]+)\">((.|[\r\n])*?)<\/\1>\s*/gi,"\n\n$1($2). $3\n\n");a=a.replace(/<br ?\/?>\s*/gi,"\n");a=a.replace(/<(?:b|strong)>((.|[\r\n])*?)<\/(?:b|strong)>/gi,"*$1*");a=a.replace(/<(?:i|em)>((.|[\r\n])*?)<\/(?:i|em)>/gi,"_$1_");a=a.replace(/<(?:strike|del)>((.|[\r\n])*?)<\/(?:strike|del)>/gi,"-$1-");a=a.replace(/<(?:u|ins)>((.|[\r\n])*?)<\/(?:u|ins)>/gi,"+$1+");a=a.replace(/<img[^>]*>/gi,
-function(a){var b=j(a);a=b.attr("src");(b=b.attr("title"))&&!/^\s*$/.test(b)&&(a=a+"("+b+")");return"!"+a+"!"});a=a.replace(/<a href="([^\"]*)">((.|[\r\n])*?)<\/a>/gi,function(a,b,e){return/^\s*![^!]+!\s*$/.test(e)?j.trim(e)+":"+b:'"'+e+'":'+b});a=a.replace(/\s*<code[^>]*>((.|[\r\n])*?)<\/code>\s*/gi," @$1@ ");a=a.replace(/(\r\n|\n){3,}/g,"\n\n");return a=a.replace(/^[\r\n]+|[\r\n]+$/g,"")},getStates:function(){var a=this.getExtendedSelection();trace=textileCompiler.trace(a,h-l,selectionEnd-l);return this.buildStateObject(trace,
-e={})},getExtendedSelection:function(){var a;a=0;h=this.textArea[0].selectionStart;selectionEnd=this.textArea[0].selectionEnd;b=this.textArea.val();l=0;for(m=-1;(a=b.indexOf("\n\n",a)+2)!==1;)if(h>a)l=a;else if(selectionEnd<a){m=a-2;break}a=m===-1?b.slice(l):b.slice(l,m);g=a.length;return a},getParagraphs:function(){return this.getExtendedSelection().split(/\n\n+/)},setParagraphs:function(a){a=a.join("\n\n");m===-1?this.textArea.val(b.slice(0,l)+a):this.textArea.val(b.slice(0,l)+a+b.slice(m));this.moveCaret(a.length-
-g)},moveCaret:function(a){console.log("Moving caret: "+a);Math.abs(h-l)>Math.abs(a)?h+=a:h=l;this.textArea.focus();this.textArea[0].setSelectionRange(h,h)}}});
-textileCompiler=function(){function k(b){b=b.exec(o);var h,l;if(b)return h=/^\s*/.exec(b[0])[0].length,l=b[0].length,h&&d.advancePointer(h),l-h&&d.advancePointer(l-h),o=o.slice(l),b||!0}var d=function(){function b(a,b){var c=["class"],e;definableAttributes[a]&&(c=c.concat(definableAttributes[a]));for(e=c.length;e--;)b(c[e])}function d(a){var c=!0,f=j==-1,g,h,l;if(f)g=e[0];else{h=j+1;for(l=e.length;h<l;h++)if(e[h].tag===a.tag){g=e[h];e[h]=e[j+1];e[j+1]=g;break}}g&&g.attributes&&b(g.tag,function(b){g.attributes[b]!==
-a.attributes[b]&&(c=!1,delete g.attributes[b])});return g&&(f||c)}function l(a){var b="";for(attr in a.attributes)a.attributes.hasOwnProperty(attr)&&(b+=" "+attr+'="'+a.attributes[attr]+'"');return"<"+a.tag+b+">"}function k(a){var b;for(b=g.length;b--;)if(g[b].tag===a)return b}var g,e,j,a,c,f,o,r,n,p,q=!1;definableAttributes={img:["title","src"],a:["href"]};return{init:function(){g=[{content:""}]},initTrace:function(a,b){e=[];n=void 0;f=0;o=a;r=b},finalizeTrace:function(){n&&(n=p=!1)},advancePointer:function(b){f+=
-b;if(n===void 0&&f>o){b=g.length;var c;a=n=!0;for(c=1;c<b;c++)e[c-1]={tag:g[c].tag,attributes:g[c].attributes};j=e.length-1}p?n=p=!1:n&&f>r&&(p=!0)},pushTag:function(b,c){var f={tag:b,attributes:c||{},content:""};g.push(f);n&&(a?(e[j+1]={tag:f.tag,attributes:f.attributes},j+=1):e[j+1]&&(d(f)?j+=1:q=!0))},closeTag:function(b){var d;b?(d=k(b),b=g.splice(d,1)[0]):b=g.pop();n&&(a=!1,q&&(e=e.slice(0,j+1),q=!1),e[j].tag===b.tag&&(j-=1),c=!0);this.pushString(l(b)+b.content,g[d-1]);this.pushString("</"+b.tag+
-">");c=!1},popLineEnd:function(){for(var a=!1,b={b:"*",i:"_"},c;"a,i,b,li".indexOf(g[g.length-1].tag)!=-1;)"li"===g[g.length-1].tag?(this.closeTag(),a=!0):(c=g.pop(),this.pushString(b[c.tag]+c.content));return a},popParagraphEnd:function(){for(;g.length>1;)this.closeTag()},pushString:function(b,d){d||(d=g[g.length-1]);d.content+=b;/^([ ]+|<br\/>)?$/.test(b)||(a&&(a=!1),n&&!c&&e[j+1]&&(e=e.slice(0,j+1)))},isOpen:function(a){return typeof k(a)==="number"},getTrace:function(){return e},toHtml:function(){return g[0].content}}}(),
-o;return{compile:function(b){d.init();for(o=b;!/^\s*$/.test(o);){if(b=k(/^\s*(h\d|p|bq)(\(([^#\)]*)(#[^\)]+)?\))?\. /)){var h={};b[3]&&(h["class"]=b[3]);if(b[4])h.id=b[4];d.pushTag(b[1],h)}else d.pushTag("p");for(;!k(/^\n/)&&!/^\s*$/.test(o);){if(k(/^ *\* /))d.isOpen("ul")||d.pushTag("ul"),d.pushTag("li");else if(k(/^ *# /))d.isOpen("ol")||d.pushTag("ol"),d.pushTag("li");else for(;d.isOpen("ul")||d.isOpen("ol");)d.closeTag();match=k(/^ */);d.pushString(match[0]);for(b=b=void 0;;)if(k(/^_(?=[^ \n]+)/))d.isOpen("i")?
-d.pushString("_"):d.pushTag("i");else if(k(/^\*(?=[^ \n]+)/))d.isOpen("b")?d.pushString("*"):d.pushTag("b");else if(b=k(/^([^ \n]+)_( +|(?=\n|$))/))d.isOpen("i")?(d.pushString(b[1]),d.closeTag("i"),d.pushString(b[2])):d.pushString(b[1]+"_"+b[2]);else if(b=k(/^([^ \n]+)\*( +|(?=\n|$))/))d.isOpen("b")?(d.pushString(b[1]),d.closeTag("b"),d.pushString(b[2])):d.pushString(b[1]+"*"+b[2]);else if(b=k(/^( *)"([^"]*)":([^ \n]+)/))d.pushString(b[1]),d.pushTag("a",{href:b[3]}),d.pushString(b[2]),d.closeTag();
-else if(b=k(/^( *)!([^!\(]+)(\(([^\)]*)\))?!(:([^ ]+))?/)){d.pushString(b[1]);b[6]&&d.pushTag("a",{href:b[6]});h={src:b[2]};if(b[4])h.title=b[4];d.pushTag("img",h);d.closeTag();b[6]&&d.closeTag()}else if(b=k(/^([^ \n]+)/))d.pushString(b[1]);else if(b=k(/^( +)/))d.pushString(b[1]);else{k(/^\n/);b=d.popLineEnd();!b&&!/^\s*(\n|$|[\*#] )/.test(o)&&d.pushString("<br/>");break}}d.popParagraphEnd()}return d.toHtml()},trace:function(b,h,l){d.initTrace(h,l);this.compile(b);d.finalizeTrace();return d.getTrace()}}}();
-ME.addMode("wysiwyg",function(){function k(b){return b.parent().is(".preview")?b:b.parentsUntil(".preview").last()}function d(){var b,d,a=-1;b=k(jQuery(m.getRangeAt(0).startContainer));d=k(jQuery(m.getRangeAt(0).endContainer))[0];return b[0]!==d?b.nextAll().filter(function(b){this==d&&(a=b);if(a===-1||a===b)return!0}).add(b):b}function o(b){d().removeClass("left").removeClass("right").removeClass("center").addClass(b)}function b(b){var d=b[0];b.length>1?(b=b[b.length-1],g.setStart(d,0),g.setEnd(b,
-b.childNodes.length)):g.selectNodeContents(d);m.removeAllRanges();m.addRange(g)}var h={},l=jQuery,m=getSelection(),g=document.createRange();return{name:"Preview Mode",buttons:{"default":{clicked:function(b,d){var a=m.getRangeAt(0),c;/ on$/.test(d.className)?document.execCommand(this.name,!1,null):(c=document.createElement(this.tag),a.surroundContents(c),m.removeAllRanges(),m.addRange(a))}},bold:{tag:"b"},italic:{tag:"i"},alignLeft:{clicked:function(){o("left")}},alignRight:{clicked:function(){o("right")}},
-alignCenter:{clicked:function(){o("center")}},link:{clicked:function(b,d){var a,c,f,g=m.getRangeAt(0),k={remove:function(){var a=c.text();c.replaceWith(a)},close:function(){b.htmlDiv.focus();b.editor.checkState()}};/ on$/.test(d.className)?(c=l(h.a),a=ME.dialog.link(["Update","Remove","Cancel"]),k.submit=function(a,b){c.attr("href",b).text(a);g.selectNodeContents(c[0]);m.removeAllRanges();m.addRange(g)},f=c.text(),a.val("input.uri",c.attr("href"))):(a=ME.dialog.link(["Ok","Cancel"]),k.submit=function(a,
-b){var c=l('<a href="'+b+'">'+a+"</a>")[0];g.deleteContents();g.insertNode(c);g.selectNodeContents(c);m.removeAllRanges();m.addRange(g)},f=g.toString());/^\s*$/.test(f)||a.val(".title",f);a.dialog("open",k)}},insertImage:{clicked:function(b,d){var a,c,f=window.getSelection(),g=f.getRangeAt(0);/ on$/.test(d.className)?(a=ME.dialog.insertImage(["Update","Remove","Cancel"]),h.a&&(c=l(h.a),a.val("input.uri",c.attr("href")),g.selectNode(h.a)),imageNode=l(h.img),a.val("input.imageUri",imageNode.attr("src")),
-a.val("input.title",imageNode.attr("title"))):a=ME.dialog.insertImage(["Ok","Cancel"]);a.dialog("open",{submit:function(a,b,c){var d=a=l('<img src="'+a+'"/>');/^\s*$/.test(b)||a.attr({alt:b,title:b});/^\s*$/.test(c)||(d=l('<a href="'+c+'"/>').append(a));g.deleteContents();g.insertNode(d[0]);g.selectNode(a[0]);f.removeAllRanges();f.addRange(g)},remove:function(){imageNode.remove()},close:function(){b.htmlDiv.focus();b.editor.checkState()}})}}},selects:{formatBlock:{clicked:function(e,g){var a,c=[];
-d().replaceWith(function(){a=l("<"+g.value+"></"+g.value+">").addClass(this.className).append(this.childNodes);c.push(a[0]);return a});b(c)}}},afterActivation:function(){this.textArea.hide();this.htmlDiv.attr("contentEditable",!0);jQuery.browser.mozilla&&document.execCommand("styleWithCSS",null,!1)},getStates:function(){if(l(document.activeElement).is(".preview")){var b=jQuery(m.getRangeAt(0).startContainer),d=m.getRangeAt(0).cloneContents().firstChild;d&&d.nodeName!="#text"&&b[0].nodeName!="#text"&&
-(b=b.find(d.nodeName.toLowerCase()));return this.buildStateObject(b.parentsUntil(".preview").add(b),h={})}},toText:function(){return this.editor.getDataMode().toText()},toHTML:function(){return this.textArea.val()}}});
+(function(f){var b,m;m={check:function(){var e=!0;this.find(b.errorElement+"."+b.errorClass).remove();this.find(":input."+b.inputErrorClass).removeClass(b.inputErrorClass);this.find(":input.required").each(function(){var r=f(this),m=f.trim(r.val()),s=r.siblings("label").text().replace(b.removeLabelChar,""),q="";if(m==="")q=hasLabelPlaceholder?q=b.errorText.replace("{label}",s):q=b.errorText,e=!1;else if(r.hasClass("email")&&!/^([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/.test(m))q=
+hasLabelPlaceholder?q=b.emailErrorText.replace("{label}",s):q=b.emailErrorText,e=!1;q!==""&&r.parent().addClass(b.errorClass)});return e},reset:function(){return this.find(":input.required").each(function(){f(this).parent().removeClass(b.errorClass)})}};f.fn.isValid=function(b){m[b]||(b="check");return m[b].apply(this,Array.prototype.slice.call(arguments,1))};f.fn.isValid.init=function(e){b=f.extend({},f.fn.isValid.defaults,e);hasLabelPlaceholder=b.errorText.indexOf("{label}")>-1};f.fn.isValid.defaults=
+{errorClass:"error",errorText:"{label} is a required field.",emailErrorText:"Please enter a valid {label}",errorElement:"strong",removeLabelChar:"*"}})(jQuery);
+var ME=function(f){function b(a){f.extend(this,a);this.prototype=b.prototype}function m(a,c){this.name=a;if(c)this.clicked=c,t.push(a)}function e(a,c,d){m.apply(this,[a,d]);this.options=c||[]}function r(){var a,c,d;if(!p){a=0;for(c=o.length;a<c;a++)(d=l[o[a]])&&(p+=d.getButton())}return p}function u(a){var c=f('<div class="toolbar"></div>'),d=this;this.textArea=a.textArea;this.htmlDiv=a.htmlDiv;this.editor=a;this.div=c;c.html(r());c.mouseup(function(c){c=c.target;if(!/(select|option)/i.test(c.nodeName)){if(/span/i.test(c.nodeName))c=
+c.parentNode;if(c.disabled)return a.is("wysiwyg")?a.htmlDiv.focus():a.textArea.focus(),!1;var k=c.className;k=k.split(" ")[0];d.runAction(k,c);a.checkState()}}).change(function(a){a=a.target;d.runAction(a.className,a);return!1}).click(function(){return!1});a.container.prepend(c)}function s(a,c){var d=this,h=0;this.loadedModes={};this.setDataType(a.attr("class"));this.settings=c;if(this.dataType)this.textArea=a.bind("mouseup keyup",function(){d.checkState();clearTimeout(h);h=setTimeout(function(){d.currentMode.updatePreview()},
+1E3)}).keydown(function(a){return d.currentMode.pressed(a.keyCode)}).keyup(function(a){return d.currentMode.released(a.keyCode)}),this.htmlDiv=f('<div class="preview"></div>').bind("mouseup keyup",function(){d.is("wysiwyg")&&d.checkState()}).keydown(function(a){return d.currentMode.pressed(a.keyCode)}),this.container=a.wrap('<div class="markupEditor"></div>').parent().append(d.htmlDiv),a.wrap('<div class="textarea">'),this.toolbar=new u(this)}function q(a,c){var d;d={};f.extend(d,g,c);d=new s(a,d);
+d.currentMode=d.getDataMode();if(a.hasClass("wysiwyg"))d.currentMode.activate(),d.currentMode=d.getMode("wysiwyg");d.currentMode.activate();d.toolbar.setActive({changeMode:d.currentMode.id});return d}var g={},j={},l={},p="",o=["bold","italic","alignLeft","alignCenter","alignRight","unorderedList","orderedList","link","insertImage","save","changeMode","formatBlock"],t=[];b.prototype={load:function(a){this.editor=a;this.htmlDiv=a.htmlDiv;this.textArea=a.textArea;console.log("loaded Mode "+this.name)},
+getStates:f.noop,pressed:function(a){if(a===16)this.holdShift=!0},released:function(a){if(a===16)this.holdShift=!1},activate:function(){this.htmlDiv.is(":empty")?this.updatePreview():this.updateTextArea();this.editor.toolbar.loadModeToolbar();this.afterActivation()},updatePreview:function(){console.log("updating preview in Mode "+this.name);this.htmlDiv.html(this.toHTML()||"<p>&nbsp;</p>")},updateTextArea:function(){console.log("updating TA in Mode "+this.name);this.textArea.val(this.toText())},afterActivation:function(){this.textArea.parent().show().find(":first-child").focus()[0].setSelectionRange(0,
+0);this.htmlDiv.attr("contentEditable",!1)},buildStateObject:function(a,c){for(var d,h=a.length,k={};h--;)switch(d=a[h],d.tag?d.tag:d.nodeName.toLowerCase()){case "a":c.a=d;k.link=!0;break;case "img":c.img=d;k.insertImage=!0;break;case "i":k.italic=!0;break;case "b":k.bold=!0;break;case "ol":k.orderedList=!0;k.unorderedList=!1;k.formatBlock="disable";k.alignLeft="disable";k.alignRight="disable";k.alignCenter="disable";c.list=d;break;case "ul":k.orderedList=!1;k.unorderedList=!0;k.formatBlock="disable";
+k.alignLeft="disable";k.alignRight="disable";k.alignCenter="disable";c.list=d;break;case "li":break;default:k.formatBlock=d.tag?d.tag:d.nodeName.toLowerCase(),c.block=d}return k},getSelection:function(a){var c=this.textArea,d=c.val(),h;c.focus();this.scrollPosition=c.scrollTop;this.selectionStart=c[0].selectionStart;this.selectionEnd=c[0].selectionEnd;d[this.selectionEnd-1]==="\n"&&(this.selectionEnd-=1);if(a){c=Math.max(d.lastIndexOf(a,this.selectionStart),d.lastIndexOf("\n",this.selectionStart));
+this.selectionStart=c!==-1?c+1:0;c=d.indexOf("\n",this.selectionEnd);h=c===-1?d.slice(this.selectionStart):d.slice(this.selectionStart,c);c=0;do c=h.indexOf(a,c+1);while(c!==-1&&this.selectionEnd>this.selectionStart+c);if(c===-1)c=h.length;this.selectionEnd=this.selectionStart+c}return this.selection=d.slice(this.selectionStart,this.selectionEnd)},replaceSelection:function(a,c){var d=this.textArea,h=this.selectionStart,k=this.selectionStart+a.length;d.val(d.val().slice(0,this.selectionStart)+a+d.val().slice(this.selectionEnd,
+d.val().length));c===!0?k=h:c===!1&&(h=k);d[0].setSelectionRange(h,k);d.focus()},extendRightSelection:function(a){var c;a=RegExp(a.source,"g");a.lastIndex=this.selectionEnd;if((c=a.exec(this.textArea.val()))&&a.lastIndex==this.selectionEnd+c[0].length)return this.selectionEnd+=c[0].length,c[0]},extendLeftSelection:function(a){var c=this.textArea.val().slice(0,this.selectionStart);a=RegExp(a.source+"$");if(a=a.exec(c))return this.selectionStart-=a[0].length,a[0]}};m.prototype={getButton:function(){return'<a href="#" class="'+
+this.name+'" ><span>'+this.name+"</span></a>"}};e.prototype={getButton:function(){var a='<select class="'+this.name+'">',c=this.options.length,d;a.className=this.name;for(d=0;d<c;d+=1)a+='<option value="'+this.options[d][0]+'">'+this.options[d][1]+"</option>";return a+"</select>"}};u.prototype={loadModeToolbar:function(){var a=this.editor.currentMode.supportedItems,c=this.editor.settings.save,d=this.visibleItems,h=[];this.div.children().each(function(){var k=this.className;a.indexOf(k)!=-1&&(k!==
+"save"||c)?((!d||d.indexOf(k)==-1)&&f(this).show(),h.push(k)):(!d||d.indexOf(k)!=-1)&&f(this).hide()});this.visibleItems=h},runAction:function(a,c){var d=l[a],h=this.editor,k=h.currentMode;(d[k.id]||d).clicked(h,k,c);a!="changeMode"&&!h.is("wysiwyg")&&k.updatePreview()},setActive:function(a){a&&this.div.children().each(function(){var c=this.className.split(" ")[0];if(a[c]=="disable")this.disabled=!0,this.className=c+" disabled";else if(this.disabled=!1,this.className=c,a[c]===!0)this.className=c+
+" on";else if(a[c])this.value=a[c]})}};s.prototype={changeMode:function(a){if(!a||a===this.currentMode.id)return!1;a=this.getMode(a);this.commit();this.currentMode=a;a.activate()},getDataMode:function(){return this.getMode(this.dataType)},getMode:function(a){if(this.loadedModes[a])return this.loadedModes[a];else if(j[a])return this.loadedModes[a]=j[a](this),this.loadedModes[a];else console.log("Mode "+a+" is not defined")},setDataType:function(a){var c,d=a.split(/\s+/);for(a=0;a<d.length;a+=1)if(c=
+d[a],c!=="wysiwyg"&&j[c])this.dataType=c},commit:function(){this.is("wysiwyg")?this.getMode(this.dataType).updateTextArea():this.currentMode.updatePreview()},is:function(a){return this.currentMode.id===a},checkState:function(){this.toolbar.setActive(this.currentMode.getStates())}};var n={addMode:function(a,c){var d=c(),h=d.items,k,n=t.slice();d.id=a;if(h)for(item in h)h.hasOwnProperty(item)&&item!=="default"&&(n.push(item),l[item]||(k=h[item].options?e:m,l[item]=new k(item)),l[item][a]=f.extend({name:item},
+h["default"],h[item]));l.changeMode.options.push([a,d.name]);d.supportedItems=n;j[a]=function(a){var c=new b(d);c.load(a);return c};return d},options:{},setOptions:function(a){this.options=a}};f.fn.initMarkupEditor=function(a){n.settings=a;this.each(function(c,d){var h=f(d);if(h.is("textarea"))q(h,a);else{h.css("min-height",h.height());var k;k=f('<textarea class="'+h[0].className+'">').prependTo(h);k=q(k,a);k.htmlDiv.append(k.container.nextAll());k.currentMode.updateTextArea();k.changeMode("wysiwyg");
+k.toolbar.setActive({changeMode:"wysiwyg"});h.append(k.container)}});return this};l.changeMode=new e("changeMode",[],function(a,c,d){a.changeMode(d.value)});l.formatBlock=new e("formatBlock",[["p","Paragraph"],["h1","Heading 1"],["h2","Heading 2"],["h3","Heading 3"]]);l.save=new m("save",function(a){a.commit();a.settings.save(a)});return n}(jQuery);
+(function(f){neutralKeys="9.16.17.18.20.27.33.34.35.36.37.38.39.40.45.91.93.93";f.util={isNeutralKey:function(b){return neutralKeys.indexOf(""+b)!=-1},isRemovalKey:function(b){return b==46||b==8}}})(ME);
+(function(f,b){var m=b.util.isNeutralKey,e=b.util.isRemovalKey;f.fn.enhanceTextfield=function(b){b=b||{};return this.each(function(){function u(){g.css("color","grey").data("hasPrompt",!0).val(b.prompt)}function s(){g.css("color",p).data("hasPrompt",!1).val("")}function q(){g.val()&&g.val()!==b.prompt?(g.css("color",p),j.show()):(u(),j.hide(),l=!1)}var g=f(this),j,l,p=g.css("color");g.is("input")&&(j=f("<span>x</span>").click(function(){s();g.focus();j.hide();l=!1}),$p=g.wrap('<span class="clearButton">').focus(function(){!l&&
+g.val()===b.prompt&&s();g.parent().addClass("focus")}).focusout(function(){!l&&!g.val()&&u();g.parent().removeClass("focus")}).keydown(function(j){!m(j.which)&&(!l||e(j.which))&&g.val()===b.prompt&&s()}).keyup(function(b){!l&&!m(b.which)&&!e(b.which)&&(l=!0,q())}).bind("blur change",function(){q()}).submit(function(){g.val()===b.prompt&&s()}).parent().append(j),g.hasClass("ui-corner-left")?$p.addClass("ui-corner-left"):$p.addClass("ui-corner-all"))})}})(jQuery,ME);
+(function(f,b){f.fn.required=function(b){return this.each(function(){b?f(this).addClass("required"):f(this).removeClass("required")})};f.widget("ui.combobox",{_create:function(){var m=this.element,e=this.options.key,r=f.ui.autocomplete.escapeRegex;m.autocomplete({delay:0,minLength:0,source:function(f,m){var q=RegExp(r(f.term),"i"),g=b.options[e]||[],j=g.length,l=[],p,o;if(f.term)for(o=0;o<j;o++)p=g[o],q.test(p)&&l.push({label:p.replace(RegExp("(?![^&;]+;)(?!<[^<>]*)("+r(f.term)+")(?![^<>]*>)(?![^&;]+;)",
+"gi"),"<strong>$1</strong>"),value:p});else l=g;m(l)},focus:function(b,e){m.val(e.item.value).change()}}).addClass("ui-corner-left");m.data("autocomplete")._renderItem=function(b,e){return f("<li></li>").data("item.autocomplete",e).append("<a>"+e.label+"</a>").appendTo(b)};this.button=f("<button type='button'>&nbsp;</button>").attr("tabIndex",-1).attr("title","Show All Items").insertAfter(m).button({icons:{primary:"ui-icon-triangle-1-s"},text:!1}).removeClass("ui-corner-all").addClass("ui-corner-right ui-button-icon").click(function(){m.autocomplete("widget").is(":visible")?
+m.autocomplete("close"):(m.data("hasPrompt")&&m.val(""),m.autocomplete("search",""),m.focus())})},destroy:function(){this.button.remove();f.Widget.prototype.destroy.call(this)}})})(jQuery,ME);
+(function(f){function b(b,f,m){var g=f.length,j=b.find(":first-child");b.dialog({autoOpen:!1,width:600,close:function(){e.close&&e.close();for(i=0;i<g;i++)f[i].val("").removeAttr("checked").removeAttr("selected")},open:function(){for(i=0;i<g;i++)f[i].change();f[0][0].setSelectionRange(0,0);j.isValid("reset")}});return{dialog:function(f,g){g&&(e=g);b.dialog(f)},find:function(e){return b.find(e)},selectButtons:function(e){for(var f={},g=e.length;g--;)f[e[g]]=m[e[g]];b.dialog("option","buttons",f)},
+val:function(b,e){this.find(b).val(e)}}}function m(m,s){var q,g,j,l,p,o,t,n=s.length,a=[];q=f('<div id="'+m+'-dialog" title="'+r[m+"Title"]+'"><form>');var c=q.find(":first-child");for(j=0;j<n;j++){l=s[j][0];p=s[j][1];c.append('<label for="'+l+'">'+r[l]+"</label>");a[j]=f('<input type="text" class="'+l+'" name="'+l+'">').appendTo(c);if(p)for(o in p)p.hasOwnProperty(o)&&(t=p[o],a[j][o](t));a[j].enhanceTextfield({prompt:r[l+"Prompt"]})}submit=function(){var d=[],h;for(h=0;h<n;h++)d[h]=a[h].submit().val();
+c.isValid()&&(e.submit.apply(this,d),q.dialog("close"))};g=b(q,a,{Create:submit,Update:submit,Remove:function(){e.remove();q.dialog("close")},Cancel:function(){q.dialog("close")}});return function(a){g.selectButtons(a);return g}}var e;f.fn.isValid.init();var r={linkTitle:"Link",insertImageTitle:"Image",uri:"Link",uriPrompt:"Enter or select link",title:"Title",titlePrompt:"Enter title",imageUri:"Image Source"};ME.dialog={link:m("link",[["title",{required:!0}],["uri",{combobox:{key:"uri"},required:!0}]]),
+insertImage:m("insertImage",[["imageUri",{combobox:{key:"imageUri"},required:!0}],["title"],["uri",{combobox:{key:"uri"}}]])}})(jQuery);
+ME.addMode("textile",function(){function f(a,c){var d=a.getParagraphs(),h=d.length;for(i=0;i<h;i++)d[i]=c(d[i]);a.setParagraphs(d)}function b(a,c){var d=a.length,h,b,e;for(h=0;h<d;h++)b=a[h],/^\s*$/.test(b)||(b=b.match(/^((?:\w+\. )?(?: *[\*#] )?)\s*(.*)/),e=b[1],b=b[2],c(h,e,b))}function m(a,c,d){var h=a.getSelection(c).split("\n");b(h,function(a,c,b){h[a]=d(c,b)});a.replaceSelection(h.join("\n"))}function e(a){var c=a.getSelection("\n").split("\n").slice(0,1);a.selectionEnd=a.selectionStart+c[0].length;
+b(c,function(d,b,k){a.selectionStart+=b.length;c[d]=k});return c[0]}function r(a,c){f(a,function(a){var b,k,e=[];if(/^\w+\([^)]+\)\./.test(a)){b=jQuery.trim(a.slice(a.indexOf("(")+1,a.indexOf(")"))).split(/\s+/);k=b.length;for(i=0;i<k;i++)b[i]!="right"&&b[i]!="left"&&b[i]!="center"&&e.push(b[i]);e.push(c);return a.replace(/^(\w+)[^.]+.\s+/,"$1("+e.join(" ")+"). ")}else return/^\w+\./.test(a)?a.replace(/^(\w+)\.\s*/,"$1("+c+"). "):"p("+c+"). "+a})}function u(a,c){var d=c.exec(q);if(c.lastIndex!==0){for(;c.lastIndex<
+g;)d=c.exec(q);a.selectionStart=c.lastIndex-d[0].length;a.selectionEnd=c.lastIndex;return d}}function s(a,c,d){m(a,"\n",function(a,b){/ on$/.test(c.className)||(b=d+" "+b);return b})}var q,g,j,l,p,o={},t=jQuery,n={ul:"*",ol:"#"};regexpes={"*":[/^(\w+\. )?\s*\*/,/\*([\.]*)$/],_:[/^(\w+\. )?\s*_/,/_([\.]*)$/]};return{name:"Textile Mode",items:{"default":{clicked:function(a,c,d){var b,e=this;m(c," ",function(a,n){/ on$/.test(d.className)?(n=(b=n.match(regexpes[e.delimiter][0]))?(b[1]||"")+n.slice(b[0].length):
+e.delimiter+c.extendLeftSelection(/[ .]+/)+n,(b=n.match(regexpes[e.delimiter][1]))?n=n.slice(0,-b[0].length)+(b[1]||""):n+=c.extendRightSelection(/ +/)+e.delimiter):n=t.trim(e.delimiter+n)+e.delimiter;return a+n})}},bold:{delimiter:"*"},italic:{delimiter:"_"},alignLeft:{clicked:function(a,c){r(c,"left")}},alignRight:{clicked:function(a,c){r(c,"right")}},alignCenter:{clicked:function(a,c){r(c,"center")}},unorderedList:{clicked:function(a,c,d){s(c,d,"*")}},orderedList:{clicked:function(a,c,d){s(c,d,
+"#")}},link:{clicked:function(a,c,d){var b,n,f;/ on$/.test(d.className)?(d=ME.dialog.link(["Update","Remove","Cancel"]),n=o.a.attributes.href,f=u(c,RegExp('"([^"]*)":'+n,"g")),b=f[1],d.val("input.uri",n)):(d=ME.dialog.link(["Create","Cancel"]),b=e(c));/^\s*$/.test(b)||d.val(".title",b);d.dialog("open",{submit:function(a,d){c.replaceSelection('"'+a+'":'+d)},remove:function(){c.replaceSelection(f[1])},close:function(){c.updatePreview();a.checkState()}})}},insertImage:{clicked:function(a,c,d){var b,
+n;if(/ on$/.test(d.className)){d=ME.dialog.insertImage(["Update","Remove","Cancel"]);n=o.img.attributes.src;u(c,RegExp("!"+n+"(\\([^\\)]*\\))?!(:[^ \n]*)?","g"));if(o.a)b=o.a.attributes.href;d.val("input.uri",b);d.val("input.imageUri",n);d.val("input.title",o.img.attributes.title)}else d=ME.dialog.insertImage(["Create","Cancel"]),e(c);d.dialog("open",{submit:function(a,d,b){d&&!/^\s*$/.test(d)&&(a=a+"("+d+")");a="!"+a+"!";b&&!/^\s*$/.test(b)&&(a=a+":"+b);c.replaceSelection(a)},remove:function(){c.replaceSelection("")},
+close:function(){c.updatePreview();a.checkState()}})}},formatBlock:{clicked:function(a,c,d){f(c,function(a){return/^\w+(\([\w ]+\))?\./.test(a)?a.replace(/^\w+(\([\w ]+\))?\.\s+/,d.value+"$1. "):/^[\*#] /.test(a)?a:d.value+". "+a})}}},updatePreview:function(){this.htmlDiv.html(textileCompiler.compile(this.textArea.val()))},toText:function(a){a||(a=this.htmlDiv.html());a=a.replace(/\s*<(ul|ol)>((.|[\r\n])*?)<\/\1>\s*/gi,function(a,d,b){return b.replace(/\s*<li>((.|[\r\n])*?)<\/li>\s*/gi,(d=="ul"?"*":
+"#")+" $1\n")+"\n"});a=a.replace(/ *<(p|h[1-4])([^>]*)>((.|[\r\n])*?)<\/\1>\s*/gi,function(a,d,b,n){a="";(b=b.match(/class=\"([^"]*)/))?a=d+"("+b[1]+"). ":d!="p"&&(a=d+". ");return a+n.replace(/<br ?\/?>\s*/gi,"\n")+"\n\n"});a=a.replace(/<(?:b|strong)>((.|[\r\n])*?)<\/(?:b|strong)>/gi,"*$1*");a=a.replace(/<(?:i|em)>((.|[\r\n])*?)<\/(?:i|em)>/gi,"_$1_");a=a.replace(/<(?:strike|del)>((.|[\r\n])*?)<\/(?:strike|del)>/gi,"-$1-");a=a.replace(/<(?:u|ins)>((.|[\r\n])*?)<\/(?:u|ins)>/gi,"+$1+");a=a.replace(/<img[^>]*>/gi,
+function(a){var d=t(a);a=d.attr("src");(d=d.attr("title"))&&!/^\s*$/.test(d)&&(a=a+"("+d+")");return"!"+a+"!"});a=a.replace(/<a href="([^\"]*)">((.|[\r\n])*?)<\/a>/gi,function(a,d,b){return/^\s*![^!]+!\s*$/.test(b)?t.trim(b)+":"+d:'"'+b+'":'+d});a=a.replace(/\s*<code[^>]*>((.|[\r\n])*?)<\/code>\s*/gi," @$1@ ");a=a.replace(/(\r\n|\n){3,}/g,"\n\n");a=a.replace(/&nbsp;/g," ");return a=a.replace(/^[\r\n]+|[\r\n]+$/g,"")},getStates:function(){var a=this.getExtendedSelection();trace=textileCompiler.trace(a,
+g-j,selectionEnd-j);return this.buildStateObject(trace,o={})},getExtendedSelection:function(){var a;a=0;g=this.textArea[0].selectionStart;selectionEnd=this.textArea[0].selectionEnd;q=this.textArea.val();j=0;for(l=-1;(a=q.indexOf("\n\n",a)+2)!==1;)if(g>a)j=a;else if(selectionEnd<a){l=a-2;break}a=l===-1?q.slice(j):q.slice(j,l);p=a.length;return a},getParagraphs:function(){return this.getExtendedSelection().split(/\n\n+/)},setParagraphs:function(a){a=a.join("\n\n");l===-1?this.textArea.val(q.slice(0,
+j)+a):this.textArea.val(q.slice(0,j)+a+q.slice(l));this.moveCaret(a.length-p)},moveCaret:function(a){console.log("Moving caret: "+a);Math.abs(g-j)>Math.abs(a)?g+=a:g=j;this.textArea.focus();this.textArea[0].setSelectionRange(g,g)},pressed:function(a){switch(a){case 13:var c;if((a=o.list)&&/(u|o)l/i.test(a.tag))this.getSelection(),c=this.holdShift?" <br> ":"\n"+n[a.tag]+" ",this.replaceSelection(c,!1),c=!1;return c;default:this.prototype.pressed.apply(this,[a])}}}});
+textileCompiler=function(){function f(e){e=e.exec(m);var f,u;if(e)return f=/^\s*/.exec(e[0])[0].length,u=e[0].length,f&&b.advancePointer(f,!0),u-f&&b.advancePointer(u-f),m=m.slice(u),e||!0}var b=function(){function b(a,c){var d=["class"],n;w[a]&&(d=d.concat(w[a]));for(n=d.length;n--;)c(d[n])}function f(a){var c=!0,d=l==-1,n,h,k;if(d)/(o|u)l/.test(j[0].tag)?(/(o|u)l/.test(a.tag)&&a.tag!=j[0].tag&&(a={tag:"p"}),n=j[0]=a):n=j[0];else{h=l+1;for(k=j.length;h<k;h++)if(j[h].tag===a.tag){n=j[h];j[h]=j[l+
+1];j[l+1]=n;break}}n&&n.attributes&&b(n.tag,function(d){n.attributes[d]!==a.attributes[d]&&(c=!1,delete n.attributes[d])});return n&&(d||c)}function m(a){this.tag=a.tag;this.attributes=a.attributes}function s(a){var c="";for(attr in a.attributes)a.attributes.hasOwnProperty(attr)&&(c+=" "+attr+'="'+a.attributes[attr]+'"');return"<"+a.tag+c+">"}function q(a){var c;for(c=g.length;c--;)if(g[c].tag===a)return c}var g,j,l,p,o,t,n,a,c,d,h=!1,k,v=["li"],w={img:["title","src"],a:["href"]};return{init:function(){g=
+[{content:""}]},initTrace:function(d,b,e){j=[];c=void 0;t=0;n=d;a=b;k=e},finalizeTrace:function(){c&&(c=d=!1)},advancePointer:function(b,e){t+=b;if(c===void 0&&(t>n||t==k)){var h=g.length,f,o=0;p=c=!0;for(f=1;f<h;f++)v.indexOf(g[f].tag)==-1?j[f-1-o]=new m(g[f]):o+=1;l=j.length-1}c&&t>a&&(d||e?c=d=!1:d=!0)},pushTag:function(a,d){var b={tag:a,attributes:d||{},content:""};g.push(b);c&&v.indexOf(a)==-1&&(p?(j[l+1]=new m(b),l+=1):j[l+1]&&(f(b)?l+=1:h=!0))},closeTag:function(a){var d,b;a?(b=q(a),d=g.splice(b,
+1)[0]):d=g.pop();c&&v.indexOf(a)==-1&&(p=!1,h&&(j=j.slice(0,l+1),h=!1),j[l].tag===d.tag&&(l-=1),o=!0);this.pushString(s(d)+d.content,g[b-1]);this.pushString("</"+d.tag+">");o=!1},popLineEnd:function(){for(var a=!1,c={b:"*",i:"_"},d;"a,i,b,li".indexOf(g[g.length-1].tag)!=-1;)"li"===g[g.length-1].tag?(this.closeTag(),a=!0):(d=g.pop(),this.pushString(c[d.tag]+d.content));return a},popParagraphEnd:function(){for(;g.length>1;)this.closeTag()},pushString:function(a,d){d||(d=g[g.length-1]);d.content+=a;
+/^([ ]+|<br\/>)?$/.test(a)||(p&&(p=!1),c&&!o&&j[l+1]&&(j=j.slice(0,l+1)))},isOpen:function(a){return typeof q(a)==="number"},blockTagIsOpen:function(){return!!g[1]},closeBlockTag:function(){for(;g[1];)this.closeTag()},getTrace:function(){return j},toHtml:function(){return g[0].content}}}(),m;return{compile:function(e){b.init();for(m=e;!/^\s*$/.test(m);){if(e=f(/^\s*(h\d|p|bq)(\(([^#\)]*)(#[^\)]+)?\))?\. /)){var r={};e[3]&&(r["class"]=e[3]);if(e[4])r.id=e[4];b.pushTag(e[1],r)}for(;!f(/^\n/)&&!/^\s*$/.test(m);){e=
+void 0;if(f(/^ *\* /))b.isOpen("ul")||(b.closeBlockTag(),b.pushTag("ul")),b.pushTag("li");else if(f(/^ *# /))b.isOpen("ol")||(b.closeBlockTag(),b.pushTag("ol")),b.pushTag("li");else{for(;b.isOpen("ul")||b.isOpen("ol");)b.closeTag();b.blockTagIsOpen()||b.pushTag("p")}e=f(/^ */);b.pushString(e[0]);for(e=e=void 0;;)if(f(/^_(?=[^ \n]+)/))b.isOpen("i")?b.pushString("_"):b.pushTag("i");else if(f(/^\*(?=[^ \n]+)/))b.isOpen("b")?b.pushString("*"):b.pushTag("b");else if(e=f(/^([^ \n]+)_( +|(?=\n|$))/))b.isOpen("i")?
+(b.pushString(e[1]),b.closeTag("i"),b.pushString(e[2])):b.pushString(e[1]+"_"+e[2]);else if(e=f(/^([^ \n]+)\*( +|(?=\n|$))/))b.isOpen("b")?(b.pushString(e[1]),b.closeTag("b"),b.pushString(e[2])):b.pushString(e[1]+"*"+e[2]);else if(e=f(/^( *)"([^"]*)":([^ \n]+)/))b.pushString(e[1]),b.pushTag("a",{href:e[3]}),b.pushString(e[2]),b.closeTag();else if(e=f(/^( *)!([^!\(]+)(\(([^\)]*)\))?!(:([^ \n]+))?/)){b.pushString(e[1]);e[6]&&b.pushTag("a",{href:e[6]});r={src:e[2]};if(e[4])r.title=e[4];b.pushTag("img",
+r);b.closeTag();e[6]&&b.closeTag()}else if(e=f(/^([^ \n]+)/))b.pushString(e[1]);else if(e=f(/^( +)/))b.pushString(e[1]);else{f(/^\n/);e=b.popLineEnd();!e&&!/^\s*(\n|$|[\*#] )/.test(m)&&b.pushString("<br/>");break}}b.popParagraphEnd()}return b.toHtml()},trace:function(e,f,m){b.initTrace(f,m,e.length);this.compile(e);b.finalizeTrace();return b.getTrace()}}}();
+ME.addMode("wysiwyg",function(){function f(b){return b.parent().is(".preview")?b:b.parentsUntil(".preview").last()}function b(){var b,a,c=-1;b=f(jQuery(o.getRangeAt(0).startContainer));a=f(jQuery(o.getRangeAt(0).endContainer))[0];return b[0]!==a?b.nextAll().filter(function(d){this==a&&(c=d);if(c===-1||c===d)return!0}).add(b):b}function m(n){b().removeClass("left").removeClass("right").removeClass("center").addClass(n)}function e(b,a){var c=b[0],d;b.length>1?(d=b[b.length-1],t.setStart(c,0),t.setEnd(d,
+d.childNodes.length)):t.selectNodeContents(c);a!==void 0&&t.collapse(a);o.removeAllRanges();o.addRange(t)}function r(b,a,c,d){/ on$/.test(c.className)?(c=a.getSelection("li"),d=[],g(d,c.firstChild),c=p("<p>").html(d.join("<br>")),a.replaceSelection(b,c)):(c=a.getSelection("br"),d=p("<"+d+">"),s(d,c.firstChild),q(a.leftBorder,d),q(a.rightBorder,d),a.replaceSelection(b,d))}function u(b,a,c){this.nextProperty=c;this.ancestors=b.parentsUntil(".preview");this.block=this.ancestors[this.ancestors.length-
+1]||b[0];for(this.borderNode=this.ancestors[this.ancestors.length-2]||b[0];this.borderNode;){this.node=this.borderNode;if(this.borderNode.nodeName.toLowerCase()===a)break;this.borderNode=this.borderNode[c]}this.safeBlock=this.borderNode?this.block:this.block[c]}function s(b,a){function c(){/^\s*$/.test(d.textContent)||b.append(d);d=document.createElement("li")}for(var d=document.createElement("li"),e;a!==null;)e=a.nextSibling,/br/i.test(a.nodeName)?c():/(p|h\d)/i.test(a.nodeName)?(c(),s(b,a.firstChild)):
+/(o|u)l/i.test(a.nodeName)?p(a).children().appendTo(b):/li/i.test(a.nodeName)?(c(),b.append(a)):d.appendChild(a),a=e;c()}function q(b,a){var c;if(b.safeBlock&&/ul/i.test(b.safeBlock.nodeName))next=b.safeBlock[b.nextProperty],c=p(b.safeBlock).remove().children(),b.nextProperty==="previousSibling"?c.prependTo(a):c.appendTo(a),b.safeBlock=next}function g(b,a){for(;a;)/(o|u)l/i.test(a.nodeName)?g(b,a.firstChild):/li/i.test(a.nodeName)&&b.push(a.innerHTML),a=a.nextSibling}function j(b,a){var c=o.getRangeAt(0);
+if(!p.browser.mozilla||c.collapsed||ME.util.isNeutralKey(a))return!0;c=c.extractContents();if(b.is(":empty")||/^ *$/.test(b.text()))return c=document.createElement(c.childNodes[0].nodeName),b.html(c),e([c]),/^8|13|46$/.test(""+a)?!1:!0}var l={},p=jQuery,o=getSelection(),t=document.createRange();return{name:"Preview Mode",items:{"default":{clicked:function(b,a,c){b=o.getRangeAt(0);/ on$/.test(c.className)?document.execCommand(this.name,!1,null):(c=document.createElement(this.tag),b.surroundContents(c),
+o.removeAllRanges(),o.addRange(b))}},bold:{tag:"b"},italic:{tag:"i"},alignLeft:{clicked:function(){m("left")}},alignRight:{clicked:function(){m("right")}},alignCenter:{clicked:function(){m("center")}},unorderedList:{clicked:function(b,a,c){r(b,a,c,"ul")}},orderedList:{clicked:function(b,a,c){r(b,a,c,"ol")}},link:{clicked:function(b,a,c){var d,e,f=o.getRangeAt(0);a={remove:function(){var a=d.text();d.replaceWith(a)},close:function(){b.htmlDiv.focus();b.checkState()}};/ on$/.test(c.className)?(d=p(l.a),
+c=ME.dialog.link(["Update","Remove","Cancel"]),a.submit=function(a,c){d.attr("href",c).text(a);f.selectNodeContents(d[0]);o.removeAllRanges();o.addRange(f)},e=d.text(),c.val("input.uri",d.attr("href"))):(c=ME.dialog.link(["Create","Cancel"]),a.submit=function(a,c){var d=p('<a href="'+c+'">'+a+"</a>")[0];f.deleteContents();f.insertNode(d);f.selectNodeContents(d);o.removeAllRanges();o.addRange(f)},e=f.toString());/^\s*$/.test(e)||c.val(".title",e);c.dialog("open",a)}},insertImage:{clicked:function(b,
+a,c){var d=window.getSelection(),e=d.getRangeAt(0);/ on$/.test(c.className)?(a=ME.dialog.insertImage(["Update","Remove","Cancel"]),l.a&&(c=p(l.a),a.val("input.uri",c.attr("href")),e.selectNode(l.a)),imageNode=p(l.img),a.val("input.imageUri",imageNode.attr("src")),a.val("input.title",imageNode.attr("title"))):a=ME.dialog.insertImage(["Create","Cancel"]);a.dialog("open",{submit:function(a,c,b){var f=a=p('<img src="'+a+'"/>');/^\s*$/.test(c)||a.attr({alt:c,title:c});/^\s*$/.test(b)||(f=p('<a href="'+
+b+'"/>').append(a));e.deleteContents();e.insertNode(f[0]);e.selectNode(a[0]);d.removeAllRanges();d.addRange(e)},remove:function(){imageNode.remove()},close:function(){b.htmlDiv.focus();b.checkState()}})}},formatBlock:{clicked:function(f,a,c){var d,h=[],g;b().replaceWith(function(){g=/(u|o)l/i.test(this.nodeName)?this.nodeName:c.value;d=p("<"+g+">").addClass(this.className).append(this.childNodes);h.push(d[0]);return d});e(h)}}},getSelection:function(){var b=o.getRangeAt(0);this.leftBorder=new u(jQuery(o.getRangeAt(0).startContainer),
+"li","previousSibling");this.rightBorder=new u(jQuery(o.getRangeAt(0).endContainer),"li","nextSibling");b.setStartBefore(this.leftBorder.node);b.setEndAfter(this.rightBorder.node);return b.extractContents()},replaceSelection:function(b,a){this.leftBorder.safeBlock?a.insertAfter(this.leftBorder.safeBlock):b.htmlDiv.prepend(a);e(a);/^\s*$/.test(this.leftBorder.block.textContent)&&p(this.leftBorder.block).remove();/^\s*$/.test(this.rightBorder.block.textContent)&&p(this.rightBorder.block).remove()},
+afterActivation:function(){this.textArea.parent().hide();this.htmlDiv.attr("contentEditable",!0);p.browser.mozilla&&document.execCommand("styleWithCSS",null,!1)},getStates:function(){function b(a,c){c&&c.nodeName!="#text"&&a[0].nodeName!="#text"&&(a=a.find(c.nodeName.toLowerCase()));return a.parentsUntil(".preview").add(a)}if(p(document.activeElement).is(".preview")){var a=[],c;c=o.getRangeAt(0).cloneContents().firstChild;a=b(jQuery(o.getRangeAt(0).startContainer),c);c=b(jQuery(o.getRangeAt(0).endContainer),
+c);/(u|o)l/i.test(a[0].nodeName)&&a[0].nodeName!==c[0].nodeName&&(a=a.toArray(),a[0]=p("<p>")[0]);return this.buildStateObject(a,l={})}},pressed:function(b){switch(b){case 13:var a;var c=this.htmlDiv;if(j(c,13)===!1)a=!1;else{b=!0;var d,f,g,m=l.block;if(/h[1-5]/i.test(m.nodeName)){f=o.getRangeAt(0);for(d=g=f.endContainer;d.parentNode!==c[0];){if(d.nextSibling){b=!1;break}d=d.parentNode}b&&f.endOffset===g.textContent.length&&(d=p("<p>").insertAfter(m),e(d),a=!1)}}return a;case 8:b=this.htmlDiv;if(j(b,
+8)===!1)c=!1;else if(p.browser.webkit){d=!0;g=l.block;f=o.getRangeAt(0);for(a=f.startContainer;a.parentNode!==b[0];){if(a.previousSibling){d=!1;break}a=a.parentNode}d&&f.startOffset===0&&(a=p(g),b=a.contents(),d=a.prev(),d[0]&&(d.append(b),a.remove(),e([b[0]],!0),c=!1))}else c=!0;return c;default:return j(this.htmlDiv,b)}},toText:function(){return this.editor.getDataMode().toText()},toHTML:function(){return this.htmlDiv.html()}}});
View
1,719 public/javascripts/joined.js
@@ -1,39 +1,167 @@
-ME = function ($) {
+(function($) {
+ // TODO cache fields
+ var opts, errorMsgType, initialized, methods;
+
+ methods = {
+ check: function(options) {
+ var $this = this, valid = true;
+ //Hide any errors that are already showing
+ $this.find(opts.errorElement + '.' + opts.errorClass).remove();
+ $this.find(':input.' + opts.inputErrorClass).removeClass(opts.inputErrorClass);
+
+ //Get all the required inputs
+ $this.find(':input.required').each(function() {
+ var $input = $(this),
+ fieldValue = $.trim($input.val()),
+ labelText = $input.siblings('label').text().replace(opts.removeLabelChar, ''),
+ errorMsg = '';
+
+ //Check if it's empty or an invalid email
+ if(fieldValue === '') {
+ errorMsg = hasLabelPlaceholder ? errorMsg = opts.errorText.replace('{label}',labelText) : errorMsg = opts.errorText;
+ valid = false;
+ } else if($input.hasClass('email')) {
+ if(!(/^([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/.test(fieldValue))) {
+ errorMsg = hasLabelPlaceholder ? errorMsg = opts.emailErrorText.replace('{label}',labelText) : errorMsg = opts.emailErrorText;
+ valid = false;
+ }
+ }
+
+ //If there is an error, display it
+ if(errorMsg !== '') {
+ $input.parent().addClass(opts.errorClass);
+ //$input.addClass(opts.inputErrorClass).after('<'+opts.errorElement+' class="'+opts.errorClass+'">' + errorMsg + '</'+opts.errorElement+'>');
+ }
+ });
- var globalSettings = {}, availableModes = {}, toolbarItems = {};
+ return valid;
+ },
+ reset: function(){
+ return this.find(':input.required').each(function(){
+ $(this).parent().removeClass(opts.errorClass);
+ });
+ }
+ };
+ $.fn.isValid = function(method) {
+ if (!methods[method]) {
+ method = 'check';
+ }
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ };
+
+ $.fn.isValid.init = function(options){
+ opts = $.extend({}, $.fn.isValid.defaults, options);
+ hasLabelPlaceholder = opts.errorText.indexOf("{label}") > -1;
+ };
+
+ // default options
+ $.fn.isValid.defaults = {
+ errorClass: 'error',
+ errorText: '{label} is a required field.',
+ emailErrorText: 'Please enter a valid {label}',
+ errorElement: 'strong',
+ removeLabelChar: '*'
+ };
+})(jQuery);
+var ME = function ($) {
+ var globalSettings = {}, availableModes = {}, toolbarItems = {}, toolbarHTML = "",
+ availableItems = ['bold','italic','alignLeft','alignCenter','alignRight','unorderedList','orderedList','link','insertImage','save','changeMode','formatBlock'],
+ globalItems = [],
+ emptyFunction = $.noop;
+
+ /**
+ * Create a new Mode
+ * @constructor
+ * @param {Object} customFunctions these functions will be added to the Mode object
+ */
function Mode(customFunctions){
$.extend(this, customFunctions);
+ this.prototype = Mode.prototype;
}
Mode.prototype = {
+ /**
+ * This loads the mode for the current Editor
+ * TODO this is ugly. why should every editor have every mode?
+ * @param {Editor} editor
+ */
load: function(editor) {
this.editor = editor;
this.htmlDiv = editor.htmlDiv;
this.textArea = editor.textArea;
console.log("loaded Mode " + this.name);
},
- getStates: $.noop,
+ /**
+ * This is a placeholder. Each mode should define its version
+ * @returns {Object} an object that describes the states
+ * @see Toolbar#setActive
+ * @api
+ */
+ getStates: emptyFunction,
+ /**
+ * The default pressed function to handle key combos (shift + x)
+ */
+ pressed: function(keyCode){
+ if(keyCode === 16){
+ this.holdShift = true;
+ }
+ },
+ /**
+ * Handle special keys (shift press) to deal with key combos
+ */
+ released: function(keyCode){
+ if(keyCode === 16){
+ this.holdShift = false;
+ }
+ },
+ /**
+ * Activate this mode for the editor
+ */
activate: function() {
if(this.htmlDiv.is(":empty")) {
this.updatePreview();
} else {
this.updateTextArea();
}
+ this.editor.toolbar.loadModeToolbar();
this.afterActivation();
},
+ /**
+ * Update the preview html div with the html representation of the mode
+ */
updatePreview: function() {
console.log("updating preview in Mode " + this.name);
- this.htmlDiv.html(this.toHTML());
+ this.htmlDiv.html(this.toHTML() || "<p>&nbsp;</p>");
},
+ /**
+ * Update the textarea with the text representation of the mode
+ */
updateTextArea: function() {
console.log("updating TA in Mode " + this.name);
this.textArea.val(this.toText());
},
+ /**
+ * Run after activation. Default behaviour for text modes. wysiwyg mode has
+ * its own version
+ */
afterActivation: function() {
- this.textArea.show();
+ this.textArea
+ .parent().show()
+ .find(":first-child").focus()[0]
+ .setSelectionRange(0,0);
this.htmlDiv.attr("contentEditable",false);
},
+ /**
+ * Iterates over the given nodes and builds the state object which defines
+ * the active buttons
+ *
+ * CONSIDER make currentNodes a property
+ * @param {Array} nodes The active nodes (e.g. a,li). The highest node is on the right
+ * @param {Object} currentNodes A reference that will be filled with important nodes (e.g. a) to be used by the mode
+ *
+ * @returns {Object} The state object
+ */
buildStateObject: function(nodes, currentNodes){
function getTag(node){
return node.tag ? node.tag : node.nodeName.toLowerCase();
@@ -53,56 +181,229 @@ ME = function ($) {
case "i":
states.italic = true;
break;
- case "li":
- break;
- case "ol":
- states.insertOrderedList = true;
- states.insertUnorderedList = false;
- break;
case "b":
states.bold = true;
break;
+ case "ol":
+ states.orderedList = true;
+ states.unorderedList = false;
+ states.formatBlock = 'disable';
+ states.alignLeft = 'disable';
+ states.alignRight = 'disable';
+ states.alignCenter = 'disable';
+ currentNodes.list = node;
+ break;
case "ul":
- states.insertOrderedList = false;
- states.insertUnorderedList = true;
+ states.orderedList = false;
+ states.unorderedList = true;
+ states.formatBlock = 'disable';
+ states.alignLeft = 'disable';
+ states.alignRight = 'disable';
+ states.alignCenter = 'disable';
+ currentNodes.list = node;
+ break;
+ case "li":
break;
default:
states.formatBlock = getTag(node);
+ currentNodes.block = node;
break;
}
}
return states;
+ },
+ /**
+ * @param {String} [boundary] The right and left boundary the
+ * selection should be extended to
+ * @returns {String} The currently selected string
+ */
+ getSelection: function(boundary) {
+ var textArea = this.textArea, text = textArea.val(), boundaryPosition, subString;
+ textArea.focus();
+
+ // gecko & webkit
+ this.scrollPosition = textArea.scrollTop;
+ this.selectionStart = textArea[0].selectionStart;
+ this.selectionEnd = textArea[0].selectionEnd;
+
+ if(text[this.selectionEnd-1] === "\n"){
+ this.selectionEnd -= 1;
+ }
+
+ if(boundary) {
+ // find left boundary
+ boundaryPosition = Math.max(text.lastIndexOf(boundary, this.selectionStart), text.lastIndexOf("\n", this.selectionStart));
+ if(boundaryPosition !== -1) {
+ this.selectionStart = boundaryPosition + 1;
+ } else {
+ this.selectionStart = 0;
+ }
+
+ // find right boundary, first limit the text to the
+ // next new line
+ boundaryPosition = text.indexOf("\n", this.selectionEnd);
+ if(boundaryPosition === -1) {
+ subString = text.slice(this.selectionStart);
+ } else {
+ subString = text.slice(this.selectionStart, boundaryPosition);
+ }
+
+ // Then find the next boundary
+ boundaryPosition = 0;
+ do{
+ boundaryPosition = subString.indexOf(boundary, boundaryPosition + 1);
+ } while(boundaryPosition !== -1 && this.selectionEnd > this.selectionStart + boundaryPosition);
+
+ // when it doesn't exist, extend the selection to the
+ // paragraph end
+ if(boundaryPosition === -1) {
+ boundaryPosition = subString.length;
+ }
+ this.selectionEnd = this.selectionStart + boundaryPosition;
+ }
+ this.selection = text.slice(this.selectionStart, this.selectionEnd);
+ return this.selection;
+ },
+ /**
+ * Replace the current selection with the given string
+ * @param {String} string The replacement string
+ * @param {Boolean} collapseToStart If the selection should collapse
+ */
+ replaceSelection: function(string, collapseToStart) {
+ var textArea = this.textArea,
+ newSelectionStart = this.selectionStart,
+ newSelectionEnd = this.selectionStart + string.length;
+
+ // gecko & webkit
+ textArea.val(textArea.val().slice(0, this.selectionStart) + string + textArea.val().slice(this.selectionEnd, textArea.val().length));
+
+ // move caret gecko
+ if(collapseToStart === true){
+ newSelectionEnd = newSelectionStart;
+ } else if(collapseToStart === false){
+ newSelectionStart = newSelectionEnd;
+ }
+
+ textArea[0].setSelectionRange(newSelectionStart, newSelectionEnd);
+ textArea.focus();
+ },
+ /**
+ * Extend the right selection with a regexp. Everything matched will be added
+ * to the selection. Useful for special cases like toggling parts of a bolded
+ * String in textile
+ *
+ * @param {Regexp} regexp The regexp
+ *
+ * @example
+ * extendRightSelection(/ +/)
+ */
+ extendRightSelection: function(regexp){
+ var match;
+ regexp = new RegExp(regexp.source,'g');
+ regexp.lastIndex = this.selectionEnd;
+ match = regexp.exec(this.textArea.val());
+
+ if(match && regexp.lastIndex == this.selectionEnd + match[0].length){
+ this.selectionEnd += match[0].length;
+ return match[0];
+ }
+ },
+ /**
+ * Extend the left selection with a regexp. Everything matched will be added
+ * to the selection. Useful for special cases like toggling parts of a bolded
+ * String in textile
+ *
+ * @param {Regexp} regexp The regexp.
+ *
+ * @example
+ * extendLeftSelection(/[ .]+/)
+ */
+ extendLeftSelection: function(regexp){
+ var match, substring = this.textArea.val().slice(0,this.selectionStart);
+ regexp = new RegExp(regexp.source + "$");
+ match = regexp.exec(substring);
+
+ if(match){
+ this.selectionStart -= match[0].length;
+ return match[0];
+ }
}
};
- function ToolbarButton(name){
+ /**
+ * Create a button for the toolbar
+ * @constructor
+ *
+ * @param {String} name The class name of the button
+ * @param {Function} [clicked] The default action if the button is clicked
+ */
+ function ToolbarButton(name, clicked){
this.name = name;
+ if(clicked){
+ this.clicked = clicked;
+ globalItems.push(name);
+ }
}
ToolbarButton.prototype = {
+ /**
+ * @returns {String} A html string of the button
+ */
getButton: function() {
return '<a href="#" class=\"'+ this.name +'" ><span>'+ this.name +'</span></a>';
}
};
- function ToolbarSelect(name, options){
- this.name = name;
+ /**
+ * Create a select for the toolbar
+ * @constructor
+ *
+ * @param {String} name The class name of the button
+ * @param {Array} [options] The options of the select
+ * @param {Function} [clicked] The default action if the button is clicked
+ */
+ function ToolbarSelect(name, options, clicked){
+ ToolbarButton.apply(this, [name, clicked]);
this.options = options || [];
}
ToolbarSelect.prototype = {
+ /**
+ * @returns {String} A html string of the button
+ */
getButton: function() {
- var select = $("<select class=\"" + this.name + "\"></select>"),
+ var select = "<select class=\"" + this.name + "\">",
optionsLength = this.options.length,
i;
select.className = this.name;
for (i = 0; i < optionsLength; i += 1){
- $("<option/>").val(this.options[i][0]).text(this.options[i][1]).appendTo(select);
+ select += "<option value=\"" + this.options[i][0] + "\">" + this.options[i][1] + "</option>";
}
- return select;
+ return select + "</select>";
}
}; // end ToolbarSelect
+
+ function getToolbarHTML(){
+ var i,l, item;
+
+ if(!toolbarHTML){
+ for(i=0,l=availableItems.length; i < l ; i++){
+ item = toolbarItems[availableItems[i]];
+ if(item){
+ toolbarHTML += item.getButton();
+ }
+ }
+ }
+
+ return toolbarHTML;
+ }
+ /**
+ * Create a toolbar for an editor. Every editor has its own toolbar, since the
+ * items of the toolbar can be defined on a per editor basis (save callback)
+ *
+ * @constructor
+ */
function Toolbar(editor) {
// init Toolbar Items
@@ -114,14 +415,10 @@ ME = function ($) {
this.htmlDiv = editor.htmlDiv;
this.editor = editor;
this.div = toolbarDiv;
+
+ toolbarDiv.html(getToolbarHTML());
- for(item in toolbarItems) {
- if(toolbarItems.hasOwnProperty(item)) {
- toolbarDiv.append(toolbarItems[item].getButton());
- }
- }
-
- toolbarDiv.mouseup(function(e) { // buttons
+ toolbarDiv.mouseup(function(e) { // Trigger on button click
var target = e.target;
if(!(/(select|option)/i).test(target.nodeName)) {
@@ -130,181 +427,172 @@ ME = function ($) {
if(/span/i.test(target.nodeName)) {
target = target.parentNode;
}
+ if(target.disabled){
+ // TODO handle focus somewhere
+ if(editor.is('wysiwyg')){
+ editor.htmlDiv.focus();
+ } else {
+ editor.textArea.focus();
+ }
+ return false;
+ }
var action = target.className;
action = action.split(" ")[0];
that.runAction(action, target);
- editor.checkState(); // TODO this does not work with dialogs
+ // TODO this does not work with dialogs
+ // in dialogs this gets set manually, but perhaps there is a
+ // more general way?
+ editor.checkState();
}
- return false;
- }).change(function(e) { // select lists
+ }).change(function(e) { // trigger on select change
var target = e.target;
that.runAction(target.className, target);
return false;
- });
+ }).click(function(e){return false; }); //
editor.container.prepend(toolbarDiv);
} // end initToolbar
Toolbar.prototype = {
- getDivSelection: function() {
- this.htmlDiv.focus();
-
- // gecko & webkit
- theSelection = window.getSelection();
- theRange = theSelection.getRangeAt(0);
- return theRange.toString();
- },
- getTextAreaSelection: function(extendSelectionToWordBoundaries) {
- var textArea = this.textArea, text = textArea.val(), spacePos, subString;
- textArea.focus();
-
- // gecko & webkit
- this.scrollPosition = textArea.scrollTop;
- this.selectionStart = textArea[0].selectionStart;
- this.selectionEnd = textArea[0].selectionEnd;
+ /**
+ * Load the toolbar for the current mode. If a toolbar item is not
+ * supported, it will be hidden.
+ */
+ loadModeToolbar: function(){
+ var supportedItems = this.editor.currentMode.supportedItems,
+ hasSave = this.editor.settings.save,
+ oldVisibleItems = this.visibleItems,
+ newVisibleItems = [];
- if(extendSelectionToWordBoundaries) {
- // find left word boundary
- spacePos = Math.max(text.lastIndexOf(" ", this.selectionStart), text.lastIndexOf("\n", this.selectionStart));
- if(spacePos !== -1) {
- this.selectionStart = spacePos + 1;
- } else {
- this.selectionStart = 0;
- }
-
- // find right word boundary, first limit the text to the
- // next paragraph
- spacePos = text.indexOf("\n", this.selectionEnd);
- if(spacePos === -1) {
- subString = text.slice(this.selectionStart);
+ // Optimize: better scheme. Calculate the differences between
+ // the modes once and use them here
+ this.div.children().each(function(){
+ var item = this.className;
+ if(supportedItems.indexOf(item) != -1 && (item !== "save" || hasSave)){
+ if(!oldVisibleItems || oldVisibleItems.indexOf(item) == -1){
+ $(this).show();
+ }
+ newVisibleItems.push(item);
} else {
- subString = text.slice(this.selectionStart, spacePos);
- }
-
- // Then find the next space
- spacePos = 0;
- do{
- spacePos = subString.indexOf(" ", spacePos + 1);
- } while(spacePos !== -1 && this.selectionEnd > this.selectionStart + spacePos);
-
- // when it doesn't exist, extend the selection to the
- // paragraph end
- if(spacePos === -1) {
- spacePos = subString.length;
+ if(!oldVisibleItems || oldVisibleItems.indexOf(item) != -1){
+ $(this).hide();
+ }
}
- this.selectionEnd = this.selectionStart + spacePos;
- }
- this.selection = text.slice(this.selectionStart, this.selectionEnd);
- return this.selection;
- },
- replaceTextAreaSelection: function(string) {
- var textArea = this.textArea,
- position = this.selectionStart;
- // gecko & webkit
- textArea.val(textArea.val().slice(0, this.selectionStart) + string + textArea.val().slice(this.selectionEnd, textArea.val().length));
-
- // move caret gecko
- textArea[0].setSelectionRange(position, position + string.length);
- textArea.focus();
- },
- extendRightSelection: function(regexp){
- var match;
- regexp = new RegExp(regexp.source,'g');
- regexp.lastIndex = this.selectionEnd;
- match = regexp.exec(this.textArea.val());
-
- if(match && regexp.lastIndex == this.selectionEnd + match[0].length){
- this.selectionEnd += match[0].length;
- return match[0];
- }
- },
- extendLeftSelection: function(regexp){
- var match, substring = this.textArea.val().slice(0,this.selectionStart);
- regexp = new RegExp(regexp.source + "$");
- match = regexp.exec(substring);
-
- if(match){
- this.selectionStart -= match[0].length;
- return match[0];
- }
- },
- replaceDivSelection: function(string) {
- },
- getSelection: function(extendSelectionToWordBoundaries) {
- if(this.editor.is("wysiwyg")) {
- return this.getDivSelection(extendSelectionToWordBoundaries);
- }else {
- return this.getTextAreaSelection(extendSelectionToWordBoundaries);
- }
- },
- replaceSelection: function(string) {
- if (this.editor.is("wysiwyg")) {
- this.replaceDivSelection(string);
- } else {
- this.replaceTextAreaSelection(string);
- }
+ });
+ this.visibleItems = newVisibleItems;
},
+ /**
+ * Execute the given action of the current mode
+ *
+ * @param {String} action The action to execute
+ * @param {HTMLElement} target The target of the click
+ */
runAction: function(action,target) {
- toolbarItems[action][this.editor.currentMode.id].clicked(this,target);
+ var item = toolbarItems[action],
+ editor = this.editor,
+ mode = editor.currentMode;
+ (item[mode.id] || item).clicked(editor, mode, target);
// Update Preview in case something has changed
- if(action != "changeMode" && !this.editor.is("wysiwyg")) {
- this.editor.currentMode.updatePreview();
+ if(action != "changeMode" && !editor.is("wysiwyg")) {
+ mode.updatePreview();
}
},
+ /**
+ * Activate the buttons/selects of the given actions on the toolbar
+ *
+ * @param {Object} actions The actions which should be active
+ */
setActive: function( actions ) {
// activate each action in actions
if(actions) {
this.div.children().each(function(i) {
var action = this.className.split(" ")[0];
- if(actions[action] === true) { // buttons
- this.className = action + " on";
- } else if (actions[action]) { // selects
- this.value = actions[action];
- } else { // deactivate
+ if (actions[action] == 'disable') { // deactivate
+ this.disabled = true;
+ this.className = action + " disabled";
+ } else {
+ this.disabled = false;
this.className = action;
+ if(actions[action] === true) { // buttons
+ this.className = action + " on";
+ } else if(actions[action]){ // selects
+ this.value = actions[action];
+ }
}
});
}
}
}; // end Toolbar prototype
+ /**
+ * Create a new Editor
+ *
+ * An editor has a current mode and a textarea mode. Both are the same if you
+ * edit the textarea directly (e.g. textile). In the wysiwyg mode you edit the
+ * html directly.
+ * @constructor
+ *
+ * @param {jQuery} textArea The textarea
+ * @param {Object} settings Editor specific settings
+ */
function Editor(textArea, settings) {
- var container, that = this;
+ var container, editor = this, timer = 0;
this.loadedModes = {};
this.setDataType(textArea.attr("class"));
+ this.settings = settings;
if(!this.dataType) { return ;}
this.textArea = textArea.bind("mouseup keyup", function() {
// TODO check for specific mouse keys
- that.checkState();
+ editor.checkState();
+ clearTimeout(timer);
+ timer = setTimeout(function(){
+ editor.currentMode.updatePreview();
+ },1000);
+ }).keydown(function(e){
+ return editor.currentMode.pressed(e.keyCode);
+ }).keyup(function(e){
+ return editor.currentMode.released(e.keyCode);
});
- this.htmlDiv = $("<div class=\"preview\"></div>").bind("mouseup keyup", function() {
+ this.htmlDiv = $("<div class=\"preview\"></div>")
+ .bind("mouseup keyup", function() {
// TODO check for specific mouse keys
- if(that.is("wysiwyg")) {
- that.checkState();
+ if(editor.is("wysiwyg")) {
+ editor.checkState();
}
- });
+ }).keydown(function(e){
+ return editor.currentMode.pressed(e.keyCode);
+ });
this.container = textArea.wrap("<div class=\"markupEditor\"></div>")
- .parent().append(that.htmlDiv);
- this.toolbar = new Toolbar(that);
+ .parent().append(editor.htmlDiv);
+ textArea.wrap("<div class=\"textarea\">");
+ this.toolbar = new Toolbar(this);
} // Editor
Editor.prototype = {
+ /**
+ * Change the current mode to the given id
+ *
+ * @param {String} modeId The id of the mode (e.g. textile)
+ */
changeMode: function(modeId) {
var nextMode;
if(!modeId || modeId === this.currentMode.id) {
return false;
}
nextMode = this.getMode(modeId);
this.commit();
- nextMode.activate();
this.currentMode = nextMode;
+ nextMode.activate();
},
+ /**
+ * @returns {Mode} The current datamode
+ */
getDataMode: function() {
return this.getMode(this.dataType);
},
@@ -348,64 +636,59 @@ ME = function ($) {
}
}; // end Editor prototype
- function initEditor(textArea,instanceSettings){
+ function initEditorFromHTML(container, settings){
+ container.css("min-height", container.height());
+ var editor,
+ textarea = $("<textarea class=\"" + container[0].className + "\">")
+ .prependTo(container); // needs to be attached to DOM in firefox
+
+ editor = initEditorFromTextarea(textarea, settings);
+ editor.htmlDiv.append(editor.container.nextAll());
+ editor.currentMode.updateTextArea();
+ editor.changeMode("wysiwyg");
+ editor.toolbar.setActive({changeMode: "wysiwyg"});
+
+ container.append(editor.container);
+ }
+
+ function initEditorFromTextarea(textarea,instanceSettings){
var editor,settings = {};
$.extend(settings,globalSettings,instanceSettings);
- editor = new Editor(textArea, settings);
+ editor = new Editor(textarea, settings);
editor.currentMode = editor.getDataMode();
- if(textArea.hasClass("wysiwyg")) {
+ if(textarea.hasClass("wysiwyg")) {
+ // TODO better flow here
+ editor.currentMode.activate();
editor.currentMode = editor.getMode("wysiwyg");
}
editor.currentMode.activate();
editor.toolbar.setActive({changeMode: editor.currentMode.id});
+ return editor;
}
- $.fn.initMarkupEditor = function(settings) {
- this.each(function(index,textArea) {
- textArea = $(textArea);
- if(textArea.is("textarea")) {
- initEditor(textArea, settings);
- }
- });
- return this;
- };
-
- toolbarItems.changeMode = new ToolbarSelect("changeMode");
-
- toolbarItems.formatBlock = new ToolbarSelect("formatBlock",[
- ["p", "Paragraph"],
- ["h1", "Heading 1"],
- ["h2", "Heading 2"],
- ["h3", "Heading 3"]
- ]);
-
- function processToolbarElements(constructor, elements, modeId){
- if(elements) {
- for( element in elements) {
- if(elements.hasOwnProperty(element) && element !== "default") {
- if(!toolbarItems[element]) {
- toolbarItems[element] = new constructor(element);
+ var ME = {
+ addMode: function(modeId, spec) {
+ var mode = spec(), items = mode.items, constructor, supportedItems = globalItems.slice();
+ mode.id = modeId;
+
+ if(items) {
+ for( item in items) {
+ if(items.hasOwnProperty(item) && item !== "default") {
+ supportedItems.push(item);
+ if(!toolbarItems[item]) {
+ constructor = items[item].options ? ToolbarSelect : ToolbarButton;
+ toolbarItems[item] = new constructor(item);
+ }
+ toolbarItems[item][modeId] = $.extend({name: item}, items["default"], items[item]);
}
- toolbarItems[element][modeId] = $.extend({name: element}, elements["default"], elements[element]);
}
}
- }
- }
-
- return {
- addMode: function(modeId, spec) {
- var mode = spec(), buttons = mode.buttons, selects = mode.selects;
- mode.id = modeId;
- processToolbarElements(ToolbarButton, buttons, modeId);
- processToolbarElements(ToolbarSelect, selects, modeId);
toolbarItems.changeMode.options.push([modeId, mode.name]);
- // TODO this method definition should be elsewhere
- toolbarItems.changeMode[modeId] = {clicked: function(toolbar, target) {
- toolbar.editor.changeMode(target.value);
- }};
+
+ mode.supportedItems = supportedItems;
availableModes[modeId] = function(editor) {
var modeInstance = new Mode(mode);
@@ -415,29 +698,263 @@ ME = function ($) {
return modeInstance;
};
return mode;
+ },
+ options: {},
+ setOptions: function(options){