CustomAlloyEditor.coffee 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. class CustomAlloyEditor extends Class
  2. constructor: (@tag) ->
  3. editor = AlloyEditor.editable(@tag)
  4. # Add top padding to avoid toolbar movement
  5. el = editor._editor.element.$
  6. height_before = el.getClientRects()[0].height
  7. style = getComputedStyle(el)
  8. el.style.position = "relative"
  9. el.style.paddingTop = (parseInt(style["padding-top"]) + 20) + "px"
  10. height_added = el.getClientRects()[0].height - height_before
  11. el.style.top = (parseInt(style["marginTop"]) - 20 - height_added) + "px"
  12. el.style.marginBottom = (parseInt(style["marginBottom"]) + parseInt(el.style.top)) + "px"
  13. # Add listeners
  14. editor.get('nativeEditor').on "selectionChange", @handleSelectionChange
  15. editor.get('nativeEditor').on "focus", (e) =>
  16. setTimeout ( =>
  17. @handleSelectionChange(e)
  18. ), 100
  19. editor.get('nativeEditor').on "click", @handleSelectionChange
  20. editor.get('nativeEditor').on "change", @handleChange
  21. editor.get('nativeEditor').on 'imageAdd', (e) =>
  22. if e.data.el.$.width > 0
  23. @handleImageAdd(e)
  24. else
  25. setTimeout ( =>
  26. @handleImageAdd(e)
  27. ), 100
  28. editor.get('nativeEditor').on "actionPerformed", @handleAction
  29. editor.get('nativeEditor').on 'afterCommandExec', @handleCommand
  30. window.editor = editor
  31. @el_last_created = null
  32. @image_size_limit = 200*1024
  33. @image_resize_width = 1200
  34. @image_resize_height = 900
  35. @image_preverse_ratio = true
  36. @image_try_png = false
  37. return @
  38. calcSize: (source_width, source_height, target_width, target_height) ->
  39. if source_width <= target_width and source_height <= target_height
  40. return [source_width, source_height]
  41. width = target_width
  42. height = width * (source_height / source_width);
  43. if height > target_height
  44. height = target_height
  45. width = height * (source_width / source_height)
  46. return [Math.round(width), Math.round(height)]
  47. scaleHalf: (image) ->
  48. canvas = document.createElement("canvas")
  49. canvas.width = image.width / 1.5
  50. canvas.height = image.height / 1.5
  51. ctx = canvas.getContext("2d")
  52. ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
  53. return canvas
  54. resizeImage: (image, width, height) =>
  55. canvas = document.createElement("canvas")
  56. if @image_preverse_ratio
  57. [canvas.width, canvas.height] = @calcSize(image.width, image.height, width, height)
  58. else
  59. canvas.width = width
  60. canvas.height = height
  61. ctx = canvas.getContext("2d")
  62. ctx.fillStyle = "#FFF"
  63. ctx.fillRect(0, 0, canvas.width, canvas.height)
  64. image_resized = image
  65. while image_resized.width > width * 1.5
  66. image_resized = @scaleHalf(image_resized)
  67. ctx.drawImage(image_resized, 0, 0, canvas.width, canvas.height)
  68. if @image_try_png and @getExtension(image.src) == "png" # and canvas.width < 1400 and canvas.height < 1000
  69. ###
  70. quant = new RgbQuant({colors: 256, method: 1})
  71. quant.sample(canvas)
  72. quant.palette(true)
  73. canvas_quant = drawPixels(quant.reduce(canvas), width)
  74. optimizer = new CanvasTool.PngEncoder(canvas_quant, { bitDepth: 8, colourType: CanvasTool.PngEncoder.ColourType.TRUECOLOR })
  75. image_base64uri = "data:image/png;base64," + btoa(optimizer.convert())
  76. ###
  77. image_base64uri = canvas.toDataURL("image/png", 0.1)
  78. if image_base64uri.length > @image_size_limit
  79. # Too large, convert to jpg
  80. @log "PNG too large (#{image_base64uri.length} bytes), convert to jpg instead"
  81. image_base64uri = canvas.toDataURL("image/jpeg", 0.7)
  82. else
  83. @log "Converted to PNG"
  84. else
  85. image_base64uri = canvas.toDataURL("image/jpeg", 0.7)
  86. @log "Resized #{image.width}x#{image.height} to #{canvas.width}x#{canvas.height} (#{image_base64uri.length} bytes)"
  87. return [image_base64uri, canvas.width, canvas.height]
  88. getExtension: (data) =>
  89. return data.match("/[a-z]+")[0].replace("/", "").replace("jpeg", "jpg")
  90. handleImageAdd: (e) =>
  91. if e.data.file.name
  92. name = e.data.file.name.replace(/[^\w\.-]/gi, "_")
  93. else
  94. name = Time.timestamp() + "." + @getExtension(e.data.file.type)
  95. e.data.el.$.style.maxWidth = "2400px" # Better resize quality
  96. if e.data.file.size > @image_size_limit
  97. @log "File size #{e.data.file.size} larger than allowed #{@image_size_limit}, resizing..."
  98. [image_base64uri, width, height] = @resizeImage(e.data.el.$, @image_resize_width, @image_resize_height)
  99. e.data.el.$.src = image_base64uri
  100. name = name.replace(/(png|gif|jpg)/, @getExtension(image_base64uri)) # Change extension if necessary
  101. else
  102. image_base64uri = e.data.el.$.src
  103. width = e.data.el.$.width
  104. height = e.data.el.$.height
  105. # e.data.el.remove() # Don't allow image upload yet
  106. e.data.el.$.style.maxWidth = "" # Show in standard size
  107. e.data.el.$.alt = "#{name} (#{width}x#{height})"
  108. @handleImageSave(name, image_base64uri, e.data.el.$)
  109. # Html fixes
  110. handleAction: (e) =>
  111. el = e.editor.getSelection().getStartElement()
  112. # Convert Pre to Pre > Code
  113. if el.getName() == "pre"
  114. @log("Fix pre")
  115. new_el = new CKEDITOR.dom.element("code")
  116. new_el.setHtml(el.getHtml().replace(/\u200B/g, ''))
  117. el.setHtml("")
  118. e.editor.insertElement(new_el)
  119. ranges = e.editor.getSelection().getRanges()
  120. ranges[0].startContainer = new_el
  121. e.editor.getSelection().selectRanges(ranges)
  122. # Remove Pre > Code
  123. if el.getName() == "pre" and e.data._style.hasOwnProperty("removeFromRange")
  124. @log("Remove pre")
  125. new_el = new CKEDITOR.dom.element("p");
  126. new_el.insertAfter(el)
  127. new_el.setHtml(el.getFirst().getHtml().replace(/\n/g, "<br>").replace(/\u200B/g, ''))
  128. el.remove()
  129. selectElement(e.editor, new_el)
  130. # Remove Pre > Code focused on code
  131. else if el.getName() == "code" and e.data._style.hasOwnProperty("removeFromRange")
  132. @log("Remove code")
  133. new_el = new CKEDITOR.dom.element("p")
  134. new_el.insertAfter(el.getParent())
  135. new_el.setHtml(el.getHtml().replace(/\n/g, "<br>").replace(/\u200B/g, ''))
  136. el.getParent().remove()
  137. selectElement(e.editor, new_el)
  138. # Convert multi-line code to Pre > Code
  139. else if el.getName() == "code" && el.getHtml().indexOf("<br>") > 0
  140. @log("Fix multiline code")
  141. new_el = new CKEDITOR.dom.element("pre");
  142. new_el.insertAfter(el)
  143. el.appendTo(new_el)
  144. selectElement(e.editor, new_el)
  145. if el.getName() == "h2" or el.getName() == "h3"
  146. selectElement(e.editor, el)
  147. @handleChange(e)
  148. handleCommand: (e) =>
  149. # Reset style on enter
  150. if e.data.name == 'enter'
  151. el = e.editor.getSelection().getStartElement()
  152. if el.getText().replace(/\u200B/g, '') == "" and el.getName() != "p" and el.getParent().getName() == "p"
  153. el.remove()
  154. # Reset style on shift+enter within code
  155. else if e.data.name == 'shiftEnter'
  156. el = e.editor.getSelection().getStartElement();
  157. if el.getName() == "code" && el.getNext() && el.getNext().getName && el.getNext().getName() == "br"
  158. el.getNext().remove()
  159. handleChange: (e) =>
  160. @handleSelectionChange(e)
  161. handleSelectionChange: (e) =>
  162. if @el_last_created and @el_last_created.getText().replace(/\u200B/g, '').trim() != ""
  163. @el_last_created.removeClass("empty")
  164. @el_last_created = null
  165. el = e.editor.getSelection().getStartElement()
  166. if el.getName() == "br"
  167. el = el.getParent()
  168. toolbar_add = document.querySelector(".ae-toolbar-add")
  169. if !toolbar_add or !el
  170. return false
  171. if el.getText().replace(/\u200B/g, '').trim() == ""
  172. if el.getName() == "h2" or el.getName() == "h3"
  173. el.addClass("empty")
  174. @el_last_created = el
  175. toolbar_add.classList.remove("lineselected")
  176. toolbar_add.classList.add("emptyline")
  177. else
  178. toolbar_add.classList.add("lineselected")
  179. toolbar_add.classList.remove("emptyline")
  180. # Remove toolbar moving
  181. ###
  182. if e.editor.element.getPrivate().events.mouseout?.listeners.length
  183. e.editor.element.removeListener("mouseout", e.editor.element.getPrivate().events.mouseout.listeners[0].fn)
  184. if e.editor.element.getPrivate().events.mouseleave?.listeners.length
  185. # Keep only mouseout
  186. func = e.editor.element.getPrivate().events.mouseleave.listeners[0]
  187. console.log "remove", e.editor.element.removeListener("mouseleave", func.fn)
  188. e.editor.element.on "mouseleave", (e_leave) ->
  189. if document.querySelector(".ae-toolbar-styles") == null
  190. window.editor._mainUI.forceUpdate()
  191. func(e_leave, e_leave.data)
  192. ###
  193. window.CustomAlloyEditor = CustomAlloyEditor