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,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
+ }