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.
- data/LICENSE +25 -0
- data/app/controllers/mercury/images_controller.rb +5 -0
- data/app/controllers/mercury_controller.rb +34 -0
- data/app/models/mercury/image.rb +17 -0
- data/app/views/layouts/mercury.html.erb +33 -0
- data/app/views/layouts/popup.html.haml +9 -0
- data/app/views/mercury/images/_list.html.haml +22 -0
- data/app/views/mercury/images/index.html.haml +4 -0
- data/app/views/mercury/images/index.js.erb +2 -0
- data/app/views/mercury/lightviews/about.html +11 -0
- data/app/views/mercury/modals/character.html +255 -0
- data/app/views/mercury/modals/htmleditor.html +13 -0
- data/app/views/mercury/modals/link.html +94 -0
- data/app/views/mercury/modals/media.html +1 -0
- data/app/views/mercury/modals/table.html +84 -0
- data/app/views/mercury/palettes/backcolor.html +73 -0
- data/app/views/mercury/palettes/forecolor.html +73 -0
- data/app/views/mercury/panels/history.html +3 -0
- data/app/views/mercury/panels/notes.html +3 -0
- data/app/views/mercury/panels/snippets.html +12 -0
- data/app/views/mercury/selects/formatblock.html +11 -0
- data/app/views/mercury/selects/style.html +5 -0
- data/app/views/mercury/snippets/example/options.html.erb +34 -0
- data/app/views/mercury/snippets/example/preview.html.erb +1 -0
- data/config/engine.rb +44 -0
- data/db/migrate/20110526035601_create_mercury_images.rb +11 -0
- data/features/loading/loading.feature +22 -0
- data/features/loading/navigating.feature +77 -0
- data/features/loading/user_interface.feature +67 -0
- data/features/regions/editable/advanced_editing.feature +0 -0
- data/features/regions/editable/basic_editing.feature +195 -0
- data/features/regions/editable/inserting_links.feature +98 -0
- data/features/regions/editable/inserting_media.feature +110 -0
- data/features/regions/editable/inserting_snippets.feature +102 -0
- data/features/regions/editable/inserting_special_characters.feature +24 -0
- data/features/regions/editable/inserting_tables.feature +109 -0
- data/features/regions/editable/pasting.feature +0 -0
- data/features/regions/editable/uploading_images.feature +0 -0
- data/features/regions/markupable/advanced_editing.feature +0 -0
- data/features/regions/markupable/basic_editing.feature +0 -0
- data/features/regions/markupable/inserting_links.feature +0 -0
- data/features/regions/markupable/inserting_media.feature +0 -0
- data/features/regions/markupable/inserting_snippets.feature +0 -0
- data/features/regions/markupable/inserting_special_characters.feature +0 -0
- data/features/regions/markupable/inserting_tables.feature +0 -0
- data/features/regions/markupable/uploading_images.feature +0 -0
- data/features/regions/snippetable/advanced_editing.feature +0 -0
- data/features/regions/snippetable/basic_editing.feature +0 -0
- data/features/regions/snippetable/inserting_snippets.feature +0 -0
- data/features/saving/saving.feature +33 -0
- data/features/step_definitions/debug_steps.rb +14 -0
- data/features/step_definitions/mercury_steps.rb +438 -0
- data/features/step_definitions/web_steps.rb +211 -0
- data/features/support/env.rb +46 -0
- data/features/support/mercury_contents.rb +25 -0
- data/features/support/mercury_selectors.rb +148 -0
- data/features/support/paths.rb +38 -0
- data/features/support/selectors.rb +44 -0
- data/lib/generators/mercury/install/install_generator.rb +49 -0
- data/lib/generators/mercury/install/templates/mongoid_paperclip_image.rb +17 -0
- data/lib/mercury/authentication.rb +8 -0
- data/lib/mercury-rails.rb +3 -0
- data/spec/javascripts/mercury/dialog_spec.js.coffee +281 -0
- data/spec/javascripts/mercury/dialogs/backcolor_spec.js.coffee +37 -0
- data/spec/javascripts/mercury/dialogs/forecolor_spec.js.coffee +37 -0
- data/spec/javascripts/mercury/dialogs/formatblock_spec.js.coffee +25 -0
- data/spec/javascripts/mercury/dialogs/snippetpanel_spec.js.coffee +30 -0
- data/spec/javascripts/mercury/dialogs/style_spec.js.coffee +25 -0
- data/spec/javascripts/mercury/history_buffer_spec.js.coffee +76 -0
- data/spec/javascripts/mercury/lightview_spec.js.coffee +497 -0
- data/spec/javascripts/mercury/mercury_spec.js.coffee +132 -0
- data/spec/javascripts/mercury/modal_spec.js.coffee +504 -0
- data/spec/javascripts/mercury/modals/htmleditor_spec.js.coffee +30 -0
- data/spec/javascripts/mercury/modals/insertcharacter_spec.js.coffee +29 -0
- data/spec/javascripts/mercury/modals/insertlink_spec.js.coffee +220 -0
- data/spec/javascripts/mercury/modals/insertmedia_spec.js.coffee +167 -0
- data/spec/javascripts/mercury/modals/insertsnippet_spec.js.coffee +52 -0
- data/spec/javascripts/mercury/modals/inserttable_spec.js.coffee +160 -0
- data/spec/javascripts/mercury/native_extensions_spec.js.coffee +60 -0
- data/spec/javascripts/mercury/page_editor_spec.js.coffee +750 -0
- data/spec/javascripts/mercury/palette_spec.js.coffee +49 -0
- data/spec/javascripts/mercury/panel_spec.js.coffee +185 -0
- data/spec/javascripts/mercury/region_spec.js.coffee +298 -0
- data/spec/javascripts/mercury/regions/editable_spec.js.coffee +561 -0
- data/spec/javascripts/mercury/regions/markupable_spec.js.coffee +367 -0
- data/spec/javascripts/mercury/regions/snippetable_spec.js.coffee +370 -0
- data/spec/javascripts/mercury/select_spec.js.coffee +49 -0
- data/spec/javascripts/mercury/snippet_spec.js.coffee +245 -0
- data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +184 -0
- data/spec/javascripts/mercury/statusbar_spec.js.coffee +150 -0
- data/spec/javascripts/mercury/table_editor_spec.js.coffee +194 -0
- data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +90 -0
- data/spec/javascripts/mercury/toolbar.button_spec.js.coffee +360 -0
- data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +118 -0
- data/spec/javascripts/mercury/toolbar_spec.js.coffee +222 -0
- data/spec/javascripts/mercury/tooltip_spec.js.coffee +186 -0
- data/spec/javascripts/mercury/uploader_spec.js.coffee +514 -0
- data/spec/javascripts/spec_helper.js +513 -0
- data/spec/javascripts/templates/mercury/dialog.html +2 -0
- data/spec/javascripts/templates/mercury/dialogs/backcolor.html +5 -0
- data/spec/javascripts/templates/mercury/dialogs/forecolor.html +5 -0
- data/spec/javascripts/templates/mercury/dialogs/formatblock.html +3 -0
- data/spec/javascripts/templates/mercury/dialogs/snippetpanel.html +16 -0
- data/spec/javascripts/templates/mercury/dialogs/style.html +3 -0
- data/spec/javascripts/templates/mercury/lightview.html +13 -0
- data/spec/javascripts/templates/mercury/modal.html +13 -0
- data/spec/javascripts/templates/mercury/modals/htmleditor.html +5 -0
- data/spec/javascripts/templates/mercury/modals/insertcharacter.html +5 -0
- data/spec/javascripts/templates/mercury/modals/insertlink.html +30 -0
- data/spec/javascripts/templates/mercury/modals/insertmedia.html +35 -0
- data/spec/javascripts/templates/mercury/modals/insertsnippet.html +6 -0
- data/spec/javascripts/templates/mercury/modals/inserttable.html +27 -0
- data/spec/javascripts/templates/mercury/page_editor.html +35 -0
- data/spec/javascripts/templates/mercury/palette.html +16 -0
- data/spec/javascripts/templates/mercury/panel.html +16 -0
- data/spec/javascripts/templates/mercury/region.html +2 -0
- data/spec/javascripts/templates/mercury/regions/editable.html +3 -0
- data/spec/javascripts/templates/mercury/regions/snippetable.html +4 -0
- data/spec/javascripts/templates/mercury/select.html +16 -0
- data/spec/javascripts/templates/mercury/snippet.html +1 -0
- data/spec/javascripts/templates/mercury/snippet_toolbar.html +16 -0
- data/spec/javascripts/templates/mercury/statusbar.html +8 -0
- data/spec/javascripts/templates/mercury/table_editor.html +65 -0
- data/spec/javascripts/templates/mercury/toolbar.button.html +64 -0
- data/spec/javascripts/templates/mercury/toolbar.button_group.html +9 -0
- data/spec/javascripts/templates/mercury/toolbar.expander.html +18 -0
- data/spec/javascripts/templates/mercury/toolbar.html +11 -0
- data/spec/javascripts/templates/mercury/tooltip.html +12 -0
- data/spec/javascripts/templates/mercury/uploader.html +11 -0
- data/vendor/assets/images/mercury/button.png +0 -0
- data/vendor/assets/images/mercury/close.png +0 -0
- data/vendor/assets/images/mercury/default-snippet.png +0 -0
- data/vendor/assets/images/mercury/loading-dark.gif +0 -0
- data/vendor/assets/images/mercury/loading-light.gif +0 -0
- data/vendor/assets/images/mercury/missing-image.png +0 -0
- data/vendor/assets/images/mercury/search-icon.png +0 -0
- data/vendor/assets/images/mercury/temp-logo.png +0 -0
- data/vendor/assets/images/mercury/toolbar/editable/buttons.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/_expander.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/_pressed.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/historypanel.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/insertcharacter.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/insertlink.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/insertmedia.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/inserttable.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/inspectorpanel.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/notespanel.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/preview.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/redo.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/save.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/snippetpanel.png +0 -0
- data/vendor/assets/images/mercury/toolbar/primary/undo.png +0 -0
- data/vendor/assets/images/mercury/toolbar/snippetable/buttons.png +0 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery-1.7.js +9300 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery-ui-1.8.13.custom.js +1328 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery-ui-1.8.18.custom.min.js +356 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery-ui.1.8.13.custom.min.js +356 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery.additions.js +206 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery.htmlClean.js +527 -0
- data/vendor/assets/javascripts/mercury/dependencies/liquidmetal.js +88 -0
- data/vendor/assets/javascripts/mercury/dependencies/showdown.js +1340 -0
- data/vendor/assets/javascripts/mercury/dialog.js.coffee +159 -0
- data/vendor/assets/javascripts/mercury/dialogs/backcolor.js.coffee +6 -0
- data/vendor/assets/javascripts/mercury/dialogs/forecolor.js.coffee +6 -0
- data/vendor/assets/javascripts/mercury/dialogs/formatblock.js.coffee +4 -0
- data/vendor/assets/javascripts/mercury/dialogs/snippetpanel.js.coffee +10 -0
- data/vendor/assets/javascripts/mercury/dialogs/style.js.coffee +4 -0
- data/vendor/assets/javascripts/mercury/finalize.js.coffee +3 -0
- data/vendor/assets/javascripts/mercury/history_buffer.js.coffee +30 -0
- data/vendor/assets/javascripts/mercury/lightview.js.coffee +205 -0
- data/vendor/assets/javascripts/mercury/locales/da.locale.js.coffee +0 -0
- data/vendor/assets/javascripts/mercury/locales/de.locale.js.coffee +206 -0
- data/vendor/assets/javascripts/mercury/locales/es.locale.js.coffee +211 -0
- data/vendor/assets/javascripts/mercury/locales/example.local.js.coffee +211 -0
- data/vendor/assets/javascripts/mercury/locales/fr.locale.js.coffee +211 -0
- data/vendor/assets/javascripts/mercury/locales/it.locale.js.coffee +208 -0
- data/vendor/assets/javascripts/mercury/locales/ko.local.js.coffee +206 -0
- data/vendor/assets/javascripts/mercury/locales/nl.locale.js.coffee +206 -0
- data/vendor/assets/javascripts/mercury/locales/pt.locale.js.coffee +211 -0
- data/vendor/assets/javascripts/mercury/locales/sv.local.js.coffee +209 -0
- data/vendor/assets/javascripts/mercury/locales/swedish_chef.locale.js.coffee +213 -0
- data/vendor/assets/javascripts/mercury/mercury.js.coffee +109 -0
- data/vendor/assets/javascripts/mercury/modal.js.coffee +204 -0
- data/vendor/assets/javascripts/mercury/modals/htmleditor.js.coffee +11 -0
- data/vendor/assets/javascripts/mercury/modals/insertcharacter.js.coffee +4 -0
- data/vendor/assets/javascripts/mercury/modals/insertlink.js.coffee +95 -0
- data/vendor/assets/javascripts/mercury/modals/insertmedia.js.coffee +107 -0
- data/vendor/assets/javascripts/mercury/modals/insertsnippet.js.coffee +12 -0
- data/vendor/assets/javascripts/mercury/modals/inserttable.js.coffee +54 -0
- data/vendor/assets/javascripts/mercury/native_extensions.js.coffee +55 -0
- data/vendor/assets/javascripts/mercury/page_editor.js.coffee +241 -0
- data/vendor/assets/javascripts/mercury/palette.js.coffee +29 -0
- data/vendor/assets/javascripts/mercury/panel.js.coffee +115 -0
- data/vendor/assets/javascripts/mercury/plugins/save_as_xml/mercury/page_editor.js.coffee +28 -0
- data/vendor/assets/javascripts/mercury/plugins/save_as_xml/plugin.js +9 -0
- data/vendor/assets/javascripts/mercury/region.js.coffee +107 -0
- data/vendor/assets/javascripts/mercury/regions/editable.js.coffee +600 -0
- data/vendor/assets/javascripts/mercury/regions/markupable.js.coffee +398 -0
- data/vendor/assets/javascripts/mercury/regions/simple.js.coffee +339 -0
- data/vendor/assets/javascripts/mercury/regions/snippetable.js.coffee +124 -0
- data/vendor/assets/javascripts/mercury/select.js.coffee +44 -0
- data/vendor/assets/javascripts/mercury/snippet.js.coffee +104 -0
- data/vendor/assets/javascripts/mercury/snippet_toolbar.js.coffee +72 -0
- data/vendor/assets/javascripts/mercury/statusbar.js.coffee +51 -0
- data/vendor/assets/javascripts/mercury/support/history.js +1 -0
- data/vendor/assets/javascripts/mercury/table_editor.js.coffee +265 -0
- data/vendor/assets/javascripts/mercury/toolbar.button.js.coffee +173 -0
- data/vendor/assets/javascripts/mercury/toolbar.button_group.js.coffee +42 -0
- data/vendor/assets/javascripts/mercury/toolbar.expander.js.coffee +56 -0
- data/vendor/assets/javascripts/mercury/toolbar.js.coffee +86 -0
- data/vendor/assets/javascripts/mercury/tooltip.js.coffee +74 -0
- data/vendor/assets/javascripts/mercury/uploader.js.coffee +227 -0
- data/vendor/assets/javascripts/mercury.js +479 -0
- data/vendor/assets/javascripts/mercury_loader.js +193 -0
- data/vendor/assets/javascripts/mercury_overrides.js +6 -0
- data/vendor/assets/javascripts/popup.js +8 -0
- data/vendor/assets/stylesheets/mercury/all_images.css.erb +89 -0
- data/vendor/assets/stylesheets/mercury/dialog.css +208 -0
- data/vendor/assets/stylesheets/mercury/lightview.css +151 -0
- data/vendor/assets/stylesheets/mercury/mercury.css +151 -0
- data/vendor/assets/stylesheets/mercury/modal.css +183 -0
- data/vendor/assets/stylesheets/mercury/statusbar.css +32 -0
- data/vendor/assets/stylesheets/mercury/toolbar.css +304 -0
- data/vendor/assets/stylesheets/mercury/tooltip.css +26 -0
- data/vendor/assets/stylesheets/mercury/uploader.css +111 -0
- data/vendor/assets/stylesheets/mercury.css +28 -0
- data/vendor/assets/stylesheets/mercury_overrides.css +17 -0
- data/vendor/assets/stylesheets/popup.css.erb +37 -0
- 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: ' '})
|
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
|