mercury-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +152 -0
  3. data/VERSION +1 -0
  4. data/app/assets/images/mercury/button.png +0 -0
  5. data/app/assets/images/mercury/clippy.png +0 -0
  6. data/app/assets/images/mercury/default-snippet.png +0 -0
  7. data/app/assets/images/mercury/loading-dark.gif +0 -0
  8. data/app/assets/images/mercury/loading-light.gif +0 -0
  9. data/app/assets/images/mercury/search-icon.png +0 -0
  10. data/app/assets/images/mercury/toolbar/editable/buttons.png +0 -0
  11. data/app/assets/images/mercury/toolbar/markupable/buttons.png +0 -0
  12. data/app/assets/images/mercury/toolbar/primary/_expander.png +0 -0
  13. data/app/assets/images/mercury/toolbar/primary/_pressed.png +0 -0
  14. data/app/assets/images/mercury/toolbar/primary/historypanel.png +0 -0
  15. data/app/assets/images/mercury/toolbar/primary/insertcharacter.png +0 -0
  16. data/app/assets/images/mercury/toolbar/primary/insertlink.png +0 -0
  17. data/app/assets/images/mercury/toolbar/primary/insertmedia.png +0 -0
  18. data/app/assets/images/mercury/toolbar/primary/inserttable.png +0 -0
  19. data/app/assets/images/mercury/toolbar/primary/inspectorpanel.png +0 -0
  20. data/app/assets/images/mercury/toolbar/primary/notespanel.png +0 -0
  21. data/app/assets/images/mercury/toolbar/primary/objectspanel.png +0 -0
  22. data/app/assets/images/mercury/toolbar/primary/preview.png +0 -0
  23. data/app/assets/images/mercury/toolbar/primary/redo.png +0 -0
  24. data/app/assets/images/mercury/toolbar/primary/save.png +0 -0
  25. data/app/assets/images/mercury/toolbar/primary/todospanel.png +0 -0
  26. data/app/assets/images/mercury/toolbar/primary/undo.png +0 -0
  27. data/app/assets/images/mercury/toolbar/snippetable/buttons.png +0 -0
  28. data/app/assets/javascripts/mercury.js +30 -0
  29. data/app/assets/javascripts/mercury/dialog.js.coffee +75 -0
  30. data/app/assets/javascripts/mercury/dialogs/backcolor.js.coffee +6 -0
  31. data/app/assets/javascripts/mercury/dialogs/forecolor.js.coffee +6 -0
  32. data/app/assets/javascripts/mercury/dialogs/formatblock.js.coffee +4 -0
  33. data/app/assets/javascripts/mercury/dialogs/objectspanel.js.coffee +10 -0
  34. data/app/assets/javascripts/mercury/dialogs/style.js.coffee +4 -0
  35. data/app/assets/javascripts/mercury/history_buffer.js.coffee +30 -0
  36. data/app/assets/javascripts/mercury/mercury.js.coffee +293 -0
  37. data/app/assets/javascripts/mercury/modal.js.coffee +177 -0
  38. data/app/assets/javascripts/mercury/modals/htmleditor.js.coffee +10 -0
  39. data/app/assets/javascripts/mercury/modals/insertcharacter.js.coffee +4 -0
  40. data/app/assets/javascripts/mercury/modals/insertlink.js.coffee +92 -0
  41. data/app/assets/javascripts/mercury/modals/insertmedia.js.coffee +72 -0
  42. data/app/assets/javascripts/mercury/modals/insertsnippet.js.coffee +11 -0
  43. data/app/assets/javascripts/mercury/modals/inserttable.js.coffee +56 -0
  44. data/app/assets/javascripts/mercury/native_extensions.js.coffee +47 -0
  45. data/app/assets/javascripts/mercury/page_editor.js.coffee +139 -0
  46. data/app/assets/javascripts/mercury/palette.js.coffee +29 -0
  47. data/app/assets/javascripts/mercury/panel.js.coffee +97 -0
  48. data/app/assets/javascripts/mercury/region.js.coffee +103 -0
  49. data/app/assets/javascripts/mercury/regions/editable.js.coffee +546 -0
  50. data/app/assets/javascripts/mercury/regions/markupable.js.coffee +380 -0
  51. data/app/assets/javascripts/mercury/regions/snippetable.js.coffee +127 -0
  52. data/app/assets/javascripts/mercury/select.js.coffee +40 -0
  53. data/app/assets/javascripts/mercury/snippet.js.coffee +92 -0
  54. data/app/assets/javascripts/mercury/snippet_toolbar.js.coffee +69 -0
  55. data/app/assets/javascripts/mercury/statusbar.js.coffee +25 -0
  56. data/app/assets/javascripts/mercury/table_editor.js.coffee +266 -0
  57. data/app/assets/javascripts/mercury/toolbar.button.js.coffee +152 -0
  58. data/app/assets/javascripts/mercury/toolbar.button_group.js.coffee +42 -0
  59. data/app/assets/javascripts/mercury/toolbar.expander.js.coffee +56 -0
  60. data/app/assets/javascripts/mercury/toolbar.js.coffee +72 -0
  61. data/app/assets/javascripts/mercury/tooltip.js.coffee +67 -0
  62. data/app/assets/javascripts/mercury/uploader.js.coffee +213 -0
  63. data/app/assets/javascripts/mercury/websocket.js.coffee +34 -0
  64. data/app/assets/stylesheets/mercury.css +31 -0
  65. data/app/assets/stylesheets/mercury/dialog.scss +178 -0
  66. data/app/assets/stylesheets/mercury/mercury.scss +119 -0
  67. data/app/assets/stylesheets/mercury/modal.scss +192 -0
  68. data/app/assets/stylesheets/mercury/statusbar.scss +23 -0
  69. data/app/assets/stylesheets/mercury/toolbar.scss +417 -0
  70. data/app/assets/stylesheets/mercury/tooltip.scss +26 -0
  71. data/app/assets/stylesheets/mercury/uploader.scss +109 -0
  72. data/app/controllers/images_controller.rb +19 -0
  73. data/app/controllers/mercury_controller.rb +20 -0
  74. data/app/models/image.rb +14 -0
  75. data/app/views/layouts/mercury.html.haml +12 -0
  76. data/app/views/mercury/modals/character.html.haml +252 -0
  77. data/app/views/mercury/modals/htmleditor.html.haml +8 -0
  78. data/app/views/mercury/modals/link.html.haml +31 -0
  79. data/app/views/mercury/modals/media.html.haml +33 -0
  80. data/app/views/mercury/modals/sanitizer.html.haml +4 -0
  81. data/app/views/mercury/modals/table.html.haml +49 -0
  82. data/app/views/mercury/palettes/backcolor.html.haml +79 -0
  83. data/app/views/mercury/palettes/forecolor.html.haml +79 -0
  84. data/app/views/mercury/panels/history.html.haml +0 -0
  85. data/app/views/mercury/panels/notes.html.haml +0 -0
  86. data/app/views/mercury/panels/snippets.html.haml +10 -0
  87. data/app/views/mercury/selects/formatblock.html.haml +10 -0
  88. data/app/views/mercury/selects/style.html.haml +4 -0
  89. data/app/views/mercury/snippets/example.html.haml +2 -0
  90. data/app/views/mercury/snippets/example_options.html.haml +16 -0
  91. data/config/engine.rb +6 -0
  92. data/config/routes.rb +15 -0
  93. data/db/migrate/20110526035601_create_images.rb +11 -0
  94. data/features/editing/basic.feature +11 -0
  95. data/features/step_definitions/debug_steps.rb +14 -0
  96. data/features/step_definitions/web_steps.rb +211 -0
  97. data/features/support/env.rb +46 -0
  98. data/features/support/paths.rb +35 -0
  99. data/features/support/selectors.rb +42 -0
  100. data/lib/mercury-rails.rb +4 -0
  101. data/log/.gitkeep +0 -0
  102. data/mercury-rails.gemspec +230 -0
  103. data/spec/javascripts/mercury/dialog_spec.js.coffee +258 -0
  104. data/spec/javascripts/mercury/history_buffer_spec.js.coffee +79 -0
  105. data/spec/javascripts/mercury/mercury_spec.js.coffee +52 -0
  106. data/spec/javascripts/mercury/native_extensions_spec.js.coffee +66 -0
  107. data/spec/javascripts/mercury/page_editor_spec.js.coffee +435 -0
  108. data/spec/javascripts/mercury/palette_spec.js.coffee +51 -0
  109. data/spec/javascripts/mercury/panel_spec.js.coffee +147 -0
  110. data/spec/javascripts/mercury/region_spec.js.coffee +261 -0
  111. data/spec/javascripts/mercury/regions/_editable_.js.coffee +0 -0
  112. data/spec/javascripts/mercury/regions/_markupable_.js.coffee +0 -0
  113. data/spec/javascripts/mercury/regions/snippetable_spec.js.coffee +368 -0
  114. data/spec/javascripts/mercury/select_spec.js.coffee +51 -0
  115. data/spec/javascripts/mercury/snippet_spec.js.coffee +246 -0
  116. data/spec/javascripts/mercury/snippet_toolbar_spec.js.coffee +186 -0
  117. data/spec/javascripts/mercury/statusbar_spec.js.coffee +78 -0
  118. data/spec/javascripts/mercury/table_editor_spec.js.coffee +192 -0
  119. data/spec/javascripts/mercury/toolbar.button_group_spec.js.coffee +92 -0
  120. data/spec/javascripts/mercury/toolbar.button_spec.js.coffee +341 -0
  121. data/spec/javascripts/mercury/toolbar.expander_spec.js.coffee +120 -0
  122. data/spec/javascripts/mercury/toolbar_spec.js.coffee +152 -0
  123. data/spec/javascripts/mercury/tooltip_spec.js.coffee +188 -0
  124. data/spec/javascripts/mercury/uploader_spec.js.coffee +512 -0
  125. data/spec/javascripts/responses/blank.html +1 -0
  126. data/spec/javascripts/spec_helper.js +513 -0
  127. data/spec/javascripts/templates/mercury/dialog.html +2 -0
  128. data/spec/javascripts/templates/mercury/page_editor.html +24 -0
  129. data/spec/javascripts/templates/mercury/palette.html +16 -0
  130. data/spec/javascripts/templates/mercury/panel.html +16 -0
  131. data/spec/javascripts/templates/mercury/region.html +2 -0
  132. data/spec/javascripts/templates/mercury/regions/snippetable.html +4 -0
  133. data/spec/javascripts/templates/mercury/select.html +16 -0
  134. data/spec/javascripts/templates/mercury/snippet.html +1 -0
  135. data/spec/javascripts/templates/mercury/snippet_toolbar.html +16 -0
  136. data/spec/javascripts/templates/mercury/statusbar.html +7 -0
  137. data/spec/javascripts/templates/mercury/table_editor.html +65 -0
  138. data/spec/javascripts/templates/mercury/toolbar.button.html +64 -0
  139. data/spec/javascripts/templates/mercury/toolbar.button_group.html +9 -0
  140. data/spec/javascripts/templates/mercury/toolbar.expander.html +18 -0
  141. data/spec/javascripts/templates/mercury/toolbar.html +10 -0
  142. data/spec/javascripts/templates/mercury/tooltip.html +12 -0
  143. data/spec/javascripts/templates/mercury/uploader.html +11 -0
  144. data/vendor/assets/javascripts/jquery-1.6.js +8865 -0
  145. data/vendor/assets/javascripts/jquery-ui-1.8.13.custom.min.js +249 -0
  146. data/vendor/assets/javascripts/jquery-ui-1.8.13.sortable.custom.js +1078 -0
  147. data/vendor/assets/javascripts/jquery.easing.js +173 -0
  148. data/vendor/assets/javascripts/jquery.json2.js +178 -0
  149. data/vendor/assets/javascripts/jquery.serialize_object.js +16 -0
  150. data/vendor/assets/javascripts/jquery.ujs.js +289 -0
  151. data/vendor/assets/javascripts/liquidmetal.js +88 -0
  152. data/vendor/assets/javascripts/showdown.js +1362 -0
  153. metadata +364 -0
@@ -0,0 +1,380 @@
1
+ # todo:
2
+ # context for the toolbar buttons and groups needs to change so we can do the following:
3
+ # how to handle context for buttons? if the cursor is within a bold area (**bo|ld**), or selecting it -- it would be
4
+ # nice if we could activate the bold button for instance.
5
+
6
+ class @Mercury.Regions.Markupable extends Mercury.Region
7
+ type = 'markupable'
8
+
9
+ constructor: (@element, @window, @options = {}) ->
10
+ @type = 'markupable'
11
+ super
12
+ @converter = new Showdown.converter()
13
+
14
+
15
+ build: ->
16
+ width = @element.width()
17
+ width = '100%' unless width
18
+ height = @element.height()
19
+
20
+ value = @element.html().replace(/^\s+|\s+$/g, '')
21
+ @textarea = $('<textarea>', @document).val(value)
22
+ @textarea.attr('class', @element.attr('class')).addClass('mercury-textarea')
23
+ @textarea.css({border: 0, background: 'transparent', display: 'block', width: width, height: height, fontFamily: '"Courier New", Courier, monospace', fontSize: '14px'})
24
+ @element.after(@textarea)
25
+ @element.hide()
26
+ @resize()
27
+
28
+
29
+ focus: ->
30
+ @textarea.focus()
31
+
32
+
33
+ bindEvents: ->
34
+ Mercury.bind 'mode', (event, options) =>
35
+ @togglePreview() if options.mode == 'preview'
36
+
37
+ Mercury.bind 'focus:frame', =>
38
+ return if @previewing
39
+ return unless Mercury.region == @
40
+ @focus()
41
+
42
+ Mercury.bind 'action', (event, options) =>
43
+ return if @previewing
44
+ return unless Mercury.region == @
45
+ @execCommand(options.action, options) if options.action
46
+
47
+ @textarea.bind 'dragenter', (event) =>
48
+ return if @previewing
49
+ event.preventDefault()
50
+ event.originalEvent.dataTransfer.dropEffect = 'copy'
51
+
52
+ @textarea.bind 'dragover', (event) =>
53
+ return if @previewing
54
+ event.preventDefault()
55
+ event.originalEvent.dataTransfer.dropEffect = 'copy'
56
+
57
+ @textarea.bind 'drop', (event) =>
58
+ return if @previewing
59
+
60
+ # handle dropping snippets
61
+ if Mercury.snippet
62
+ event.preventDefault()
63
+ @focus()
64
+ Mercury.Snippet.displayOptionsFor(Mercury.snippet)
65
+
66
+ # handle any files that were dropped
67
+ if event.originalEvent.dataTransfer.files.length
68
+ event.preventDefault()
69
+ @focus()
70
+ Mercury.uploader(event.originalEvent.dataTransfer.files[0])
71
+
72
+ @textarea.focus =>
73
+ return if @previewing
74
+ Mercury.region = @
75
+ @textarea.addClass('focus')
76
+ Mercury.trigger('region:focused', {region: @})
77
+
78
+ @textarea.blur =>
79
+ return if @previewing
80
+ @textarea.removeClass('focus')
81
+ Mercury.trigger('region:blurred', {region: @})
82
+
83
+ @textarea.keydown (event) =>
84
+ return if @previewing
85
+ Mercury.changes = true
86
+ @resize()
87
+ switch event.keyCode
88
+
89
+ when 13 # enter or return
90
+ selection = @selection()
91
+ text = @textarea.val()
92
+ start = text.lastIndexOf('\n', selection.start)
93
+ end = text.indexOf('\n', selection.end)
94
+ end = text.length if end < start
95
+ start = text.lastIndexOf('\n', selection.start - 1) if text[start] == '\n'
96
+ if text[start + 1] == '-'
97
+ selection.replace('\n- ', false, true)
98
+ event.preventDefault()
99
+ if /\d/.test(text[start + 1])
100
+ lineText = text.substring(start, end)
101
+ console.debug(lineText)
102
+ if /(\d+)\./.test(lineText)
103
+ console.debug(2)
104
+ number = parseInt(RegExp.$1)
105
+ selection.replace("\n#{number += 1}. ", false, true)
106
+ event.preventDefault()
107
+
108
+ when 90 # undo / redo
109
+ return unless event.metaKey
110
+ event.preventDefault()
111
+ if event.shiftKey then @execCommand('redo') else @execCommand('undo')
112
+ return
113
+
114
+ if event.metaKey
115
+ switch event.keyCode
116
+
117
+ when 66 # b
118
+ @execCommand('bold')
119
+ event.preventDefault()
120
+
121
+ when 73 # i
122
+ @execCommand('italic')
123
+ event.preventDefault()
124
+
125
+ when 85 # u
126
+ @execCommand('underline')
127
+ event.preventDefault()
128
+
129
+ @pushHistory(event.keyCode)
130
+
131
+ @textarea.keyup =>
132
+ return if @previewing
133
+ Mercury.trigger('region:update', {region: @})
134
+
135
+ @element.click (event) =>
136
+ $(event.target).closest('a').attr('target', '_top') if @previewing
137
+
138
+
139
+ html: (value = null, filterSnippets = true) ->
140
+ if value != null
141
+ if $.type(value) == 'string'
142
+ @textarea.val(value)
143
+ else
144
+ @textarea.val(value.html)
145
+ @selection().select(value.selection.start, value.selection.end)
146
+ else
147
+ return @textarea.val()
148
+
149
+
150
+ togglePreview: ->
151
+ if @previewing
152
+ @element.hide()
153
+ @textarea.show()
154
+ else
155
+ value = @converter.makeHtml(@textarea.val())
156
+ @element.html(value)
157
+ @element.show()
158
+ @textarea.hide()
159
+ super
160
+
161
+
162
+ execCommand: (action, options = {}) ->
163
+ super
164
+
165
+ handler.call(@, @selection(), options) if handler = Mercury.Regions.Markupable.actions[action]
166
+ @resize()
167
+
168
+
169
+ htmlAndSelection: ->
170
+ return {html: @html(null, false), selection: @selection().serialize()}
171
+
172
+
173
+ pushHistory: (keyCode) ->
174
+ # when pressing return, delete or backspace it should push to the history
175
+ # all other times it should store if there's a 1 second pause
176
+ keyCodes = [13, 46, 8]
177
+ waitTime = 2.5
178
+ knownKeyCode = keyCodes.indexOf(keyCode) if keyCode
179
+
180
+ # clear any pushes to the history
181
+ clearTimeout(@historyTimeout)
182
+
183
+ # if the key code was return, delete, or backspace store now -- unless it was the same as last time
184
+ if knownKeyCode >= 0 && knownKeyCode != @lastKnownKeyCode # || !keyCode
185
+ @history.push(@htmlAndSelection())
186
+ else if keyCode
187
+ # set a timeout for pushing to the history
188
+ @historyTimeout = setTimeout((=> @history.push(@htmlAndSelection())), waitTime * 1000)
189
+ else
190
+ # push to the history immediately
191
+ @history.push(@htmlAndSelection())
192
+
193
+ @lastKnownKeyCode = knownKeyCode
194
+
195
+
196
+ selection: ->
197
+ return new Mercury.Regions.Markupable.Selection(@textarea)
198
+
199
+
200
+ resize: ->
201
+ # adjustedHeight = Math.max(@textarea.get(0).scrollHeight, @textarea.get(0).clientHeight)
202
+ # @textarea.height(adjustedHeight) if adjustedHeight >= @textarea.get(0).clientHeight
203
+
204
+
205
+ snippets: ->
206
+
207
+
208
+ # Actions
209
+ @actions: {
210
+
211
+ undo: -> @html(@history.undo())
212
+
213
+ redo: -> @html(@history.redo())
214
+
215
+ insertHTML: (selection, options) ->
216
+ if options.value.get && element = options.value.get(0)
217
+ options.value = $('<div>').html(element).html()
218
+ selection.replace(options.value, false, true)
219
+
220
+ insertImage: (selection, options) ->
221
+ selection.replace('![add alt text](' + encodeURI(options.value.src) + ')', true)
222
+
223
+ insertLink: (selection, options) ->
224
+ selection.replace("[#{options.value.content}](#{options.value.attrs.href} 'optional title')", true)
225
+
226
+ insertunorderedlist: (selection) -> selection.addList('unordered')
227
+
228
+ insertorderedlist: (selection) -> selection.addList('ordered')
229
+
230
+ style: (selection, options) -> selection.wrap("<span class=\"#{options.value}\">", '</span>')
231
+
232
+ formatblock: (selection, options) ->
233
+ wrappers = {
234
+ h1: ['# ', ' #']
235
+ h2: ['## ', ' ##']
236
+ h3: ['### ', ' ###']
237
+ h4: ['#### ', ' ####']
238
+ h5: ['##### ', ' #####']
239
+ h6: ['###### ', ' ######']
240
+ pre: [' ', '']
241
+ blockquote: ['> ', '']
242
+ p: ['\n', '\n']
243
+ }
244
+ selection.unWrapLine("#{wrapper[0]}", "#{wrapper[1]}") for wrapperName, wrapper of wrappers
245
+ if options.value == 'blockquote'
246
+ Mercury.Regions.Markupable.actions.indent.call(@, selection, options)
247
+ return
248
+ selection.wrapLine("#{wrappers[options.value][0]}", "#{wrappers[options.value][1]}")
249
+
250
+ bold: (selection) -> selection.wrap('**', '**')
251
+
252
+ italic: (selection) -> selection.wrap('_', '_')
253
+
254
+ subscript: (selection) -> selection.wrap('<sub>', '</sub>')
255
+
256
+ superscript: (selection) -> selection.wrap('<sup>', '</sup>')
257
+
258
+ indent: (selection) ->
259
+ selection.wrapLine('> ', '', false, true)
260
+
261
+ outdent: (selection) ->
262
+ selection.unWrapLine('> ', '', false, true)
263
+
264
+ horizontalrule: (selection) -> selection.replace('\n- - -\n')
265
+
266
+ insertsnippet: (selection, options) ->
267
+ snippet = options.value
268
+ selection.replace(snippet.getText())
269
+
270
+ }
271
+
272
+
273
+ # Helper class for managing selection and getting information from it
274
+ class Mercury.Regions.Markupable.Selection
275
+
276
+ constructor: (@element) ->
277
+ @el = @element.get(0)
278
+ @getDetails()
279
+
280
+
281
+ serialize: ->
282
+ return {start: @start, end: @end}
283
+
284
+
285
+ getDetails: ->
286
+ @length = @el.selectionEnd - @el.selectionStart
287
+ @start = @el.selectionStart
288
+ @end = @el.selectionEnd
289
+ @text = @element.val().substr(@start, @length)
290
+
291
+
292
+ replace: (text, select = false, placeCursor = false) ->
293
+ @getDetails()
294
+ val = @element.val()
295
+ savedVal = @element.val()
296
+ @element.val(val.substr(0, @start) + text + val.substr(@end, val.length))
297
+ changed = @element.val() != savedVal
298
+ @select(@start, @start + text.length) if select
299
+ @select(@start + text.length, @start + text.length) if placeCursor
300
+ return changed
301
+
302
+
303
+ select: (@start, @end) ->
304
+ @element.focus()
305
+ @el.selectionStart = @start
306
+ @el.selectionEnd = @end
307
+ @getDetails()
308
+
309
+
310
+ wrap: (left, right) ->
311
+ @getDetails()
312
+ @deselectNewLines()
313
+ @replace(left + @text + right, @text != '')
314
+ @select(@start + left.length, @start + left.length) if @text == ''
315
+
316
+
317
+ wrapLine: (left, right, selectAfter = true, reselect = false) ->
318
+ @getDetails()
319
+ savedSelection = @serialize()
320
+ text = @element.val()
321
+ start = text.lastIndexOf('\n', @start)
322
+ end = text.indexOf('\n', @end)
323
+ end = text.length if end < start
324
+ start = text.lastIndexOf('\n', @start - 1) if text[start] == '\n'
325
+ @select(start + 1, end)
326
+ @replace(left + @text + right, selectAfter)
327
+ @select(savedSelection.start + left.length, savedSelection.end + left.length) if reselect
328
+
329
+
330
+ unWrapLine: (left, right, selectAfter = true, reselect = false) ->
331
+ @getDetails()
332
+ savedSelection = @serialize()
333
+ text = @element.val()
334
+ start = text.lastIndexOf('\n', @start)
335
+ end = text.indexOf('\n', @end)
336
+ end = text.length if end < start
337
+ start = text.lastIndexOf('\n', @start - 1) if text[start] == '\n'
338
+ @select(start + 1, end)
339
+ window.something = @text
340
+ leftRegExp = new RegExp("^#{left.regExpEscape()}")
341
+ rightRegExp = new RegExp("#{right.regExpEscape()}$")
342
+ changed = @replace(@text.replace(leftRegExp, '').replace(rightRegExp, ''), selectAfter)
343
+ @select(savedSelection.start - left.length, savedSelection.end - left.length) if reselect && changed
344
+
345
+
346
+ addList: (type) ->
347
+ text = @element.val()
348
+ start = text.lastIndexOf('\n', @start)
349
+ end = text.indexOf('\n', @end)
350
+ end = text.length if end < start
351
+ start = text.lastIndexOf('\n', @start - 1) if text[start] == '\n'
352
+ @select(start + 1, end)
353
+ lines = @text.split('\n')
354
+ if type == 'unordered'
355
+ @replace("- " + lines.join("\n- "), true)
356
+ else
357
+ @replace(("#{index + 1}. #{line}" for line, index in lines).join('\n'), true)
358
+
359
+
360
+ deselectNewLines: ->
361
+ text = @text
362
+ length = text.replace(/\n+$/g, '').length
363
+ @select(@start, @start + length)
364
+
365
+
366
+ placeMarker: ->
367
+ @wrap('[mercury-marker]', '[mercury-marker]')
368
+
369
+
370
+ removeMarker: ->
371
+ val = @element.val()
372
+ start = val.indexOf('[mercury-marker]')
373
+ return unless start > -1
374
+ end = val.indexOf('[mercury-marker]', start + 1) - '[mercury-marker]'.length
375
+ @element.val(@element.val().replace(/\[mercury-marker\]/g, ''))
376
+ @select(start, end)
377
+
378
+
379
+ textContent: ->
380
+ return @text
@@ -0,0 +1,127 @@
1
+ class @Mercury.Regions.Snippetable extends Mercury.Region
2
+ type = 'snippetable'
3
+
4
+ constructor: (@element, @window, @options = {}) ->
5
+ @type = 'snippetable'
6
+ super
7
+ @makeSortable()
8
+
9
+
10
+ build: ->
11
+ @element.css({minHeight: 20}) if @element.css('minHeight') == '0px'
12
+
13
+
14
+ bindEvents: ->
15
+ super
16
+
17
+ Mercury.bind 'unfocus:regions', (event) =>
18
+ return if @previewing
19
+ if Mercury.region == @
20
+ @element.removeClass('focus')
21
+ @element.sortable('destroy')
22
+ Mercury.trigger('region:blurred', {region: @})
23
+
24
+ Mercury.bind 'focus:window', (event) =>
25
+ return if @previewing
26
+ if Mercury.region == @
27
+ @element.removeClass('focus')
28
+ @element.sortable('destroy')
29
+ Mercury.trigger('region:blurred', {region: @})
30
+
31
+ $(@document).keydown (event) =>
32
+ return if @previewing
33
+ return unless Mercury.region == @
34
+ Mercury.changes = true
35
+ switch event.keyCode
36
+
37
+ when 90 # undo / redo
38
+ return unless event.metaKey
39
+ event.preventDefault()
40
+ if event.shiftKey
41
+ @execCommand('redo')
42
+ else
43
+ @execCommand('undo')
44
+
45
+ return
46
+
47
+ @element.mouseup =>
48
+ return if @previewing
49
+ @focus()
50
+ Mercury.trigger('region:focused', {region: @})
51
+
52
+ @element.bind 'dragover', (event) =>
53
+ return if @previewing
54
+ event.preventDefault()
55
+ event.originalEvent.dataTransfer.dropEffect = 'copy'
56
+
57
+ @element.bind 'drop', (event) =>
58
+ return if @previewing
59
+ return unless Mercury.snippet
60
+ @focus()
61
+ event.preventDefault()
62
+ Mercury.Snippet.displayOptionsFor(Mercury.snippet)
63
+
64
+
65
+ focus: ->
66
+ Mercury.region = @
67
+ @makeSortable()
68
+ @element.addClass('focus')
69
+
70
+
71
+ togglePreview: ->
72
+ if @previewing
73
+ @makeSortable()
74
+ else
75
+ @element.sortable('destroy')
76
+ @element.removeClass('focus')
77
+ super
78
+
79
+
80
+ execCommand: (action, options = {}) ->
81
+ super
82
+
83
+ handler.call(@, options) if handler = Mercury.Regions.Snippetable.actions[action]
84
+
85
+
86
+ makeSortable: ->
87
+ @element.sortable('destroy').sortable {
88
+ document: @document,
89
+ scroll: false, #scrolling is buggy
90
+ containment: 'parent',
91
+ items: '.mercury-snippet',
92
+ opacity: .4,
93
+ revert: 100,
94
+ tolerance: 'pointer',
95
+ beforeStop: =>
96
+ Mercury.trigger('hide:toolbar', {type: 'snippet', immediately: true})
97
+ return true
98
+ stop: =>
99
+ setTimeout((=> @pushHistory()), 100)
100
+ return true
101
+ }
102
+
103
+
104
+ # Actions
105
+ @actions: {
106
+
107
+ undo: -> @html(@history.undo())
108
+
109
+ redo: -> @html(@history.redo())
110
+
111
+ insertsnippet: (options) ->
112
+ snippet = options.value
113
+ if (existing = @element.find("[data-snippet=#{snippet.identity}]")).length
114
+ existing.replaceWith(snippet.getHTML(@document, => @pushHistory()))
115
+ else
116
+ @element.append(snippet.getHTML(@document, => @pushHistory()))
117
+
118
+ editsnippet: ->
119
+ return unless @snippet
120
+ snippet = Mercury.Snippet.find(@snippet.data('snippet'))
121
+ snippet.displayOptions()
122
+
123
+ removesnippet: ->
124
+ @snippet.remove() if @snippet
125
+ Mercury.trigger('hide:toolbar', {type: 'snippet', immediately: true})
126
+
127
+ }