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,40 @@
1
+ class @Mercury.Select extends Mercury.Dialog
2
+
3
+ constructor: (@url, @name, @options = {}) ->
4
+ super
5
+
6
+
7
+ build: ->
8
+ @element = $('<div>', {class: "mercury-select mercury-#{@name}-select loading", style: 'display:none'})
9
+ @element.appendTo($(@options.appendTo).get(0) ? 'body')
10
+
11
+
12
+ bindEvents: ->
13
+ Mercury.bind 'hide:dialogs', (event, dialog) => @hide() unless dialog == @
14
+ super
15
+
16
+
17
+ position: (keepVisible) ->
18
+ @element.css({top: 0, left: 0, display: 'block', visibility: 'hidden'})
19
+ position = @button.offset()
20
+ elementWidth = @element.width()
21
+ elementHeight = @element.height()
22
+ documentHeight = $(document).height()
23
+
24
+ top = position.top + (@button.height() / 2) - (elementHeight / 2)
25
+ top = position.top - 100 if top < position.top - 100
26
+ top = 20 if top < 20
27
+
28
+ height = if @loaded then 'auto' else elementHeight
29
+ height = documentHeight - top - 20 if top + elementHeight >= documentHeight - 20
30
+
31
+ left = position.left
32
+ left = left - elementWidth + @button.width() if left + elementWidth > $(window).width()
33
+
34
+ @element.css {
35
+ top: top,
36
+ left: left,
37
+ height: height,
38
+ display: if keepVisible then 'block' else 'none',
39
+ visibility: 'visible'
40
+ }
@@ -0,0 +1,92 @@
1
+ class @Mercury.Snippet
2
+
3
+ @all: []
4
+
5
+ @displayOptionsFor: (name) ->
6
+ Mercury.modal("/mercury/snippets/#{name}/options", {title: 'Snippet Options', handler: 'insertsnippet'})
7
+ Mercury.snippet = null
8
+
9
+
10
+ @create: (name, options) ->
11
+ identity = "snippet_#{@all.length}"
12
+ instance = new Mercury.Snippet(name, identity, options)
13
+ @all.push(instance)
14
+ return instance
15
+
16
+
17
+ @find: (identity) ->
18
+ for snippet in @all
19
+ return snippet if snippet.identity == identity
20
+ return null
21
+
22
+
23
+ @load: (snippets) ->
24
+ for identity, details of snippets
25
+ instance = new Mercury.Snippet(details.name, identity, details.options)
26
+ @all.push(instance)
27
+
28
+
29
+ constructor: (@name, @identity, options = {}) ->
30
+ @version = 0
31
+ @data = ''
32
+ @history = new Mercury.HistoryBuffer()
33
+ @setOptions(options)
34
+
35
+
36
+ getHTML: (context, callback = null) ->
37
+ element = $('<div class="mercury-snippet" contenteditable="false">', context)
38
+ element.attr({'data-snippet': @identity})
39
+ element.attr({'data-version': @version})
40
+ element.html("[#{@identity}]")
41
+ @loadPreview(element, callback)
42
+ return element
43
+
44
+
45
+ getText: (callback) ->
46
+ return "[--#{@identity}--]"
47
+
48
+
49
+ loadPreview: (element, callback = null) ->
50
+ $.ajax "/mercury/snippets/#{@name}/preview", {
51
+ type: 'POST'
52
+ data: @options
53
+ success: (data) =>
54
+ @data = data
55
+ element.html(data)
56
+ callback() if callback
57
+ error: =>
58
+ alert("Error loading the preview for the #{@name} snippet.")
59
+ }
60
+
61
+
62
+ displayOptions: ->
63
+ Mercury.snippet = @
64
+ Mercury.modal "/mercury/snippets/#{@name}/options", {
65
+ title: 'Snippet Options',
66
+ handler: 'insertsnippet',
67
+ loadType: 'post',
68
+ loadData: @options
69
+ }
70
+
71
+
72
+ setOptions: (@options) ->
73
+ delete(@options['authenticity_token'])
74
+ delete(@options['utf8'])
75
+ @version += 1
76
+ @history.push(@options)
77
+
78
+
79
+ setVersion: (version = null) ->
80
+ version = parseInt(version)
81
+ if version && @history.stack[version - 1]
82
+ @version = version - 1
83
+ @options = @history.stack[@version]
84
+ return true
85
+ return false
86
+
87
+
88
+ serialize: ->
89
+ return {
90
+ name: @name,
91
+ options: @options
92
+ }
@@ -0,0 +1,69 @@
1
+ class @Mercury.SnippetToolbar extends Mercury.Toolbar
2
+
3
+ constructor: (@document, @options = {}) ->
4
+ super(@options)
5
+
6
+
7
+ build: ->
8
+ @element = $('<div>', {class: 'mercury-toolbar mercury-snippet-toolbar', style: 'display:none'})
9
+ @element.appendTo($(@options.appendTo).get(0) ? 'body')
10
+
11
+ for buttonName, options of Mercury.config.toolbars.snippetable
12
+ button = @buildButton(buttonName, options)
13
+ button.appendTo(@element) if button
14
+
15
+
16
+ bindEvents: ->
17
+ Mercury.bind 'show:toolbar', (event, options) =>
18
+ return unless options.snippet
19
+ options.snippet.mouseout => @hide()
20
+ @show(options.snippet)
21
+
22
+ Mercury.bind 'hide:toolbar', (event, options) =>
23
+ return unless options.type && options.type == 'snippet'
24
+ @hide(options.immediately)
25
+
26
+ $(@document).scroll => @position() if @visible
27
+
28
+ @element.mousemove => clearTimeout(@hideTimeout)
29
+ @element.mouseout => @hide()
30
+
31
+
32
+ show: (@snippet) ->
33
+ Mercury.tooltip.hide()
34
+ @position()
35
+ @appear()
36
+
37
+
38
+ position: ->
39
+ offset = @snippet.offset()
40
+
41
+ top = offset.top + Mercury.displayRect.top - $(@document).scrollTop() - @height() + 10
42
+ left = offset.left - $(@document).scrollLeft()
43
+
44
+ @element.css {
45
+ top: top,
46
+ left: left
47
+ }
48
+
49
+
50
+ appear: ->
51
+ clearTimeout(@hideTimeout)
52
+ return if @visible
53
+ @visible = true
54
+ @element.css({display: 'block', opacity: 0})
55
+ @element.stop().animate({opacity: 1}, 200, 'easeInOutSine')
56
+
57
+
58
+ hide: (immediately = false) ->
59
+ clearTimeout(@hideTimeout)
60
+ if immediately
61
+ @element.hide()
62
+ @visible = false
63
+ else
64
+ @hideTimeout = setTimeout((=>
65
+ @element.stop().animate {opacity: 0}, 300, 'easeInOutSine', =>
66
+ @element.hide()
67
+ @visible = false
68
+ ), 500)
69
+
@@ -0,0 +1,25 @@
1
+ class @Mercury.Statusbar
2
+
3
+ constructor: (@options = {}) ->
4
+ @build()
5
+ @bindEvents()
6
+
7
+
8
+ build: ->
9
+ @element = $('<div>', {class: 'mercury-statusbar'}).appendTo($(@options.appendTo).get(0) ? 'body')
10
+
11
+
12
+ bindEvents: ->
13
+ Mercury.bind 'region:update', (event, options) =>
14
+ @setPath(options.region.path()) if options.region && $.type(options.region.path) == 'function'
15
+
16
+
17
+ height: ->
18
+ @element.outerHeight()
19
+
20
+
21
+ setPath: (elements) ->
22
+ path = []
23
+ path.push("<a>#{element.tagName.toLowerCase()}</a>") for element in elements
24
+
25
+ @element.html("<span><strong>Path: </strong></span>#{path.reverse().join(' &raquo; ')}")
@@ -0,0 +1,266 @@
1
+ @Mercury.tableEditor = (table, cell) ->
2
+ Mercury.tableEditor.load(table, cell)
3
+ return Mercury.tableEditor
4
+
5
+ $.extend Mercury.tableEditor, {
6
+
7
+ load: (@table, @cell) ->
8
+ @row = @cell.parent('tr')
9
+ @columnCount = @getColumnCount()
10
+ @rowCount = @getRowCount()
11
+
12
+
13
+ addColumn: (position = 'after') ->
14
+ sig = @cellSignatureFor(@cell)
15
+
16
+ for row, i in @table.find('tr')
17
+ rowSpan = 1
18
+ matchOptions = if position == 'after' then {right: sig.right} else {left: sig.left}
19
+ if matching = @findCellByOptionsFor(row, matchOptions)
20
+ newCell = $("<#{matching.cell.get(0).tagName}>").text('\00')
21
+ @setRowspanFor(newCell, matching.height)
22
+ if position == 'before' then matching.cell.before(newCell) else matching.cell.after(newCell)
23
+ i += matching.height - 1
24
+ else if intersecting = @findCellByIntersectionFor(row, sig)
25
+ @setColspanFor(intersecting.cell, intersecting.width + 1)
26
+
27
+
28
+ removeColumn: ->
29
+ sig = @cellSignatureFor(@cell)
30
+ return if sig.width > 1
31
+
32
+ removing = []
33
+ adjusting = []
34
+ for row, i in @table.find('tr')
35
+ if matching = @findCellByOptionsFor(row, {left: sig.left, width: sig.width})
36
+ removing.push(matching.cell)
37
+ i += matching.height - 1
38
+ else if intersecting = @findCellByIntersectionFor(row, sig)
39
+ adjusting.push(intersecting.cell)
40
+
41
+ $(cell).remove() for cell in removing
42
+ @setColspanFor(cell, @colspanFor(cell) - 1) for cell in adjusting
43
+
44
+
45
+ addRow: (position = 'after') ->
46
+ newRow = $('<tr>')
47
+
48
+ if (rowspan = @rowspanFor(@cell)) > 1 && position == 'after'
49
+ @row = $(@row.nextAll('tr')[rowspan - 2])
50
+
51
+ cellCount = 0
52
+ for cell in @row.find('th, td')
53
+ colspan = @colspanFor(cell)
54
+ newCell = $("<#{cell.tagName}>").text('\00')
55
+ @setColspanFor(newCell, colspan)
56
+ cellCount += colspan
57
+ if (rowspan = @rowspanFor(cell)) > 1 && position == 'after'
58
+ @setRowspanFor(cell, rowspan + 1)
59
+ continue
60
+
61
+ newRow.append(newCell)
62
+
63
+ if cellCount < @columnCount
64
+ rowCount = 0
65
+ for previousRow in @row.prevAll('tr')
66
+ rowCount += 1
67
+ for cell in $(previousRow).find('td[rowspan], th[rowspan]')
68
+ rowspan = @rowspanFor(cell)
69
+ if rowspan - 1 >= rowCount && position == 'before'
70
+ @setRowspanFor(cell, rowspan + 1)
71
+ else if rowspan - 1 >= rowCount && position == 'after'
72
+ if rowspan - 1 == rowCount
73
+ newCell = $("<#{cell.tagName}>", {colspan: @colspanFor(cell)})
74
+ newRow.append(newCell);
75
+ else
76
+ @setRowspanFor(cell, rowspan + 1)
77
+
78
+ if position == 'before' then @row.before(newRow) else @row.after(newRow)
79
+
80
+
81
+ removeRow: ->
82
+ # check to see that all cells have the same rowspan, and figure out the minimum rowspan
83
+ rowspansMatch = true
84
+ prevRowspan = 0
85
+ minRowspan = 0
86
+ for cell in @row.find('td, th')
87
+ rowspan = @rowspanFor(cell)
88
+ rowspansMatch = false if prevRowspan && rowspan != prevRowspan
89
+ minRowspan = rowspan if rowspan < minRowspan || !minRowspan
90
+ prevRowspan = rowspan
91
+
92
+ return if !rowspansMatch && @rowspanFor(@cell) > minRowspan
93
+
94
+ # remove any emtpy rows below
95
+ if minRowspan > 1
96
+ $(@row.nextAll('tr')[i]).remove() for i in [0..minRowspan - 2]
97
+
98
+ # find and move down any cells that have a larger rowspan
99
+ for cell in @row.find('td[rowspan], th[rowspan]')
100
+ sig = @cellSignatureFor(cell)
101
+ continue if sig.height == minRowspan
102
+ if match = @findCellByOptionsFor(@row.nextAll('tr')[minRowspan - 1], {left: sig.left, forceAdjacent: true})
103
+ @setRowspanFor(cell, @rowspanFor(cell) - @rowspanFor(@cell))
104
+ if match.direction == 'before' then match.cell.before($(cell).clone()) else match.cell.after($(cell).clone())
105
+
106
+ if @columnsFor(@row.find('td, th')) < @columnCount
107
+ # move up rows looking for cells with rowspans that might intersect
108
+ rowsAbove = 0
109
+ for aboveRow in @row.prevAll('tr')
110
+ rowsAbove += 1
111
+ for cell in $(aboveRow).find('td[rowspan], th[rowspan]')
112
+ # if the cell intersects with the row we're trying to calculate on, and it's index is less than where we've
113
+ # gotten so far, add it
114
+ rowspan = @rowspanFor(cell)
115
+ @setRowspanFor(cell, rowspan - @rowspanFor(@cell)) if rowspan > rowsAbove
116
+
117
+ @row.remove()
118
+
119
+
120
+ increaseColspan: ->
121
+ cell = @cell.next('td, th')
122
+ return unless cell.length
123
+ return if @rowspanFor(cell) != @rowspanFor(@cell)
124
+ return if @cellIndexFor(cell) > @cellIndexFor(@cell) + @colspanFor(@cell)
125
+ @setColspanFor(@cell, @colspanFor(@cell) + @colspanFor(cell))
126
+ cell.remove()
127
+
128
+
129
+ decreaseColspan: ->
130
+ return if @colspanFor(@cell) == 1
131
+ @setColspanFor(@cell, @colspanFor(@cell) - 1)
132
+ newCell = $("<#{@cell.get(0).tagName}>").text('\00')
133
+ @setRowspanFor(newCell, @rowspanFor(@cell))
134
+ @cell.after(newCell)
135
+
136
+
137
+ increaseRowspan: ->
138
+ sig = @cellSignatureFor(@cell)
139
+ nextRow = @row.nextAll('tr')[sig.height - 1]
140
+ if nextRow && match = @findCellByOptionsFor(nextRow, {left: sig.left, width: sig.width})
141
+ @setRowspanFor(@cell, sig.height + match.height)
142
+ match.cell.remove()
143
+
144
+ decreaseRowspan: ->
145
+ sig = @cellSignatureFor(@cell)
146
+ return if sig.height == 1
147
+ nextRow = @row.nextAll('tr')[sig.height - 2]
148
+ if match = @findCellByOptionsFor(nextRow, {left: sig.left, forceAdjacent: true})
149
+ newCell = $("<#{@cell.get(0).tagName}>").text('\00')
150
+ @setColspanFor(newCell, @colspanFor(@cell))
151
+ @setRowspanFor(@cell, sig.height - 1)
152
+ if match.direction == 'before' then match.cell.before(newCell) else match.cell.after(newCell)
153
+
154
+ # Counts the columns of the first row (alpha row) in the table. We can safely rely on the first row always being
155
+ # comprised of a full set of cells or cells with colspans.
156
+ getColumnCount: ->
157
+ return @columnsFor(@table.find('thead tr:first-child, tbody tr:first-child, tfoot tr:first-child').first().find('td, th'))
158
+
159
+
160
+ # Counts the rows of the table.
161
+ getRowCount: ->
162
+ return @table.find('tr').length
163
+
164
+
165
+ # Gets the index for a given cell, taking into account that rows above it can have cells that have rowspans.
166
+ cellIndexFor: (cell) ->
167
+ cell = $(cell)
168
+
169
+ # get the row for the cell and calculate all the columns in it
170
+ row = cell.parent('tr')
171
+ columns = @columnsFor(row.find('td, th'))
172
+ index = @columnsFor(cell.prevAll('td, th'))
173
+
174
+ # if the columns is less than expected, we need to look above for rowspans
175
+ if columns < @columnCount
176
+ # move up rows looking for cells with rowspans that might intersect
177
+ rowsAbove = 0
178
+ for aboveRow in row.prevAll('tr')
179
+ rowsAbove += 1
180
+ for aboveCell in $(aboveRow).find('td[rowspan], th[rowspan]')
181
+ # if the cell intersects with the row we're trying to calculate on, and it's index is less than where we've
182
+ # gotten so far, add it
183
+ if @rowspanFor(aboveCell) > rowsAbove && @cellIndexFor(aboveCell) <= index
184
+ index += @colspanFor(aboveCell)
185
+
186
+ return index
187
+
188
+ # Creates a signature for a given cell, which is made up if it's size, and itself.
189
+ cellSignatureFor: (cell) ->
190
+ sig = {cell: $(cell)}
191
+ sig.left = @cellIndexFor(cell)
192
+ sig.width = @colspanFor(cell)
193
+ sig.height = @rowspanFor(cell)
194
+ sig.right = sig.left + sig.width
195
+ return sig
196
+
197
+ # Find a cell based on options. Options can be:
198
+ # right
199
+ # or
200
+ # left, [width], [forceAdjacent]
201
+ # eg. findCellByOptionsFor(@row, {left: 1, width: 2, forceAdjacent: true})
202
+ findCellByOptionsFor: (row, options) ->
203
+ for cell in $(row).find('td, th')
204
+ sig = @cellSignatureFor(cell)
205
+ if typeof(options.right) != 'undefined'
206
+ return sig if sig.right == options.right
207
+ if typeof(options.left) != 'undefined'
208
+
209
+ if options.width
210
+ return sig if sig.left == options.left && sig.width == options.width
211
+ else if !options.forceAdjacent
212
+ return sig if sig.left == options.left
213
+ else if options.forceAdjacent
214
+ if sig.left > options.left
215
+ prev = $(cell).prev('td, th')
216
+ if prev.length
217
+ sig = @cellSignatureFor(prev)
218
+ sig.direction = 'after'
219
+ else
220
+ sig.direction = 'before'
221
+ return sig
222
+
223
+ if options.forceAdjacent
224
+ sig.direction = 'after'
225
+ return sig
226
+
227
+ return null
228
+
229
+ # Finds a cell that intersects with the current signature
230
+ findCellByIntersectionFor: (row, signature) ->
231
+ for cell in $(row).find('td, th')
232
+ sig = @cellSignatureFor(cell)
233
+ return sig if sig.right - signature.left >= 0 && sig.right > signature.left
234
+ return null
235
+
236
+
237
+ # Counts all the columns in a given array of columns, taking colspans into
238
+ # account.
239
+ columnsFor: (cells) ->
240
+ count = 0
241
+ count += @colspanFor(cell) for cell in cells
242
+ return count
243
+
244
+
245
+ # Tries to get the colspan of a cell, falling back to 1 if there's none
246
+ # specified.
247
+ colspanFor: (cell) ->
248
+ return parseInt($(cell).attr('colspan')) || 1
249
+
250
+
251
+ # Tries to get the rowspan of a cell, falling back to 1 if there's none
252
+ # specified.
253
+ rowspanFor: (cell) ->
254
+ return parseInt($(cell).attr('rowspan')) || 1
255
+
256
+
257
+ # Sets the colspan of a cell, removing it if it's 1.
258
+ setColspanFor: (cell, value) ->
259
+ $(cell).attr('colspan', if value > 1 then value else null)
260
+
261
+
262
+ # Sets the rowspan of a cell, removing it if it's 1
263
+ setRowspanFor: (cell, value) ->
264
+ $(cell).attr('rowspan', if value > 1 then value else null)
265
+
266
+ }