kiteditor 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. data/LICENSE +25 -0
  2. data/app/controllers/mercury/images_controller.rb +5 -0
  3. data/app/controllers/mercury_controller.rb +34 -0
  4. data/app/models/mercury/image.rb +17 -0
  5. data/app/views/layouts/mercury.html.erb +33 -0
  6. data/app/views/layouts/popup.html.haml +9 -0
  7. data/app/views/mercury/images/_list.html.haml +22 -0
  8. data/app/views/mercury/images/index.html.haml +4 -0
  9. data/app/views/mercury/images/index.js.erb +2 -0
  10. data/app/views/mercury/lightviews/about.html +11 -0
  11. data/app/views/mercury/modals/character.html +255 -0
  12. data/app/views/mercury/modals/htmleditor.html +13 -0
  13. data/app/views/mercury/modals/link.html +94 -0
  14. data/app/views/mercury/modals/media.html +1 -0
  15. data/app/views/mercury/modals/table.html +84 -0
  16. data/app/views/mercury/palettes/backcolor.html +73 -0
  17. data/app/views/mercury/palettes/forecolor.html +73 -0
  18. data/app/views/mercury/panels/history.html +3 -0
  19. data/app/views/mercury/panels/notes.html +3 -0
  20. data/app/views/mercury/panels/snippets.html +12 -0
  21. data/app/views/mercury/selects/formatblock.html +11 -0
  22. data/app/views/mercury/selects/style.html +5 -0
  23. data/app/views/mercury/snippets/example/options.html.erb +34 -0
  24. data/app/views/mercury/snippets/example/preview.html.erb +1 -0
  25. data/config/engine.rb +44 -0
  26. data/db/migrate/20110526035601_create_mercury_images.rb +11 -0
  27. data/features/loading/loading.feature +22 -0
  28. data/features/loading/navigating.feature +77 -0
  29. data/features/loading/user_interface.feature +67 -0
  30. data/features/regions/editable/advanced_editing.feature +0 -0
  31. data/features/regions/editable/basic_editing.feature +195 -0
  32. data/features/regions/editable/inserting_links.feature +98 -0
  33. data/features/regions/editable/inserting_media.feature +110 -0
  34. data/features/regions/editable/inserting_snippets.feature +102 -0
  35. data/features/regions/editable/inserting_special_characters.feature +24 -0
  36. data/features/regions/editable/inserting_tables.feature +109 -0
  37. data/features/regions/editable/pasting.feature +0 -0
  38. data/features/regions/editable/uploading_images.feature +0 -0
  39. data/features/regions/markupable/advanced_editing.feature +0 -0
  40. data/features/regions/markupable/basic_editing.feature +0 -0
  41. data/features/regions/markupable/inserting_links.feature +0 -0
  42. data/features/regions/markupable/inserting_media.feature +0 -0
  43. data/features/regions/markupable/inserting_snippets.feature +0 -0
  44. data/features/regions/markupable/inserting_special_characters.feature +0 -0
  45. data/features/regions/markupable/inserting_tables.feature +0 -0
  46. data/features/regions/markupable/uploading_images.feature +0 -0
  47. data/features/regions/snippetable/advanced_editing.feature +0 -0
  48. data/features/regions/snippetable/basic_editing.feature +0 -0
  49. data/features/regions/snippetable/inserting_snippets.feature +0 -0
  50. data/features/saving/saving.feature +33 -0
  51. data/features/step_definitions/debug_steps.rb +14 -0
  52. data/features/step_definitions/mercury_steps.rb +438 -0
  53. data/features/step_definitions/web_steps.rb +211 -0
  54. data/features/support/env.rb +46 -0
  55. data/features/support/mercury_contents.rb +25 -0
  56. data/features/support/mercury_selectors.rb +148 -0
  57. data/features/support/paths.rb +38 -0
  58. data/features/support/selectors.rb +44 -0
  59. data/lib/generators/mercury/install/install_generator.rb +49 -0
  60. data/lib/generators/mercury/install/templates/mongoid_paperclip_image.rb +17 -0
  61. data/lib/mercury/authentication.rb +8 -0
  62. data/lib/mercury-rails.rb +3 -0
  63. data/spec/javascripts/mercury/dialog_spec.js.coffee +281 -0
  64. data/spec/javascripts/mercury/dialogs/backcolor_spec.js.coffee +37 -0
  65. data/spec/javascripts/mercury/dialogs/forecolor_spec.js.coffee +37 -0
  66. data/spec/javascripts/mercury/dialogs/formatblock_spec.js.coffee +25 -0
  67. data/spec/javascripts/mercury/dialogs/snippetpanel_spec.js.coffee +30 -0
  68. data/spec/javascripts/mercury/dialogs/style_spec.js.coffee +25 -0
  69. data/spec/javascripts/mercury/history_buffer_spec.js.coffee +76 -0
  70. data/spec/javascripts/mercury/lightview_spec.js.coffee +497 -0
  71. data/spec/javascripts/mercury/mercury_spec.js.coffee +132 -0
  72. data/spec/javascripts/mercury/modal_spec.js.coffee +504 -0
  73. data/spec/javascripts/mercury/modals/htmleditor_spec.js.coffee +30 -0
  74. data/spec/javascripts/mercury/modals/insertcharacter_spec.js.coffee +29 -0
  75. data/spec/javascripts/mercury/modals/insertlink_spec.js.coffee +220 -0
  76. data/spec/javascripts/mercury/modals/insertmedia_spec.js.coffee +167 -0
  77. data/spec/javascripts/mercury/modals/insertsnippet_spec.js.coffee +52 -0
  78. data/spec/javascripts/mercury/modals/inserttable_spec.js.coffee +160 -0
  79. data/spec/javascripts/mercury/native_extensions_spec.js.coffee +60 -0
  80. data/spec/javascripts/mercury/page_editor_spec.js.coffee +750 -0
  81. data/spec/javascripts/mercury/palette_spec.js.coffee +49 -0
  82. data/spec/javascripts/mercury/panel_spec.js.coffee +185 -0
  83. data/spec/javascripts/mercury/region_spec.js.coffee +298 -0
  84. data/spec/javascripts/mercury/regions/editable_spec.js.coffee +561 -0
  85. data/spec/javascripts/mercury/regions/markupable_spec.js.coffee +367 -0
  86. data/spec/javascripts/mercury/regions/snippetable_spec.js.coffee +370 -0
  87. data/spec/javascripts/mercury/select_spec.js.coffee +49 -0
  88. data/spec/javascripts/mercury/snippet_spec.js.coffee +245 -0
  89. data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +184 -0
  90. data/spec/javascripts/mercury/statusbar_spec.js.coffee +150 -0
  91. data/spec/javascripts/mercury/table_editor_spec.js.coffee +194 -0
  92. data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +90 -0
  93. data/spec/javascripts/mercury/toolbar.button_spec.js.coffee +360 -0
  94. data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +118 -0
  95. data/spec/javascripts/mercury/toolbar_spec.js.coffee +222 -0
  96. data/spec/javascripts/mercury/tooltip_spec.js.coffee +186 -0
  97. data/spec/javascripts/mercury/uploader_spec.js.coffee +514 -0
  98. data/spec/javascripts/spec_helper.js +513 -0
  99. data/spec/javascripts/templates/mercury/dialog.html +2 -0
  100. data/spec/javascripts/templates/mercury/dialogs/backcolor.html +5 -0
  101. data/spec/javascripts/templates/mercury/dialogs/forecolor.html +5 -0
  102. data/spec/javascripts/templates/mercury/dialogs/formatblock.html +3 -0
  103. data/spec/javascripts/templates/mercury/dialogs/snippetpanel.html +16 -0
  104. data/spec/javascripts/templates/mercury/dialogs/style.html +3 -0
  105. data/spec/javascripts/templates/mercury/lightview.html +13 -0
  106. data/spec/javascripts/templates/mercury/modal.html +13 -0
  107. data/spec/javascripts/templates/mercury/modals/htmleditor.html +5 -0
  108. data/spec/javascripts/templates/mercury/modals/insertcharacter.html +5 -0
  109. data/spec/javascripts/templates/mercury/modals/insertlink.html +30 -0
  110. data/spec/javascripts/templates/mercury/modals/insertmedia.html +35 -0
  111. data/spec/javascripts/templates/mercury/modals/insertsnippet.html +6 -0
  112. data/spec/javascripts/templates/mercury/modals/inserttable.html +27 -0
  113. data/spec/javascripts/templates/mercury/page_editor.html +35 -0
  114. data/spec/javascripts/templates/mercury/palette.html +16 -0
  115. data/spec/javascripts/templates/mercury/panel.html +16 -0
  116. data/spec/javascripts/templates/mercury/region.html +2 -0
  117. data/spec/javascripts/templates/mercury/regions/editable.html +3 -0
  118. data/spec/javascripts/templates/mercury/regions/snippetable.html +4 -0
  119. data/spec/javascripts/templates/mercury/select.html +16 -0
  120. data/spec/javascripts/templates/mercury/snippet.html +1 -0
  121. data/spec/javascripts/templates/mercury/snippet_toolbar.html +16 -0
  122. data/spec/javascripts/templates/mercury/statusbar.html +8 -0
  123. data/spec/javascripts/templates/mercury/table_editor.html +65 -0
  124. data/spec/javascripts/templates/mercury/toolbar.button.html +64 -0
  125. data/spec/javascripts/templates/mercury/toolbar.button_group.html +9 -0
  126. data/spec/javascripts/templates/mercury/toolbar.expander.html +18 -0
  127. data/spec/javascripts/templates/mercury/toolbar.html +11 -0
  128. data/spec/javascripts/templates/mercury/tooltip.html +12 -0
  129. data/spec/javascripts/templates/mercury/uploader.html +11 -0
  130. data/vendor/assets/images/mercury/button.png +0 -0
  131. data/vendor/assets/images/mercury/close.png +0 -0
  132. data/vendor/assets/images/mercury/default-snippet.png +0 -0
  133. data/vendor/assets/images/mercury/loading-dark.gif +0 -0
  134. data/vendor/assets/images/mercury/loading-light.gif +0 -0
  135. data/vendor/assets/images/mercury/missing-image.png +0 -0
  136. data/vendor/assets/images/mercury/search-icon.png +0 -0
  137. data/vendor/assets/images/mercury/temp-logo.png +0 -0
  138. data/vendor/assets/images/mercury/toolbar/editable/buttons.png +0 -0
  139. data/vendor/assets/images/mercury/toolbar/primary/_expander.png +0 -0
  140. data/vendor/assets/images/mercury/toolbar/primary/_pressed.png +0 -0
  141. data/vendor/assets/images/mercury/toolbar/primary/historypanel.png +0 -0
  142. data/vendor/assets/images/mercury/toolbar/primary/insertcharacter.png +0 -0
  143. data/vendor/assets/images/mercury/toolbar/primary/insertlink.png +0 -0
  144. data/vendor/assets/images/mercury/toolbar/primary/insertmedia.png +0 -0
  145. data/vendor/assets/images/mercury/toolbar/primary/inserttable.png +0 -0
  146. data/vendor/assets/images/mercury/toolbar/primary/inspectorpanel.png +0 -0
  147. data/vendor/assets/images/mercury/toolbar/primary/notespanel.png +0 -0
  148. data/vendor/assets/images/mercury/toolbar/primary/preview.png +0 -0
  149. data/vendor/assets/images/mercury/toolbar/primary/redo.png +0 -0
  150. data/vendor/assets/images/mercury/toolbar/primary/save.png +0 -0
  151. data/vendor/assets/images/mercury/toolbar/primary/snippetpanel.png +0 -0
  152. data/vendor/assets/images/mercury/toolbar/primary/undo.png +0 -0
  153. data/vendor/assets/images/mercury/toolbar/snippetable/buttons.png +0 -0
  154. data/vendor/assets/javascripts/mercury/dependencies/jquery-1.7.js +9300 -0
  155. data/vendor/assets/javascripts/mercury/dependencies/jquery-ui-1.8.13.custom.js +1328 -0
  156. data/vendor/assets/javascripts/mercury/dependencies/jquery-ui-1.8.18.custom.min.js +356 -0
  157. data/vendor/assets/javascripts/mercury/dependencies/jquery-ui.1.8.13.custom.min.js +356 -0
  158. data/vendor/assets/javascripts/mercury/dependencies/jquery.additions.js +206 -0
  159. data/vendor/assets/javascripts/mercury/dependencies/jquery.htmlClean.js +527 -0
  160. data/vendor/assets/javascripts/mercury/dependencies/liquidmetal.js +88 -0
  161. data/vendor/assets/javascripts/mercury/dependencies/showdown.js +1340 -0
  162. data/vendor/assets/javascripts/mercury/dialog.js.coffee +159 -0
  163. data/vendor/assets/javascripts/mercury/dialogs/backcolor.js.coffee +6 -0
  164. data/vendor/assets/javascripts/mercury/dialogs/forecolor.js.coffee +6 -0
  165. data/vendor/assets/javascripts/mercury/dialogs/formatblock.js.coffee +4 -0
  166. data/vendor/assets/javascripts/mercury/dialogs/snippetpanel.js.coffee +10 -0
  167. data/vendor/assets/javascripts/mercury/dialogs/style.js.coffee +4 -0
  168. data/vendor/assets/javascripts/mercury/finalize.js.coffee +3 -0
  169. data/vendor/assets/javascripts/mercury/history_buffer.js.coffee +30 -0
  170. data/vendor/assets/javascripts/mercury/lightview.js.coffee +205 -0
  171. data/vendor/assets/javascripts/mercury/locales/da.locale.js.coffee +0 -0
  172. data/vendor/assets/javascripts/mercury/locales/de.locale.js.coffee +206 -0
  173. data/vendor/assets/javascripts/mercury/locales/es.locale.js.coffee +211 -0
  174. data/vendor/assets/javascripts/mercury/locales/example.local.js.coffee +211 -0
  175. data/vendor/assets/javascripts/mercury/locales/fr.locale.js.coffee +211 -0
  176. data/vendor/assets/javascripts/mercury/locales/it.locale.js.coffee +208 -0
  177. data/vendor/assets/javascripts/mercury/locales/ko.local.js.coffee +206 -0
  178. data/vendor/assets/javascripts/mercury/locales/nl.locale.js.coffee +206 -0
  179. data/vendor/assets/javascripts/mercury/locales/pt.locale.js.coffee +211 -0
  180. data/vendor/assets/javascripts/mercury/locales/sv.local.js.coffee +209 -0
  181. data/vendor/assets/javascripts/mercury/locales/swedish_chef.locale.js.coffee +213 -0
  182. data/vendor/assets/javascripts/mercury/mercury.js.coffee +109 -0
  183. data/vendor/assets/javascripts/mercury/modal.js.coffee +204 -0
  184. data/vendor/assets/javascripts/mercury/modals/htmleditor.js.coffee +11 -0
  185. data/vendor/assets/javascripts/mercury/modals/insertcharacter.js.coffee +4 -0
  186. data/vendor/assets/javascripts/mercury/modals/insertlink.js.coffee +95 -0
  187. data/vendor/assets/javascripts/mercury/modals/insertmedia.js.coffee +107 -0
  188. data/vendor/assets/javascripts/mercury/modals/insertsnippet.js.coffee +12 -0
  189. data/vendor/assets/javascripts/mercury/modals/inserttable.js.coffee +54 -0
  190. data/vendor/assets/javascripts/mercury/native_extensions.js.coffee +55 -0
  191. data/vendor/assets/javascripts/mercury/page_editor.js.coffee +241 -0
  192. data/vendor/assets/javascripts/mercury/palette.js.coffee +29 -0
  193. data/vendor/assets/javascripts/mercury/panel.js.coffee +115 -0
  194. data/vendor/assets/javascripts/mercury/plugins/save_as_xml/mercury/page_editor.js.coffee +28 -0
  195. data/vendor/assets/javascripts/mercury/plugins/save_as_xml/plugin.js +9 -0
  196. data/vendor/assets/javascripts/mercury/region.js.coffee +107 -0
  197. data/vendor/assets/javascripts/mercury/regions/editable.js.coffee +600 -0
  198. data/vendor/assets/javascripts/mercury/regions/markupable.js.coffee +398 -0
  199. data/vendor/assets/javascripts/mercury/regions/simple.js.coffee +339 -0
  200. data/vendor/assets/javascripts/mercury/regions/snippetable.js.coffee +124 -0
  201. data/vendor/assets/javascripts/mercury/select.js.coffee +44 -0
  202. data/vendor/assets/javascripts/mercury/snippet.js.coffee +104 -0
  203. data/vendor/assets/javascripts/mercury/snippet_toolbar.js.coffee +72 -0
  204. data/vendor/assets/javascripts/mercury/statusbar.js.coffee +51 -0
  205. data/vendor/assets/javascripts/mercury/support/history.js +1 -0
  206. data/vendor/assets/javascripts/mercury/table_editor.js.coffee +265 -0
  207. data/vendor/assets/javascripts/mercury/toolbar.button.js.coffee +173 -0
  208. data/vendor/assets/javascripts/mercury/toolbar.button_group.js.coffee +42 -0
  209. data/vendor/assets/javascripts/mercury/toolbar.expander.js.coffee +56 -0
  210. data/vendor/assets/javascripts/mercury/toolbar.js.coffee +86 -0
  211. data/vendor/assets/javascripts/mercury/tooltip.js.coffee +74 -0
  212. data/vendor/assets/javascripts/mercury/uploader.js.coffee +227 -0
  213. data/vendor/assets/javascripts/mercury.js +479 -0
  214. data/vendor/assets/javascripts/mercury_loader.js +193 -0
  215. data/vendor/assets/javascripts/mercury_overrides.js +6 -0
  216. data/vendor/assets/javascripts/popup.js +8 -0
  217. data/vendor/assets/stylesheets/mercury/all_images.css.erb +89 -0
  218. data/vendor/assets/stylesheets/mercury/dialog.css +208 -0
  219. data/vendor/assets/stylesheets/mercury/lightview.css +151 -0
  220. data/vendor/assets/stylesheets/mercury/mercury.css +151 -0
  221. data/vendor/assets/stylesheets/mercury/modal.css +183 -0
  222. data/vendor/assets/stylesheets/mercury/statusbar.css +32 -0
  223. data/vendor/assets/stylesheets/mercury/toolbar.css +304 -0
  224. data/vendor/assets/stylesheets/mercury/tooltip.css +26 -0
  225. data/vendor/assets/stylesheets/mercury/uploader.css +111 -0
  226. data/vendor/assets/stylesheets/mercury.css +28 -0
  227. data/vendor/assets/stylesheets/mercury_overrides.css +17 -0
  228. data/vendor/assets/stylesheets/popup.css.erb +37 -0
  229. metadata +634 -0
@@ -0,0 +1,600 @@
1
+ class @Mercury.Regions.Editable extends Mercury.Region
2
+ # No IE support yet because it doesn't follow the W3C standards for HTML5 contentEditable (aka designMode).
3
+ @supported: document.designMode && !jQuery.browser.konqueror && !jQuery.browser.msie
4
+ @supportedText: "Chrome 10+, Firefox 4+, Safari 5+"
5
+
6
+ type = 'editable'
7
+
8
+ constructor: (@element, @window, @options = {}) ->
9
+ @type = 'editable'
10
+ super
11
+
12
+
13
+ build: ->
14
+ # mozilla: set some initial content so everything works correctly
15
+ @content(' ') if jQuery.browser.mozilla && @content() == ''
16
+
17
+ # set overflow just in case
18
+ @element.data({originalOverflow: @element.css('overflow')})
19
+ @element.css({overflow: 'auto'})
20
+
21
+ # mozilla: there's some weird behavior when the element isn't a div
22
+ @specialContainer = jQuery.browser.mozilla && @element.get(0).tagName != 'DIV'
23
+
24
+ # make it editable
25
+ # mozilla: this makes double clicking in textareas fail: https://bugzilla.mozilla.org/show_bug.cgi?id=490367
26
+ @element.get(0).contentEditable = true
27
+
28
+ # make all snippets not editable, and set their versions to 1
29
+ for element in @element.find('.mercury-snippet')
30
+ element.contentEditable = false
31
+ jQuery(element).attr('data-version', '1')
32
+
33
+ # add the basic editor settings to the document (only once)
34
+ unless @document.mercuryEditing
35
+ @document.mercuryEditing = true
36
+ try
37
+ @document.execCommand('styleWithCSS', false, false)
38
+ @document.execCommand('insertBROnReturn', false, true)
39
+ @document.execCommand('enableInlineTableEditing', false, false)
40
+ @document.execCommand('enableObjectResizing', false, false)
41
+ catch e
42
+ # intentionally do nothing if any of these fail, to broaden support for Opera
43
+
44
+
45
+ bindEvents: ->
46
+ super
47
+
48
+ Mercury.on 'region:update', =>
49
+ return if @previewing || Mercury.region != @
50
+ setTimeout(1, => @selection().forceSelection(@element.get(0)))
51
+ currentElement = @currentElement()
52
+ if currentElement.length
53
+ # setup the table editor if we're inside a table
54
+ table = currentElement.closest('table', @element)
55
+ Mercury.tableEditor(table, currentElement.closest('tr, td'), '<br/>') if table.length
56
+ # display a tooltip if we're in an anchor
57
+ anchor = currentElement.closest('a', @element)
58
+ if anchor.length && anchor.attr('href')
59
+ Mercury.tooltip(anchor, "#{anchor.attr('href')}: <a class='internal_link' href=\"#{anchor.attr('href')}\" >Follow Link</a>", {position: 'below'})
60
+ else
61
+ Mercury.tooltip.hide()
62
+
63
+ @element.on 'dragenter', (event) =>
64
+ return if @previewing
65
+ event.preventDefault() unless Mercury.snippet
66
+ event.originalEvent.dataTransfer.dropEffect = 'copy'
67
+
68
+ @element.on 'dragover', (event) =>
69
+ return if @previewing
70
+ event.preventDefault() unless Mercury.snippet
71
+ event.originalEvent.dataTransfer.dropEffect = 'copy'
72
+ if jQuery.browser.webkit
73
+ clearTimeout(@dropTimeout)
74
+ @dropTimeout = setTimeout(10, => @element.trigger('possible:drop'))
75
+
76
+ @element.on 'drop', (event) =>
77
+ return if @previewing
78
+
79
+ # handle dropping snippets
80
+ clearTimeout(@dropTimeout)
81
+ @dropTimeout = setTimeout(1, => @element.trigger('possible:drop'))
82
+
83
+ # handle any files that were dropped
84
+ return unless event.originalEvent.dataTransfer.files.length
85
+ event.preventDefault()
86
+ @focus()
87
+ Mercury.uploader(event.originalEvent.dataTransfer.files[0])
88
+
89
+ # possible:drop custom event: we have to do this because webkit doesn't fire the drop event unless both dragover and
90
+ # dragstart default behaviors are canceled.. but when we do that and observe the drop event, the default behavior
91
+ # isn't handled (eg, putting the image where it was dropped,) so to allow the browser to do it's thing, and also do
92
+ # our thing we have this little hack. *sigh*
93
+ # read: http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
94
+ @element.on 'possible:drop', =>
95
+ return if @previewing
96
+ if snippet = @element.find('img[data-snippet]').get(0)
97
+ @focus()
98
+ Mercury.Snippet.displayOptionsFor(jQuery(snippet).data('snippet'))
99
+ @document.execCommand('undo', false, null)
100
+
101
+ # custom paste handling: we have to do some hackery to get the pasted content since it's not exposed normally
102
+ # through a clipboard in firefox (heaven forbid), and to keep the behavior across all browsers, we manually detect
103
+ # what was pasted by focusing a different (hidden) region, letting it paste there, making our adjustments, and then
104
+ # inserting the content where it was intended. This is possible, so it doesn't make sense why it wouldn't be
105
+ # exposed in a sensible way. *sigh*
106
+ @element.on 'paste', (event) =>
107
+ return if @previewing || Mercury.region != @
108
+ if @specialContainer
109
+ event.preventDefault()
110
+ return
111
+ return if @pasting
112
+ Mercury.changes = true
113
+ @handlePaste(event.originalEvent)
114
+
115
+ @element.on 'focus', =>
116
+ return if @previewing
117
+ Mercury.region = @
118
+ setTimeout(1, => @selection().forceSelection(@element.get(0)))
119
+ Mercury.trigger('region:focused', {region: @})
120
+
121
+ @element.on 'blur', =>
122
+ return if @previewing
123
+ Mercury.trigger('region:blurred', {region: @})
124
+ Mercury.tooltip.hide()
125
+
126
+ @element.on 'click', (event) =>
127
+ jQuery(event.target).closest('a').attr('target', '_parent') if @previewing
128
+
129
+ @element.on 'dblclick', (event) =>
130
+ return if @previewing
131
+ image = jQuery(event.target).closest('img', @element)
132
+ if image.length
133
+ @selection().selectNode(image.get(0), true)
134
+ Mercury.trigger('button', {action: 'insertMedia'})
135
+
136
+ @element.on 'mouseup', =>
137
+ return if @previewing
138
+ @pushHistory()
139
+ Mercury.trigger('region:update', {region: @})
140
+
141
+ @element.on 'keydown', (event) =>
142
+ return if @previewing
143
+ switch event.keyCode
144
+ when 90 # undo / redo
145
+ return unless event.metaKey
146
+ event.preventDefault()
147
+ if event.shiftKey then @execCommand('redo') else @execCommand('undo')
148
+ return
149
+
150
+ when 13 # enter
151
+ if jQuery.browser.webkit && @selection().commonAncestor().closest('li, ul, ol', @element).length == 0
152
+ event.preventDefault()
153
+ @document.execCommand('insertParagraph', false, null)
154
+ else if @specialContainer || jQuery.browser.opera
155
+ # mozilla: pressing enter in any element besides a div handles strangely
156
+ event.preventDefault()
157
+ @document.execCommand('insertHTML', false, '<br/>')
158
+
159
+ when 9 # tab
160
+ event.preventDefault()
161
+ container = @selection().commonAncestor()
162
+
163
+ # indent when inside of an li
164
+ if container.closest('li', @element).length
165
+ unless event.shiftKey
166
+ @execCommand('indent')
167
+ # do not outdent on last ul/ol parent, or we break out of the list
168
+ else if container.parents('ul, ol').length > 1
169
+ @execCommand('outdent')
170
+ else
171
+ @execCommand('insertHTML', {value: '&nbsp;&nbsp;'})
172
+
173
+ if event.metaKey
174
+ switch event.keyCode
175
+ when 66 # b
176
+ @execCommand('bold')
177
+ event.preventDefault()
178
+
179
+ when 73 # i
180
+ @execCommand('italic')
181
+ event.preventDefault()
182
+
183
+ when 85 # u
184
+ @execCommand('underline')
185
+ event.preventDefault()
186
+
187
+ @pushHistory(event.keyCode)
188
+
189
+ @element.on 'keyup', =>
190
+ return if @previewing
191
+ Mercury.trigger('region:update', {region: @})
192
+ Mercury.changes = true
193
+
194
+
195
+ focus: ->
196
+ if Mercury.region != @
197
+ setTimeout(10, => @element.focus())
198
+ try
199
+ @selection().selection.collapseToStart()
200
+ catch e
201
+ # intentially do nothing
202
+ else
203
+ setTimeout(10, => @element.focus())
204
+
205
+ Mercury.trigger('region:focused', {region: @})
206
+ Mercury.trigger('region:update', {region: @})
207
+
208
+
209
+ content: (value = null, filterSnippets = true, includeMarker = false) ->
210
+ if value != null
211
+ # sanitize the html before we insert it
212
+ container = jQuery('<div>').appendTo(@document.createDocumentFragment())
213
+ container.html(value)
214
+
215
+ # fill in the snippet contents
216
+ for element in container.find('[data-snippet]')
217
+ element.contentEditable = false
218
+ element = jQuery(element)
219
+ if snippet = Mercury.Snippet.find(element.data('snippet'))
220
+ unless element.data('version')
221
+ try
222
+ version = parseInt(element.html().match(/\/(\d+)\]/)[1])
223
+ if version
224
+ snippet.setVersion(version)
225
+ element.attr({'data-version': version})
226
+ element.html(snippet.data)
227
+ catch error
228
+
229
+ # set the html
230
+ @element.html(container.html())
231
+
232
+ # create a selection if there's markers
233
+ @selection().selectMarker(@element)
234
+ else
235
+ # remove any meta tags
236
+ @element.find('meta').remove()
237
+
238
+ # place markers for the selection
239
+ if includeMarker
240
+ selection = @selection()
241
+ selection.placeMarker()
242
+
243
+ # sanitize the html before we return it
244
+ container = jQuery('<div>').appendTo(@document.createDocumentFragment())
245
+ container.html(@element.html().replace(/^\s+|\s+$/g, ''))
246
+
247
+ # replace snippet contents to be an identifier
248
+ if filterSnippets then for element, index in container.find('[data-snippet]')
249
+ element = jQuery(element)
250
+ if snippet = Mercury.Snippet.find(element.data("snippet"))
251
+ snippet.data = element.html()
252
+ element.html("[#{element.data("snippet")}/#{element.data("version")}]")
253
+ element.attr({contenteditable: null, 'data-version': null})
254
+
255
+ # get the html before removing the markers
256
+ content = container.html()
257
+
258
+ # remove the markers from the dom
259
+ selection.removeMarker() if includeMarker
260
+
261
+ return content
262
+
263
+
264
+ togglePreview: ->
265
+ if @previewing
266
+ @element.get(0).contentEditable = true
267
+ @element.css({overflow: 'auto'})
268
+ else
269
+ @content(@content())
270
+ @element.get(0).contentEditable = false
271
+ @element.css({overflow: @element.data('originalOverflow')})
272
+ @element.blur()
273
+ super
274
+
275
+
276
+ execCommand: (action, options = {}) ->
277
+ super
278
+
279
+ # use a custom handler if there's one, otherwise use execCommand
280
+ if handler = Mercury.config.behaviors[action] || Mercury.Regions.Editable.actions[action]
281
+ handler.call(@, @selection(), options)
282
+ else
283
+ sibling = @element.get(0).previousSibling if action == 'indent'
284
+ options.value = jQuery('<div>').html(options.value).html() if action == 'insertHTML' && options.value && options.value.get
285
+ try
286
+ @document.execCommand(action, false, options.value)
287
+ catch error
288
+ # mozilla: indenting when there's no br tag handles strangely
289
+ # mozilla: trying to justify the first line of any contentEditable fails
290
+ @element.prev().remove() if action == 'indent' && @element.prev() != sibling
291
+
292
+ # handle any broken images by replacing the source with an alert image
293
+ @element.find('img').one 'error', -> jQuery(@).attr({src: '/assets/mercury/missing-image.png', title: 'Image not found'})
294
+
295
+
296
+ pushHistory: (keyCode) ->
297
+ # when pressing return, delete or backspace it should push to the history
298
+ # all other times it should store if there's a 1 second pause
299
+ keyCodes = [13, 46, 8]
300
+ waitTime = 2.5
301
+ knownKeyCode = keyCodes.indexOf(keyCode) if keyCode
302
+
303
+ # clear any pushes to the history
304
+ clearTimeout(@historyTimeout)
305
+
306
+ # if the key code was return, delete, or backspace store now -- unless it was the same as last time
307
+ if knownKeyCode >= 0 && knownKeyCode != @lastKnownKeyCode # || !keyCode
308
+ @history.push(@content(null, false, true))
309
+ else if keyCode
310
+ # set a timeout for pushing to the history
311
+ @historyTimeout = setTimeout(waitTime * 1000, => @history.push(@content(null, false, true)))
312
+ else
313
+ # push to the history immediately
314
+ @history.push(@content(null, false, true))
315
+
316
+ @lastKnownKeyCode = knownKeyCode
317
+
318
+
319
+ selection: ->
320
+ return new Mercury.Regions.Editable.Selection(@window.getSelection(), @document)
321
+
322
+
323
+ path: ->
324
+ container = @selection().commonAncestor()
325
+ return [] unless container
326
+ return if container.get(0) == @element.get(0) then [] else container.parentsUntil(@element)
327
+
328
+
329
+ currentElement: ->
330
+ element = []
331
+ selection = @selection()
332
+ if selection.range
333
+ element = selection.commonAncestor()
334
+ element = element.parent() if element.get(0).nodeType == 3
335
+ return element
336
+
337
+
338
+ handlePaste: (event) ->
339
+ # get the text content from the clipboard and fall back to using the sanitizer if unavailable
340
+ if Mercury.config.pasting.sanitize == 'text' && event.clipboardData
341
+ @execCommand('insertHTML', {value: event.clipboardData.getData('text/plain')})
342
+ event.preventDefault()
343
+ return
344
+ else
345
+ # get current selection & range
346
+ selection = @selection()
347
+ selection.placeMarker()
348
+
349
+ sanitizer = jQuery('#mercury_sanitizer', @document).focus()
350
+
351
+ # set 1ms timeout to allow paste event to complete
352
+ setTimeout 1, =>
353
+ # sanitize the content
354
+ content = @sanitize(sanitizer)
355
+
356
+ # move cursor back to original element & position
357
+ selection.selectMarker(@element)
358
+ selection.removeMarker()
359
+
360
+ # paste sanitized content
361
+ @element.focus()
362
+ @execCommand('insertHTML', {value: content})
363
+
364
+
365
+ sanitize: (sanitizer) ->
366
+ # always remove nested regions
367
+ sanitizer.find(".#{Mercury.config.regions.className}").remove()
368
+
369
+ if Mercury.config.pasting.sanitize
370
+ switch Mercury.config.pasting.sanitize
371
+ when 'blacklist'
372
+ # todo: finish writing black list functionality
373
+ sanitizer.find('[style]').removeAttr('style')
374
+ sanitizer.find('[class="Apple-style-span"]').removeClass('Apple-style-span')
375
+ content = sanitizer.html()
376
+ when 'whitelist'
377
+ for element in sanitizer.find('*')
378
+ allowed = false
379
+ for allowedTag, allowedAttributes of Mercury.config.pasting.whitelist
380
+ if element.tagName.toLowerCase() == allowedTag.toLowerCase()
381
+ allowed = true
382
+ for attr in jQuery(element.attributes)
383
+ jQuery(element).removeAttr(attr.name) unless attr.name in allowedAttributes
384
+ break
385
+ jQuery(element).replaceWith(jQuery(element).contents()) unless allowed
386
+ content = sanitizer.html()
387
+ else content = sanitizer.text()
388
+ else
389
+ # force text if it looks like it's from word/pages, even if there's no sanitizing requested
390
+ content = sanitizer.html()
391
+ if content.indexOf('<!--StartFragment-->') > -1 || content.indexOf('="mso-') > -1 || content.indexOf('<o:') > -1 || content.indexOf('="Mso') > -1
392
+ content = sanitizer.text()
393
+
394
+ sanitizer.html('')
395
+ return content
396
+
397
+
398
+ # Custom actions (eg. things that execCommand doesn't do, or doesn't do well)
399
+ @actions: {
400
+
401
+ insertRowBefore: -> Mercury.tableEditor.addRow('before')
402
+
403
+ insertRowAfter: -> Mercury.tableEditor.addRow('after')
404
+
405
+ insertColumnBefore: -> Mercury.tableEditor.addColumn('before')
406
+
407
+ insertColumnAfter: -> Mercury.tableEditor.addColumn('after')
408
+
409
+ deleteColumn: -> Mercury.tableEditor.removeColumn()
410
+
411
+ deleteRow: -> Mercury.tableEditor.removeRow()
412
+
413
+ increaseColspan: -> Mercury.tableEditor.increaseColspan()
414
+
415
+ decreaseColspan: -> Mercury.tableEditor.decreaseColspan()
416
+
417
+ increaseRowspan: -> Mercury.tableEditor.increaseRowspan()
418
+
419
+ decreaseRowspan: -> Mercury.tableEditor.decreaseRowspan()
420
+
421
+ undo: -> @content(@history.undo())
422
+
423
+ redo: -> @content(@history.redo())
424
+
425
+ horizontalRule: -> this.execCommand('insertHorizontalRule')
426
+
427
+ removeFormatting: (selection) -> selection.insertTextNode(selection.textContent())
428
+
429
+ backColor: (selection, options) -> selection.wrap("<span style=\"background-color:#{options.value.toHex()}\">", true)
430
+
431
+ overline: (selection) -> selection.wrap('<span style="text-decoration:overline">', true)
432
+
433
+ style: (selection, options) -> selection.wrap("<span class=\"#{options.value}\">", true)
434
+
435
+ replaceHTML: (selection, options) -> @content(options.value)
436
+
437
+ insertImage: (selection, options) -> @execCommand('insertHTML', {value: jQuery('<img/>', options.value)})
438
+
439
+ insertTable: (selection, options) -> @execCommand('insertHTML', {value: options.value})
440
+
441
+ insertLink: (selection, options) ->
442
+ anchor = jQuery("<#{options.value.tagName}>", @document).attr(options.value.attrs).html(options.value.content)
443
+ selection.insertNode(anchor)
444
+
445
+ replaceLink: (selection, options) ->
446
+ anchor = jQuery("<#{options.value.tagName}>", @document).attr(options.value.attrs).html(options.value.content)
447
+ selection.selectNode(options.node)
448
+ html = jQuery('<div>').html(selection.content()).find('a').html()
449
+ selection.replace(jQuery(anchor, selection.context).html(html))
450
+
451
+ insertSnippet: (selection, options) ->
452
+ snippet = options.value
453
+ if (existing = @element.find("[data-snippet=#{snippet.identity}]")).length
454
+ selection.selectNode(existing.get(0))
455
+ selection.insertNode(snippet.getHTML(@document))
456
+
457
+ editSnippet: ->
458
+ return unless @snippet
459
+ snippet = Mercury.Snippet.find(@snippet.data('snippet'))
460
+ snippet.displayOptions()
461
+
462
+ removeSnippet: ->
463
+ @snippet.remove() if @snippet
464
+ Mercury.trigger('hide:toolbar', {type: 'snippet', immediately: true})
465
+ }
466
+
467
+
468
+ # Helper class for managing selection and getting information from it
469
+ class Mercury.Regions.Editable.Selection
470
+
471
+ constructor: (@selection, @context) ->
472
+ return unless @selection.rangeCount >= 1
473
+ @range = @selection.getRangeAt(0)
474
+ @fragment = @range.cloneContents()
475
+ @clone = @range.cloneRange()
476
+ @collapsed = @selection.isCollapsed
477
+
478
+
479
+ commonAncestor: (onlyTag = false) ->
480
+ return null unless @range
481
+ ancestor = @range.commonAncestorContainer
482
+ ancestor = ancestor.parentNode if ancestor.nodeType == 3 && onlyTag
483
+ return jQuery(ancestor)
484
+
485
+
486
+ wrap: (element, replace = false) ->
487
+ element = jQuery(element, @context).html(@fragment)
488
+ @replace(element) if replace
489
+ return element
490
+
491
+
492
+ textContent: ->
493
+ return @range.cloneContents().textContent
494
+
495
+
496
+ content: ->
497
+ return @range.cloneContents()
498
+
499
+
500
+ is: (elementType) ->
501
+ content = @content()
502
+ return jQuery(content.firstChild) if jQuery(content).length == 1 && jQuery(content.firstChild).is(elementType)
503
+ return false
504
+
505
+
506
+ forceSelection: (element) ->
507
+ return unless jQuery.browser.webkit
508
+ range = @context.createRange()
509
+
510
+ if @range
511
+ if @commonAncestor(true).closest('.mercury-snippet').length
512
+ lastChild = @context.createTextNode('\x00')
513
+ element.appendChild(lastChild)
514
+ else
515
+ if element.lastChild && element.lastChild.nodeType == 3 && element.lastChild.textContent.replace(/^[\s+|\n+]|[\s+|\n+]$/, '') == ''
516
+ lastChild = element.lastChild
517
+ element.lastChild.textContent = '\x00'
518
+ else
519
+ lastChild = @context.createTextNode('\x00')
520
+ element.appendChild(lastChild)
521
+
522
+ if lastChild
523
+ range.setStartBefore(lastChild)
524
+ range.setEndBefore(lastChild)
525
+ @selection.addRange(range)
526
+
527
+
528
+ selectMarker: (context) ->
529
+ markers = context.find('em.mercury-marker')
530
+ return unless markers.length
531
+
532
+ range = @context.createRange()
533
+ range.setStartBefore(markers.get(0))
534
+ range.setEndBefore(markers.get(1)) if markers.length >= 2
535
+
536
+ markers.remove()
537
+
538
+ @selection.removeAllRanges()
539
+ @selection.addRange(range)
540
+
541
+
542
+ placeMarker: ->
543
+ return unless @range
544
+
545
+ @startMarker = jQuery('<em class="mercury-marker"/>', @context).get(0)
546
+ @endMarker = jQuery('<em class="mercury-marker"/>', @context).get(0)
547
+
548
+ # put a single marker (the end)
549
+ rangeEnd = @range.cloneRange()
550
+ rangeEnd.collapse(false)
551
+ rangeEnd.insertNode(@endMarker)
552
+
553
+ unless @range.collapsed
554
+ # put a start marker
555
+ rangeStart = @range.cloneRange()
556
+ rangeStart.collapse(true)
557
+ rangeStart.insertNode(@startMarker)
558
+
559
+ @selection.removeAllRanges()
560
+ @selection.addRange(@range)
561
+
562
+
563
+ removeMarker: ->
564
+ jQuery(@startMarker).remove()
565
+ jQuery(@endMarker).remove()
566
+
567
+
568
+ insertTextNode: (string) ->
569
+ node = @context.createTextNode(string)
570
+ @range.extractContents()
571
+ @range.insertNode(node)
572
+ @range.selectNodeContents(node)
573
+ @selection.addRange(@range)
574
+
575
+
576
+ insertNode: (element) ->
577
+ element = element.get(0) if element.get
578
+ element = jQuery(element, @context).get(0) if jQuery.type(element) == 'string'
579
+
580
+ @range.deleteContents()
581
+ @range.insertNode(element)
582
+ @range.selectNodeContents(element)
583
+ @selection.addRange(@range)
584
+
585
+
586
+ selectNode: (node, removeExisting = false) ->
587
+ @range.selectNode(node)
588
+ @selection.removeAllRanges() if removeExisting
589
+ @selection.addRange(@range)
590
+
591
+
592
+ replace: (element, collapse) ->
593
+ element = element.get(0) if element.get
594
+ element = jQuery(element, @context).get(0) if jQuery.type(element) == 'string'
595
+
596
+ @range.deleteContents()
597
+ @range.insertNode(element)
598
+ @range.selectNodeContents(element)
599
+ @selection.addRange(@range)
600
+ @range.collapse(false) if collapse