mercury-rails 0.2.0 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|