mercury-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +152 -0
- data/VERSION +1 -0
- data/app/assets/images/mercury/button.png +0 -0
- data/app/assets/images/mercury/clippy.png +0 -0
- data/app/assets/images/mercury/default-snippet.png +0 -0
- data/app/assets/images/mercury/loading-dark.gif +0 -0
- data/app/assets/images/mercury/loading-light.gif +0 -0
- data/app/assets/images/mercury/search-icon.png +0 -0
- data/app/assets/images/mercury/toolbar/editable/buttons.png +0 -0
- data/app/assets/images/mercury/toolbar/markupable/buttons.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/_expander.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/_pressed.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/historypanel.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/insertcharacter.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/insertlink.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/insertmedia.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/inserttable.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/inspectorpanel.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/notespanel.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/objectspanel.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/preview.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/redo.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/save.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/todospanel.png +0 -0
- data/app/assets/images/mercury/toolbar/primary/undo.png +0 -0
- data/app/assets/images/mercury/toolbar/snippetable/buttons.png +0 -0
- data/app/assets/javascripts/mercury.js +30 -0
- data/app/assets/javascripts/mercury/dialog.js.coffee +75 -0
- data/app/assets/javascripts/mercury/dialogs/backcolor.js.coffee +6 -0
- data/app/assets/javascripts/mercury/dialogs/forecolor.js.coffee +6 -0
- data/app/assets/javascripts/mercury/dialogs/formatblock.js.coffee +4 -0
- data/app/assets/javascripts/mercury/dialogs/objectspanel.js.coffee +10 -0
- data/app/assets/javascripts/mercury/dialogs/style.js.coffee +4 -0
- data/app/assets/javascripts/mercury/history_buffer.js.coffee +30 -0
- data/app/assets/javascripts/mercury/mercury.js.coffee +293 -0
- data/app/assets/javascripts/mercury/modal.js.coffee +177 -0
- data/app/assets/javascripts/mercury/modals/htmleditor.js.coffee +10 -0
- data/app/assets/javascripts/mercury/modals/insertcharacter.js.coffee +4 -0
- data/app/assets/javascripts/mercury/modals/insertlink.js.coffee +92 -0
- data/app/assets/javascripts/mercury/modals/insertmedia.js.coffee +72 -0
- data/app/assets/javascripts/mercury/modals/insertsnippet.js.coffee +11 -0
- data/app/assets/javascripts/mercury/modals/inserttable.js.coffee +56 -0
- data/app/assets/javascripts/mercury/native_extensions.js.coffee +47 -0
- data/app/assets/javascripts/mercury/page_editor.js.coffee +139 -0
- data/app/assets/javascripts/mercury/palette.js.coffee +29 -0
- data/app/assets/javascripts/mercury/panel.js.coffee +97 -0
- data/app/assets/javascripts/mercury/region.js.coffee +103 -0
- data/app/assets/javascripts/mercury/regions/editable.js.coffee +546 -0
- data/app/assets/javascripts/mercury/regions/markupable.js.coffee +380 -0
- data/app/assets/javascripts/mercury/regions/snippetable.js.coffee +127 -0
- data/app/assets/javascripts/mercury/select.js.coffee +40 -0
- data/app/assets/javascripts/mercury/snippet.js.coffee +92 -0
- data/app/assets/javascripts/mercury/snippet_toolbar.js.coffee +69 -0
- data/app/assets/javascripts/mercury/statusbar.js.coffee +25 -0
- data/app/assets/javascripts/mercury/table_editor.js.coffee +266 -0
- data/app/assets/javascripts/mercury/toolbar.button.js.coffee +152 -0
- data/app/assets/javascripts/mercury/toolbar.button_group.js.coffee +42 -0
- data/app/assets/javascripts/mercury/toolbar.expander.js.coffee +56 -0
- data/app/assets/javascripts/mercury/toolbar.js.coffee +72 -0
- data/app/assets/javascripts/mercury/tooltip.js.coffee +67 -0
- data/app/assets/javascripts/mercury/uploader.js.coffee +213 -0
- data/app/assets/javascripts/mercury/websocket.js.coffee +34 -0
- data/app/assets/stylesheets/mercury.css +31 -0
- data/app/assets/stylesheets/mercury/dialog.scss +178 -0
- data/app/assets/stylesheets/mercury/mercury.scss +119 -0
- data/app/assets/stylesheets/mercury/modal.scss +192 -0
- data/app/assets/stylesheets/mercury/statusbar.scss +23 -0
- data/app/assets/stylesheets/mercury/toolbar.scss +417 -0
- data/app/assets/stylesheets/mercury/tooltip.scss +26 -0
- data/app/assets/stylesheets/mercury/uploader.scss +109 -0
- data/app/controllers/images_controller.rb +19 -0
- data/app/controllers/mercury_controller.rb +20 -0
- data/app/models/image.rb +14 -0
- data/app/views/layouts/mercury.html.haml +12 -0
- data/app/views/mercury/modals/character.html.haml +252 -0
- data/app/views/mercury/modals/htmleditor.html.haml +8 -0
- data/app/views/mercury/modals/link.html.haml +31 -0
- data/app/views/mercury/modals/media.html.haml +33 -0
- data/app/views/mercury/modals/sanitizer.html.haml +4 -0
- data/app/views/mercury/modals/table.html.haml +49 -0
- data/app/views/mercury/palettes/backcolor.html.haml +79 -0
- data/app/views/mercury/palettes/forecolor.html.haml +79 -0
- data/app/views/mercury/panels/history.html.haml +0 -0
- data/app/views/mercury/panels/notes.html.haml +0 -0
- data/app/views/mercury/panels/snippets.html.haml +10 -0
- data/app/views/mercury/selects/formatblock.html.haml +10 -0
- data/app/views/mercury/selects/style.html.haml +4 -0
- data/app/views/mercury/snippets/example.html.haml +2 -0
- data/app/views/mercury/snippets/example_options.html.haml +16 -0
- data/config/engine.rb +6 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20110526035601_create_images.rb +11 -0
- data/features/editing/basic.feature +11 -0
- data/features/step_definitions/debug_steps.rb +14 -0
- data/features/step_definitions/web_steps.rb +211 -0
- data/features/support/env.rb +46 -0
- data/features/support/paths.rb +35 -0
- data/features/support/selectors.rb +42 -0
- data/lib/mercury-rails.rb +4 -0
- data/log/.gitkeep +0 -0
- data/mercury-rails.gemspec +230 -0
- data/spec/javascripts/mercury/dialog_spec.js.coffee +258 -0
- data/spec/javascripts/mercury/history_buffer_spec.js.coffee +79 -0
- data/spec/javascripts/mercury/mercury_spec.js.coffee +52 -0
- data/spec/javascripts/mercury/native_extensions_spec.js.coffee +66 -0
- data/spec/javascripts/mercury/page_editor_spec.js.coffee +435 -0
- data/spec/javascripts/mercury/palette_spec.js.coffee +51 -0
- data/spec/javascripts/mercury/panel_spec.js.coffee +147 -0
- data/spec/javascripts/mercury/region_spec.js.coffee +261 -0
- data/spec/javascripts/mercury/regions/_editable_.js.coffee +0 -0
- data/spec/javascripts/mercury/regions/_markupable_.js.coffee +0 -0
- data/spec/javascripts/mercury/regions/snippetable_spec.js.coffee +368 -0
- data/spec/javascripts/mercury/select_spec.js.coffee +51 -0
- data/spec/javascripts/mercury/snippet_spec.js.coffee +246 -0
- data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +186 -0
- data/spec/javascripts/mercury/statusbar_spec.js.coffee +78 -0
- data/spec/javascripts/mercury/table_editor_spec.js.coffee +192 -0
- data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +92 -0
- data/spec/javascripts/mercury/toolbar.button_spec.js.coffee +341 -0
- data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +120 -0
- data/spec/javascripts/mercury/toolbar_spec.js.coffee +152 -0
- data/spec/javascripts/mercury/tooltip_spec.js.coffee +188 -0
- data/spec/javascripts/mercury/uploader_spec.js.coffee +512 -0
- data/spec/javascripts/responses/blank.html +1 -0
- data/spec/javascripts/spec_helper.js +513 -0
- data/spec/javascripts/templates/mercury/dialog.html +2 -0
- data/spec/javascripts/templates/mercury/page_editor.html +24 -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/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 +7 -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 +10 -0
- data/spec/javascripts/templates/mercury/tooltip.html +12 -0
- data/spec/javascripts/templates/mercury/uploader.html +11 -0
- data/vendor/assets/javascripts/jquery-1.6.js +8865 -0
- data/vendor/assets/javascripts/jquery-ui-1.8.13.custom.min.js +249 -0
- data/vendor/assets/javascripts/jquery-ui-1.8.13.sortable.custom.js +1078 -0
- data/vendor/assets/javascripts/jquery.easing.js +173 -0
- data/vendor/assets/javascripts/jquery.json2.js +178 -0
- data/vendor/assets/javascripts/jquery.serialize_object.js +16 -0
- data/vendor/assets/javascripts/jquery.ujs.js +289 -0
- data/vendor/assets/javascripts/liquidmetal.js +88 -0
- data/vendor/assets/javascripts/showdown.js +1362 -0
- metadata +364 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
class @Mercury.Toolbar.Button
|
2
|
+
|
3
|
+
constructor: (@name, @title, @summary = null, @types = [], @options = {}) ->
|
4
|
+
@build()
|
5
|
+
@bindEvents()
|
6
|
+
return @element
|
7
|
+
|
8
|
+
|
9
|
+
build: ->
|
10
|
+
@element = $('<div>', {title: @summary ? @title, class: "mercury-button mercury-#{@name}-button"}).html("<em>#{@title}</em>")
|
11
|
+
@element.data('expander', "<div class=\"mercury-expander-button\" data-button=\"#{@name}\"><em></em><span>#{@title}</span></div>")
|
12
|
+
|
13
|
+
@handled = []
|
14
|
+
dialogOptions = {title: @summary || @title, preload: @types.preload, appendTo: @options.appendDialogsTo || 'body', for: @element}
|
15
|
+
for type, mixed of @types
|
16
|
+
switch type
|
17
|
+
|
18
|
+
when 'preload' then true
|
19
|
+
|
20
|
+
when 'regions'
|
21
|
+
@element.addClass('disabled')
|
22
|
+
@handled[type] = if $.isFunction(mixed) then mixed.call(@, @name) else mixed
|
23
|
+
|
24
|
+
when 'toggle'
|
25
|
+
@handled[type] = true
|
26
|
+
|
27
|
+
when 'mode'
|
28
|
+
@handled[type] = if mixed == true then @name else mixed
|
29
|
+
|
30
|
+
when 'context'
|
31
|
+
@handled[type] = if $.isFunction(mixed) then mixed else Mercury.Toolbar.Button.contexts[@name]
|
32
|
+
|
33
|
+
when 'palette'
|
34
|
+
@element.addClass("mercury-button-palette")
|
35
|
+
url = if $.isFunction(mixed) then mixed.call(@, @name) else mixed
|
36
|
+
@handled[type] = new Mercury.Palette(url, @name, dialogOptions)
|
37
|
+
|
38
|
+
when 'select'
|
39
|
+
@element.addClass("mercury-button-select").find('em').html(@title)
|
40
|
+
url = if $.isFunction(mixed) then mixed.call(@, @name) else mixed
|
41
|
+
@handled[type] = new Mercury.Select(url, @name, dialogOptions)
|
42
|
+
|
43
|
+
when 'panel'
|
44
|
+
@element.addClass('mercury-button-panel')
|
45
|
+
url = if $.isFunction(mixed) then mixed.call(@, @name) else mixed
|
46
|
+
@handled['toggle'] = true
|
47
|
+
@handled[type] = new Mercury.Panel(url, @name, dialogOptions)
|
48
|
+
|
49
|
+
when 'modal'
|
50
|
+
@handled[type] = if $.isFunction(mixed) then mixed.apply(@, @name) else mixed
|
51
|
+
|
52
|
+
else throw "Unknown button type #{type} used for the #{@name} button."
|
53
|
+
|
54
|
+
|
55
|
+
bindEvents: ->
|
56
|
+
Mercury.bind 'button', (event, options) =>
|
57
|
+
@element.click() if options.action == @name
|
58
|
+
|
59
|
+
Mercury.bind 'region:update', (event, options) =>
|
60
|
+
if @handled.context && options.region && $.type(options.region.currentElement) == 'function'
|
61
|
+
element = options.region.currentElement()
|
62
|
+
if element.length && @handled.context.call(@, element, options.region.element)
|
63
|
+
@element.addClass('active')
|
64
|
+
else
|
65
|
+
@element.removeClass('active')
|
66
|
+
else
|
67
|
+
@element.removeClass('active')
|
68
|
+
|
69
|
+
Mercury.bind 'region:focused', (event, options) =>
|
70
|
+
if @handled.regions && options.region && options.region.type
|
71
|
+
if @handled.regions.indexOf(options.region.type) > -1
|
72
|
+
@element.removeClass('disabled')
|
73
|
+
else
|
74
|
+
@element.addClass('disabled')
|
75
|
+
|
76
|
+
Mercury.bind 'region:blurred', (event, options) =>
|
77
|
+
@element.addClass('disabled') if @handled.regions
|
78
|
+
|
79
|
+
@element.mousedown (event) =>
|
80
|
+
@element.addClass('active')
|
81
|
+
|
82
|
+
@element.mouseup (event) =>
|
83
|
+
@element.removeClass('active')
|
84
|
+
|
85
|
+
@element.click (event) =>
|
86
|
+
if @element.closest('.disabled').length then return
|
87
|
+
|
88
|
+
handled = false
|
89
|
+
for type, mixed of @handled
|
90
|
+
switch type
|
91
|
+
|
92
|
+
when 'toggle'
|
93
|
+
@togglePressed()
|
94
|
+
|
95
|
+
when 'mode'
|
96
|
+
handled = true
|
97
|
+
Mercury.trigger('mode', {mode: mixed})
|
98
|
+
|
99
|
+
when 'modal'
|
100
|
+
Mercury.modal(@handled.modal, {title: @summary || @title, handler: @name})
|
101
|
+
|
102
|
+
when 'palette', 'select', 'panel'
|
103
|
+
event.stopPropagation()
|
104
|
+
handled = true
|
105
|
+
@handled[type].toggle()
|
106
|
+
|
107
|
+
Mercury.trigger('action', {action: @name}) unless handled
|
108
|
+
Mercury.trigger('focus:frame')
|
109
|
+
|
110
|
+
|
111
|
+
togglePressed: ->
|
112
|
+
@element.toggleClass('pressed')
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
# Button contexts
|
117
|
+
@Mercury.Toolbar.Button.contexts =
|
118
|
+
|
119
|
+
backcolor: (node) -> @element.css('background-color', node.css('background-color'))
|
120
|
+
|
121
|
+
forecolor: (node) -> @element.css('background-color', node.css('color'))
|
122
|
+
|
123
|
+
bold: (node) ->
|
124
|
+
weight = node.css('font-weight')
|
125
|
+
weight == 'bold' || weight > 400
|
126
|
+
|
127
|
+
italic: (node) -> node.css('font-style') == 'italic'
|
128
|
+
|
129
|
+
# todo: overline is a bit weird because <u> and <strike> override text-decoration, so we can't always tell
|
130
|
+
# todo: maybe walk up the tree if it's not too expensive?
|
131
|
+
overline: (node) -> node.css('text-decoration') == 'overline'
|
132
|
+
|
133
|
+
# todo: this should never check for tags, because they could be styled differently
|
134
|
+
strikethrough: (node, region) -> node.css('text-decoration') == 'line-through' || !!node.closest('strike', region).length
|
135
|
+
|
136
|
+
underline: (node, region) -> node.css('text-decoration') == 'underline' || !!node.closest('u', region).length
|
137
|
+
|
138
|
+
subscript: (node, region) -> !!node.closest('sub', region).length
|
139
|
+
|
140
|
+
superscript: (node, region) -> !!node.closest('sup', region).length
|
141
|
+
|
142
|
+
justifyleft: (node) -> node.css('text-align').indexOf('left') > -1
|
143
|
+
|
144
|
+
justifycenter: (node) -> node.css('text-align').indexOf('center') > -1
|
145
|
+
|
146
|
+
justifyright: (node) -> node.css('text-align').indexOf('right') > -1
|
147
|
+
|
148
|
+
justifyfull: (node) -> node.css('text-align').indexOf('justify') > -1
|
149
|
+
|
150
|
+
insertorderedlist: (node, region) -> !!node.closest('ol', region.element).length
|
151
|
+
|
152
|
+
insertunorderedlist: (node, region) -> !!node.closest('ul', region.element).length
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class @Mercury.Toolbar.ButtonGroup
|
2
|
+
|
3
|
+
constructor: (@name, @options = {}) ->
|
4
|
+
@build()
|
5
|
+
@bindEvents()
|
6
|
+
@regions = @options._regions
|
7
|
+
return @element
|
8
|
+
|
9
|
+
|
10
|
+
build: ->
|
11
|
+
@element = $('<div>', {class: "mercury-button-group mercury-#{@name}-group"})
|
12
|
+
if @options._context || @options._regions
|
13
|
+
@element.addClass('disabled')
|
14
|
+
|
15
|
+
|
16
|
+
bindEvents: ->
|
17
|
+
Mercury.bind 'region:update', (event, options) =>
|
18
|
+
context = Mercury.Toolbar.ButtonGroup.contexts[@name]
|
19
|
+
if context
|
20
|
+
if options.region && $.type(options.region.currentElement) == 'function'
|
21
|
+
element = options.region.currentElement()
|
22
|
+
if element.length && context.call(@, element, options.region.element)
|
23
|
+
@element.removeClass('disabled')
|
24
|
+
else
|
25
|
+
@element.addClass('disabled')
|
26
|
+
|
27
|
+
Mercury.bind 'region:focused', (event, options) =>
|
28
|
+
if @regions && options.region && options.region.type
|
29
|
+
if @regions.indexOf(options.region.type) > -1
|
30
|
+
@element.removeClass('disabled') unless @options._context
|
31
|
+
else
|
32
|
+
@element.addClass('disabled')
|
33
|
+
|
34
|
+
Mercury.bind 'region:blurred', (event, options) =>
|
35
|
+
@element.addClass('disabled') if @options.regions
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
# ButtonGroup contexts
|
40
|
+
@Mercury.Toolbar.ButtonGroup.contexts =
|
41
|
+
|
42
|
+
table: (node, region) -> !!node.closest('table', region).length
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class @Mercury.Toolbar.Expander extends Mercury.Palette
|
2
|
+
|
3
|
+
constructor: (@name, @options) ->
|
4
|
+
@container = @options.for
|
5
|
+
@containerWidth = @container.outerWidth()
|
6
|
+
super(null, @name, @options)
|
7
|
+
return @element
|
8
|
+
|
9
|
+
|
10
|
+
build: ->
|
11
|
+
@container.css({whiteSpace: 'normal'})
|
12
|
+
@trigger = $('<div>', {class: 'mercury-toolbar-expander'}).appendTo($(@options.appendTo).get(0) ? 'body')
|
13
|
+
@element = $('<div>', {class: "mercury-palette mercury-expander mercury-#{@name}-expander", style: 'display:none'})
|
14
|
+
@windowResize()
|
15
|
+
|
16
|
+
|
17
|
+
bindEvents: ->
|
18
|
+
Mercury.bind 'hide:dialogs', (event, dialog) => @hide() unless dialog == @
|
19
|
+
Mercury.bind 'resize', => @windowResize()
|
20
|
+
|
21
|
+
super
|
22
|
+
|
23
|
+
@trigger.click (event) =>
|
24
|
+
event.stopPropagation()
|
25
|
+
hiddenButtons = []
|
26
|
+
for button in @container.find('.mercury-button')
|
27
|
+
button = $(button)
|
28
|
+
hiddenButtons.push(button.data('expander')) if button.position().top > 5
|
29
|
+
|
30
|
+
@loadContent(hiddenButtons.join(''))
|
31
|
+
@toggle()
|
32
|
+
|
33
|
+
@element.click (event) =>
|
34
|
+
buttonName = $(event.target).closest('[data-button]').data('button')
|
35
|
+
button = @container.find(".mercury-#{buttonName}-button")
|
36
|
+
button.click()
|
37
|
+
|
38
|
+
|
39
|
+
windowResize: ->
|
40
|
+
if @containerWidth > $(window).width() then @trigger.show() else @trigger.hide()
|
41
|
+
@hide()
|
42
|
+
|
43
|
+
|
44
|
+
position: (keepVisible) ->
|
45
|
+
@element.css({top: 0, left: 0, display: 'block', visibility: 'hidden'})
|
46
|
+
position = @trigger.offset()
|
47
|
+
width = @element.width()
|
48
|
+
|
49
|
+
position.left = position.left - width + @trigger.width() if position.left + width > $(window).width()
|
50
|
+
|
51
|
+
@element.css {
|
52
|
+
top: position.top + @trigger.height(),
|
53
|
+
left: position.left,
|
54
|
+
display: if keepVisible then 'block' else 'none',
|
55
|
+
visibility: 'visible'
|
56
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class @Mercury.Toolbar
|
2
|
+
|
3
|
+
constructor: (@options = {}) ->
|
4
|
+
@build()
|
5
|
+
@bindEvents()
|
6
|
+
|
7
|
+
|
8
|
+
build: ->
|
9
|
+
@element = $('<div>', {class: 'mercury-toolbar-container', style: 'width:10000px'})
|
10
|
+
@element.appendTo($(@options.appendTo).get(0) ? 'body')
|
11
|
+
|
12
|
+
for toolbarName, buttons of Mercury.config.toolbars
|
13
|
+
continue if buttons._custom
|
14
|
+
toolbar = $('<div>', {class: "mercury-toolbar mercury-#{toolbarName}-toolbar"}).appendTo(@element)
|
15
|
+
toolbar.attr('data-regions', buttons._regions) if buttons._regions
|
16
|
+
container = $('<div>', {class: 'mercury-toolbar-button-container'}).appendTo(toolbar)
|
17
|
+
|
18
|
+
for buttonName, options of buttons
|
19
|
+
continue if buttonName == '_regions'
|
20
|
+
button = @buildButton(buttonName, options)
|
21
|
+
button.appendTo(container) if button
|
22
|
+
|
23
|
+
if container.css('white-space') == 'nowrap'
|
24
|
+
expander = new Mercury.Toolbar.Expander(toolbarName, {appendTo: toolbar, for: container})
|
25
|
+
expander.appendTo(@element)
|
26
|
+
|
27
|
+
toolbar.addClass('disabled') unless toolbarName == 'primary'
|
28
|
+
|
29
|
+
@element.css({width: '100%'})
|
30
|
+
|
31
|
+
|
32
|
+
buildButton: (name, options) ->
|
33
|
+
return false if name[0] == '_'
|
34
|
+
switch $.type(options)
|
35
|
+
|
36
|
+
when 'array' # button
|
37
|
+
[title, summary, handled] = options
|
38
|
+
new Mercury.Toolbar.Button(name, title, summary, handled, {appendDialogsTo: @element})
|
39
|
+
|
40
|
+
when 'object' # button group
|
41
|
+
group = new Mercury.Toolbar.ButtonGroup(name, options)
|
42
|
+
for a, o of options
|
43
|
+
button = @buildButton(a, o)
|
44
|
+
button.appendTo(group) if button
|
45
|
+
group
|
46
|
+
|
47
|
+
when 'string' # separator
|
48
|
+
$('<hr>', {class: "mercury-#{if options == '-' then 'line-separator' else 'separator'}"})
|
49
|
+
|
50
|
+
else throw "Unknown button structure -- please provide an array, object, or string for #{name}."
|
51
|
+
|
52
|
+
|
53
|
+
bindEvents: ->
|
54
|
+
Mercury.bind 'region:focused', (event, options) =>
|
55
|
+
for toolbar in @element.find(".mercury-toolbar")
|
56
|
+
toolbar = $(toolbar)
|
57
|
+
if regions = toolbar.data('regions')
|
58
|
+
toolbar.removeClass('disabled') if regions.split(',').indexOf(options.region.type) > -1
|
59
|
+
|
60
|
+
Mercury.bind 'region:blurred', (event, options) =>
|
61
|
+
for toolbar in @element.find(".mercury-toolbar")
|
62
|
+
toolbar = $(toolbar)
|
63
|
+
if regions = toolbar.data('regions')
|
64
|
+
toolbar.addClass('disabled') if regions.split(',').indexOf(options.region.type) > -1
|
65
|
+
|
66
|
+
|
67
|
+
@element.click -> Mercury.trigger('hide:dialogs')
|
68
|
+
@element.mousedown (event) -> event.preventDefault()
|
69
|
+
|
70
|
+
|
71
|
+
height: ->
|
72
|
+
@element.outerHeight()
|
@@ -0,0 +1,67 @@
|
|
1
|
+
@Mercury.tooltip = (forElement, content, options = {}) ->
|
2
|
+
Mercury.tooltip.show(forElement, content, options)
|
3
|
+
return Mercury.tooltip
|
4
|
+
|
5
|
+
$.extend Mercury.tooltip, {
|
6
|
+
|
7
|
+
show: (@forElement, @content, @options = {}) ->
|
8
|
+
@document = $(@forElement.get(0).ownerDocument)
|
9
|
+
@initialize()
|
10
|
+
if @visible then @update() else @appear()
|
11
|
+
|
12
|
+
|
13
|
+
initialize: ->
|
14
|
+
return if @initialized
|
15
|
+
@build()
|
16
|
+
@bindEvents()
|
17
|
+
@initialized = true
|
18
|
+
|
19
|
+
|
20
|
+
build: ->
|
21
|
+
@element = $('<div>', {class: 'mercury-tooltip'})
|
22
|
+
@element.appendTo($(@options.appendTo).get(0) ? 'body')
|
23
|
+
|
24
|
+
|
25
|
+
bindEvents: ->
|
26
|
+
Mercury.bind 'resize', => @position() if @visible
|
27
|
+
@document.scroll => @position() if @visible
|
28
|
+
@element.mousedown (event) ->
|
29
|
+
event.preventDefault()
|
30
|
+
event.stopPropagation()
|
31
|
+
|
32
|
+
|
33
|
+
appear: ->
|
34
|
+
@update()
|
35
|
+
|
36
|
+
@element.show()
|
37
|
+
@element.animate {opacity: 1}, 200, 'easeInOutSine', =>
|
38
|
+
@visible = true
|
39
|
+
|
40
|
+
|
41
|
+
update: ->
|
42
|
+
@element.html(@content)
|
43
|
+
@position()
|
44
|
+
|
45
|
+
|
46
|
+
position: ->
|
47
|
+
offset = @forElement.offset()
|
48
|
+
width = @element.width()
|
49
|
+
|
50
|
+
top = offset.top + (Mercury.displayRect.top - $(@document).scrollTop()) + @forElement.outerHeight()
|
51
|
+
left = offset.left - $(@document).scrollLeft()
|
52
|
+
|
53
|
+
left = left - (left + width + 25) - Mercury.displayRect.width if (left + width + 25) > Mercury.displayRect.width
|
54
|
+
left = if left <= 0 then 0 else left
|
55
|
+
|
56
|
+
@element.css {
|
57
|
+
top: top,
|
58
|
+
left: left
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
hide: ->
|
63
|
+
return unless @initialized
|
64
|
+
@element.hide()
|
65
|
+
@visible = false
|
66
|
+
|
67
|
+
}
|
@@ -0,0 +1,213 @@
|
|
1
|
+
@Mercury.uploader = (file, options) ->
|
2
|
+
Mercury.uploader.show(file, options)
|
3
|
+
return Mercury.uploader
|
4
|
+
|
5
|
+
$.extend Mercury.uploader, {
|
6
|
+
|
7
|
+
show: (file, @options = {}) ->
|
8
|
+
@file = new Mercury.uploader.File(file)
|
9
|
+
if @file.errors
|
10
|
+
alert("Error: #{@file.errors}")
|
11
|
+
return
|
12
|
+
return unless @supported()
|
13
|
+
|
14
|
+
Mercury.trigger('focus:window')
|
15
|
+
@initialize()
|
16
|
+
@appear()
|
17
|
+
|
18
|
+
|
19
|
+
initialize: ->
|
20
|
+
return if @initialized
|
21
|
+
@build()
|
22
|
+
@bindEvents()
|
23
|
+
@initialized = true
|
24
|
+
|
25
|
+
|
26
|
+
supported: ->
|
27
|
+
xhr = new XMLHttpRequest
|
28
|
+
fileReader = window.FileReader
|
29
|
+
|
30
|
+
if window.Uint8Array && window.ArrayBuffer && !XMLHttpRequest.prototype.sendAsBinary
|
31
|
+
XMLHttpRequest::sendAsBinary = (datastr) ->
|
32
|
+
ui8a = new Uint8Array(datastr.length)
|
33
|
+
ui8a[index] = (datastr.charCodeAt(index) & 0xff) for data, index in datastr
|
34
|
+
@send(ui8a.buffer)
|
35
|
+
|
36
|
+
return !!(xhr.upload && xhr.sendAsBinary && fileReader)
|
37
|
+
|
38
|
+
|
39
|
+
build: ->
|
40
|
+
@element = $('<div>', {class: 'mercury-uploader', style: 'display:none'})
|
41
|
+
@element.append('<div class="mercury-uploader-preview"><b><img/></b></div>')
|
42
|
+
@element.append('<div class="mercury-uploader-details"></div>')
|
43
|
+
@element.append('<div class="mercury-uploader-progress"><span>Processing...</span><div class="mercury-uploader-indicator"><div><b>0%</b></div></div></div>')
|
44
|
+
|
45
|
+
@overlay = $('<div>', {class: 'mercury-uploader-overlay', style: 'display:none'})
|
46
|
+
|
47
|
+
@element.appendTo($(@options.appendTo).get(0) ? 'body')
|
48
|
+
@overlay.appendTo($(@options.appendTo).get(0) ? 'body')
|
49
|
+
|
50
|
+
|
51
|
+
bindEvents: ->
|
52
|
+
Mercury.bind 'resize', => @position()
|
53
|
+
|
54
|
+
|
55
|
+
appear: ->
|
56
|
+
@fillDisplay()
|
57
|
+
@position()
|
58
|
+
|
59
|
+
@overlay.show()
|
60
|
+
@overlay.animate {opacity: 1}, 200, 'easeInOutSine', =>
|
61
|
+
@element.show()
|
62
|
+
@element.animate {opacity: 1}, 200, 'easeInOutSine', =>
|
63
|
+
@visible = true
|
64
|
+
@loadImage()
|
65
|
+
|
66
|
+
|
67
|
+
position: ->
|
68
|
+
viewportWidth = $(window).width()
|
69
|
+
viewportHeight = $(window).height()
|
70
|
+
|
71
|
+
width = @element.outerWidth()
|
72
|
+
height = @element.outerHeight()
|
73
|
+
|
74
|
+
@element.css {
|
75
|
+
top: (viewportHeight - height) / 2
|
76
|
+
left: (viewportWidth - width) / 2
|
77
|
+
}
|
78
|
+
|
79
|
+
|
80
|
+
fillDisplay: ->
|
81
|
+
details = ["Name: #{@file.name}", "Size: #{@file.readableSize}", "Type: #{@file.type}"]
|
82
|
+
@element.find('.mercury-uploader-details').html(details.join('<br/>'))
|
83
|
+
|
84
|
+
|
85
|
+
loadImage: ->
|
86
|
+
@file.readAsDataURL (result) =>
|
87
|
+
@element.find('.mercury-uploader-preview b').html($('<img>', {src: result}))
|
88
|
+
@upload()
|
89
|
+
|
90
|
+
|
91
|
+
upload: ->
|
92
|
+
xhr = new XMLHttpRequest
|
93
|
+
$.each ['onloadstart', 'onprogress', 'onload', 'onabort', 'onerror'], (index, eventName) =>
|
94
|
+
xhr.upload[eventName] = (event) => @uploaderEvents[eventName].call(@, event)
|
95
|
+
xhr.onload = (event) =>
|
96
|
+
try
|
97
|
+
response = $.evalJSON(event.target.responseText)
|
98
|
+
Mercury.trigger('action', {action: 'insertImage', value: {src: response.url}})
|
99
|
+
catch error
|
100
|
+
@updateStatus('Unable to process response')
|
101
|
+
|
102
|
+
xhr.open('post', Mercury.config.uploading.url, true)
|
103
|
+
xhr.setRequestHeader('Accept', 'application/json, text/javascript, text/html, application/xml, text/xml, */*')
|
104
|
+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
105
|
+
xhr.setRequestHeader('X-CSRF-Token', Mercury.csrfToken)
|
106
|
+
|
107
|
+
@file.readAsBinaryString (result) =>
|
108
|
+
# build the multipart post string
|
109
|
+
multipart = new Mercury.uploader.MultiPartPost(Mercury.config.uploading.inputName, @file, result)
|
110
|
+
|
111
|
+
# update the content size so we can calculate
|
112
|
+
@file.updateSize(multipart.delta)
|
113
|
+
|
114
|
+
# set the content type and send
|
115
|
+
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + multipart.boundary)
|
116
|
+
xhr.sendAsBinary(multipart.body)
|
117
|
+
|
118
|
+
|
119
|
+
updateStatus: (message, loaded) ->
|
120
|
+
@element.find('.mercury-uploader-progress span').html(message)
|
121
|
+
if loaded
|
122
|
+
percent = Math.floor(loaded * 100 / @file.size) + '%'
|
123
|
+
@element.find('.mercury-uploader-indicator div').css({width: percent})
|
124
|
+
@element.find('.mercury-uploader-indicator b').html(percent).show()
|
125
|
+
|
126
|
+
|
127
|
+
hide: (delay = 0) ->
|
128
|
+
setTimeout((=>
|
129
|
+
@element.animate {opacity: 0}, 200, 'easeInOutSine', =>
|
130
|
+
@overlay.animate {opacity: 0}, 200, 'easeInOutSine', =>
|
131
|
+
@overlay.hide()
|
132
|
+
@element.hide()
|
133
|
+
@reset()
|
134
|
+
@visible = false
|
135
|
+
Mercury.trigger('focus:frame')
|
136
|
+
), delay * 1000)
|
137
|
+
|
138
|
+
|
139
|
+
reset: ->
|
140
|
+
@element.find('.mercury-uploader-preview b').html('')
|
141
|
+
@element.find('.mercury-uploader-indicator div').css({width: 0})
|
142
|
+
@element.find('.mercury-uploader-indicator b').html('0%').hide()
|
143
|
+
@updateStatus('Processing...',)
|
144
|
+
|
145
|
+
|
146
|
+
uploaderEvents: {
|
147
|
+
onloadstart: -> @updateStatus('Uploading...')
|
148
|
+
|
149
|
+
onprogress: (event) -> @updateStatus('Uploading...', event.loaded)
|
150
|
+
|
151
|
+
onabort: ->
|
152
|
+
@updateStatus('Aborted')
|
153
|
+
@hide(1)
|
154
|
+
|
155
|
+
onload: ->
|
156
|
+
@updateStatus('Successfully uploaded', @file.size)
|
157
|
+
@hide(1)
|
158
|
+
|
159
|
+
onerror: ->
|
160
|
+
@updateStatus('Error: Unable to upload the file')
|
161
|
+
@hide(1)
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
class Mercury.uploader.File
|
168
|
+
|
169
|
+
constructor: (@file) ->
|
170
|
+
@size = @file.size
|
171
|
+
@fullSize = @file.size
|
172
|
+
@readableSize = @file.size.toBytes()
|
173
|
+
@name = @file.fileName
|
174
|
+
@type = @file.type
|
175
|
+
|
176
|
+
# add any errors if we need to
|
177
|
+
errors = []
|
178
|
+
errors.push('Too large') if @size >= Mercury.config.uploading.maxFileSize
|
179
|
+
errors.push('Unsupported format') unless Mercury.config.uploading.allowedMimeTypes.indexOf(@type) > -1
|
180
|
+
@errors = errors.join(' / ') if errors.length
|
181
|
+
|
182
|
+
|
183
|
+
readAsDataURL: (callback = null) ->
|
184
|
+
reader = new FileReader()
|
185
|
+
reader.readAsDataURL(@file)
|
186
|
+
reader.onload = => callback(reader.result) if callback
|
187
|
+
|
188
|
+
|
189
|
+
readAsBinaryString: (callback = null) ->
|
190
|
+
reader = new FileReader()
|
191
|
+
reader.readAsBinaryString(@file)
|
192
|
+
reader.onload = => callback(reader.result) if callback
|
193
|
+
|
194
|
+
|
195
|
+
updateSize: (delta) ->
|
196
|
+
@fullSize = @size + delta
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
class Mercury.uploader.MultiPartPost
|
201
|
+
|
202
|
+
constructor: (@inputName, @file, @contents, @formInputs = {}) ->
|
203
|
+
@boundary = 'Boundaryx20072377098235644401115438165x'
|
204
|
+
@body = ''
|
205
|
+
@buildBody()
|
206
|
+
@delta = @body.length - @file.size
|
207
|
+
|
208
|
+
|
209
|
+
buildBody: ->
|
210
|
+
boundary = '--' + @boundary
|
211
|
+
for name, value of @formInputs
|
212
|
+
@body += "#{boundary}\r\nContent-Disposition: form-data; name=\"#{name}\"\r\n\r\n#{unescape(encodeURIComponent(value))}\r\n"
|
213
|
+
@body += "#{boundary}\r\nContent-Disposition: form-data; name=\"#{@inputName}\"; filename=\"#{@file.name}\"\r\nContent-Type: #{@file.type}\r\nContent-Transfer-Encoding: binary\r\n\r\n#{@contents}\r\n#{boundary}--"
|