mercury-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }