Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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...
commit f2867c25a0a34adf40dd42e970b20ac5897ba126 1 parent 7c2e68a
Jonas von Andrian authored

Showing 88 changed files with 4,970 additions and 7,959 deletions. Show diff stats Hide diff stats

  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
4 .gitignore
@@ -3,6 +3,10 @@
3 3 # dirs
4 4 build/
5 5 inspiration/
  6 +.sass-cache/
  7 +.bundle/
  8 +bin/
  9 +selenium/support/firefox_profile/Cache
6 10
7 11 # tempfiles
8 12 *[~#]
5 Gemfile
... ... @@ -0,0 +1,5 @@
  1 +source "http://rubygems.org"
  2 +gem "middleman"
  3 +gem "closure-compiler"
  4 +gem "selenium-webdriver"
  5 +gem "rspec"
101 Gemfile.lock
... ... @@ -0,0 +1,101 @@
  1 +GEM
  2 + remote: http://rubygems.org/
  3 + specs:
  4 + activesupport (3.0.9)
  5 + childprocess (0.1.9)
  6 + ffi (~> 1.0.6)
  7 + chunky_png (1.2.0)
  8 + closure-compiler (1.1.1)
  9 + coffee-script (2.2.0)
  10 + coffee-script-source
  11 + execjs
  12 + coffee-script-source (1.1.1)
  13 + compass (0.11.2)
  14 + chunky_png (~> 1.1)
  15 + fssm (>= 0.2.7)
  16 + sass (~> 3.1)
  17 + crack (0.1.8)
  18 + daemons (1.1.4)
  19 + diff-lcs (1.1.2)
  20 + eventmachine (0.12.10)
  21 + execjs (1.2.0)
  22 + multi_json (~> 1.0)
  23 + ffi (1.0.9)
  24 + fssm (0.2.7)
  25 + haml (3.1.2)
  26 + http_router (0.7.10)
  27 + rack (>= 1.0.0)
  28 + url_mount (~> 0.2.1)
  29 + httparty (0.7.8)
  30 + crack (= 0.1.8)
  31 + i18n (0.6.0)
  32 + json_pure (1.5.3)
  33 + middleman (1.2.8)
  34 + coffee-script (~> 2.2.0)
  35 + compass (= 0.11.2)
  36 + haml (~> 3.1.0)
  37 + httparty (~> 0.7.0)
  38 + padrino-core (~> 0.9.23)
  39 + padrino-helpers (~> 0.9.23)
  40 + rack (~> 1.0)
  41 + rack-test (~> 0.5.0)
  42 + sass (~> 3.1.0)
  43 + shotgun (~> 0.8.0)
  44 + sinatra (~> 1.2.0)
  45 + thin (~> 1.2.0)
  46 + thor (~> 0.14.0)
  47 + tilt (~> 1.3.0)
  48 + uglifier (~> 0.5.0)
  49 + multi_json (1.0.3)
  50 + padrino-core (0.9.29)
  51 + activesupport (>= 3.0.0)
  52 + http_router (~> 0.7.8)
  53 + sinatra (~> 1.2.6)
  54 + thor (>= 0.14.3)
  55 + tilt (~> 1.3.0)
  56 + padrino-helpers (0.9.29)
  57 + i18n (>= 0.4.1)
  58 + padrino-core (= 0.9.29)
  59 + rack (1.3.0)
  60 + rack-test (0.5.7)
  61 + rack (>= 1.0)
  62 + rspec (2.6.0)
  63 + rspec-core (~> 2.6.0)
  64 + rspec-expectations (~> 2.6.0)
  65 + rspec-mocks (~> 2.6.0)
  66 + rspec-core (2.6.4)
  67 + rspec-expectations (2.6.0)
  68 + diff-lcs (~> 1.1.2)
  69 + rspec-mocks (2.6.0)
  70 + rubyzip (0.9.4)
  71 + sass (3.1.4)
  72 + selenium-webdriver (0.2.2)
  73 + childprocess (>= 0.1.9)
  74 + ffi (>= 1.0.7)
  75 + json_pure
  76 + rubyzip
  77 + shotgun (0.8)
  78 + rack (>= 1.0)
  79 + sinatra (1.2.6)
  80 + rack (~> 1.1)
  81 + tilt (>= 1.2.2, < 2.0)
  82 + thin (1.2.11)
  83 + daemons (>= 1.0.9)
  84 + eventmachine (>= 0.12.6)
  85 + rack (>= 1.0.0)
  86 + thor (0.14.6)
  87 + tilt (1.3.2)
  88 + uglifier (0.5.4)
  89 + execjs (>= 0.3.0)
  90 + multi_json (>= 1.0.2)
  91 + url_mount (0.2.1)
  92 + rack
  93 +
  94 +PLATFORMS
  95 + ruby
  96 +
  97 +DEPENDENCIES
  98 + closure-compiler
  99 + middleman
  100 + rspec
  101 + selenium-webdriver
14 Rakefile
... ... @@ -0,0 +1,14 @@
  1 +require 'rubygems'
  2 +require 'rspec/core/rake_task'
  3 +
  4 +desc "Run specs, run a specific spec with"
  5 +task :spec => [ "spec:default" ]
  6 +
  7 +SPEC_OPTS = ["--options", "./selenium/spec.opts"]
  8 +
  9 +namespace :spec do
  10 + RSpec::Core::RakeTask.new('default') do |t|
  11 + t.rspec_opts = SPEC_OPTS
  12 + t.pattern = 'selenium/*_spec.rb'
  13 + end
  14 +end
166 TODO
... ... @@ -1,35 +1,150 @@
1   -* [5/11] Further improvements
2   - - [X] Fix bugs in textileCompiler
3   - - [X] get specs running in Firefox
4   - - [X] W: check if selection is in preview before calculating states
5   - and doing things: Firefox select text -> h1 -> p -> all gone
6   - - [X] formatBlock should select multiple paragraphs
7   - - [X] refactor to use prototypes
8   - - [X] abstract getStates
  1 +* [17/29] Further improvements
9 2 - [-] publish project
10 3 - [ ] documentation
11 4 - [X] better static files handling:
12 5 - [X] package files
13 6 - [X] join and compress js files
14 7 - [X] push to github
15   - - [ ] update preview periodically (and track changes)
16   - - [ ] clean pasted text
17   - - [ ] Better Dialogs (for inspiration see gollum)
18   - - [ ] dialogs should be created on the fly
19   - - [ ] values for the selects should be defined by a method call
  8 + - [X] decouple preview and data-modes
  9 + - [-] wysiwyg
  10 + - [ ] Toolbar should work only if there is a range inside the preview
  11 + (also the state checking)
  12 + - [ ] expand selection to word boundaries, just as in textile
  13 + - [ ] join lists on backspace/entf between lists
  14 + - [ ] Firefox: fix checkState for one bold word
  15 + - [ ] br tags should be top level, because Border expects that
  16 + or alternatively change behavior of the node search within Border
  17 + This would be the approach with a future since there might be other
  18 + cases of nested nodes ( b>i, i>b would be different now, but should
  19 + be the same)
  20 + - [X] handle delete key
  21 + - [X] selecting all and pressing bold should bolden each (part of a) paragraph in turn
  22 + - [X] ignore special keyCodes on deletion
  23 + - [X] on keyup check if the content is still in a valid block tag
  24 + - [X] chrome problems
  25 + - [X] return at the end of a heading
  26 + - [X] backspace into another paragraph
  27 + this will keep the style of the backspaced paragraph with span tags
  28 + - [X] firefox problems
  29 + - [X] selecting all and deleting/overwriting it
  30 + overwriting with normal keys won't work
  31 + - [X] respect the top blockformat on overwriting
  32 + - [X] return at the end of a heading
  33 + - [X] enter in the middle of a heading does not move the rest of the heading down
  34 + - [X] selecting all by hand and deleting it leaves empty tags
  35 + this is a result of the current implementation of checkIfDeletedAll
  36 + - [ ] textile
  37 + - [ ] replace this.tag
  38 + - [ ] add close editor
  39 + - [ ] flexible toolbar, which stays on top
  40 + - [ ] support haml
  41 + - [ ] provide support for external mode select
  42 + - [-] Better Dialogs (for inspiration see gollum)
  43 + - [ ] display errors as tooltips
  44 + - [X] reset errors on open
  45 + - [X] enhancedTextfield: type, click clear, type again (no button), click cancel (button appears and button click did not work)
  46 + - [X] style clearButton + autocomplete
  47 + - [X] use errors
  48 + - [X] autocomplete select should trigger change (file bug)
  49 + - [X] support prompts in textfields
  50 + - [X] add a clear button to text fields
  51 + - [X] dialogs should be created on the fly
  52 + - [X] values for the selects should be defined by a method call if functions are defined
  53 + - [X] fields should share the combobox
20 54 - [ ] localization
21   - - [ ] different toolbars for different modes.
  55 + - [ ] spec with selenium
  56 + - [ ] spec press enter
  57 + - [ ] in list
  58 + - [ ] in heading
  59 + - [ ] spec press shift enter
  60 + - [ ] in list
  61 + - [ ] show window in different awesome tab
  62 + - [ ] use existing window (not supported in ruby bindings)
  63 + - [ ] fire native events (not supported in awesome, try in kde or windows)
  64 + - [ ] clean and parse pasted html
  65 + - [ ] handle blocktags within lists
  66 + - [X] list functionality
  67 + - [X] wysiwyg
  68 + - [X] button click
  69 + - [X] own
  70 + - [X] on
  71 + - [X] off
  72 + - [X] off in the middle should seperate the list
  73 + - [X] align
  74 + - [X] bold/italic
  75 + - [X] conversion to textile should handle linebreaks
  76 + - [X] link
  77 + - [X] image
  78 + - [X] paragraph
  79 + - [X] backspace out of list (also at the beginning of the div) should remove list
  80 + and replace it with a paragraph
  81 + - [X] ensure <br> only inside block tags (this is now handled during conversion.
  82 + this way html import is easier)
  83 + - [X] shift + enter throws error
  84 + - [X] enter in list (by default)
  85 + - [X] fix conversion
  86 + - [X] refactor selection api to mode
  87 + - [X] fix textile compiler
  88 + - [X] textile
  89 + - [X] shift enter in list
  90 + - [X] enter in list
  91 + - [X] button click
  92 + - [X] own
  93 + - [X] align
  94 + - [X] bold/italic
  95 + - [X] link
  96 + - [X] image
  97 + - [X] paragraph (ignore it)
  98 + - [X] create own selenium view template
  99 + - [X] load firebug into selenium window
  100 + - [X] fix selection on end of textfield
  101 + - [X] should not catch strg-r
  102 + - [X] fix joined file
  103 + - [X] generalize spec helpers
  104 + - [X] load editor from any html element
  105 + - [X] optimize hiding and showing. use least possible dom calls
  106 + - [X] dynamically change the toolbar. Each mode should define the
  107 + visible fields. If save is visible is defined by the
  108 + presence of the save setting. The order is defined in markupEditor and not
  109 + changeable. It would confuse the user otherwise
  110 + - [X] spec
  111 + - [X] add save button which calls a submit callback
  112 + - [X] basics
  113 + - [X] keep styles from the original html element
  114 + - [X] different toolbars for different modes.
  115 + - [X] Fix bugs in textileCompiler
  116 + - [X] get specs running in Firefox
  117 + - [X] W: check if selection is in preview before calculating states
  118 + and doing things: Firefox select text -> h1 -> p -> all gone
  119 + - [X] formatBlock should select multiple paragraphs
  120 + - [X] refactor to use prototypes
  121 + - [X] abstract getStates
  122 + - [X] update preview periodically (and track changes)
  123 + - [X] prevent link click follow
  124 + - [X] preview div should always have a p-tag inside
  125 + - [X] on mode change clear selection (select something in ta
  126 + -> preview -> ta selection should be gone)
  127 + - [X] fix rare bug: updating preview in preview mode should not write textile into preview
  128 + reproduce: load dev.html -> press any key -> wait
22 129
23   -* [0/2] Bugs
24   - - [ ] Select Paragraph Box -> up and down arrows don't change the
25   - text, but they should
26   - - [ ] pressing enter at the end of a heading sets the cursor outside any paragraph
  130 +* [1/3] Bugs
  131 + - [X] pressing enter at the end of a heading sets the cursor outside
  132 + any paragraph
  133 + - [ ] h1. * item1
  134 + does not compile right
  135 + - [ ] set cursor between _a and click italic two times -> not the
  136 + same situation as before. Such two clicks should be idempotent
27 137
28   -* [/] Consider
  138 +* [0/5] Consider
  139 + - [ ] Partial list change should either
  140 + change the whole list (currently wysiwyg)
  141 + change part off the list (currently textile and the norm)
29 142 - [ ] turn off url display in bottom (consider accessability, gollum
30 143 has the same construct)
31 144 - [ ] double click should work: use hover to change contentEditable
32 145 value and remember selection
  146 + - [ ] do not focus the textarea after toolbar action, so that the current select
  147 + is still selected and can be changed via the arrow keys
33 148 - [ ] remove conflicting markup in textile in one line (means if you
34 149 select a string with parts of it already marked bold and click the
35 150 unselected bold button, the parts should be marked as bold as a whole)
@@ -38,10 +153,15 @@
38 153
39 154 * List of Firefox Bugs
40 155
  156 +- Selecting everything and pressing delete removes all content within the wysiwyg area
41 157 - doubleclicking a word does not select the Word itself but some
42 158 whitespace left of it too
43   -- first paragraph in contentEditable cannot be changed
44   -- pressing enter at the end of a heading does not create a new p tag
45   - (webkit too)
  159 +- pressing enter at the end of a heading lands outside any tag
46 160 - justify* does only work with contentEditable = true on body
47   -- double click in textarea does not select the word if there is an element with contentEditable = true on the page
  161 +- double click in textarea does not select the word if there is an element with contentEditable = true on the page
  162 +
  163 +* List of Chrome Bugs
  164 +
  165 +- pressing enter at the end of a heading lands inside a div
  166 +- backspacing into a different paragraph type will keep the
  167 + style of the backspaced paragraph with span tags
5 config.rb
@@ -9,9 +9,8 @@ def text_field(name, options = {})
9 9 end
10 10 end
11 11
12   -with_layout :devLayout do
13   - page "/test.html"
14   - page "/dev.html"
  12 +with_layout :releaseLayout do
  13 + page "/index.html"
15 14 end
16 15
17 16 # Change the JS directory
16 join
... ... @@ -1,10 +1,22 @@
1 1 #!/usr/bin/ruby
2 2
3   -files = %w{markupEditor modes/textile/textileMode modes/textile/bruteForceCompiler modes/wysiwygMode}
  3 +files = %w{plugins/isValid markupEditor util plugins/enhanceTextfield plugins/combobox dialog modes/textile/textileMode modes/textile/bruteForceCompiler modes/wysiwygMode}
4 4
5 5 File.open('public/javascripts/joined.js','w') do |file|
6 6 files.each do |filename|
7   - file.puts File.open('public/javascripts/' + filename + '.js').read
  7 + regexp = /( # Match she-bang style C-comment
  8 + \/\*[!*] # Opening delimiter.
  9 + [^*]*\*+ # {normal*} Zero or more non-*, one or more *
  10 + (?: # Begin {(special normal*)*} construct.
  11 + [^*\/] # {special} a non-*, non-\/ following star.
  12 + [^*]*\*+ # More {normal*}
  13 + )* # Finish "Unrolling-the-Loop"
  14 + \/ # Closing delimiter.
  15 + | # Or
  16 + $\s*\/\/.* # One line comments
  17 + )/x
  18 +
  19 + file.puts File.open('public/javascripts/' + filename + '.js').read #.gsub(regexp, '')
8 20 end
9 21 end
10 22
139 public/javascripts/dialog.js
... ... @@ -0,0 +1,139 @@
  1 +(function($){
  2 + var callback;
  3 + $.fn.isValid.init();
  4 + function initDialog(dialogNode, fields, availableButtons){
  5 + var fieldsLength = fields.length, $form = dialogNode.find(":first-child");
  6 + dialogNode.dialog({
  7 + autoOpen: false,
  8 + width: 600,
  9 + close: function() {
  10 + if(callback.close){
  11 + callback.close();
  12 + }
  13 + for(i = 0; i < fieldsLength; i++){
  14 + fields[i].val('')
  15 + .removeAttr('checked')
  16 + .removeAttr('selected');
  17 + }
  18 + },
  19 + open: function(){
  20 + for(i = 0; i < fieldsLength; i++){
  21 + fields[i].change();
  22 + }
  23 + fields[0][0].setSelectionRange(0,0);
  24 + $form.isValid('reset');
  25 + }
  26 + });
  27 +
  28 + return {
  29 + dialog: function(task, cb){
  30 + if(cb){
  31 + callback = cb;
  32 + }
  33 + dialogNode.dialog(task);
  34 + },
  35 + find: function(query){ return dialogNode.find(query); },
  36 + selectButtons: function(buttonNames){
  37 + var buttons={},i=buttonNames.length;
  38 + while(i--){
  39 + buttons[buttonNames[i]] = availableButtons[buttonNames[i]];
  40 + }
  41 + dialogNode.dialog('option','buttons',buttons);
  42 + },
  43 + val: function(query,value){
  44 + this.find(query).val(value);
  45 + }
  46 + };
  47 + }
  48 +
  49 + var t10n = {
  50 + linkTitle: 'Link',
  51 + insertImageTitle: 'Image',
  52 + uri: 'Link',
  53 + uriPrompt: 'Enter or select link',
  54 + title: 'Title',
  55 + titlePrompt: 'Enter title',
  56 + imageUri: 'Image Source'
  57 + };
  58 +
  59 + function createDialog(name, fields){
  60 + var $dialogNode, proxy, i, fieldName, jQueryFunctions, method, args,
  61 + fieldsLength = fields.length,
  62 + $fields = [];
  63 +
  64 + $dialogNode = $('<div id=\"'+ name + '-dialog\" title=\"' +
  65 + t10n[name + "Title"] + '\"><form>');
  66 + var $form = $dialogNode.find(":first-child");
  67 +
  68 + for(i=0; i < fieldsLength; i++){
  69 + fieldName = fields[i][0];
  70 + jQueryFunctions = fields[i][1];
  71 +
  72 + $form.append(
  73 + '<label for=\"' + fieldName + '\">'+ t10n[fieldName] + '</label>'
  74 + );
  75 + $fields[i] = $('<input type=\"text\" class=\"' + fieldName + '\" name=\"' + fieldName + "\">")
  76 + .appendTo($form);
  77 +
  78 + if(jQueryFunctions){
  79 + for(method in jQueryFunctions){
  80 + if(jQueryFunctions.hasOwnProperty(method)){
  81 + args = jQueryFunctions[method];
  82 + $fields[i][method](args);
  83 + }
  84 + }
  85 + }
  86 + $fields[i].enhanceTextfield({prompt: t10n[fieldName+"Prompt"]});
  87 + }
  88 +
  89 + submit = function() {
  90 + var args = [],i;
  91 + for(i=0; i < fieldsLength; i++){
  92 + args[i] = $fields[i].submit().val();
  93 + }
  94 + if($form.isValid()){
  95 + callback.submit.apply(this,args);
  96 + $dialogNode.dialog("close");
  97 + }
  98 + };
  99 +
  100 + proxy = initDialog($dialogNode, $fields, {
  101 + Create: submit,
  102 + Update: submit,
  103 + Remove: function(){
  104 + callback.remove();
  105 + $dialogNode.dialog("close");
  106 + },
  107 + Cancel: function() {
  108 + $dialogNode.dialog("close");
  109 + }
  110 + });
  111 +
  112 + return function(buttonNames){
  113 + proxy.selectButtons(buttonNames);
  114 + return proxy;
  115 + };
  116 + }
  117 +
  118 + ME.dialog = {
  119 + link: createDialog('link',[
  120 + ['title', {
  121 + required: true
  122 + }],
  123 + ['uri', {
  124 + combobox: {key: 'uri'},
  125 + required: true
  126 + }]
  127 + ]),
  128 + insertImage: createDialog('insertImage', [
  129 + ['imageUri', {
  130 + combobox: {key: 'imageUri'},
  131 + required: true
  132 + }],
  133 + ['title'],
  134 + ['uri', {
  135 + combobox: {key: 'uri'}
  136 + }]
  137 + ])
  138 + };
  139 +})(jQuery);
99 public/javascripts/joined-min.js
... ... @@ -1,39 +1,60 @@
1   -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=
2   -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)&&
3   -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())},
4   -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=
5   -!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();
6   -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,
7   -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=
8   -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")?
9   -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();
10   -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()},
11   -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",
12   -[["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);
13   -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("#"+
14   -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",
15   -function(b){return[b.find("input.imageUri"),b.find("input.title"),b.find("input.uri")]})}}(jQuery);$(document).ready(function(){$("textarea.markup").initMarkupEditor({})});
16   -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*/,
17   -"$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)?
18   -(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,
19   -"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();
20   -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+"!";
21   -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,
22   -"\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,
23   -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,
24   -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-
25   -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)}}});
26   -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]!==
27   -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+=
28   -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+
29   -">");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}}}(),
30   -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")?
31   -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();
32   -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()}}}();
33   -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,
34   -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")}},
35   -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,
36   -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")),
37   -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=[];
38   -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"&&
39   -(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()}}});
  1 +(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=
  2 +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=
  3 +{errorClass:"error",errorText:"{label} is a required field.",emailErrorText:"Please enter a valid {label}",errorElement:"strong",removeLabelChar:"*"}})(jQuery);
  4 +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=
  5 +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()},
  6 +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);
  7 +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)},
  8 +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,
  9 +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";
  10 +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));
  11 +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,
  12 +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="'+
  13 +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!==
  14 +"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+
  15 +" 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=
  16 +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},
  17 +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");
  18 +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);
  19 +(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);
  20 +(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&&
  21 +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);
  22 +(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)+")(?![^<>]*>)(?![^&;]+;)",
  23 +"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")?
  24 +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);
  25 +(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)},
  26 +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();
  27 +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}]]),
  28 +insertImage:m("insertImage",[["imageUri",{combobox:{key:"imageUri"},required:!0}],["title"],["uri",{combobox:{key:"uri"}}]])}})(jQuery);
  29 +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;
  30 +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<
  31 +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):
  32 +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,
  33 +"#")}},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,
  34 +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("")},
  35 +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"?"*":
  36 +"#")+" $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,
  37 +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,
  38 +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,
  39 +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])}}}});
  40 +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+
  41 +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=
  42 +[{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,
  43 +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;
  44 +/^([ ]+|<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=
  45 +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")?
  46 +(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",
  47 +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()}}}();
  48 +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,
  49 +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-
  50 +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)):
  51 +/(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);
  52 +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),
  53 +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),
  54 +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,
  55 +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="'+
  56 +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),
  57 +"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()},
  58 +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),
  59 +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,
  60 +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()}}});
1,719 public/javascripts/joined.js
... ... @@ -1,11 +1,90 @@
1   -ME = function ($) {
  1 +(function($) {
  2 + // TODO cache fields
  3 + var opts, errorMsgType, initialized, methods;
  4 +
  5 + methods = {
  6 + check: function(options) {
  7 + var $this = this, valid = true;
  8 + //Hide any errors that are already showing
  9 + $this.find(opts.errorElement + '.' + opts.errorClass).remove();
  10 + $this.find(':input.' + opts.inputErrorClass).removeClass(opts.inputErrorClass);
  11 +
  12 + //Get all the required inputs
  13 + $this.find(':input.required').each(function() {
  14 + var $input = $(this),
  15 + fieldValue = $.trim($input.val()),
  16 + labelText = $input.siblings('label').text().replace(opts.removeLabelChar, ''),
  17 + errorMsg = '';
  18 +
  19 + //Check if it's empty or an invalid email
  20 + if(fieldValue === '') {
  21 + errorMsg = hasLabelPlaceholder ? errorMsg = opts.errorText.replace('{label}',labelText) : errorMsg = opts.errorText;
  22 + valid = false;
  23 + } else if($input.hasClass('email')) {
  24 + if(!(/^([_a-z0-9-]+)(\.[_a-z0-9-]+)*@([a-z0-9-]+)(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/.test(fieldValue))) {
  25 + errorMsg = hasLabelPlaceholder ? errorMsg = opts.emailErrorText.replace('{label}',labelText) : errorMsg = opts.emailErrorText;
  26 + valid = false;
  27 + }
  28 + }
  29 +
  30 + //If there is an error, display it
  31 + if(errorMsg !== '') {
  32 + $input.parent().addClass(opts.errorClass);
  33 + //$input.addClass(opts.inputErrorClass).after('<'+opts.errorElement+' class="'+opts.errorClass+'">' + errorMsg + '</'+opts.errorElement+'>');
  34 + }
  35 + });
2 36
3   - var globalSettings = {}, availableModes = {}, toolbarItems = {};
  37 + return valid;
  38 + },
  39 + reset: function(){
  40 + return this.find(':input.required').each(function(){
  41 + $(this).parent().removeClass(opts.errorClass);
  42 + });
  43 + }
  44 + };
4 45
  46 + $.fn.isValid = function(method) {
  47 + if (!methods[method]) {
  48 + method = 'check';
  49 + }
  50 + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  51 + };
  52 +
  53 + $.fn.isValid.init = function(options){
  54 + opts = $.extend({}, $.fn.isValid.defaults, options);
  55 + hasLabelPlaceholder = opts.errorText.indexOf("{label}") > -1;
  56 + };
  57 +
  58 + // default options
  59 + $.fn.isValid.defaults = {
  60 + errorClass: 'error',
  61 + errorText: '{label} is a required field.',
  62 + emailErrorText: 'Please enter a valid {label}',
  63 + errorElement: 'strong',
  64 + removeLabelChar: '*'
  65 + };
  66 +})(jQuery);
  67 +var ME = function ($) {
  68 + var globalSettings = {}, availableModes = {}, toolbarItems = {}, toolbarHTML = "",
  69 + availableItems = ['bold','italic','alignLeft','alignCenter','alignRight','unorderedList','orderedList','link','insertImage','save','changeMode','formatBlock'],
  70 + globalItems = [],
  71 + emptyFunction = $.noop;
  72 +
  73 + /**
  74 + * Create a new Mode
  75 + * @constructor
  76 + * @param {Object} customFunctions these functions will be added to the Mode object
  77 + */
5 78 function Mode(customFunctions){
6 79 $.extend(this, customFunctions);
  80 + this.prototype = Mode.prototype;
7 81 }
8 82 Mode.prototype = {
  83 + /**
  84 + * This loads the mode for the current Editor
  85 + * TODO this is ugly. why should every editor have every mode?
  86 + * @param {Editor} editor
  87 + */
9 88 load: function(editor) {
10 89 this.editor = editor;
11 90 this.htmlDiv = editor.htmlDiv;
@@ -13,27 +92,76 @@ ME = function ($) {
13 92
14 93 console.log("loaded Mode " + this.name);
15 94 },
16   - getStates: $.noop,
  95 + /**
  96 + * This is a placeholder. Each mode should define its version
  97 + * @returns {Object} an object that describes the states
  98 + * @see Toolbar#setActive
  99 + * @api
  100 + */
  101 + getStates: emptyFunction,
  102 + /**
  103 + * The default pressed function to handle key combos (shift + x)
  104 + */
  105 + pressed: function(keyCode){
  106 + if(keyCode === 16){
  107 + this.holdShift = true;
  108 + }
  109 + },
  110 + /**
  111 + * Handle special keys (shift press) to deal with key combos
  112 + */
  113 + released: function(keyCode){
  114 + if(keyCode === 16){
  115 + this.holdShift = false;
  116 + }
  117 + },
  118 + /**
  119 + * Activate this mode for the editor
  120 + */
17 121 activate: function() {
18 122 if(this.htmlDiv.is(":empty")) {
19 123 this.updatePreview();
20 124 } else {
21 125 this.updateTextArea();
22 126 }
  127 + this.editor.toolbar.loadModeToolbar();
23 128 this.afterActivation();
24 129 },
  130 + /**
  131 + * Update the preview html div with the html representation of the mode
  132 + */
25 133 updatePreview: function() {
26 134 console.log("updating preview in Mode " + this.name);
27   - this.htmlDiv.html(this.toHTML());
  135 + this.htmlDiv.html(this.toHTML() || "<p>&nbsp;</p>");
28 136 },
  137 + /**
  138 + * Update the textarea with the text representation of the mode
  139 + */
29 140 updateTextArea: function() {
30 141 console.log("updating TA in Mode " + this.name);
31 142 this.textArea.val(this.toText());
32 143 },
  144 + /**
  145 + * Run after activation. Default behaviour for text modes. wysiwyg mode has
  146 + * its own version
  147 + */
33 148 afterActivation: function() {
34   - this.textArea.show();
  149 + this.textArea
  150 + .parent().show()
  151 + .find(":first-child").focus()[0]
  152 + .setSelectionRange(0,0);
35 153 this.htmlDiv.attr("contentEditable",false);
36 154 },
  155 + /**
  156 + * Iterates over the given nodes and builds the state object which defines
  157 + * the active buttons
  158 + *
  159 + * CONSIDER make currentNodes a property
  160 + * @param {Array} nodes The active nodes (e.g. a,li). The highest node is on the right
  161 + * @param {Object} currentNodes A reference that will be filled with important nodes (e.g. a) to be used by the mode
  162 + *
  163 + * @returns {Object} The state object
  164 + */
37 165 buildStateObject: function(nodes, currentNodes){
38 166 function getTag(node){
39 167 return node.tag ? node.tag : node.nodeName.toLowerCase();
@@ -53,56 +181,229 @@ ME = function ($) {
53 181 case "i":
54 182 states.italic = true;
55 183 break;
56   - case "li":
57   - break;
58   - case "ol":
59   - states.insertOrderedList = true;
60   - states.insertUnorderedList = false;
61   - break;
62 184 case "b":
63 185 states.bold = true;
64 186 break;
  187 + case "ol":
  188 + states.orderedList = true;
  189 + states.unorderedList = false;
  190 + states.formatBlock = 'disable';
  191 + states.alignLeft = 'disable';
  192 + states.alignRight = 'disable';
  193 + states.alignCenter = 'disable';
  194 + currentNodes.list = node;
  195 + break;
65 196 case "ul":
66   - states.insertOrderedList = false;
67   - states.insertUnorderedList = true;
  197 + states.orderedList = false;
  198 + states.unorderedList = true;
  199 + states.formatBlock = 'disable';
  200 + states.alignLeft = 'disable';
  201 + states.alignRight = 'disable';
  202 + states.alignCenter = 'disable';
  203 + currentNodes.list = node;
  204 + break;
  205 + case "li":
68 206 break;
69 207 default:
70 208 states.formatBlock = getTag(node);
  209 + currentNodes.block = node;
71 210 break;
72 211 }
73 212 }
74 213 return states;
  214 + },
  215 + /**
  216 + * @param {String} [boundary] The right and left boundary the
  217 + * selection should be extended to
  218 + * @returns {String} The currently selected string
  219 + */
  220 + getSelection: function(boundary) {
  221 + var textArea = this.textArea, text = textArea.val(), boundaryPosition, subString;
  222 + textArea.focus();
  223 +
  224 + // gecko & webkit
  225 + this.scrollPosition = textArea.scrollTop;
  226 + this.selectionStart = textArea[0].selectionStart;
  227 + this.selectionEnd = textArea[0].selectionEnd;
  228 +
  229 + if(text[this.selectionEnd-1] === "\n"){
  230 + this.selectionEnd -= 1;
  231 + }
  232 +
  233 + if(boundary) {
  234 + // find left boundary
  235 + boundaryPosition = Math.max(text.lastIndexOf(boundary, this.selectionStart), text.lastIndexOf("\n", this.selectionStart));
  236 + if(boundaryPosition !== -1) {
  237 + this.selectionStart = boundaryPosition + 1;
  238 + } else {
  239 + this.selectionStart = 0;
  240 + }
  241 +
  242 + // find right boundary, first limit the text to the
  243 + // next new line
  244 + boundaryPosition = text.indexOf("\n", this.selectionEnd);
  245 + if(boundaryPosition === -1) {
  246 + subString = text.slice(this.selectionStart);
  247 + } else {
  248 + subString = text.slice(this.selectionStart, boundaryPosition);
  249 + }
  250 +
  251 + // Then find the next boundary
  252 + boundaryPosition = 0;
  253 + do{
  254 + boundaryPosition = subString.indexOf(boundary, boundaryPosition + 1);
  255 + } while(boundaryPosition !== -1 && this.selectionEnd > this.selectionStart + boundaryPosition);
  256 +
  257 + // when it doesn't exist, extend the selection to the
  258 + // paragraph end
  259 + if(boundaryPosition === -1) {
  260 + boundaryPosition = subString.length;
  261 + }
  262 + this.selectionEnd = this.selectionStart + boundaryPosition;
  263 + }
  264 + this.selection = text.slice(this.selectionStart, this.selectionEnd);
  265 + return this.selection;
  266 + },
  267 + /**
  268 + * Replace the current selection with the given string
  269 + * @param {String} string The replacement string
  270 + * @param {Boolean} collapseToStart If the selection should collapse
  271 + */
  272 + replaceSelection: function(string, collapseToStart) {
  273 + var textArea = this.textArea,
  274 + newSelectionStart = this.selectionStart,
  275 + newSelectionEnd = this.selectionStart + string.length;
  276 +
  277 + // gecko & webkit
  278 + textArea.val(textArea.val().slice(0, this.selectionStart) + string + textArea.val().slice(this.selectionEnd, textArea.val().length));
  279 +
  280 + // move caret gecko
  281 + if(collapseToStart === true){
  282 + newSelectionEnd = newSelectionStart;
  283 + } else if(collapseToStart === false){
  284 + newSelectionStart = newSelectionEnd;
  285 + }
  286 +
  287 + textArea[0].setSelectionRange(newSelectionStart, newSelectionEnd);
  288 + textArea.focus();
  289 + },
  290 + /**
  291 + * Extend the right selection with a regexp. Everything matched will be added
  292 + * to the selection. Useful for special cases like toggling parts of a bolded
  293 + * String in textile
  294 + *
  295 + * @param {Regexp} regexp The regexp
  296 + *
  297 + * @example
  298 + * extendRightSelection(/ +/)
  299 + */
  300 + extendRightSelection: function(regexp){
  301 + var match;
  302 + regexp = new RegExp(regexp.source,'g');
  303 + regexp.lastIndex = this.selectionEnd;
  304 + match = regexp.exec(this.textArea.val());
  305 +
  306 + if(match && regexp.lastIndex == this.selectionEnd + match[0].length){
  307 + this.selectionEnd += match[0].length;
  308 + return match[0];
  309 + }
  310 + },
  311 + /**
  312 + * Extend the left selection with a regexp. Everything matched will be added
  313 + * to the selection. Useful for special cases like toggling parts of a bolded
  314 + * String in textile
  315 + *
  316 + * @param {Regexp} regexp The regexp.
  317 + *
  318 + * @example
  319 + * extendLeftSelection(/[ .]+/)
  320 + */
  321 + extendLeftSelection: function(regexp){
  322 + var match, substring = this.textArea.val().slice(0,this.selectionStart);
  323 + regexp = new RegExp(regexp.source + "$");
  324 + match = regexp.exec(substring);
  325 +
  326 + if(match){
  327 + this.selectionStart -= match[0].length;
  328 + return match[0];
  329 + }
75 330 }
76 331 };
77 332
78   - function ToolbarButton(name){
  333 + /**
  334 + * Create a button for the toolbar
  335 + * @constructor
  336 + *
  337 + * @param {String} name The class name of the button
  338 + * @param {Function} [clicked] The default action if the button is clicked
  339 + */
  340 + function ToolbarButton(name, clicked){
79 341 this.name = name;
  342 + if(clicked){
  343 + this.clicked = clicked;
  344 + globalItems.push(name);
  345 + }
80 346 }
81 347 ToolbarButton.prototype = {
  348 + /**
  349 + * @returns {String} A html string of the button
  350 + */
82 351 getButton: function() {
83 352 return '<a href="#" class=\"'+ this.name +'" ><span>'+ this.name +'</span></a>';
84 353 }
85 354 };
86 355
87   - function ToolbarSelect(name, options){
88   - this.name = name;
  356 + /**
  357 + * Create a select for the toolbar
  358 + * @constructor
  359 + *
  360 + * @param {String} name The class name of the button
  361 + * @param {Array} [options] The options of the select
  362 + * @param {Function} [clicked] The default action if the button is clicked
  363 + */
  364 + function ToolbarSelect(name, options, clicked){
  365 + ToolbarButton.apply(this, [name, clicked]);
89 366 this.options = options || [];
90 367 }
91 368 ToolbarSelect.prototype = {
  369 + /**
  370 + * @returns {String} A html string of the button
  371 + */
92 372 getButton: function() {
93   - var select = $("<select class=\"" + this.name + "\"></select>"),
  373 + var select = "<select class=\"" + this.name + "\">",
94 374 optionsLength = this.options.length,
95 375 i;
96 376
97 377 select.className = this.name;
98 378
99 379 for (i = 0; i < optionsLength; i += 1){
100   - $("<option/>").val(this.options[i][0]).text(this.options[i][1]).appendTo(select);
  380 + select += "<option value=\"" + this.options[i][0] + "\">" + this.options[i][1] + "</option>";
101 381 }
102   - return select;
  382 + return select + "</select>";
103 383 }
104 384 }; // end ToolbarSelect
  385 +
  386 + function getToolbarHTML(){
  387 + var i,l, item;
  388 +
  389 + if(!toolbarHTML){
  390 + for(i=0,l=availableItems.length; i < l ; i++){
  391 + item = toolbarItems[availableItems[i]];
  392 + if(item){
  393 + toolbarHTML += item.getButton();
  394 + }
  395 + }
  396 + }
  397 +
  398 + return toolbarHTML;
  399 + }
105 400
  401 + /**
  402 + * Create a toolbar for an editor. Every editor has its own toolbar, since the
  403 + * items of the toolbar can be defined on a per editor basis (save callback)
  404 + *
  405 + * @constructor
  406 + */
106 407 function Toolbar(editor) {
107 408
108 409 // init Toolbar Items
@@ -114,14 +415,10 @@ ME = function ($) {
114 415 this.htmlDiv = editor.htmlDiv;
115 416 this.editor = editor;
116 417 this.div = toolbarDiv;
  418 +
  419 + toolbarDiv.html(getToolbarHTML());
117 420
118   - for(item in toolbarItems) {
119   - if(toolbarItems.hasOwnProperty(item)) {
120   - toolbarDiv.append(toolbarItems[item].getButton());
121   - }
122   - }
123   -
124   - toolbarDiv.mouseup(function(e) { // buttons
  421 + toolbarDiv.mouseup(function(e) { // Trigger on button click
125 422 var target = e.target;
126 423
127 424 if(!(/(select|option)/i).test(target.nodeName)) {
@@ -130,171 +427,159 @@ ME = function ($) {
130 427 if(/span/i.test(target.nodeName)) {
131 428 target = target.parentNode;
132 429 }
  430 + if(target.disabled){
  431 + // TODO handle focus somewhere
  432 + if(editor.is('wysiwyg')){
  433 + editor.htmlDiv.focus();
  434 + } else {
  435 + editor.textArea.focus();
  436 + }
  437 + return false;
  438 + }
133 439 var action = target.className;
134 440
135 441 action = action.split(" ")[0];
136 442 that.runAction(action, target);
137   - editor.checkState(); // TODO this does not work with dialogs
  443 + // TODO this does not work with dialogs
  444 + // in dialogs this gets set manually, but perhaps there is a
  445 + // more general way?
  446 + editor.checkState();
138 447 }
139   - return false;
140   - }).change(function(e) { // select lists
  448 + }).change(function(e) { // trigger on select change
141 449 var target = e.target;
142 450 that.runAction(target.className, target);
143 451 return false;
144   - });
  452 + }).click(function(e){return false; }); //
145 453
146 454 editor.container.prepend(toolbarDiv);
147 455 } // end initToolbar
148 456
149 457 Toolbar.prototype = {
150   - getDivSelection: function() {
151   - this.htmlDiv.focus(