DiffView.coffee 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. define [
  2. "ace/ace"
  3. "ace/mode/latex"
  4. "ace/range"
  5. "moment"
  6. "libs/backbone"
  7. "libs/mustache"
  8. ], (Ace, LatexMode, Range, moment)->
  9. DiffView = Backbone.View.extend
  10. template: $("#trackChangesDiffTemplate").html()
  11. events:
  12. "click .restore": () ->
  13. console.log "click"
  14. @trigger "restore"
  15. initialize: () ->
  16. @model.on "change:diff", () => @render()
  17. render: ->
  18. diff = @model.get("diff")
  19. return unless diff?
  20. changes = @getNumberOfChanges()
  21. html = Mustache.to_html @template, {
  22. changes: "#{changes} change#{if changes == 1 then "" else "s"}"
  23. name: @model.get("doc")?.get("name")
  24. }
  25. @$el.html(html)
  26. if !@model.get("from")? or !@model.get("to")? or changes == 0
  27. @$(".restore").hide()
  28. @createAceEditor()
  29. @aceEditor.setValue(@getPlainDiffContent())
  30. @aceEditor.clearSelection()
  31. @$ace = $(@aceEditor.renderer.container).find(".ace_scroller")
  32. @insertMarkers()
  33. @insertNameTag()
  34. @insertMoreChangeLabels()
  35. @bindToScrollEvents()
  36. @scrollToFirstChange()
  37. return @
  38. remove: () ->
  39. @$editor?.remove()
  40. @undelegateEvents()
  41. createAceEditor: () ->
  42. @$editor = @$(".track-changes-diff-editor")
  43. @$el.append(@$editor)
  44. @aceEditor = Ace.edit(@$editor[0])
  45. @aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
  46. @aceEditor.setReadOnly true
  47. @aceEditor.setShowPrintMargin(false)
  48. session = @aceEditor.getSession()
  49. session.setMode(new LatexMode.Mode())
  50. session.setUseWrapMode(true)
  51. @aceEditor.on "mousemove", (e) =>
  52. position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
  53. e.position = position
  54. @updateVisibleNames(e)
  55. bindToScrollEvents: () ->
  56. @aceEditor.getSession().on "changeScrollTop", (e) =>
  57. @updateMoreChangeLabels()
  58. getPlainDiffContent: () ->
  59. content = ""
  60. for entry in @model.get("diff") or []
  61. content += entry.u or entry.i or entry.d or ""
  62. return content
  63. getNumberOfChanges: () ->
  64. changes = 0
  65. for entry in @model.get("diff") or []
  66. changes += 1 if entry.i? or entry.d?
  67. return changes
  68. insertMarkers: () ->
  69. row = 0
  70. column = 0
  71. @entries = []
  72. for entry, i in @model.get("diff") or []
  73. content = entry.u or entry.i or entry.d
  74. content ||= ""
  75. lines = content.split("\n")
  76. startRow = row
  77. startColumn = column
  78. if lines.length > 1
  79. endRow = startRow + lines.length - 1
  80. endColumn = lines[lines.length - 1].length
  81. else
  82. endRow = startRow
  83. endColumn = startColumn + lines[0].length
  84. row = endRow
  85. column = endColumn
  86. range = new Range.Range(
  87. startRow, startColumn, endRow, endColumn
  88. )
  89. entry.range = range
  90. @addMarker(range, entry)
  91. if entry.i? or entry.d?
  92. @entries.push entry
  93. addMarker: (range, entry) ->
  94. session = @aceEditor.getSession()
  95. markerBackLayer = @aceEditor.renderer.$markerBack
  96. markerFrontLayer = @aceEditor.renderer.$markerFront
  97. lineHeight = @aceEditor.renderer.lineHeight
  98. dark = @_isDarkTheme()
  99. if entry.i? or entry.d?
  100. hue = entry.meta.user.hue()
  101. if entry.i?
  102. if dark
  103. style = "background-color : hsl(#{hue}, 100%, 28%);"
  104. else
  105. style = "background-color : hsl(#{hue}, 70%, 85%);"
  106. @_addMarkerWithCustomStyle session, markerBackLayer, range, "inserted-change-background", false, style
  107. if entry.d?
  108. if dark
  109. bgStyle = "background-color: hsl(#{hue}, 100%, 20%);"
  110. fgStyle = "border-bottom: 2px solid hsl(#{hue}, 100%, 60%);"
  111. else
  112. bgStyle = "background-color: hsl(#{hue}, 70%, 95%);"
  113. fgStyle = "border-bottom: 2px solid hsl(#{hue}, 70%, 40%);"
  114. fgStyle += "; height: #{Math.round(lineHeight/2) - 1}px;"
  115. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-background", false, bgStyle
  116. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-foreground", true, fgStyle
  117. _addMarkerWithCustomStyle: (session, markerLayer, range, klass, foreground, style) ->
  118. session.addMarker range, klass, (html, range, left, top, config) ->
  119. if range.isMultiLine()
  120. markerLayer.drawTextMarker(html, range, klass, config, style)
  121. else
  122. markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style)
  123. , foreground
  124. _isDarkTheme: () ->
  125. rgb = $(".ace_editor").css("background-color");
  126. [m, r, g, b] = rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/)
  127. r = parseInt(r, 10)
  128. g = parseInt(g, 10)
  129. b = parseInt(b, 10)
  130. return r + g + b < 3 * 128
  131. insertNameTag: () ->
  132. @$nameTagEl = $("<div class='change-name-marker'></div>")
  133. @$nameTagEl.css({
  134. position: "absolute"
  135. })
  136. @$nameTagEl.hide()
  137. @$ace.append(@$nameTagEl)
  138. insertMoreChangeLabels: () ->
  139. @$changesBefore = $("<a class='changes-before' href='#'><span></span> <i class='icon-arrow-up'></a>")
  140. @$changesAfter = $("<a class='changes-after' href='#'><span></span> <i class='icon-arrow-down'></a>")
  141. @$ace.append(@$changesBefore)
  142. @$ace.append(@$changesAfter)
  143. @$changesBefore.on "click", () =>
  144. @gotoLastHiddenChangeBefore()
  145. @$changesAfter.on "click", () =>
  146. @gotoFirstHiddenChangeAfter()
  147. @updateMoreChangeLabels()
  148. scrollToFirstChange: () ->
  149. @aceEditor.scrollToLine(0)
  150. setTimeout () =>
  151. if @entries? and @entries[0]?
  152. row = @entries[0].range.start.row
  153. @aceEditor.scrollToLine(row, true, false)
  154. , 10
  155. _drawNameTag: (entry, position) ->
  156. @$nameTagEl.show()
  157. if entry.i?
  158. text = "Added by #{entry.meta.user.name()}"
  159. else if entry.d?
  160. text = "Deleted by #{entry.meta.user.name()}"
  161. date = moment(parseInt(entry.meta.end_ts, 10)).format("Do MMM YYYY, h:mm a")
  162. text += " on #{date}"
  163. @$nameTagEl.text(text)
  164. position = @aceEditor.renderer.textToScreenCoordinates(position.row, position.column)
  165. offset = @$ace.offset()
  166. position.pageX = position.pageX - offset.left
  167. position.pageY = position.pageY - offset.top
  168. height = @$ace.height()
  169. hue = entry.meta.user.hue()
  170. if @_isDarkTheme()
  171. css = { "background-color" : "hsl(#{hue}, 100%, 20%)"; }
  172. else
  173. css = { "background-color" : "hsl(#{hue}, 70%, 90%)"; }
  174. if position.pageX + @$nameTagEl.width() < @$ace.width()
  175. css["left"] = position.pageX
  176. css["right"] = "auto"
  177. else
  178. css["right"] = 0
  179. css["left"] = "auto"
  180. if position.pageY > 2 * @$nameTagEl.height()
  181. css["bottom"] = height - position.pageY
  182. css["top"] = "auto"
  183. else
  184. css["top"] = position.pageY + @aceEditor.renderer.lineHeight
  185. css["bottom"] = "auto"
  186. @$nameTagEl.css css
  187. _hideNameTag: () ->
  188. @$nameTagEl?.hide()
  189. updateVisibleNames: (e) ->
  190. visibleName = false
  191. for entry in @entries or []
  192. if entry.range.contains(e.position.row, e.position.column)
  193. @_drawNameTag(entry, e.position)
  194. visibleName = true
  195. break
  196. if !visibleName
  197. @_hideNameTag()
  198. updateMoreChangeLabels: () ->
  199. return if !@$changesBefore or !@$changesAfter
  200. setTimeout () =>
  201. firstRow = @aceEditor.getFirstVisibleRow()
  202. lastRow = @aceEditor.getLastVisibleRow()
  203. changesBefore = 0
  204. changesAfter = 0
  205. @lastHiddenChangeBefore = null
  206. @firstHiddenChangeAfter = null
  207. for entry in @entries or []
  208. if entry.range.start.row < firstRow
  209. changesBefore += 1
  210. @lastHiddenChangeBefore = entry
  211. if entry.range.end.row > lastRow
  212. changesAfter += 1
  213. @firstHiddenChangeAfter ||= entry
  214. if changesBefore > 0
  215. @$changesBefore.find("span").text("#{changesBefore} more change#{if changesBefore > 1 then "s" else ""} above")
  216. @$changesBefore.show()
  217. else
  218. @$changesBefore.hide()
  219. if changesAfter > 0
  220. @$changesAfter.find("span").text("#{changesAfter} more change#{if changesAfter > 1 then "s" else ""} below")
  221. @$changesAfter.show()
  222. else
  223. @$changesAfter.hide()
  224. , 100
  225. gotoLastHiddenChangeBefore: () ->
  226. return if !@lastHiddenChangeBefore
  227. @aceEditor.scrollToLine(@lastHiddenChangeBefore.range.start.row, true, false)
  228. gotoFirstHiddenChangeAfter: () ->
  229. return if !@firstHiddenChangeAfter
  230. @aceEditor.scrollToLine(@firstHiddenChangeAfter.range.end.row, true, false)
  231. resize: () ->
  232. @aceEditor.resize()
  233. return DiffView