mercury-rails 0.2.0 → 0.2.3
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/POST_INSTALL +15 -0
- data/README.md +27 -6
- data/VERSION +1 -1
- data/app/controllers/mercury_controller.rb +4 -4
- data/app/views/layouts/mercury.html.erb +14 -3
- data/app/views/mercury/panels/snippets.html +1 -1
- data/app/views/mercury/snippets/{example_options.html.erb → example/options.html.erb} +0 -0
- data/app/views/mercury/snippets/{example.html.erb → example/preview.html.erb} +0 -0
- data/config/routes.rb +2 -2
- 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 +103 -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 +2 -2
- data/features/step_definitions/mercury_steps.rb +441 -0
- data/features/support/env.rb +3 -3
- data/features/support/mercury_contents.rb +25 -0
- data/features/support/mercury_selectors.rb +147 -0
- data/features/support/paths.rb +20 -18
- data/features/support/selectors.rb +5 -3
- data/lib/generators/mercury/install/install_generator.rb +14 -0
- data/mercury-rails.gemspec +50 -20
- data/spec/javascripts/mercury/lightview_spec.js.coffee +55 -27
- data/spec/javascripts/mercury/mercury_spec.js.coffee +3 -3
- data/spec/javascripts/mercury/modal_spec.js.coffee +2 -2
- data/spec/javascripts/mercury/native_extensions_spec.js.coffee +0 -24
- data/spec/javascripts/mercury/page_editor_spec.js.coffee +148 -67
- data/spec/javascripts/mercury/panel_spec.js.coffee +2 -2
- data/spec/javascripts/mercury/region_spec.js.coffee +10 -7
- data/spec/javascripts/mercury/regions/editable_spec.js.coffee +0 -20
- data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +2 -2
- data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +1 -1
- data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +1 -1
- data/spec/javascripts/templates/mercury/page_editor.html +3 -3
- data/vendor/assets/images/mercury/close.png +0 -0
- data/vendor/assets/javascripts/mercury.js +140 -73
- data/vendor/assets/javascripts/{mercury_dependencies → mercury/dependencies}/jquery-1.6.js +0 -0
- data/vendor/assets/javascripts/{mercury_dependencies → mercury/dependencies}/jquery-ui-1.8.13.custom.js +0 -0
- data/vendor/assets/javascripts/{mercury_dependencies → mercury/dependencies}/jquery.additions.js +0 -0
- data/vendor/assets/javascripts/mercury/dependencies/jquery.htmlClean.js +527 -0
- data/vendor/assets/javascripts/{mercury_dependencies → mercury/dependencies}/liquidmetal.js +0 -0
- data/vendor/assets/javascripts/{mercury_dependencies → mercury/dependencies}/showdown.js +0 -0
- data/vendor/assets/javascripts/mercury/lightview.js.coffee +5 -2
- data/vendor/assets/javascripts/mercury/mercury.js.coffee +9 -8
- data/vendor/assets/javascripts/mercury/modals/htmleditor.js.coffee +3 -1
- data/vendor/assets/javascripts/mercury/modals/inserttable.js.coffee +2 -2
- data/vendor/assets/javascripts/mercury/native_extensions.js.coffee +6 -17
- data/vendor/assets/javascripts/mercury/page_editor.js.coffee +29 -8
- data/vendor/assets/javascripts/mercury/plugins/save_as_xml/mercury/page_editor.js.coffee +27 -0
- data/vendor/assets/javascripts/mercury/plugins/save_as_xml/plugin.js +9 -0
- data/vendor/assets/javascripts/mercury/region.js.coffee +2 -2
- data/vendor/assets/javascripts/mercury/regions/editable.js.coffee +89 -93
- data/vendor/assets/javascripts/mercury/regions/markupable.js.coffee +1 -1
- data/vendor/assets/javascripts/mercury/support/history.js +1 -0
- data/vendor/assets/javascripts/mercury/uploader.js.coffee +0 -1
- data/vendor/assets/javascripts/mercury_loader.js +4 -4
- data/vendor/assets/stylesheets/mercury/lightview.css +8 -0
- data/vendor/assets/stylesheets/mercury/mercury.css +12 -0
- data/vendor/assets/stylesheets/mercury/modal.css +0 -12
- data/vendor/assets/stylesheets/mercury/toolbar.css +1 -0
- data/vendor/assets/stylesheets/mercury_overrides.css +17 -0
- metadata +73 -45
- data/app/views/mercury/lightviews/imageprocessor.html +0 -3
- data/app/views/mercury/modals/sanitizer.html +0 -9
- data/features/editing/basic.feature +0 -11
- data/vendor/assets/images/mercury/clippy.png +0 -0
File without changes
|
File without changes
|
@@ -27,6 +27,8 @@ jQuery.extend Mercury.lightview, {
|
|
27
27
|
@overlay = jQuery('<div>', {class: 'mercury-lightview-overlay'})
|
28
28
|
|
29
29
|
@titleElement = @element.find('.mercury-lightview-title')
|
30
|
+
@titleElement.append('<a class="mercury-lightview-close"></a>') if @options.closeButton
|
31
|
+
|
30
32
|
@contentElement = @element.find('.mercury-lightview-content')
|
31
33
|
|
32
34
|
@element.appendTo(jQuery(@options.appendTo).get(0) ? 'body')
|
@@ -39,7 +41,8 @@ jQuery.extend Mercury.lightview, {
|
|
39
41
|
Mercury.bind 'refresh', => @resize(true)
|
40
42
|
Mercury.bind 'resize', => @position() if @visible
|
41
43
|
|
42
|
-
@overlay.click => @hide()
|
44
|
+
@overlay.click => @hide() unless @options.closeButton
|
45
|
+
@titleElement.find('.mercury-lightview-close').click => @hide()
|
43
46
|
|
44
47
|
jQuery(document).bind 'keydown', (event) =>
|
45
48
|
@hide() if event.keyCode == 27 && @visible
|
@@ -52,7 +55,7 @@ jQuery.extend Mercury.lightview, {
|
|
52
55
|
@overlay.animate {opacity: 1}, 200, 'easeInOutSine', =>
|
53
56
|
@setTitle()
|
54
57
|
@element.show().css({opacity: 0})
|
55
|
-
@element.animate {opacity: 1}, 200, 'easeInOutSine', =>
|
58
|
+
@element.stop().animate {opacity: 1}, 200, 'easeInOutSine', =>
|
56
59
|
@visible = true
|
57
60
|
@load()
|
58
61
|
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# ## Require all the dependencies
|
2
|
-
#= require
|
3
|
-
#= require
|
4
|
-
#= require
|
5
|
-
#= require
|
6
|
-
#= require
|
2
|
+
#= require mercury/dependencies/jquery-1.6
|
3
|
+
#= require mercury/dependencies/jquery-ui-1.8.13.custom
|
4
|
+
#= require mercury/dependencies/jquery.additions
|
5
|
+
#= require mercury/dependencies/jquery.htmlClean
|
6
|
+
#= require mercury/dependencies/liquidmetal
|
7
|
+
#= require mercury/dependencies/showdown
|
7
8
|
#
|
8
9
|
# ## Require all mercury files
|
9
10
|
#= require_self
|
@@ -33,7 +34,7 @@
|
|
33
34
|
#
|
34
35
|
@Mercury ||= {}
|
35
36
|
jQuery.extend @Mercury, {
|
36
|
-
version: '0.2.
|
37
|
+
version: '0.2.3'
|
37
38
|
|
38
39
|
# No IE support yet because it doesn't follow the W3C standards for HTML5 contentEditable (aka designMode).
|
39
40
|
supported: document.getElementById && document.designMode && !jQuery.browser.konqueror && !jQuery.browser.msie
|
@@ -47,12 +48,12 @@ jQuery.extend @Mercury, {
|
|
47
48
|
|
48
49
|
# Custom event and logging methods
|
49
50
|
bind: (eventName, callback) ->
|
50
|
-
jQuery(
|
51
|
+
jQuery(top).bind("mercury:#{eventName}", callback)
|
51
52
|
|
52
53
|
|
53
54
|
trigger: (eventName, options) ->
|
54
55
|
Mercury.log(eventName, options)
|
55
|
-
jQuery(
|
56
|
+
jQuery(top).trigger("mercury:#{eventName}", options)
|
56
57
|
|
57
58
|
|
58
59
|
log: ->
|
@@ -1,6 +1,8 @@
|
|
1
1
|
@Mercury.modalHandlers.htmlEditor = ->
|
2
2
|
# fill the text area with the content
|
3
|
-
|
3
|
+
content = Mercury.region.content(null, true, false)
|
4
|
+
# content = jQuery.htmlClean(content, {format: true, replace: [], allowedClasses: ['mercury-snippet']})
|
5
|
+
@element.find('textarea').val(content)
|
4
6
|
|
5
7
|
# replace the contents on form submit
|
6
8
|
@element.find('form').submit (event) =>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
table.click (event) =>
|
6
6
|
cell = jQuery(event.target)
|
7
7
|
table = cell.closest('table')
|
8
|
-
table.find('.selected').
|
8
|
+
table.find('.selected').removeAttr('class')
|
9
9
|
cell.addClass('selected')
|
10
10
|
Mercury.tableEditor(table, cell, ' ')
|
11
11
|
|
@@ -44,7 +44,7 @@
|
|
44
44
|
# build the table on form submission
|
45
45
|
@element.find('form').submit (event) =>
|
46
46
|
event.preventDefault()
|
47
|
-
table.find('.selected').
|
47
|
+
table.find('.selected').removeAttr('class')
|
48
48
|
table.find('td, th').html(' ')
|
49
49
|
|
50
50
|
html = jQuery('<div>').html(table).html()
|
@@ -9,23 +9,6 @@ String::toHex = ->
|
|
9
9
|
"##{parseInt(r).toHex()}#{parseInt(g).toHex()}#{parseInt(b).toHex()}"
|
10
10
|
|
11
11
|
|
12
|
-
String::singleDiff = (that) ->
|
13
|
-
diff = ''
|
14
|
-
for char, index in that
|
15
|
-
break if char == 'each'
|
16
|
-
if char != @[index]
|
17
|
-
re = new RegExp(@substr(index).regExpEscape().replace(/^\s+|^( )+/g, '') + '$', 'm')
|
18
|
-
diff = that.substr(index).replace(re, '')
|
19
|
-
break
|
20
|
-
return diff
|
21
|
-
|
22
|
-
|
23
|
-
String::regExpEscape = ->
|
24
|
-
specials = ['/','.','*','+','?','|','(',')','[',']','{','}','\\']
|
25
|
-
escaped = new RegExp('(\\' + specials.join('|\\') + ')', 'g')
|
26
|
-
return @replace(escaped, '\\$1')
|
27
|
-
|
28
|
-
|
29
12
|
String::sanitizeHTML = ->
|
30
13
|
element = jQuery('<div>').html(@.toString())
|
31
14
|
element.find('style').remove()
|
@@ -46,3 +29,9 @@ Number::toBytes = ->
|
|
46
29
|
bytes /= 1024
|
47
30
|
i += 1
|
48
31
|
return if i then "#{bytes.toFixed(2)}#{['', ' kb', ' Mb', ' Gb', ' Tb', ' Pb', ' Eb'][i]}" else "#{bytes} bytes"
|
32
|
+
|
33
|
+
|
34
|
+
# make setTimeout not suck for coffeescript
|
35
|
+
window.originalSetTimeout = window.setTimeout
|
36
|
+
window.setTimeout = (arg1, arg2) ->
|
37
|
+
if typeof(arg1) == 'number' then window.originalSetTimeout(arg2, arg1) else window.originalSetTimeout(arg1, arg2)
|
@@ -2,6 +2,8 @@ class @Mercury.PageEditor
|
|
2
2
|
|
3
3
|
# options
|
4
4
|
# saveStyle: 'form', or 'json' (defaults to json)
|
5
|
+
# saveDataType: 'xml', 'json', 'jsonp', 'script', 'text', 'html' (defaults to json)
|
6
|
+
# saveMethod: 'POST', or 'PUT', create or update actions on save (defaults to POST)
|
5
7
|
# visible: boolean, if the interface should start visible or not (defaults to true)
|
6
8
|
constructor: (@saveUrl = null, @options = {}) ->
|
7
9
|
throw "Mercury.PageEditor is unsupported in this client. Supported browsers are chrome 10+, firefix 4+, and safari 5+." unless Mercury.supported
|
@@ -17,8 +19,9 @@ class @Mercury.PageEditor
|
|
17
19
|
|
18
20
|
|
19
21
|
initializeInterface: ->
|
20
|
-
@focusableElement = jQuery('<input>', {
|
21
|
-
|
22
|
+
@focusableElement = jQuery('<input>', {class: 'mercury-focusable', type: 'text'}).appendTo(@options.appendTo ? 'body')
|
23
|
+
|
24
|
+
@iframe = jQuery('<iframe>', {id: 'mercury_iframe', class: 'mercury-iframe', seamless: 'true', frameborder: '0', src: 'about:blank'})
|
22
25
|
@iframe.appendTo(jQuery(@options.appendTo).get(0) ? 'body')
|
23
26
|
|
24
27
|
@iframe.load => @initializeFrame()
|
@@ -32,8 +35,10 @@ class @Mercury.PageEditor
|
|
32
35
|
try
|
33
36
|
return if @iframe.data('loaded')
|
34
37
|
@iframe.data('loaded', true)
|
38
|
+
alert("Opera isn't a fully supported browser, your results may not be optimal.") if jQuery.browser.opera
|
35
39
|
@document = jQuery(@iframe.get(0).contentWindow.document)
|
36
|
-
|
40
|
+
stylesToInject = Mercury.config.injectedStyles.replace(/{{regionClass}}/g, Mercury.config.regionClass)
|
41
|
+
jQuery("<style mercury-styles=\"true\">").html(stylesToInject).appendTo(@document.find('head'))
|
37
42
|
|
38
43
|
# jquery: make jQuery evaluate scripts within the context of the iframe window -- note that this means that we
|
39
44
|
# can't use eval in mercury (eg. script tags in ajax responses) because it will eval in the wrong context (you can
|
@@ -41,13 +46,18 @@ class @Mercury.PageEditor
|
|
41
46
|
# todo: look into `context` options for ajax as an alternative
|
42
47
|
iframeWindow = @iframe.get(0).contentWindow
|
43
48
|
jQuery.globalEval = (data) -> (iframeWindow.execScript || (data) -> iframeWindow["eval"].call(iframeWindow, data))(data) if (data && /\S/.test(data))
|
49
|
+
|
44
50
|
iframeWindow.Mercury = Mercury
|
51
|
+
iframeWindow.History = History if window.History && History.Adapter
|
45
52
|
|
46
53
|
@bindEvents()
|
47
54
|
@resize()
|
48
55
|
@initializeRegions()
|
49
56
|
@finalizeInterface()
|
50
57
|
Mercury.trigger('ready')
|
58
|
+
jQuery(iframeWindow).trigger('mercury:ready')
|
59
|
+
iframeWindow.Event.fire(iframeWindow, 'mercury:ready') if iframeWindow.Event && iframeWindow.Event.fire
|
60
|
+
iframeWindow.onMercuryReady() if iframeWindow.onMercuryReady
|
51
61
|
|
52
62
|
@iframe.css({visibility: 'visible'})
|
53
63
|
catch error
|
@@ -55,7 +65,7 @@ class @Mercury.PageEditor
|
|
55
65
|
|
56
66
|
|
57
67
|
initializeRegions: ->
|
58
|
-
@buildRegion(jQuery(region)) for region in jQuery(
|
68
|
+
@buildRegion(jQuery(region)) for region in jQuery(".#{Mercury.config.regionClass}", @document)
|
59
69
|
return unless @options.visible
|
60
70
|
for region in @regions
|
61
71
|
if region.focus
|
@@ -73,6 +83,9 @@ class @Mercury.PageEditor
|
|
73
83
|
|
74
84
|
|
75
85
|
finalizeInterface: ->
|
86
|
+
@santizerElement = jQuery('<div>', {id: 'mercury_sanitizer', contenteditable: 'true', style: 'position:fixed;width:100px;height:100px;top:-100px;left:-100px;opacity:0'})
|
87
|
+
@santizerElement.appendTo(@options.appendTo ? @document.find('body'))
|
88
|
+
|
76
89
|
@snippetToolbar = new Mercury.SnippetToolbar(@document)
|
77
90
|
|
78
91
|
@hijackLinksAndForms()
|
@@ -92,7 +105,7 @@ class @Mercury.PageEditor
|
|
92
105
|
@document.mousedown (event) ->
|
93
106
|
Mercury.trigger('hide:dialogs')
|
94
107
|
if Mercury.region
|
95
|
-
Mercury.trigger('unfocus:regions') unless jQuery(event.target).closest(
|
108
|
+
Mercury.trigger('unfocus:regions') unless jQuery(event.target).closest(".#{Mercury.config.regionClass}").get(0) == Mercury.region.element.get(0)
|
96
109
|
|
97
110
|
jQuery(window).resize => @resize()
|
98
111
|
window.onbeforeunload = @beforeUnload
|
@@ -138,7 +151,7 @@ class @Mercury.PageEditor
|
|
138
151
|
if jQuery(element).hasClass(classname)
|
139
152
|
ignored = true
|
140
153
|
continue
|
141
|
-
if !ignored && (element.target == '' || element.target == '_self') && !jQuery(element).closest(
|
154
|
+
if !ignored && (element.target == '' || element.target == '_self') && !jQuery(element).closest(".#{Mercury.config.regionClass}").length
|
142
155
|
jQuery(element).attr('target', '_top')
|
143
156
|
|
144
157
|
|
@@ -148,15 +161,23 @@ class @Mercury.PageEditor
|
|
148
161
|
return null
|
149
162
|
|
150
163
|
|
164
|
+
getRegionByName: (id) ->
|
165
|
+
for region in @regions
|
166
|
+
return region if region.name == id
|
167
|
+
return null
|
168
|
+
|
169
|
+
|
151
170
|
save: ->
|
152
171
|
url = @saveUrl ? Mercury.saveURL ? @iframeSrc()
|
153
172
|
data = @serialize()
|
154
173
|
Mercury.log('saving', data)
|
155
174
|
data = jQuery.toJSON(data) unless @options.saveStyle == 'form'
|
175
|
+
method = 'PUT' if @options.saveMethod == 'PUT'
|
156
176
|
jQuery.ajax url, {
|
157
|
-
type: 'POST'
|
177
|
+
type: method || 'POST'
|
178
|
+
dataType: @options.saveDataType || 'json'
|
158
179
|
headers: @saveHeaders()
|
159
|
-
data: {content: data}
|
180
|
+
data: {content: data, _method: method}
|
160
181
|
success: =>
|
161
182
|
Mercury.changes = false
|
162
183
|
error: =>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Mercury.PageEditor extends Mercury.PageEditor
|
2
|
+
|
3
|
+
save: ->
|
4
|
+
url = @saveUrl ? Mercury.saveURL ? @iframeSrc()
|
5
|
+
data = @serializeAsXml()
|
6
|
+
console.log('saving', data)
|
7
|
+
return
|
8
|
+
method = 'PUT' if @options.saveMethod == 'PUT'
|
9
|
+
jQuery.ajax url, {
|
10
|
+
type: method || 'POST'
|
11
|
+
dataType: 'xml'
|
12
|
+
data: data
|
13
|
+
success: =>
|
14
|
+
Mercury.changes = false
|
15
|
+
error: =>
|
16
|
+
alert("Mercury was unable to save to the url: #{url}")
|
17
|
+
}
|
18
|
+
|
19
|
+
serializeAsXml: ->
|
20
|
+
data = @serialize()
|
21
|
+
regionNodes = []
|
22
|
+
for regionName, regionProperties of data
|
23
|
+
snippetNodes = []
|
24
|
+
for snippetName, snippetProperties of regionProperties['snippets']
|
25
|
+
snippetNodes.push("<#{snippetName} name=\"#{snippetProperties['name']}\"><![CDATA[#{jQuery.toJSON(snippetProperties['options'])}]]></#{snippetName}>")
|
26
|
+
regionNodes.push("<region name=\"#{regionName}\" type=\"#{regionProperties['type']}\"><value>\n<![CDATA[#{regionProperties['value']}]]>\n</value><snippets>#{snippetNodes.join('')}</snippets></region>")
|
27
|
+
return "<regions>#{regionNodes.join('')}</regions>"
|
@@ -0,0 +1,9 @@
|
|
1
|
+
/*!
|
2
|
+
* This is an example plugin that will serialize to XML instead of JSON before saving. This could be useful to someone,
|
3
|
+
* but is mostly provided as an example of how to write a simple plugin.
|
4
|
+
*
|
5
|
+
* This file is could be a nice place to provide configuration options for your plugin.
|
6
|
+
*
|
7
|
+
*= require_self
|
8
|
+
*= require_tree ./mercury
|
9
|
+
*/
|
@@ -66,11 +66,11 @@ class @Mercury.Region
|
|
66
66
|
togglePreview: ->
|
67
67
|
if @previewing
|
68
68
|
@previewing = false
|
69
|
-
@element.addClass(
|
69
|
+
@element.addClass(Mercury.config.regionClass).removeClass("#{Mercury.config.regionClass}-preview")
|
70
70
|
@focus() if Mercury.region == @
|
71
71
|
else
|
72
72
|
@previewing = true
|
73
|
-
@element.addClass(
|
73
|
+
@element.addClass("#{Mercury.config.regionClass}-preview").removeClass(Mercury.config.regionClass)
|
74
74
|
Mercury.trigger('region:blurred', {region: @})
|
75
75
|
|
76
76
|
|
@@ -28,11 +28,14 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
28
28
|
|
29
29
|
# add the basic editor settings to the document (only once)
|
30
30
|
unless @document.mercuryEditing
|
31
|
-
@document.execCommand('styleWithCSS', false, false)
|
32
|
-
@document.execCommand('insertBROnReturn', false, true)
|
33
|
-
@document.execCommand('enableInlineTableEditing', false, false)
|
34
|
-
@document.execCommand('enableObjectResizing', false, false)
|
35
31
|
@document.mercuryEditing = true
|
32
|
+
try
|
33
|
+
@document.execCommand('styleWithCSS', false, false)
|
34
|
+
@document.execCommand('insertBROnReturn', false, true)
|
35
|
+
@document.execCommand('enableInlineTableEditing', false, false)
|
36
|
+
@document.execCommand('enableObjectResizing', false, false)
|
37
|
+
catch e
|
38
|
+
# intentionally do nothing if any of these fail, to broaden support for Opera
|
36
39
|
|
37
40
|
|
38
41
|
bindEvents: ->
|
@@ -85,7 +88,7 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
85
88
|
# isn't handled (eg, putting the image where it was dropped,) so to allow the browser to do it's thing, and also do
|
86
89
|
# our thing we have this little hack. *sigh*
|
87
90
|
# read: http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
|
88
|
-
@element.bind 'possible:drop',
|
91
|
+
@element.bind 'possible:drop', =>
|
89
92
|
return if @previewing
|
90
93
|
if snippet = @element.find('img[data-snippet]').get(0)
|
91
94
|
@focus()
|
@@ -96,16 +99,15 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
96
99
|
# through a clipboard in firefox (heaven forbid), and to keep the behavior across all browsers, we manually detect
|
97
100
|
# what was pasted by running a quick diff, removing it by calling undo, making our adjustments, and then putting the
|
98
101
|
# content back. This is possible, so it doesn't make sense why it wouldn't be exposed in a sensible way. *sigh*
|
99
|
-
@element.bind 'paste', =>
|
102
|
+
@element.bind 'paste', (event) =>
|
100
103
|
return if @previewing
|
101
104
|
return unless Mercury.region == @
|
102
|
-
Mercury.changes = true
|
103
105
|
if @specialContainer
|
104
106
|
event.preventDefault()
|
105
107
|
return
|
106
|
-
|
107
|
-
|
108
|
-
@
|
108
|
+
return if @pasting
|
109
|
+
Mercury.changes = true
|
110
|
+
@handlePaste(event.originalEvent)
|
109
111
|
|
110
112
|
@element.focus =>
|
111
113
|
return if @previewing
|
@@ -144,25 +146,27 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
144
146
|
return
|
145
147
|
|
146
148
|
when 13 # enter
|
147
|
-
if jQuery.browser.webkit && @selection().commonAncestor().closest('li, ul', @element).length == 0
|
149
|
+
if jQuery.browser.webkit && @selection().commonAncestor().closest('li, ul, ol', @element).length == 0
|
148
150
|
event.preventDefault()
|
149
|
-
@document.execCommand('
|
150
|
-
else if @specialContainer
|
151
|
-
# mozilla: pressing enter in any
|
151
|
+
@document.execCommand('insertParagraph', false, null)
|
152
|
+
else if @specialContainer || jQuery.browser.opera
|
153
|
+
# mozilla: pressing enter in any element besides a div handles strangely
|
152
154
|
event.preventDefault()
|
153
155
|
@document.execCommand('insertHTML', false, '<br/>')
|
154
156
|
|
155
157
|
when 9 # tab
|
156
158
|
event.preventDefault()
|
157
159
|
container = @selection().commonAncestor()
|
158
|
-
handled = false
|
159
160
|
|
160
161
|
# indent when inside of an li
|
161
162
|
if container.closest('li', @element).length
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
163
|
+
unless event.shiftKey
|
164
|
+
@execCommand('indent')
|
165
|
+
# do not outdent on last ul/ol parent, or we break out of the list
|
166
|
+
else if container.parents('ul, ol').length > 1
|
167
|
+
@execCommand('outdent')
|
168
|
+
else
|
169
|
+
@execCommand('insertHTML', {value: ' '})
|
166
170
|
|
167
171
|
if event.metaKey
|
168
172
|
switch event.keyCode
|
@@ -320,78 +324,69 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
320
324
|
return element
|
321
325
|
|
322
326
|
|
323
|
-
handlePaste: (
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
#
|
340
|
-
@
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
327
|
+
handlePaste: (event) ->
|
328
|
+
# get the text content from the clipboard and fall back to using the sanitizer if unavailable
|
329
|
+
if Mercury.config.pasting.sanitize == 'text' && event.clipboardData
|
330
|
+
@execCommand('insertHTML', {value: event.clipboardData.getData('text/plain')})
|
331
|
+
event.preventDefault()
|
332
|
+
return
|
333
|
+
else
|
334
|
+
console.debug('testing')
|
335
|
+
# get current selection & range
|
336
|
+
selection = @selection()
|
337
|
+
selection.placeMarker()
|
338
|
+
|
339
|
+
sanitizer = jQuery('#mercury_sanitizer', @document).focus()
|
340
|
+
|
341
|
+
# set 1ms timeout to allow paste event to complete
|
342
|
+
setTimeout 1, =>
|
343
|
+
# sanitize the content
|
344
|
+
content = @sanitize(sanitizer)
|
345
|
+
|
346
|
+
# move cursor back to original element & position
|
347
|
+
selection.selectMarker(@element)
|
348
|
+
selection.removeMarker()
|
349
|
+
|
350
|
+
# paste sanitized content
|
351
|
+
@element.focus()
|
352
|
+
@execCommand('insertHTML', {value: content})
|
353
|
+
|
354
|
+
|
355
|
+
sanitize: (sanitizer) ->
|
356
|
+
# always remove nested regions
|
357
|
+
sanitizer.find(".#{Mercury.config.regionClass}").remove()
|
358
|
+
|
359
|
+
if Mercury.config.pasting.sanitize
|
360
|
+
switch Mercury.config.pasting.sanitize
|
361
|
+
when 'blacklist'
|
362
|
+
# todo: finish writing black list functionality
|
363
|
+
sanitizer.find('[style]').removeAttr('style')
|
364
|
+
sanitizer.find('[class="Apple-style-span"]').removeClass('Apple-style-span')
|
365
|
+
content = sanitizer.html()
|
366
|
+
when 'whitelist'
|
367
|
+
for element in sanitizer.find('*')
|
368
|
+
allowed = false
|
369
|
+
for allowedTag, allowedAttributes of Mercury.config.pasting.whitelist
|
370
|
+
if element.tagName.toLowerCase() == allowedTag.toLowerCase()
|
371
|
+
allowed = true
|
372
|
+
for attr, index in element.attributes
|
373
|
+
jQuery(element).attr(attr.name, null) if attr && allowedAttributes.indexOf(attr.name) == -1
|
374
|
+
break
|
375
|
+
jQuery(element).replaceWith(jQuery(element).contents()) unless allowed
|
376
|
+
content = sanitizer.html()
|
377
|
+
else content = sanitizer.text()
|
378
|
+
else
|
379
|
+
# force text if it looks like it's from word/pages, even if there's no sanitizing requested
|
380
|
+
content = sanitizer.html()
|
381
|
+
if content.indexOf('<!--StartFragment-->') > -1 || content.indexOf('="mso-') > -1 || content.indexOf('<o:') > -1 || content.indexOf('="Mso') > -1
|
382
|
+
content = sanitizer.text()
|
351
383
|
|
352
|
-
|
353
|
-
|
384
|
+
sanitizer.html('')
|
385
|
+
return content
|
354
386
|
|
355
387
|
|
356
388
|
# Custom actions (eg. things that execCommand doesn't do, or doesn't do well)
|
357
389
|
@actions: {
|
358
|
-
# bold: (selection) ->
|
359
|
-
# unless selection.collapsed
|
360
|
-
# @document.execCommand('bold', false, null)
|
361
|
-
# else
|
362
|
-
## selection.selectWordByCursor()
|
363
|
-
## @document.execCommand('bold', false, null)
|
364
|
-
#
|
365
|
-
# selection.placeMarker()
|
366
|
-
# node = @element.find('.mercury-marker').get(0)
|
367
|
-
# prev = node.previousSibling
|
368
|
-
# selection.removeMarker()
|
369
|
-
# next = prev.nextSibling
|
370
|
-
# console.debug(prev, next)
|
371
|
-
## if prev.textContent[prev.textContent.length - 1] != ' ' &&
|
372
|
-
|
373
|
-
|
374
|
-
#[some]| content
|
375
|
-
# textContent = some, wholeText = some
|
376
|
-
# textContent = '', wholeText = some
|
377
|
-
#content |[some]
|
378
|
-
#content [s|ome]
|
379
|
-
#co|ntent [some]
|
380
|
-
#|content [some]
|
381
|
-
|
382
|
-
|
383
|
-
#
|
384
|
-
# console.debug(commonAncestor)
|
385
|
-
# beforeText = commonAncestor.textContent
|
386
|
-
# afterText = commonAncestor.wholeText.substring(commonAncestor.wholeText.lastIndexOf(beforeText) + beforeText.length, commonAncestor.wholeText.length)
|
387
|
-
# if beforeText && afterText && (beforeChar = beforeText[beforeText.length - 1]) != ' ' && (afterChar = afterText[0]) != ' '
|
388
|
-
# console.debug('bolding word', beforeChar, afterChar)
|
389
|
-
# else
|
390
|
-
# @document.execCommand('bold', false, null)
|
391
|
-
#
|
392
|
-
## selection.selectWord()
|
393
|
-
## commonAncestor.wholeText, commonAncestor.textContent
|
394
|
-
# else
|
395
390
|
|
396
391
|
insertRowBefore: -> Mercury.tableEditor.addRow('before')
|
397
392
|
|
@@ -417,6 +412,8 @@ class @Mercury.Regions.Editable extends Mercury.Region
|
|
417
412
|
|
418
413
|
redo: -> @content(@history.redo())
|
419
414
|
|
415
|
+
horizontalRule: -> this.execCommand('insertHorizontalRule')
|
416
|
+
|
420
417
|
removeFormatting: (selection) -> selection.insertTextNode(selection.textContent())
|
421
418
|
|
422
419
|
backColor: (selection, options) -> selection.wrap("<span style=\"background-color:#{options.value.toHex()}\">", true)
|
@@ -490,7 +487,7 @@ class Mercury.Regions.Editable.Selection
|
|
490
487
|
|
491
488
|
is: (elementType) ->
|
492
489
|
content = @content()
|
493
|
-
return jQuery(content.firstChild) if content.
|
490
|
+
return jQuery(content.firstChild) if jQuery(content).length == 1 && jQuery(content.firstChild).is(elementType)
|
494
491
|
return false
|
495
492
|
|
496
493
|
|
@@ -498,18 +495,16 @@ class Mercury.Regions.Editable.Selection
|
|
498
495
|
return unless jQuery.browser.webkit
|
499
496
|
range = @context.createRange()
|
500
497
|
|
501
|
-
# todo: the \00 thing breaks when using uglifier, and is escapped to "0".. it's been fixed, but isn't available yet
|
502
|
-
# https://github.com/lautis/uglifier/issues/11
|
503
498
|
if @range
|
504
499
|
if @commonAncestor(true).closest('.mercury-snippet').length
|
505
|
-
lastChild = @context.createTextNode('\00')
|
500
|
+
lastChild = @context.createTextNode('\00')
|
506
501
|
element.appendChild(lastChild)
|
507
502
|
else
|
508
503
|
if element.lastChild && element.lastChild.nodeType == 3 && element.lastChild.textContent.replace(/^[\s+|\n+]|[\s+|\n+]$/, '') == ''
|
509
504
|
lastChild = element.lastChild
|
510
|
-
element.lastChild.textContent = '\00'
|
505
|
+
element.lastChild.textContent = '\00'
|
511
506
|
else
|
512
|
-
lastChild = @context.createTextNode('
|
507
|
+
lastChild = @context.createTextNode('\00')
|
513
508
|
element.appendChild(lastChild)
|
514
509
|
|
515
510
|
if lastChild
|
@@ -582,7 +577,7 @@ class Mercury.Regions.Editable.Selection
|
|
582
577
|
@selection.addRange(@range)
|
583
578
|
|
584
579
|
|
585
|
-
replace: (element) ->
|
580
|
+
replace: (element, collapse) ->
|
586
581
|
element = element.get(0) if element.get
|
587
582
|
element = jQuery(element, @context).get(0) if jQuery.type(element) == 'string'
|
588
583
|
|
@@ -590,3 +585,4 @@ class Mercury.Regions.Editable.Selection
|
|
590
585
|
@range.insertNode(element)
|
591
586
|
@range.selectNodeContents(element)
|
592
587
|
@selection.addRange(@range)
|
588
|
+
@range.collapse(false) if collapse
|