locomotivecms 4.1.0 → 4.2.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (301) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +4 -3
  4. data/Rakefile +4 -0
  5. data/app/api/locomotive/api.rb +1 -1
  6. data/app/assets/config/locomotive_manifest.js +2 -0
  7. data/app/assets/javascripts/locomotive.js +2 -2
  8. data/app/assets/stylesheets/locomotive/account.scss +0 -1
  9. data/app/assets/stylesheets/locomotive/application.scss +0 -1
  10. data/app/assets/stylesheets/locomotive/live_editing_error.scss +0 -1
  11. data/app/assets/stylesheets/locomotive/live_editing_iframe.scss +0 -1
  12. data/app/controllers/locomotive/base_controller.rb +1 -1
  13. data/app/controllers/locomotive/concerns/authorization_controller.rb +1 -1
  14. data/app/controllers/locomotive/concerns/redirect_to_main_host_controller.rb +1 -1
  15. data/app/controllers/locomotive/concerns/site_dispatcher_controller.rb +1 -1
  16. data/app/controllers/locomotive/errors_controller.rb +1 -1
  17. data/app/controllers/locomotive/my_account_controller.rb +1 -1
  18. data/app/controllers/locomotive/passwords_controller.rb +3 -1
  19. data/app/controllers/locomotive/registrations_controller.rb +3 -1
  20. data/app/controllers/locomotive/sessions_controller.rb +3 -1
  21. data/app/controllers/locomotive/sites_controller.rb +1 -1
  22. data/app/helpers/locomotive/base_helper.rb +2 -2
  23. data/app/helpers/locomotive/custom_fields_helper.rb +2 -2
  24. data/app/helpers/locomotive/shared/activities_helper.rb +1 -1
  25. data/app/inputs/locomotive/api_key_input.rb +1 -1
  26. data/app/mailers/locomotive/notifications.rb +1 -1
  27. data/app/models/locomotive/account.rb +1 -1
  28. data/app/models/locomotive/concerns/account/api_key.rb +1 -1
  29. data/app/models/locomotive/concerns/content_entry/csv.rb +1 -1
  30. data/app/models/locomotive/concerns/content_entry/slug.rb +5 -5
  31. data/app/models/locomotive/concerns/content_type/entry_template.rb +3 -3
  32. data/app/models/locomotive/concerns/page/templatized.rb +1 -1
  33. data/app/models/locomotive/concerns/shared/json_attribute.rb +21 -2
  34. data/app/models/locomotive/concerns/site/routes.rb +1 -1
  35. data/app/models/locomotive/content_entry.rb +2 -2
  36. data/app/models/locomotive/page.rb +3 -3
  37. data/app/models/locomotive/section.rb +1 -1
  38. data/app/models/locomotive/snippet.rb +1 -1
  39. data/app/models/locomotive/translation.rb +1 -1
  40. data/app/services/locomotive/content_entry_import_service.rb +1 -1
  41. data/app/services/locomotive/content_entry_service.rb +2 -2
  42. data/app/services/locomotive/content_type_service.rb +13 -3
  43. data/app/services/locomotive/custom_field_service.rb +2 -2
  44. data/app/services/locomotive/editor_service.rb +1 -1
  45. data/app/uploaders/locomotive/base_uploader.rb +0 -10
  46. data/app/views/locomotive/dashboard/_activity.html.slim +1 -1
  47. data/app/views/locomotive/page_content/_edit.json.jbuilder +50 -0
  48. data/app/views/locomotive/page_content/edit.html.erb +1 -1
  49. data/app/views/locomotive/page_content/edit.json.jbuilder +52 -50
  50. data/config/initializers/devise.rb +5 -2
  51. data/lib/generators/locomotive/install/templates/locomotive.rb +0 -5
  52. data/lib/locomotive/action_controller/responder.rb +1 -1
  53. data/lib/locomotive/configuration.rb +2 -2
  54. data/lib/locomotive/dependencies.rb +0 -2
  55. data/lib/locomotive/engine.rb +14 -2
  56. data/lib/locomotive/mongoid/patches.rb +25 -65
  57. data/lib/locomotive/steam/services/api_entry_submission_service.rb +1 -0
  58. data/lib/locomotive/version.rb +1 -1
  59. data/vendor/assets/javascripts/locomotive/codemirror/addons/comment/comment.js +203 -0
  60. data/vendor/assets/javascripts/locomotive/codemirror/addons/comment/continuecomment.js +85 -0
  61. data/vendor/assets/javascripts/locomotive/codemirror/addons/dialog/dialog.js +157 -0
  62. data/vendor/assets/javascripts/locomotive/codemirror/addons/display/autorefresh.js +47 -0
  63. data/vendor/assets/javascripts/locomotive/codemirror/addons/display/fullscreen.js +41 -0
  64. data/vendor/assets/javascripts/locomotive/codemirror/addons/display/panel.js +112 -0
  65. data/vendor/assets/javascripts/locomotive/codemirror/addons/display/placeholder.js +62 -0
  66. data/vendor/assets/javascripts/locomotive/codemirror/addons/display/rulers.js +51 -0
  67. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/closebrackets.js +195 -0
  68. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/closetag.js +169 -0
  69. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/continuelist.js +51 -0
  70. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/matchbrackets.js +120 -0
  71. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/matchtags.js +66 -0
  72. data/vendor/assets/javascripts/locomotive/codemirror/addons/edit/trailingspace.js +27 -0
  73. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/brace-fold.js +105 -0
  74. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/comment-fold.js +59 -0
  75. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/foldcode.js +150 -0
  76. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/foldgutter.js +146 -0
  77. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/indent-fold.js +44 -0
  78. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/markdown-fold.js +49 -0
  79. data/vendor/assets/javascripts/locomotive/codemirror/addons/fold/xml-fold.js +182 -0
  80. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/anyword-hint.js +41 -0
  81. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/css-hint.js +60 -0
  82. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/html-hint.js +348 -0
  83. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/javascript-hint.js +146 -0
  84. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/show-hint.js +437 -0
  85. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/sql-hint.js +271 -0
  86. data/vendor/assets/javascripts/locomotive/codemirror/addons/hint/xml-hint.js +110 -0
  87. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/coffeescript-lint.js +41 -0
  88. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/css-lint.js +35 -0
  89. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/html-lint.js +46 -0
  90. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/javascript-lint.js +136 -0
  91. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/json-lint.js +31 -0
  92. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/lint.js +239 -0
  93. data/vendor/assets/javascripts/locomotive/codemirror/addons/lint/yaml-lint.js +28 -0
  94. data/vendor/assets/javascripts/locomotive/codemirror/addons/merge/merge.js +773 -0
  95. data/vendor/assets/javascripts/locomotive/codemirror/addons/mode/loadmode.js +64 -0
  96. data/vendor/assets/javascripts/locomotive/codemirror/addons/mode/multiplex.js +123 -0
  97. data/vendor/assets/javascripts/locomotive/codemirror/addons/mode/overlay.js +85 -0
  98. data/vendor/assets/javascripts/locomotive/codemirror/addons/mode/simple.js +213 -0
  99. data/vendor/assets/javascripts/locomotive/codemirror/addons/runmode/colorize.js +40 -0
  100. data/vendor/assets/javascripts/locomotive/codemirror/addons/runmode/runmode-standalone.js +157 -0
  101. data/vendor/assets/javascripts/locomotive/codemirror/addons/runmode/runmode.js +72 -0
  102. data/vendor/assets/javascripts/locomotive/codemirror/addons/runmode/runmode.node.js +179 -0
  103. data/vendor/assets/javascripts/locomotive/codemirror/addons/scroll/annotatescrollbar.js +118 -0
  104. data/vendor/assets/javascripts/locomotive/codemirror/addons/scroll/scrollpastend.js +48 -0
  105. data/vendor/assets/javascripts/locomotive/codemirror/addons/scroll/simplescrollbars.js +152 -0
  106. data/vendor/assets/javascripts/locomotive/codemirror/addons/search/jump-to-line.js +49 -0
  107. data/vendor/assets/javascripts/locomotive/codemirror/addons/search/match-highlighter.js +146 -0
  108. data/vendor/assets/javascripts/locomotive/codemirror/addons/search/matchesonscrollbar.js +97 -0
  109. data/vendor/assets/javascripts/locomotive/codemirror/addons/search/search.js +249 -0
  110. data/vendor/assets/javascripts/locomotive/codemirror/addons/search/searchcursor.js +189 -0
  111. data/vendor/assets/javascripts/locomotive/codemirror/addons/selection/active-line.js +74 -0
  112. data/vendor/assets/javascripts/locomotive/codemirror/addons/selection/mark-selection.js +118 -0
  113. data/vendor/assets/javascripts/locomotive/codemirror/addons/selection/selection-pointer.js +98 -0
  114. data/vendor/assets/javascripts/locomotive/codemirror/addons/tern/tern.js +701 -0
  115. data/vendor/assets/javascripts/locomotive/codemirror/addons/tern/worker.js +44 -0
  116. data/vendor/assets/javascripts/locomotive/codemirror/addons/wrap/hardwrap.js +144 -0
  117. data/vendor/assets/javascripts/locomotive/codemirror/keymaps/emacs.js +412 -0
  118. data/vendor/assets/javascripts/locomotive/codemirror/keymaps/sublime.js +580 -0
  119. data/vendor/assets/javascripts/locomotive/codemirror/keymaps/vim.js +5065 -0
  120. data/vendor/assets/javascripts/locomotive/codemirror/modes/apl.js +174 -0
  121. data/vendor/assets/javascripts/locomotive/codemirror/modes/asciiarmor.js +73 -0
  122. data/vendor/assets/javascripts/locomotive/codemirror/modes/asn.1.js +204 -0
  123. data/vendor/assets/javascripts/locomotive/codemirror/modes/asterisk.js +196 -0
  124. data/vendor/assets/javascripts/locomotive/codemirror/modes/brainfuck.js +85 -0
  125. data/vendor/assets/javascripts/locomotive/codemirror/modes/clike.js +786 -0
  126. data/vendor/assets/javascripts/locomotive/codemirror/modes/clojure.js +306 -0
  127. data/vendor/assets/javascripts/locomotive/codemirror/modes/cmake.js +97 -0
  128. data/vendor/assets/javascripts/locomotive/codemirror/modes/cobol.js +255 -0
  129. data/vendor/assets/javascripts/locomotive/codemirror/modes/coffeescript.js +355 -0
  130. data/vendor/assets/javascripts/locomotive/codemirror/modes/commonlisp.js +123 -0
  131. data/vendor/assets/javascripts/locomotive/codemirror/modes/crystal.js +391 -0
  132. data/vendor/assets/javascripts/locomotive/codemirror/modes/css.js +825 -0
  133. data/vendor/assets/javascripts/locomotive/codemirror/modes/cypher.js +146 -0
  134. data/vendor/assets/javascripts/locomotive/codemirror/modes/d.js +218 -0
  135. data/vendor/assets/javascripts/locomotive/codemirror/modes/dart.js +157 -0
  136. data/vendor/assets/javascripts/locomotive/codemirror/modes/diff.js +47 -0
  137. data/vendor/assets/javascripts/locomotive/codemirror/modes/django.js +356 -0
  138. data/vendor/assets/javascripts/locomotive/codemirror/modes/dockerfile.js +79 -0
  139. data/vendor/assets/javascripts/locomotive/codemirror/modes/dtd.js +142 -0
  140. data/vendor/assets/javascripts/locomotive/codemirror/modes/dylan.js +344 -0
  141. data/vendor/assets/javascripts/locomotive/codemirror/modes/ebnf.js +195 -0
  142. data/vendor/assets/javascripts/locomotive/codemirror/modes/ecl.js +206 -0
  143. data/vendor/assets/javascripts/locomotive/codemirror/modes/eiffel.js +160 -0
  144. data/vendor/assets/javascripts/locomotive/codemirror/modes/elm.js +205 -0
  145. data/vendor/assets/javascripts/locomotive/codemirror/modes/erlang.js +618 -0
  146. data/vendor/assets/javascripts/locomotive/codemirror/modes/factor.js +83 -0
  147. data/vendor/assets/javascripts/locomotive/codemirror/modes/fcl.js +173 -0
  148. data/vendor/assets/javascripts/locomotive/codemirror/modes/forth.js +180 -0
  149. data/vendor/assets/javascripts/locomotive/codemirror/modes/fortran.js +188 -0
  150. data/vendor/assets/javascripts/locomotive/codemirror/modes/gas.js +345 -0
  151. data/vendor/assets/javascripts/locomotive/codemirror/modes/gfm.js +130 -0
  152. data/vendor/assets/javascripts/locomotive/codemirror/modes/gherkin.js +178 -0
  153. data/vendor/assets/javascripts/locomotive/codemirror/modes/go.js +185 -0
  154. data/vendor/assets/javascripts/locomotive/codemirror/modes/groovy.js +230 -0
  155. data/vendor/assets/javascripts/locomotive/codemirror/modes/haml.js +161 -0
  156. data/vendor/assets/javascripts/locomotive/codemirror/modes/handlebars.js +62 -0
  157. data/vendor/assets/javascripts/locomotive/codemirror/modes/haskell-literate.js +43 -0
  158. data/vendor/assets/javascripts/locomotive/codemirror/modes/haskell.js +267 -0
  159. data/vendor/assets/javascripts/locomotive/codemirror/modes/haxe.js +515 -0
  160. data/vendor/assets/javascripts/locomotive/codemirror/modes/htmlembedded.js +28 -0
  161. data/vendor/assets/javascripts/locomotive/codemirror/modes/htmlmixed.js +152 -0
  162. data/vendor/assets/javascripts/locomotive/codemirror/modes/http.js +113 -0
  163. data/vendor/assets/javascripts/locomotive/codemirror/modes/idl.js +290 -0
  164. data/vendor/assets/javascripts/locomotive/codemirror/modes/jade.js +590 -0
  165. data/vendor/assets/javascripts/locomotive/codemirror/modes/javascript.js +748 -0
  166. data/vendor/assets/javascripts/locomotive/codemirror/modes/jinja2.js +142 -0
  167. data/vendor/assets/javascripts/locomotive/codemirror/modes/jsx.js +147 -0
  168. data/vendor/assets/javascripts/locomotive/codemirror/modes/julia.js +392 -0
  169. data/vendor/assets/javascripts/locomotive/codemirror/modes/livescript.js +280 -0
  170. data/vendor/assets/javascripts/locomotive/codemirror/modes/lua.js +159 -0
  171. data/vendor/assets/javascripts/locomotive/codemirror/modes/markdown.js +797 -0
  172. data/vendor/assets/javascripts/locomotive/codemirror/modes/mathematica.js +176 -0
  173. data/vendor/assets/javascripts/locomotive/codemirror/modes/mbox.js +129 -0
  174. data/vendor/assets/javascripts/locomotive/codemirror/modes/mirc.js +193 -0
  175. data/vendor/assets/javascripts/locomotive/codemirror/modes/mllike.js +205 -0
  176. data/vendor/assets/javascripts/locomotive/codemirror/modes/modelica.js +245 -0
  177. data/vendor/assets/javascripts/locomotive/codemirror/modes/mscgen.js +169 -0
  178. data/vendor/assets/javascripts/locomotive/codemirror/modes/mumps.js +148 -0
  179. data/vendor/assets/javascripts/locomotive/codemirror/modes/nginx.js +178 -0
  180. data/vendor/assets/javascripts/locomotive/codemirror/modes/nsis.js +95 -0
  181. data/vendor/assets/javascripts/locomotive/codemirror/modes/ntriples.js +186 -0
  182. data/vendor/assets/javascripts/locomotive/codemirror/modes/octave.js +135 -0
  183. data/vendor/assets/javascripts/locomotive/codemirror/modes/oz.js +252 -0
  184. data/vendor/assets/javascripts/locomotive/codemirror/modes/pascal.js +109 -0
  185. data/vendor/assets/javascripts/locomotive/codemirror/modes/pegjs.js +114 -0
  186. data/vendor/assets/javascripts/locomotive/codemirror/modes/perl.js +837 -0
  187. data/vendor/assets/javascripts/locomotive/codemirror/modes/php.js +234 -0
  188. data/vendor/assets/javascripts/locomotive/codemirror/modes/pig.js +178 -0
  189. data/vendor/assets/javascripts/locomotive/codemirror/modes/powershell.js +396 -0
  190. data/vendor/assets/javascripts/locomotive/codemirror/modes/properties.js +78 -0
  191. data/vendor/assets/javascripts/locomotive/codemirror/modes/protobuf.js +68 -0
  192. data/vendor/assets/javascripts/locomotive/codemirror/modes/puppet.js +220 -0
  193. data/vendor/assets/javascripts/locomotive/codemirror/modes/python.js +340 -0
  194. data/vendor/assets/javascripts/locomotive/codemirror/modes/q.js +139 -0
  195. data/vendor/assets/javascripts/locomotive/codemirror/modes/r.js +164 -0
  196. data/vendor/assets/javascripts/locomotive/codemirror/modes/rpm.js +109 -0
  197. data/vendor/assets/javascripts/locomotive/codemirror/modes/rst.js +557 -0
  198. data/vendor/assets/javascripts/locomotive/codemirror/modes/ruby.js +285 -0
  199. data/vendor/assets/javascripts/locomotive/codemirror/modes/rust.js +71 -0
  200. data/vendor/assets/javascripts/locomotive/codemirror/modes/sas.js +315 -0
  201. data/vendor/assets/javascripts/locomotive/codemirror/modes/sass.js +414 -0
  202. data/vendor/assets/javascripts/locomotive/codemirror/modes/scheme.js +249 -0
  203. data/vendor/assets/javascripts/locomotive/codemirror/modes/shell.js +139 -0
  204. data/vendor/assets/javascripts/locomotive/codemirror/modes/sieve.js +193 -0
  205. data/vendor/assets/javascripts/locomotive/codemirror/modes/slim.js +575 -0
  206. data/vendor/assets/javascripts/locomotive/codemirror/modes/smalltalk.js +168 -0
  207. data/vendor/assets/javascripts/locomotive/codemirror/modes/smarty.js +225 -0
  208. data/vendor/assets/javascripts/locomotive/codemirror/modes/solr.js +104 -0
  209. data/vendor/assets/javascripts/locomotive/codemirror/modes/soy.js +199 -0
  210. data/vendor/assets/javascripts/locomotive/codemirror/modes/sparql.js +180 -0
  211. data/vendor/assets/javascripts/locomotive/codemirror/modes/spreadsheet.js +112 -0
  212. data/vendor/assets/javascripts/locomotive/codemirror/modes/sql.js +413 -0
  213. data/vendor/assets/javascripts/locomotive/codemirror/modes/stex.js +251 -0
  214. data/vendor/assets/javascripts/locomotive/codemirror/modes/stylus.js +769 -0
  215. data/vendor/assets/javascripts/locomotive/codemirror/modes/swift.js +202 -0
  216. data/vendor/assets/javascripts/locomotive/codemirror/modes/tcl.js +139 -0
  217. data/vendor/assets/javascripts/locomotive/codemirror/modes/textile.js +469 -0
  218. data/vendor/assets/javascripts/locomotive/codemirror/modes/tiddlywiki.js +308 -0
  219. data/vendor/assets/javascripts/locomotive/codemirror/modes/tiki.js +312 -0
  220. data/vendor/assets/javascripts/locomotive/codemirror/modes/toml.js +88 -0
  221. data/vendor/assets/javascripts/locomotive/codemirror/modes/tornado.js +68 -0
  222. data/vendor/assets/javascripts/locomotive/codemirror/modes/troff.js +84 -0
  223. data/vendor/assets/javascripts/locomotive/codemirror/modes/ttcn-cfg.js +214 -0
  224. data/vendor/assets/javascripts/locomotive/codemirror/modes/ttcn.js +283 -0
  225. data/vendor/assets/javascripts/locomotive/codemirror/modes/turtle.js +162 -0
  226. data/vendor/assets/javascripts/locomotive/codemirror/modes/twig.js +141 -0
  227. data/vendor/assets/javascripts/locomotive/codemirror/modes/vb.js +276 -0
  228. data/vendor/assets/javascripts/locomotive/codemirror/modes/vbscript.js +350 -0
  229. data/vendor/assets/javascripts/locomotive/codemirror/modes/velocity.js +201 -0
  230. data/vendor/assets/javascripts/locomotive/codemirror/modes/verilog.js +537 -0
  231. data/vendor/assets/javascripts/locomotive/codemirror/modes/vhdl.js +189 -0
  232. data/vendor/assets/javascripts/locomotive/codemirror/modes/vue.js +69 -0
  233. data/vendor/assets/javascripts/locomotive/codemirror/modes/webidl.js +195 -0
  234. data/vendor/assets/javascripts/locomotive/codemirror/modes/xml.js +394 -0
  235. data/vendor/assets/javascripts/locomotive/codemirror/modes/xquery.js +437 -0
  236. data/vendor/assets/javascripts/locomotive/codemirror/modes/yacas.js +204 -0
  237. data/vendor/assets/javascripts/locomotive/codemirror/modes/yaml-frontmatter.js +68 -0
  238. data/vendor/assets/javascripts/locomotive/codemirror/modes/yaml.js +117 -0
  239. data/vendor/assets/javascripts/locomotive/codemirror/modes/z80.js +116 -0
  240. data/vendor/assets/javascripts/locomotive/codemirror.js +8922 -0
  241. data/vendor/assets/javascripts/locomotive/select2.js +436 -175
  242. data/vendor/assets/javascripts/locomotive/wysihtml5x-toolbar.js +1346 -564
  243. data/vendor/assets/stylesheets/locomotive/codemirror/addons/dialog/dialog.css +32 -0
  244. data/vendor/assets/stylesheets/locomotive/codemirror/addons/display/fullscreen.css +6 -0
  245. data/vendor/assets/stylesheets/locomotive/codemirror/addons/fold/foldgutter.css +20 -0
  246. data/vendor/assets/stylesheets/locomotive/codemirror/addons/hint/show-hint.css +37 -0
  247. data/vendor/assets/stylesheets/locomotive/codemirror/addons/lint/lint.css +73 -0
  248. data/vendor/assets/stylesheets/locomotive/codemirror/addons/merge/merge.css +113 -0
  249. data/vendor/assets/stylesheets/locomotive/codemirror/addons/scroll/simplescrollbars.css +66 -0
  250. data/vendor/assets/stylesheets/locomotive/codemirror/addons/search/matchesonscrollbar.css +8 -0
  251. data/vendor/assets/stylesheets/locomotive/codemirror/addons/tern/tern.css +87 -0
  252. data/vendor/assets/stylesheets/locomotive/codemirror/modes/tiddlywiki.css +14 -0
  253. data/vendor/assets/stylesheets/locomotive/codemirror/modes/tiki.css +26 -0
  254. data/vendor/assets/stylesheets/locomotive/codemirror/themes/3024-day.css +41 -0
  255. data/vendor/assets/stylesheets/locomotive/codemirror/themes/3024-night.css +39 -0
  256. data/vendor/assets/stylesheets/locomotive/codemirror/themes/abcdef.css +32 -0
  257. data/vendor/assets/stylesheets/locomotive/codemirror/themes/ambiance-mobile.css +5 -0
  258. data/vendor/assets/stylesheets/locomotive/codemirror/themes/ambiance.css +74 -0
  259. data/vendor/assets/stylesheets/locomotive/codemirror/themes/base16-dark.css +38 -0
  260. data/vendor/assets/stylesheets/locomotive/codemirror/themes/base16-light.css +38 -0
  261. data/vendor/assets/stylesheets/locomotive/codemirror/themes/bespin.css +34 -0
  262. data/vendor/assets/stylesheets/locomotive/codemirror/themes/blackboard.css +32 -0
  263. data/vendor/assets/stylesheets/locomotive/codemirror/themes/cobalt.css +25 -0
  264. data/vendor/assets/stylesheets/locomotive/codemirror/themes/colorforth.css +33 -0
  265. data/vendor/assets/stylesheets/locomotive/codemirror/themes/dracula.css +41 -0
  266. data/vendor/assets/stylesheets/locomotive/codemirror/themes/eclipse.css +23 -0
  267. data/vendor/assets/stylesheets/locomotive/codemirror/themes/elegant.css +13 -0
  268. data/vendor/assets/stylesheets/locomotive/codemirror/themes/erlang-dark.css +34 -0
  269. data/vendor/assets/stylesheets/locomotive/codemirror/themes/hopscotch.css +34 -0
  270. data/vendor/assets/stylesheets/locomotive/codemirror/themes/icecoder.css +43 -0
  271. data/vendor/assets/stylesheets/locomotive/codemirror/themes/isotope.css +34 -0
  272. data/vendor/assets/stylesheets/locomotive/codemirror/themes/lesser-dark.css +47 -0
  273. data/vendor/assets/stylesheets/locomotive/codemirror/themes/liquibyte.css +95 -0
  274. data/vendor/assets/stylesheets/locomotive/codemirror/themes/material.css +53 -0
  275. data/vendor/assets/stylesheets/locomotive/codemirror/themes/mbo.css +37 -0
  276. data/vendor/assets/stylesheets/locomotive/codemirror/themes/mdn-like.css +46 -0
  277. data/vendor/assets/stylesheets/locomotive/codemirror/themes/midnight.css +45 -0
  278. data/vendor/assets/stylesheets/locomotive/codemirror/themes/monokai.css +36 -0
  279. data/vendor/assets/stylesheets/locomotive/codemirror/themes/neat.css +12 -0
  280. data/vendor/assets/stylesheets/locomotive/codemirror/themes/neo.css +43 -0
  281. data/vendor/assets/stylesheets/locomotive/codemirror/themes/night.css +27 -0
  282. data/vendor/assets/stylesheets/locomotive/codemirror/themes/paraiso-dark.css +38 -0
  283. data/vendor/assets/stylesheets/locomotive/codemirror/themes/paraiso-light.css +38 -0
  284. data/vendor/assets/stylesheets/locomotive/codemirror/themes/pastel-on-dark.css +53 -0
  285. data/vendor/assets/stylesheets/locomotive/codemirror/themes/railscasts.css +34 -0
  286. data/vendor/assets/stylesheets/locomotive/codemirror/themes/rubyblue.css +25 -0
  287. data/vendor/assets/stylesheets/locomotive/codemirror/themes/seti.css +44 -0
  288. data/vendor/assets/stylesheets/locomotive/codemirror/themes/solarized.css +169 -0
  289. data/vendor/assets/stylesheets/locomotive/codemirror/themes/the-matrix.css +30 -0
  290. data/vendor/assets/stylesheets/locomotive/codemirror/themes/tomorrow-night-bright.css +35 -0
  291. data/vendor/assets/stylesheets/locomotive/codemirror/themes/tomorrow-night-eighties.css +38 -0
  292. data/vendor/assets/stylesheets/locomotive/codemirror/themes/ttcn.css +64 -0
  293. data/vendor/assets/stylesheets/locomotive/codemirror/themes/twilight.css +32 -0
  294. data/vendor/assets/stylesheets/locomotive/codemirror/themes/vibrant-ink.css +34 -0
  295. data/vendor/assets/stylesheets/locomotive/codemirror/themes/xq-dark.css +53 -0
  296. data/vendor/assets/stylesheets/locomotive/codemirror/themes/xq-light.css +43 -0
  297. data/vendor/assets/stylesheets/locomotive/codemirror/themes/yeti.css +44 -0
  298. data/vendor/assets/stylesheets/locomotive/codemirror/themes/zenburn.css +37 -0
  299. data/vendor/assets/stylesheets/locomotive/codemirror.css +347 -0
  300. data/vendor/assets/stylesheets/locomotive/select2.css +3 -6
  301. metadata +349 -128
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license wysihtml v0.5.1
2
+ * @license wysihtml v0.5.5
3
3
  * https://github.com/Voog/wysihtml
4
4
  *
5
5
  * Author: Christopher Blum (https://github.com/tiff)
@@ -10,7 +10,7 @@
10
10
  *
11
11
  */
12
12
  var wysihtml5 = {
13
- version: "0.5.1",
13
+ version: "0.5.5",
14
14
 
15
15
  // namespaces
16
16
  commands: {},
@@ -24,6 +24,8 @@ var wysihtml5 = {
24
24
  INVISIBLE_SPACE: "\uFEFF",
25
25
  INVISIBLE_SPACE_REG_EXP: /\uFEFF/g,
26
26
 
27
+ VOID_ELEMENTS: "area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr",
28
+
27
29
  EMPTY_FUNCTION: function() {},
28
30
 
29
31
  ELEMENT_NODE: 1,
@@ -38,7 +40,7 @@ var wysihtml5 = {
38
40
  };
39
41
  ;wysihtml5.polyfills = function(win, doc) {
40
42
 
41
- // TODO: in future try to replace most inline compability checks with polyfills for code readability
43
+ // TODO: in future try to replace most inline compability checks with polyfills for code readability
42
44
 
43
45
  // IE8 SUPPORT BLOCK
44
46
  // You can compile without all this if IE8 is not needed
@@ -133,20 +135,36 @@ var wysihtml5 = {
133
135
  };
134
136
  }
135
137
 
136
- // Element.matches Adds ie8 support and unifies nonstandard function names in other browsers
137
- win.Element && function(ElementPrototype) {
138
- ElementPrototype.matches = ElementPrototype.matches ||
139
- ElementPrototype.matchesSelector ||
140
- ElementPrototype.mozMatchesSelector ||
141
- ElementPrototype.msMatchesSelector ||
142
- ElementPrototype.oMatchesSelector ||
143
- ElementPrototype.webkitMatchesSelector ||
144
- function (selector) {
145
- var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
146
- while (nodes[++i] && nodes[i] != node);
147
- return !!nodes[i];
138
+ // closest and matches polyfill
139
+ // https://github.com/jonathantneal/closest
140
+ (function (ELEMENT) {
141
+ ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) {
142
+ var
143
+ element = this,
144
+ elements = (element.document || element.ownerDocument).querySelectorAll(selector),
145
+ index = 0;
146
+
147
+ while (elements[index] && elements[index] !== element) {
148
+ ++index;
149
+ }
150
+
151
+ return elements[index] ? true : false;
152
+ };
153
+
154
+ ELEMENT.closest = ELEMENT.closest || function closest(selector) {
155
+ var element = this;
156
+
157
+ while (element) {
158
+ if (element.matches(selector)) {
159
+ break;
160
+ }
161
+
162
+ element = element.parentElement;
163
+ }
164
+
165
+ return element;
148
166
  };
149
- }(win.Element.prototype);
167
+ }(Element.prototype));
150
168
 
151
169
  // Element.classList for ie8-9 (toggle all IE)
152
170
  // source http://purl.eligrey.com/github/classList.js/blob/master/classList.js
@@ -405,7 +423,19 @@ var wysihtml5 = {
405
423
  return all;
406
424
  };
407
425
 
426
+ var isInDom = function(node) {
427
+ var doc = node.ownerDocument,
428
+ n = node;
429
+
430
+ do {
431
+ if (n === doc) {
432
+ return true;
433
+ }
434
+ n = n.parentNode;
435
+ } while(n);
408
436
 
437
+ return false;
438
+ };
409
439
 
410
440
  var normalizeFix = function() {
411
441
  var f = Node.prototype.normalize;
@@ -466,7 +496,7 @@ var wysihtml5 = {
466
496
  aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement);
467
497
  }
468
498
 
469
- if (anode && anode.parentNode && fnode && fnode.parentNode) {
499
+ if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) {
470
500
  r.setStart(anode, aoffset);
471
501
  r.setEnd(fnode, foffset);
472
502
  s.removeAllRanges();
@@ -475,19 +505,44 @@ var wysihtml5 = {
475
505
  };
476
506
  Node.prototype.normalize = nf;
477
507
  };
478
-
508
+
479
509
  var F = function() {
480
510
  window.removeEventListener("load", F);
481
511
  if ("Node" in window && "normalize" in Node.prototype && normalizeHasCaretError()) {
482
512
  normalizeFix();
483
513
  }
484
514
  };
485
-
515
+
486
516
  if (doc.readyState !== "complete") {
487
517
  window.addEventListener("load", F);
488
518
  } else {
489
519
  F();
490
520
  }
521
+
522
+ // CustomEvent for ie9 and up
523
+ function nativeCustomEventSupported() {
524
+ try {
525
+ var p = new CustomEvent('cat', {detail: {foo: 'bar'}});
526
+ return 'cat' === p.type && 'bar' === p.detail.foo;
527
+ } catch (e) {}
528
+ return false;
529
+ }
530
+ var customEventSupported = nativeCustomEventSupported();
531
+
532
+ // Polyfills CustomEvent object for IE9 and up
533
+ (function() {
534
+ if (!customEventSupported && "CustomEvent" in window) {
535
+ function CustomEvent(event, params) {
536
+ params = params || {bubbles: false, cancelable: false, detail: undefined};
537
+ var evt = doc.createEvent('CustomEvent');
538
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
539
+ return evt;
540
+ }
541
+ CustomEvent.prototype = win.Event.prototype;
542
+ win.CustomEvent = CustomEvent;
543
+ customEventSupported = true;
544
+ }
545
+ })();
491
546
  };
492
547
 
493
548
  wysihtml5.polyfills(window, document);
@@ -4304,7 +4359,7 @@ wysihtml5.polyfills(window, document);
4304
4359
  win = null;
4305
4360
  });
4306
4361
  });
4307
-
4362
+
4308
4363
 
4309
4364
  /*----------------------------------------------------------------------------------------------------------------*/
4310
4365
 
@@ -4335,7 +4390,8 @@ wysihtml5.polyfills(window, document);
4335
4390
  }
4336
4391
 
4337
4392
  return api;
4338
- }, this);;/**
4393
+ }, this);
4394
+ ;/**
4339
4395
  * Text range module for Rangy.
4340
4396
  * Text-based manipulation and searching of ranges and selections.
4341
4397
  *
@@ -4673,7 +4729,7 @@ wysihtml5.polyfills(window, document);
4673
4729
 
4674
4730
  /*----------------------------------------------------------------------------------------------------------------*/
4675
4731
 
4676
-
4732
+
4677
4733
  // "A block node is either an Element whose "display" property does not have
4678
4734
  // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
4679
4735
  // Document, or a DocumentFragment."
@@ -6262,7 +6318,7 @@ wysihtml5.polyfills(window, document);
6262
6318
  )
6263
6319
  };
6264
6320
  });
6265
-
6321
+
6266
6322
  return rangy;
6267
6323
  }, this);;/**
6268
6324
  * Selection save and restore module for Rangy.
@@ -6513,7 +6569,7 @@ wysihtml5.polyfills(window, document);
6513
6569
  removeMarkers: removeMarkers
6514
6570
  });
6515
6571
  });
6516
-
6572
+
6517
6573
  return rangy;
6518
6574
  }, this);;/*
6519
6575
  Base.js, version 1.1a
@@ -6527,7 +6583,7 @@ var Base = function() {
6527
6583
 
6528
6584
  Base.extend = function(_instance, _static) { // subclass
6529
6585
  var extend = Base.prototype.extend;
6530
-
6586
+
6531
6587
  // build the prototype
6532
6588
  Base._prototyping = true;
6533
6589
  var proto = new this;
@@ -6536,7 +6592,7 @@ Base.extend = function(_instance, _static) { // subclass
6536
6592
  // call this method from any other method to invoke that method's ancestor
6537
6593
  };
6538
6594
  delete Base._prototyping;
6539
-
6595
+
6540
6596
  // create the wrapper for the constructor function
6541
6597
  //var constructor = proto.constructor.valueOf(); //-dean
6542
6598
  var constructor = proto.constructor;
@@ -6551,7 +6607,7 @@ Base.extend = function(_instance, _static) { // subclass
6551
6607
  }
6552
6608
  }
6553
6609
  };
6554
-
6610
+
6555
6611
  // build the class interface
6556
6612
  klass.ancestor = this;
6557
6613
  klass.extend = this.extend;
@@ -6569,7 +6625,7 @@ Base.extend = function(_instance, _static) { // subclass
6569
6625
  return klass;
6570
6626
  };
6571
6627
 
6572
- Base.prototype = {
6628
+ Base.prototype = {
6573
6629
  extend: function(source, value) {
6574
6630
  if (arguments.length > 1) { // extending with a name/value pair
6575
6631
  var ancestor = this[source];
@@ -6628,7 +6684,7 @@ Base = Base.extend({
6628
6684
  }, {
6629
6685
  ancestor: Object,
6630
6686
  version: "1.1",
6631
-
6687
+
6632
6688
  forEach: function(object, block, context) {
6633
6689
  for (var key in object) {
6634
6690
  if (this.prototype[key] === undefined) {
@@ -6636,7 +6692,7 @@ Base = Base.extend({
6636
6692
  }
6637
6693
  }
6638
6694
  },
6639
-
6695
+
6640
6696
  implement: function() {
6641
6697
  for (var i = 0; i < arguments.length; i++) {
6642
6698
  if (typeof arguments[i] == "function") {
@@ -6649,7 +6705,7 @@ Base = Base.extend({
6649
6705
  }
6650
6706
  return this;
6651
6707
  },
6652
-
6708
+
6653
6709
  toString: function() {
6654
6710
  return String(this.valueOf());
6655
6711
  }
@@ -6660,10 +6716,11 @@ wysihtml5.browser = (function() {
6660
6716
  var userAgent = navigator.userAgent,
6661
6717
  testElement = document.createElement("div"),
6662
6718
  // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
6663
- isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1,
6664
- isWebKit = userAgent.indexOf("AppleWebKit/") !== -1,
6665
- isChrome = userAgent.indexOf("Chrome/") !== -1,
6666
- isOpera = userAgent.indexOf("Opera/") !== -1;
6719
+ // We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers
6720
+ isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(),
6721
+ isWebKit = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(),
6722
+ isChrome = userAgent.indexOf("Chrome/") !== -1 && !isIE(),
6723
+ isOpera = userAgent.indexOf("Opera/") !== -1 && !isIE();
6667
6724
 
6668
6725
  function iosVersion(userAgent) {
6669
6726
  return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
@@ -6833,14 +6890,15 @@ wysihtml5.browser = (function() {
6833
6890
  */
6834
6891
  supportsCommand: (function() {
6835
6892
  // Following commands are supported but contain bugs in some browsers
6893
+ // TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly
6836
6894
  var buggyCommands = {
6837
6895
  // formatBlock fails with some tags (eg. <blockquote>)
6838
6896
  "formatBlock": isIE(10, "<="),
6839
6897
  // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
6840
6898
  // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
6841
6899
  // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
6842
- "insertUnorderedList": isIE(9, ">=") || isIE(12, "<="),
6843
- "insertOrderedList": isIE(9, ">=")|| isIE(12, "<=")
6900
+ "insertUnorderedList": isIE(),
6901
+ "insertOrderedList": isIE()
6844
6902
  };
6845
6903
 
6846
6904
  // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
@@ -6997,6 +7055,11 @@ wysihtml5.browser = (function() {
6997
7055
  return isIE();
6998
7056
  },
6999
7057
 
7058
+ /* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */
7059
+ hasLiDeletingProblem: function() {
7060
+ return isIE();
7061
+ },
7062
+
7000
7063
  hasUndoInContextMenu: function() {
7001
7064
  return isGecko || isChrome || isOpera;
7002
7065
  },
@@ -7030,6 +7093,12 @@ wysihtml5.browser = (function() {
7030
7093
  return isWebKit;
7031
7094
  },
7032
7095
 
7096
+ // In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element
7097
+ // when startContainer is element.
7098
+ hasCaretBlockElementIssue: function() {
7099
+ return isWebKit;
7100
+ },
7101
+
7033
7102
  supportsMutationEvents: function() {
7034
7103
  return ("MutationEvent" in window);
7035
7104
  },
@@ -7050,6 +7119,10 @@ wysihtml5.browser = (function() {
7050
7119
  return ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat";
7051
7120
  }
7052
7121
  return key;
7122
+ },
7123
+
7124
+ usesControlRanges: function() {
7125
+ return document.body && "createControlRange" in document.body;
7053
7126
  }
7054
7127
  };
7055
7128
  })();
@@ -7877,7 +7950,12 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7877
7950
  is: {
7878
7951
  emptyTextNode: function(ignoreWhitespace) {
7879
7952
  var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g);
7880
- return node.nodeType === wysihtml5.TEXT_NODE && (regx).test(node.data);
7953
+ return node && node.nodeType === wysihtml5.TEXT_NODE && (regx).test(node.data);
7954
+ },
7955
+
7956
+ // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
7957
+ rangyBookmark: function() {
7958
+ return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary');
7881
7959
  },
7882
7960
 
7883
7961
  visible: function() {
@@ -7889,6 +7967,20 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7889
7967
  }
7890
7968
  }
7891
7969
  return isVisible;
7970
+ },
7971
+ lineBreak: function() {
7972
+ return node && node.nodeType === 1 && node.nodeName === "BR";
7973
+ },
7974
+ block: function() {
7975
+ return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block";
7976
+ },
7977
+ // Void elements are elemens that can not have content
7978
+ // In most cases browsers should solve the cases for you when you try to insert content into those,
7979
+ // but IE does not and it is not nice to do so anyway.
7980
+ voidElement: function() {
7981
+ return wysihtml5.dom.domNode(node).test({
7982
+ query: wysihtml5.VOID_ELEMENTS
7983
+ });
7892
7984
  }
7893
7985
  },
7894
7986
 
@@ -7896,18 +7988,19 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7896
7988
  prev: function(options) {
7897
7989
  var prevNode = node.previousSibling,
7898
7990
  types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
7899
-
7991
+
7900
7992
  if (!prevNode) {
7901
7993
  return null;
7902
7994
  }
7903
7995
 
7904
7996
  if (
7997
+ wysihtml5.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
7905
7998
  (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
7906
7999
  (options && options.ignoreBlankTexts && wysihtml5.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set
7907
8000
  ) {
7908
8001
  return wysihtml5.dom.domNode(prevNode).prev(options);
7909
8002
  }
7910
-
8003
+
7911
8004
  return prevNode;
7912
8005
  },
7913
8006
 
@@ -7915,18 +8008,19 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7915
8008
  next: function(options) {
7916
8009
  var nextNode = node.nextSibling,
7917
8010
  types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
7918
-
8011
+
7919
8012
  if (!nextNode) {
7920
8013
  return null;
7921
8014
  }
7922
8015
 
7923
8016
  if (
8017
+ wysihtml5.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
7924
8018
  (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
7925
8019
  (options && options.ignoreBlankTexts && wysihtml5.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set
7926
8020
  ) {
7927
8021
  return wysihtml5.dom.domNode(nextNode).next(options);
7928
8022
  }
7929
-
8023
+
7930
8024
  return nextNode;
7931
8025
  },
7932
8026
 
@@ -7989,7 +8083,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
7989
8083
  escapeParent: function(element, newWrapper) {
7990
8084
  var parent, split2, nodeWrap,
7991
8085
  curNode = node;
7992
-
8086
+
7993
8087
  // Stop if node is not a descendant of element
7994
8088
  if (!wysihtml5.dom.contains(element, node)) {
7995
8089
  throw new Error("Child is not a descendant of node.");
@@ -8044,12 +8138,35 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
8044
8138
  }
8045
8139
  },
8046
8140
 
8141
+ transferContentTo: function(targetNode, removeOldWrapper) {
8142
+ if (node.nodeType === 1) {
8143
+ if (wysihtml5.dom.domNode(targetNode).is.voidElement() || targetNode.nodeType === 3) {
8144
+ while (node.lastChild) {
8145
+ targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling);
8146
+ }
8147
+ } else {
8148
+ while (node.firstChild) {
8149
+ targetNode.appendChild(node.firstChild);
8150
+ }
8151
+ }
8152
+ if (removeOldWrapper) {
8153
+ node.parentNode.removeChild(node);
8154
+ }
8155
+ } else if (node.nodeType === 3 || node.nodeType === 8){
8156
+ if (wysihtml5.dom.domNode(targetNode).is.voidElement()) {
8157
+ targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
8158
+ } else {
8159
+ targetNode.appendChild(node);
8160
+ }
8161
+ }
8162
+ },
8163
+
8047
8164
  /*
8048
8165
  Tests a node against properties, and returns true if matches.
8049
8166
  Tests on principle that all properties defined must have at least one match.
8050
8167
  styleValue parameter works in context of styleProperty and has no effect otherwise.
8051
8168
  Returns true if element matches and false if it does not.
8052
-
8169
+
8053
8170
  Properties for filtering element:
8054
8171
  {
8055
8172
  query: selector string,
@@ -8082,7 +8199,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
8082
8199
  }
8083
8200
  }
8084
8201
 
8085
- if (properties.nodeName && node.nodeName !== properties.nodeName) {
8202
+ if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) {
8086
8203
  return false;
8087
8204
  }
8088
8205
 
@@ -9492,7 +9609,7 @@ wysihtml5.dom.renameElement = function(element, newNodeName) {
9492
9609
  newElement.appendChild(firstChild);
9493
9610
  }
9494
9611
  wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
9495
-
9612
+
9496
9613
  if (element.parentNode) {
9497
9614
  element.parentNode.replaceChild(newElement, element);
9498
9615
  }
@@ -9517,17 +9634,10 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
9517
9634
  return;
9518
9635
  }
9519
9636
 
9520
- if (!node.firstChild) {
9521
- node.parentNode.removeChild(node);
9522
- return;
9523
- }
9524
-
9525
- var fragment = node.ownerDocument.createDocumentFragment();
9526
9637
  while (node.firstChild) {
9527
- fragment.appendChild(node.firstChild);
9638
+ node.parentNode.insertBefore(node.firstChild, node);
9528
9639
  }
9529
- node.parentNode.replaceChild(fragment, node);
9530
- node = fragment = null;
9640
+ node.parentNode.removeChild(node);
9531
9641
  };
9532
9642
  ;/**
9533
9643
  * Unwraps an unordered/ordered list
@@ -11116,12 +11226,12 @@ wysihtml5.dom.unwrap = function(node) {
11116
11226
  }
11117
11227
  return children;
11118
11228
  };
11119
- ;/*
11229
+ ;/*
11120
11230
  * Methods for fetching pasted html before it gets inserted into content
11121
11231
  **/
11122
11232
 
11123
11233
  /* Modern event.clipboardData driven approach.
11124
- * Advantage is that it does not have to loose selection or modify dom to catch the data.
11234
+ * Advantage is that it does not have to loose selection or modify dom to catch the data.
11125
11235
  * IE does not support though.
11126
11236
  **/
11127
11237
  wysihtml5.dom.getPastedHtml = function(event) {
@@ -11142,7 +11252,7 @@ wysihtml5.dom.getPastedHtmlWithDiv = function (composer, f) {
11142
11252
  doc = composer.element.ownerDocument,
11143
11253
  cleanerDiv = doc.createElement('DIV'),
11144
11254
  scrollPos = composer.getScrollPos();
11145
-
11255
+
11146
11256
  doc.body.appendChild(cleanerDiv);
11147
11257
 
11148
11258
  cleanerDiv.style.width = "1px";
@@ -11444,7 +11554,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11444
11554
 
11445
11555
  };
11446
11556
  ;(function(wysihtml5) {
11447
-
11557
+
11448
11558
  // List of supported color format parsing methods
11449
11559
  // If radix is not defined 10 is expected as default
11450
11560
  var colorParseMethods = {
@@ -11487,7 +11597,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11487
11597
  }
11488
11598
  }
11489
11599
 
11490
- // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba".
11600
+ // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba".
11491
11601
  function getColorFormat (colorStr) {
11492
11602
  var type = getColorParseMethod(colorStr);
11493
11603
 
@@ -11500,9 +11610,9 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11500
11610
  // Takes color string value as an argument and returns suitable parsing method for it
11501
11611
  getColorParseMethod : getColorParseMethod,
11502
11612
 
11503
- // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba".
11613
+ // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba".
11504
11614
  getColorFormat : getColorFormat,
11505
-
11615
+
11506
11616
  /* Parses a color string to and array of [red, green, blue, alpha].
11507
11617
  * paramName: optional argument to parse color value directly from style string parameter
11508
11618
  *
@@ -11630,6 +11740,14 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11630
11740
  return ret;
11631
11741
  }
11632
11742
 
11743
+ function getRangeNode(node, offset) {
11744
+ if (node.nodeType === 3) {
11745
+ return node;
11746
+ } else {
11747
+ return node.childNodes[offset] || node;
11748
+ }
11749
+ }
11750
+
11633
11751
  function getWebkitSelectionFixNode(container) {
11634
11752
  var blankNode = document.createElement('span');
11635
11753
 
@@ -11656,7 +11774,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11656
11774
  }
11657
11775
  };
11658
11776
 
11659
- blankNode.appendChild(document.createTextNode(wysihtml5.INVISIBLE_SPACE));
11777
+ blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml5.INVISIBLE_SPACE));
11660
11778
  blankNode.className = '_wysihtml5-temp-caret-fix';
11661
11779
  blankNode.style.display = 'block';
11662
11780
  blankNode.style.minWidth = '1px';
@@ -11700,7 +11818,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11700
11818
  /** @scope wysihtml5.Selection.prototype */ {
11701
11819
  constructor: function(editor, contain, unselectableClass) {
11702
11820
  // Make sure that our external range library is initialized
11703
- window.rangy.init();
11821
+ rangy.init();
11704
11822
 
11705
11823
  this.editor = editor;
11706
11824
  this.composer = editor.composer;
@@ -11833,7 +11951,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
11833
11951
  if (!sel || (lastSibling === node && node.nodeType === 1 && win.getComputedStyle(node).display === "block")) {
11834
11952
  if (notVisual) {
11835
11953
  // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation
11836
- // and remove itself in call stack end instead on user interaction
11954
+ // and remove itself in call stack end instead on user interaction
11837
11955
  var caretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
11838
11956
  node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
11839
11957
  this.selectNode(caretPlaceholder);
@@ -12000,11 +12118,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12000
12118
  this.deleteRangeContents(range);
12001
12119
  this.setSelection(range);
12002
12120
  },
12003
-
12121
+
12004
12122
  // Makes sure all uneditable sare notified before deleting contents
12005
12123
  deleteRangeContents: function (range) {
12006
12124
  var startParent, endParent, uneditables, ev;
12007
-
12125
+
12008
12126
  if (this.unselectableClass) {
12009
12127
  if ((startParent = wysihtml5.dom.getParentElement(range.startContainer, { query: "." + this.unselectableClass }, false, this.contain))) {
12010
12128
  range.setStartBefore(startParent);
@@ -12027,11 +12145,16 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12027
12145
  range.deleteContents();
12028
12146
  },
12029
12147
 
12148
+ getCaretNode: function () {
12149
+ var selection = this.getSelection();
12150
+ return (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
12151
+ },
12152
+
12030
12153
  getPreviousNode: function(node, ignoreEmpty) {
12031
12154
  var displayStyle;
12032
12155
  if (!node) {
12033
12156
  var selection = this.getSelection();
12034
- node = selection.anchorNode;
12157
+ node = (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
12035
12158
  }
12036
12159
 
12037
12160
  if (node === this.contain) {
@@ -12072,6 +12195,50 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12072
12195
  return (ret !== this.contain) ? ret : false;
12073
12196
  },
12074
12197
 
12198
+ // Gather info about caret location (caret node, previous and next node)
12199
+ getNodesNearCaret: function() {
12200
+ if (!this.isCollapsed()) {
12201
+ throw "Selection must be caret when using selection.getNodesNearCaret()";
12202
+ }
12203
+
12204
+ var r = this.getOwnRanges(),
12205
+ caretNode, prevNode, nextNode, offset;
12206
+
12207
+ if (r && r.length > 0) {
12208
+ if (r[0].startContainer.nodeType === 1) {
12209
+ caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1];
12210
+ if (!caretNode && r[0].startOffset === 0) {
12211
+ // Is first position before all nodes
12212
+ nextNode = r[0].startContainer.childNodes[0];
12213
+ } else if (caretNode) {
12214
+ prevNode = caretNode.previousSibling;
12215
+ nextNode = caretNode.nextSibling;
12216
+ }
12217
+ } else {
12218
+ if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) {
12219
+ caretNode = r[0].startContainer.previousSibling;
12220
+ if (caretNode.nodeType === 3) {
12221
+ offset = caretNode.data.length;
12222
+ }
12223
+ } else {
12224
+ caretNode = r[0].startContainer;
12225
+ offset = r[0].startOffset;
12226
+ }
12227
+ prevNode = caretNode.previousSibling;
12228
+ nextNode = caretNode.nextSibling;
12229
+ }
12230
+
12231
+ return {
12232
+ "caretNode": caretNode,
12233
+ "prevNode": prevNode,
12234
+ "nextNode": nextNode,
12235
+ "textOffset": offset
12236
+ };
12237
+ }
12238
+
12239
+ return null;
12240
+ },
12241
+
12075
12242
  getSelectionParentsByTag: function(tagName) {
12076
12243
  var nodes = this.getSelectedOwnNodes(),
12077
12244
  curEl, parents = [];
@@ -12107,15 +12274,24 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12107
12274
  return (/^\s*$/).test(endtxt);
12108
12275
  },
12109
12276
 
12110
- caretIsFirstInSelection: function() {
12277
+ caretIsFirstInSelection: function(includeLineBreaks) {
12111
12278
  var r = rangy.createRange(this.doc),
12112
12279
  s = this.getSelection(),
12113
12280
  range = this.getRange(),
12114
- startNode = range.startContainer;
12115
-
12281
+ startNode = getRangeNode(range.startContainer, range.startOffset);
12282
+
12116
12283
  if (startNode) {
12117
12284
  if (startNode.nodeType === wysihtml5.TEXT_NODE) {
12118
- return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
12285
+ if (!startNode.parentNode) {
12286
+ return false;
12287
+ }
12288
+ if (!this.isCollapsed() || (startNode.parentNode.firstChild !== startNode && !wysihtml5.dom.domNode(startNode.previousSibling).is.block())) {
12289
+ return false;
12290
+ }
12291
+ var ws = this.win.getComputedStyle(startNode.parentNode).whiteSpace;
12292
+ return (ws === "pre" || ws === "pre-wrap") ? range.startOffset === 0 : (/^\s*$/).test(startNode.data.substr(0,range.startOffset));
12293
+ } else if (includeLineBreaks && wysihtml5.dom.domNode(startNode).is.lineBreak()) {
12294
+ return true;
12119
12295
  } else {
12120
12296
  r.selectNodeContents(this.getRange().commonAncestorContainer);
12121
12297
  r.collapse(true);
@@ -12143,6 +12319,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12143
12319
  startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset,
12144
12320
  rng = this.createRange(), endNode, inTmpCaret;
12145
12321
 
12322
+ // If start is textnode and all is whitespace before caret. Set start offset to 0
12323
+ if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) {
12324
+ startOffset = 0;
12325
+ }
12326
+
12146
12327
  // Escape temproray helper nodes if selection in them
12147
12328
  inTmpCaret = wysihtml5.dom.getParentElement(startNode, { query: '._wysihtml5-temp-caret-fix' }, 1);
12148
12329
  if (inTmpCaret) {
@@ -12323,7 +12504,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12323
12504
  node = this.doc.createElement('DIV'),
12324
12505
  fragment = this.doc.createDocumentFragment(),
12325
12506
  lastChild, lastEditorElement;
12326
-
12507
+
12327
12508
  if (range) {
12328
12509
  range.deleteContents();
12329
12510
  node.innerHTML = html;
@@ -12333,7 +12514,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12333
12514
  fragment.appendChild(node.firstChild);
12334
12515
  }
12335
12516
  range.insertNode(fragment);
12336
-
12517
+
12337
12518
  lastEditorElement = this.contain.lastChild;
12338
12519
  while (lastEditorElement && lastEditorElement.nodeType === 3 && lastEditorElement.previousSibling && (/^\s*$/).test(lastEditorElement.data)) {
12339
12520
  lastEditorElement = lastEditorElement.previousSibling;
@@ -12462,43 +12643,6 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12462
12643
  return nodes;
12463
12644
  },
12464
12645
 
12465
- deblockAndSurround: function(nodeOptions) {
12466
- var tempElement = this.doc.createElement('div'),
12467
- range = rangy.createRange(this.doc),
12468
- tempDivElements,
12469
- tempElements,
12470
- firstChild;
12471
-
12472
- tempElement.className = nodeOptions.className;
12473
-
12474
- this.composer.commands.exec("formatBlock", nodeOptions);
12475
- tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
12476
- if (tempDivElements[0]) {
12477
- tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
12478
-
12479
- range.setStartBefore(tempDivElements[0]);
12480
- range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
12481
- tempElements = range.extractContents();
12482
-
12483
- while (tempElements.firstChild) {
12484
- firstChild = tempElements.firstChild;
12485
- if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
12486
- while (firstChild.firstChild) {
12487
- tempElement.appendChild(firstChild.firstChild);
12488
- }
12489
- if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
12490
- tempElements.removeChild(firstChild);
12491
- } else {
12492
- tempElement.appendChild(firstChild);
12493
- }
12494
- }
12495
- } else {
12496
- tempElement = null;
12497
- }
12498
-
12499
- return tempElement;
12500
- },
12501
-
12502
12646
  /**
12503
12647
  * Scroll the current caret position into the view
12504
12648
  * FIXME: This is a bit hacky, there might be a smarter way of doing this
@@ -12532,15 +12676,39 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12532
12676
  * Select line where the caret is in
12533
12677
  */
12534
12678
  selectLine: function() {
12679
+ var r = rangy.createRange();
12535
12680
  if (wysihtml5.browser.supportsSelectionModify()) {
12536
12681
  this._selectLine_W3C();
12537
- } else if (this.doc.selection) {
12538
- this._selectLine_MSIE();
12539
- } else {
12682
+ } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) {
12540
12683
  // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
12541
12684
  this._selectLineUniversal();
12542
12685
  }
12543
12686
  },
12687
+
12688
+ includeRangyRangeHelpers: function() {
12689
+ var s = this.getSelection(),
12690
+ r = s.getRangeAt(0),
12691
+ isHelperNode = function(node) {
12692
+ return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'));
12693
+ },
12694
+ getNodeLength = function (node) {
12695
+ if (node.nodeType === 1) {
12696
+ return node.childNodes && node.childNodes.length || 0;
12697
+ } else {
12698
+ return node.data && node.data.length || 0;
12699
+ }
12700
+ },
12701
+ anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
12702
+ fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode;
12703
+
12704
+ if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) {
12705
+ r.setEndAfter(fnode.nextSibling);
12706
+ }
12707
+ if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) {
12708
+ r.setStartBefore(anode.previousSibling);
12709
+ }
12710
+ r.select();
12711
+ },
12544
12712
 
12545
12713
  /**
12546
12714
  * See https://developer.mozilla.org/en/DOM/Selection/modify
@@ -12548,10 +12716,10 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12548
12716
  _selectLine_W3C: function() {
12549
12717
  var selection = this.win.getSelection(),
12550
12718
  initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset];
12551
-
12719
+
12552
12720
  selection.modify("move", "left", "lineboundary");
12553
12721
  selection.modify("extend", "right", "lineboundary");
12554
-
12722
+
12555
12723
  // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason)
12556
12724
  if (selection.anchorNode === initialBoundry[0] &&
12557
12725
  selection.anchorOffset === initialBoundry[1] &&
@@ -12559,6 +12727,8 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12559
12727
  selection.focusOffset === initialBoundry[3]
12560
12728
  ) {
12561
12729
  this._selectLineUniversal();
12730
+ } else {
12731
+ this.includeRangyRangeHelpers();
12562
12732
  }
12563
12733
  },
12564
12734
 
@@ -12610,89 +12780,88 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12610
12780
  rect,
12611
12781
  startRange, endRange, testRange,
12612
12782
  count = 0,
12613
- amount, testRect, found;
12783
+ amount, testRect, found,
12784
+ that = this,
12785
+ isLineBreakingElement = function(el) {
12786
+ return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml5.lang.array(['BR', 'HR']).contains(el.nodeName));
12787
+ },
12788
+ prevNode = function(node) {
12789
+ var pnode = node;
12790
+ if (pnode) {
12791
+ while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) {
12792
+ pnode = pnode.previousSibling;
12793
+ }
12794
+ }
12795
+ return pnode;
12796
+ };
12614
12797
 
12615
12798
  startRange = r.cloneRange();
12616
12799
  endRange = r.cloneRange();
12617
12800
 
12618
12801
  if (r.collapsed) {
12619
- r.expand('word', 1);
12620
- rect = r.nativeRange.getBoundingClientRect();
12802
+ // Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary
12803
+ // TODO: figure out a shorter and more readable way
12804
+ if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) {
12805
+ r.moveEnd('character', 1);
12806
+ } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) {
12807
+ r.moveEnd('character', 1);
12808
+ } else if (
12809
+ r.startOffset > 0 &&
12810
+ (
12811
+ r.startContainer.nodeType === 3 ||
12812
+ (
12813
+ r.startContainer.nodeType === 1 &&
12814
+ !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))
12815
+ )
12816
+ )
12817
+ ) {
12818
+ r.moveStart('character', -1);
12819
+ }
12621
12820
  }
12622
-
12821
+ if (!r.collapsed) {
12822
+ r.insertNode(this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE));
12823
+ }
12824
+
12825
+ // Is probably just empty line as can not be expanded
12826
+ rect = r.nativeRange.getBoundingClientRect();
12827
+ // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes;
12623
12828
  do {
12624
12829
  amount = r.moveStart('character', -1);
12625
12830
  testRect = r.nativeRange.getBoundingClientRect();
12831
+
12626
12832
  if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) {
12627
12833
  r.moveStart('character', 1);
12628
12834
  found = true;
12629
12835
  }
12630
12836
  count++;
12631
12837
  } while (amount !== 0 && !found && count < 2000);
12632
-
12633
12838
  count = 0;
12634
12839
  found = false;
12635
12840
  rect = r.nativeRange.getBoundingClientRect();
12636
- do {
12637
- amount = r.moveEnd('character', 1);
12638
- testRect = r.nativeRange.getBoundingClientRect();
12639
- if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
12640
- r.moveEnd('character', -1);
12641
- found = true;
12642
- }
12643
- count++;
12644
- } while (amount !== 0 && !found && count < 2000);
12645
-
12646
- r.select();
12647
- },
12648
-
12649
- _selectLine_MSIE: function() {
12650
- var range = this.doc.selection && this.doc.selection.createRange ? this.doc.selection.createRange() : this.doc.createRange(),
12651
- rangeTop = range.boundingTop,
12652
- scrollWidth = this.doc.body.scrollWidth,
12653
- rangeBottom,
12654
- rangeEnd,
12655
- measureNode,
12656
- i,
12657
- j;
12658
-
12659
- window.r = range;
12660
-
12661
- if (!range.moveToPoint) {
12662
- return;
12663
- }
12664
-
12665
- if (rangeTop === 0) {
12666
- // Don't know why, but when the selection ends at the end of a line
12667
- // range.boundingTop is 0
12668
- measureNode = this.doc.createElement("span");
12669
- this.insertNode(measureNode);
12670
- rangeTop = measureNode.offsetTop;
12671
- measureNode.parentNode.removeChild(measureNode);
12672
- }
12673
-
12674
- rangeTop += 1;
12675
-
12676
- for (i=-10; i<scrollWidth; i+=2) {
12677
- try {
12678
- range.moveToPoint(i, rangeTop);
12679
- break;
12680
- } catch(e1) {}
12681
- }
12682
-
12683
- // Investigate the following in order to handle multi line selections
12684
- // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
12685
- rangeBottom = rangeTop;
12686
- rangeEnd = this.doc.selection.createRange();
12687
- for (j=scrollWidth; j>=0; j--) {
12688
- try {
12689
- rangeEnd.moveToPoint(j, rangeBottom);
12690
- break;
12691
- } catch(e2) {}
12841
+
12842
+ if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) {
12843
+ do {
12844
+ amount = r.moveEnd('character', 1);
12845
+ testRect = r.nativeRange.getBoundingClientRect();
12846
+ if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
12847
+ r.moveEnd('character', -1);
12848
+
12849
+ // Fix a IE line end marked by linebreak element although caret is before it
12850
+ // If causes problems should be changed to be applied only to IE
12851
+ if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
12852
+ if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
12853
+ r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
12854
+ } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
12855
+ r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
12856
+ }
12857
+ }
12858
+ found = true;
12859
+ }
12860
+ count++;
12861
+ } while (amount !== 0 && !found && count < 2000);
12692
12862
  }
12693
-
12694
- range.setEndPoint("EndToEnd", rangeEnd);
12695
- range.select();
12863
+ r.select();
12864
+ this.includeRangyRangeHelpers();
12696
12865
  },
12697
12866
 
12698
12867
  getText: function() {
@@ -12863,7 +13032,7 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12863
13032
 
12864
13033
  wysihtml5.dom.removeInvisibleSpaces(this.composer.element);
12865
13034
  doSelect();
12866
-
13035
+
12867
13036
  if (this.composer.element.firstChild && notSelected()) {
12868
13037
  // Try fixing end
12869
13038
  this.composer.element.appendChild(blankEndNode);
@@ -12872,11 +13041,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
12872
13041
  if (notSelected()) {
12873
13042
  // Remove end fix
12874
13043
  blankEndNode.parentNode.removeChild(blankEndNode);
12875
-
13044
+
12876
13045
  // Try fixing beginning
12877
13046
  this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild);
12878
13047
  doSelect();
12879
-
13048
+
12880
13049
  if (notSelected()) {
12881
13050
  // Try fixing both
12882
13051
  this.composer.element.appendChild(blankEndNode);
@@ -13654,7 +13823,7 @@ wysihtml5.Commands = Base.extend(
13654
13823
  result = null;
13655
13824
 
13656
13825
  // If composer ahs placeholder unset it before command
13657
- // Do not apply on commands that are behavioral
13826
+ // Do not apply on commands that are behavioral
13658
13827
  if (this.composer.hasPlaceholderSet() && !wysihtml5.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) {
13659
13828
  this.composer.element.innerHTML = "";
13660
13829
  this.composer.selection.selectNode(this.composer.element);
@@ -13727,12 +13896,12 @@ wysihtml5.Commands = Base.extend(
13727
13896
  }
13728
13897
  });
13729
13898
  ;(function(wysihtml5) {
13730
-
13899
+
13731
13900
  var nodeOptions = {
13732
13901
  nodeName: "B",
13733
13902
  toggle: true
13734
13903
  };
13735
-
13904
+
13736
13905
  wysihtml5.commands.bold = {
13737
13906
  exec: function(composer, command) {
13738
13907
  wysihtml5.commands.formatInline.exec(composer, command, nodeOptions);
@@ -13971,9 +14140,9 @@ wysihtml5.Commands = Base.extend(
13971
14140
  };
13972
14141
  })(wysihtml5);
13973
14142
  ;/* Formatblock
13974
- * Is used to insert block level elements
14143
+ * Is used to insert block level elements
13975
14144
  * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
13976
- *
14145
+ *
13977
14146
  */
13978
14147
  (function(wysihtml5) {
13979
14148
 
@@ -13993,18 +14162,56 @@ wysihtml5.Commands = Base.extend(
13993
14162
  };
13994
14163
  }
13995
14164
 
14165
+ function getRangeNode(node, offset) {
14166
+ if (node.nodeType === 3) {
14167
+ return node;
14168
+ } else {
14169
+ return node.childNodes[offset] || node;
14170
+ }
14171
+ }
14172
+
14173
+ // Returns if node is a line break
14174
+ function isBr(n) {
14175
+ return n && n.nodeType === 1 && n.nodeName === "BR";
14176
+ }
14177
+
14178
+ // Is block level element
14179
+ function isBlock(n, composer) {
14180
+ return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block";
14181
+ }
14182
+
14183
+ // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
14184
+ function isBookmark(n) {
14185
+ return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
14186
+ }
14187
+
14188
+ // Is line breaking node
14189
+ function isLineBreaking(n, composer) {
14190
+ return isBr(n) || isBlock(n, composer);
14191
+ }
14192
+
13996
14193
  // Removes empty block level elements
13997
- function cleanup(composer) {
14194
+ function cleanup(composer, newBlockElements) {
14195
+ wysihtml5.dom.removeInvisibleSpaces(composer.element);
13998
14196
  var container = composer.element,
13999
14197
  allElements = container.querySelectorAll(BLOCK_ELEMENTS),
14000
- uneditables = container.querySelectorAll(composer.config.classNames.uneditableContainer),
14001
- elements = wysihtml5.lang.array(allElements).without(uneditables);
14198
+ noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '),
14199
+ uneditables = container.querySelectorAll(noEditQuery),
14200
+ elements = wysihtml5.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents
14201
+ nbIdx;
14002
14202
 
14003
14203
  for (var i = elements.length; i--;) {
14004
- if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "") {
14204
+ if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) {
14205
+ // If cleanup removes some new block elements. remove them from newblocks array too
14206
+ nbIdx = wysihtml5.lang.array(newBlockElements).indexOf(elements[i]);
14207
+ if (nbIdx > -1) {
14208
+ newBlockElements.splice(nbIdx, 1);
14209
+ }
14005
14210
  elements[i].parentNode.removeChild(elements[i]);
14006
14211
  }
14007
14212
  }
14213
+
14214
+ return newBlockElements;
14008
14215
  }
14009
14216
 
14010
14217
  function defaultNodeName(composer) {
@@ -14015,7 +14222,7 @@ wysihtml5.Commands = Base.extend(
14015
14222
  function findOuterBlock(node, container, allBlocks) {
14016
14223
  var n = node,
14017
14224
  block = null;
14018
-
14225
+
14019
14226
  while (n && container && n !== container) {
14020
14227
  if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) {
14021
14228
  block = n;
@@ -14026,6 +14233,8 @@ wysihtml5.Commands = Base.extend(
14026
14233
  return block;
14027
14234
  }
14028
14235
 
14236
+ // Clone for splitting the inner inline element out of its parent inline elements context
14237
+ // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return
14029
14238
  function cloneOuterInlines(node, container) {
14030
14239
  var n = node,
14031
14240
  innerNode,
@@ -14088,7 +14297,10 @@ wysihtml5.Commands = Base.extend(
14088
14297
  // Unsets element properties by options
14089
14298
  // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes)
14090
14299
  function removeOptionsFromElement(element, options, composer) {
14091
- var style, classes;
14300
+ var style, classes,
14301
+ prevNode = element.previousSibling,
14302
+ nextNode = element.nextSibling,
14303
+ unwrapped = false;
14092
14304
 
14093
14305
  if (options.styleProperty) {
14094
14306
  element.style[wysihtml5.browser.fixStyleKey(options.styleProperty)] = '';
@@ -14106,10 +14318,11 @@ wysihtml5.Commands = Base.extend(
14106
14318
  element.removeAttribute('class');
14107
14319
  }
14108
14320
 
14109
- if (options.nodeName && element.nodeName === options.nodeName) {
14321
+ if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) {
14110
14322
  style = element.getAttribute('style');
14111
14323
  if (!style || style.trim() === '') {
14112
14324
  dom.unwrap(element);
14325
+ unwrapped = true;
14113
14326
  } else {
14114
14327
  element = dom.renameElement(element, defaultNodeName(composer));
14115
14328
  }
@@ -14119,60 +14332,79 @@ wysihtml5.Commands = Base.extend(
14119
14332
  if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") {
14120
14333
  element.removeAttribute('style');
14121
14334
  }
14335
+
14336
+ if (unwrapped) {
14337
+ applySurroundingLineBreaks(prevNode, nextNode, composer);
14338
+ }
14122
14339
  }
14123
14340
 
14124
14341
  // Unwraps block level elements from inside content
14125
14342
  // Useful as not all block level elements can contain other block-levels
14126
14343
  function unwrapBlocksFromContent(element) {
14127
- var contentBlocks = element.querySelectorAll(BLOCK_ELEMENTS) || []; // Find unnestable block elements in extracted contents
14344
+ var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents
14345
+ nextEl, prevEl;
14128
14346
 
14129
- for (var i = contentBlocks.length; i--;) {
14130
- if (!contentBlocks[i].nextSibling || contentBlocks[i].nextSibling.nodeType !== 1 || contentBlocks[i].nextSibling.nodeName !== 'BR') {
14131
- if ((contentBlocks[i].innerHTML || contentBlocks[i].nodeValue || '').trim() !== '') {
14132
- contentBlocks[i].parentNode.insertBefore(contentBlocks[i].ownerDocument.createElement('BR'), contentBlocks[i].nextSibling);
14347
+ for (var i = blocks.length; i--;) {
14348
+ nextEl = wysihtml5.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
14349
+ prevEl = wysihtml5.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
14350
+
14351
+ if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
14352
+ if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
14353
+ blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
14133
14354
  }
14134
14355
  }
14135
- wysihtml5.dom.unwrap(contentBlocks[i]);
14356
+ if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
14357
+ if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
14358
+ blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
14359
+ }
14360
+ }
14361
+ wysihtml5.dom.unwrap(blocks[i]);
14136
14362
  }
14137
14363
  }
14138
14364
 
14139
14365
  // Fix ranges that visually cover whole block element to actually cover the block
14140
14366
  function fixRangeCoverage(range, composer) {
14141
- var node;
14367
+ var node,
14368
+ start = range.startContainer,
14369
+ end = range.endContainer;
14142
14370
 
14143
- if (range.startContainer && range.startContainer.nodeType === 1 && range.startContainer === range.endContainer) {
14144
- if (range.startContainer.firstChild === range.startContainer.lastChild && range.endOffset === 1) {
14145
- if (range.startContainer !== composer.element) {
14146
- range.setStartBefore(range.startContainer);
14147
- range.setEndAfter(range.endContainer);
14371
+ // If range has only one childNode and it is end to end the range, extend the range to contain the container element too
14372
+ // This ensures the wrapper node is modified and optios added to it
14373
+ if (start && start.nodeType === 1 && start === end) {
14374
+ if (start.firstChild === start.lastChild && range.endOffset === 1) {
14375
+ if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
14376
+ range.setStartBefore(start);
14377
+ range.setEndAfter(end);
14148
14378
  }
14149
14379
  }
14150
14380
  return;
14151
14381
  }
14152
14382
 
14153
- if (range.startContainer && range.startContainer.nodeType === 1 && range.endContainer.nodeType === 3) {
14154
- if (range.startContainer.firstChild === range.endContainer && range.endOffset === 1) {
14155
- if (range.startContainer !== composer.element) {
14156
- range.setEndAfter(range.startContainer);
14383
+ // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too
14384
+ if (start && start.nodeType === 1 && end.nodeType === 3) {
14385
+ if (start.firstChild === end && range.endOffset === end.data.length) {
14386
+ if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
14387
+ range.setEndAfter(start);
14157
14388
  }
14158
14389
  }
14159
14390
  return;
14160
14391
  }
14161
-
14162
- if (range.endContainer && range.endContainer.nodeType === 1 && range.startContainer.nodeType === 3) {
14163
- if (range.endContainer.firstChild === range.startContainer && range.endOffset === 1) {
14164
- if (range.endContainer !== composer.element) {
14165
- range.setStartBefore(range.endContainer);
14392
+
14393
+ // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too
14394
+ if (end && end.nodeType === 1 && start.nodeType === 3) {
14395
+ if (end.firstChild === start && range.startOffset === 0) {
14396
+ if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') {
14397
+ range.setStartBefore(end);
14166
14398
  }
14167
14399
  }
14168
14400
  return;
14169
14401
  }
14170
14402
 
14171
-
14172
- if (range.startContainer && range.startContainer.nodeType === 3 && range.startContainer === range.endContainer && range.startContainer.parentNode) {
14173
- if (range.startContainer.parentNode.firstChild === range.startContainer && range.endOffset == range.endContainer.length && range.startOffset === 0) {
14174
- node = range.startContainer.parentNode;
14175
- if (node !== composer.element) {
14403
+ // If range covers a whole textnode and the textnode is the only child of node, extend range to node
14404
+ if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) {
14405
+ if (range.endOffset == end.data.length && range.startOffset === 0) {
14406
+ node = start.parentNode;
14407
+ if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') {
14176
14408
  range.setStartBefore(node);
14177
14409
  range.setEndAfter(node);
14178
14410
  }
@@ -14180,94 +14412,123 @@ wysihtml5.Commands = Base.extend(
14180
14412
  return;
14181
14413
  }
14182
14414
  }
14183
-
14184
- // Wrap the range with a block level element
14185
- // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
14186
- function wrapRangeWithElement(range, options, defaultName, composer) {
14187
- var defaultOptions = (options) ? wysihtml5.lang.object(options).clone(true) : null;
14188
- if (defaultOptions) {
14189
- defaultOptions.nodeName = defaultOptions.nodeName || defaultName || defaultNodeName(composer);
14190
- }
14191
- fixRangeCoverage(range, composer);
14192
-
14193
- var r = range.cloneRange(),
14194
- rangeStartContainer = r.startContainer,
14195
- content = r.extractContents(),
14196
- fragment = composer.doc.createDocumentFragment(),
14197
- similarOptions = defaultOptions ? correctOptionsForSimilarityCheck(defaultOptions) : null,
14198
- similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
14199
- splitAllBlocks = !defaultOptions || (defaultName === "BLOCKQUOTE" && defaultOptions.nodeName && defaultOptions.nodeName === "BLOCKQUOTE"),
14200
- firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
14201
- wrapper, blocks, children;
14202
-
14203
- if (options && options.nodeName && options.nodeName === "BLOCKQUOTE") {
14204
- var tmpEl = applyOptionsToElement(null, options, composer);
14205
- tmpEl.appendChild(content);
14206
- fragment.appendChild(tmpEl);
14207
- blocks = [tmpEl];
14208
- } else {
14209
-
14210
- if (!content.firstChild) {
14211
- fragment.appendChild(applyOptionsToElement(null, options, composer));
14415
+
14416
+ // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges
14417
+ // Some places do not allow block level elements inbetween (inside ul and outside li)
14418
+ // TODO: might need extending for other nodes besides li (maybe dd,dl,dt)
14419
+ function fixNotPermittedInsertionPoints(ranges) {
14420
+ var newRanges = [],
14421
+ lis, j, maxj, tmpRange, rangePos, closestLI;
14422
+
14423
+ for (var i = 0, maxi = ranges.length; i < maxi; i++) {
14424
+
14425
+ // Fixes range start and end positions if inside UL or OL element (outside of LI)
14426
+ if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) {
14427
+ ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0);
14428
+ }
14429
+ if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) {
14430
+ closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)];
14431
+ if (closestLI.childNodes) {
14432
+ ranges[i].setEnd(closestLI, closestLI.childNodes.length);
14433
+ }
14434
+ }
14435
+
14436
+ // Get all LI eleemnts in selection (fully or partially covered)
14437
+ // And make sure ranges are either inside LI or outside UL/OL
14438
+ // Split and add new ranges as needed to cover same range content
14439
+ // TODO: Needs improvement to accept DL, DD, DT
14440
+ lis = ranges[i].getNodes([1], function(node) {
14441
+ return node.nodeName === "LI";
14442
+ });
14443
+ if (lis.length > 0) {
14444
+
14445
+ for (j = 0, maxj = lis.length; j < maxj; j++) {
14446
+ rangePos = ranges[i].compareNode(lis[j]);
14447
+
14448
+ // Fixes start of range that crosses LI border
14449
+ if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) {
14450
+ // Range starts before and ends inside the node
14451
+
14452
+ tmpRange = ranges[i].cloneRange();
14453
+ closestLI = wysihtml5.dom.domNode(lis[j]).prev({nodeTypes: [1]});
14454
+
14455
+ if (closestLI) {
14456
+ tmpRange.setEnd(closestLI, closestLI.childNodes.length);
14457
+ } else if (lis[j].closest('ul, ol')) {
14458
+ tmpRange.setEndBefore(lis[j].closest('ul, ol'));
14459
+ } else {
14460
+ tmpRange.setEndBefore(lis[j]);
14461
+ }
14462
+ newRanges.push(tmpRange);
14463
+ ranges[i].setStart(lis[j], 0);
14464
+ }
14465
+
14466
+ // Fixes end of range that crosses li border
14467
+ if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) {
14468
+ // Range starts inside the node and ends after node
14469
+
14470
+ tmpRange = ranges[i].cloneRange();
14471
+ tmpRange.setEnd(lis[j], lis[j].childNodes.length);
14472
+ newRanges.push(tmpRange);
14473
+
14474
+ // Find next LI in list and if present set range to it, else
14475
+ closestLI = wysihtml5.dom.domNode(lis[j]).next({nodeTypes: [1]});
14476
+ if (closestLI) {
14477
+ ranges[i].setStart(closestLI, 0);
14478
+ } else if (lis[j].closest('ul, ol')) {
14479
+ ranges[i].setStartAfter(lis[j].closest('ul, ol'));
14480
+ } else {
14481
+ ranges[i].setStartAfter(lis[j]);
14482
+ }
14483
+ }
14484
+ }
14485
+ newRanges.push(ranges[i]);
14212
14486
  } else {
14487
+ newRanges.push(ranges[i]);
14488
+ }
14489
+ }
14490
+ return newRanges;
14491
+ }
14492
+
14493
+ // Return options object with nodeName set if original did not have any
14494
+ // Node name is set to local or global default
14495
+ function getOptionsWithNodename(options, defaultName, composer) {
14496
+ var correctedOptions = (options) ? wysihtml5.lang.object(options).clone(true) : null;
14497
+ if (correctedOptions) {
14498
+ correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer);
14499
+ }
14500
+ return correctedOptions;
14501
+ }
14502
+
14503
+ // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted
14504
+ // Also wraps empty clones of split parent tags around fragment to keep formatting
14505
+ // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not)
14506
+ function injectFragmentToRange(fragment, range, composer, firstOuterBlock) {
14507
+ var rangeStartContainer = range.startContainer,
14508
+ firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true),
14509
+ outerInlines, first, last, prev, next;
14510
+
14511
+ if (firstOuterBlock) {
14512
+ // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
14513
+ first = fragment.firstChild;
14514
+ last = fragment.lastChild;
14213
14515
 
14214
- while(content.firstChild) {
14516
+ composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
14215
14517
 
14216
- if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
14518
+ next = wysihtml5.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true});
14519
+ prev = wysihtml5.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
14217
14520
 
14218
- if (options) {
14219
- // Escape(split) block formatting at caret
14220
- applyOptionsToElement(content.firstChild, options, composer);
14221
- if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
14222
- unwrapBlocksFromContent(content.firstChild);
14223
- }
14224
- fragment.appendChild(content.firstChild);
14521
+ if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) {
14522
+ first.parentNode.insertBefore(composer.doc.createElement('br'), first);
14523
+ }
14225
14524
 
14226
- } else {
14227
- // Split block formating and add new block to wrap caret
14228
- unwrapBlocksFromContent(content.firstChild);
14229
- children = wysihtml5.dom.unwrap(content.firstChild);
14230
- for (var c = 0, cmax = children.length; c < cmax; c++) {
14231
- fragment.appendChild(children[c]);
14232
- }
14525
+ if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) {
14526
+ next.parentNode.insertBefore(composer.doc.createElement('br'), next);
14527
+ }
14233
14528
 
14234
- if (fragment.childNodes.length > 0) {
14235
- fragment.appendChild(composer.doc.createElement('BR'));
14236
- }
14237
- }
14238
- } else {
14239
-
14240
- if (options) {
14241
- // Wrap subsequent non-block nodes inside new block element
14242
- wrapper = applyOptionsToElement(null, defaultOptions, composer);
14243
- while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
14244
- if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
14245
- unwrapBlocksFromContent(content.firstChild);
14246
- }
14247
- wrapper.appendChild(content.firstChild);
14248
- }
14249
- fragment.appendChild(wrapper);
14250
-
14251
- } else {
14252
- // Escape(split) block formatting at selection
14253
- if (content.firstChild.nodeType == 1) {
14254
- unwrapBlocksFromContent(content.firstChild);
14255
- }
14256
- fragment.appendChild(content.firstChild);
14257
- }
14258
-
14259
- }
14260
- }
14261
- }
14262
-
14263
- blocks = wysihtml5.lang.array(fragment.childNodes).get();
14264
- }
14265
- if (firstOuterBlock) {
14266
- // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
14267
- composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
14268
14529
  } else {
14269
14530
  // Ensure node does not get inserted into an inline where it is not allowed
14270
- var outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
14531
+ outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
14271
14532
  if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
14272
14533
  if (fragment.childNodes.length === 1) {
14273
14534
  while(fragment.firstChild.firstChild) {
@@ -14277,11 +14538,228 @@ wysihtml5.Commands = Base.extend(
14277
14538
  }
14278
14539
  composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
14279
14540
  } else {
14280
- // Otherwise just insert
14281
- r.insertNode(fragment);
14541
+ var fc = fragment.firstChild,
14542
+ lc = fragment.lastChild;
14543
+
14544
+ range.insertNode(fragment);
14545
+ // restore range position as it might get lost in webkit sometimes
14546
+ range.setStartBefore(fc);
14547
+ range.setEndAfter(lc);
14548
+ }
14549
+ }
14550
+ }
14551
+
14552
+ // Removes all block formatting from range
14553
+ function clearRangeBlockFromating(range, closestBlockName, composer) {
14554
+ var r = range.cloneRange(),
14555
+ prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling,
14556
+ nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling,
14557
+ content = r.extractContents(),
14558
+ fragment = composer.doc.createDocumentFragment(),
14559
+ children, blocks,
14560
+ first = true;
14561
+
14562
+ while(content.firstChild) {
14563
+ // Iterate over all selection content first level childNodes
14564
+ if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
14565
+ // If node is a block element
14566
+ // Split block formating and add new block to wrap caret
14567
+
14568
+ unwrapBlocksFromContent(content.firstChild);
14569
+ children = wysihtml5.dom.unwrap(content.firstChild);
14570
+
14571
+ // Add line break before if needed
14572
+ if (children.length > 0) {
14573
+ if (
14574
+ (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) ||
14575
+ (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer)))
14576
+ ){
14577
+ fragment.appendChild(composer.doc.createElement('BR'));
14578
+ }
14579
+ }
14580
+
14581
+ for (var c = 0, cmax = children.length; c < cmax; c++) {
14582
+ fragment.appendChild(children[c]);
14583
+ }
14584
+
14585
+ // Add line break after if needed
14586
+ if (children.length > 0) {
14587
+ if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) {
14588
+ if (nextNode || fragment.lastChild !== content.lastChild) {
14589
+ fragment.appendChild(composer.doc.createElement('BR'));
14590
+ }
14591
+ }
14592
+ }
14593
+
14594
+ } else {
14595
+ fragment.appendChild(content.firstChild);
14596
+ }
14597
+
14598
+ first = false;
14599
+ }
14600
+ blocks = wysihtml5.lang.array(fragment.childNodes).get();
14601
+ injectFragmentToRange(fragment, r, composer);
14602
+ return blocks;
14603
+ }
14604
+
14605
+ // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself)
14606
+ function removeSurroundingLineBreaks(prevNode, nextNode, composer) {
14607
+ var prevPrev = prevNode && wysihtml5.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
14608
+ if (isBr(nextNode)) {
14609
+ nextNode.parentNode.removeChild(nextNode);
14610
+ }
14611
+ if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) {
14612
+ prevNode.parentNode.removeChild(prevNode);
14613
+ }
14614
+ }
14615
+
14616
+ function applySurroundingLineBreaks(prevNode, nextNode, composer) {
14617
+ var prevPrev;
14618
+
14619
+ if (prevNode && isBookmark(prevNode)) {
14620
+ prevNode = prevNode.previousSibling;
14621
+ }
14622
+ if (nextNode && isBookmark(nextNode)) {
14623
+ nextNode = nextNode.nextSibling;
14624
+ }
14625
+
14626
+ prevPrev = prevNode && prevNode.previousSibling;
14627
+
14628
+ if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) {
14629
+ prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling);
14630
+ }
14631
+
14632
+ if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) {
14633
+ nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode);
14634
+ }
14635
+ }
14636
+
14637
+ var isWhitespaceBefore = function (textNode, offset) {
14638
+ var str = textNode.data ? textNode.data.slice(0, offset) : "";
14639
+ return (/^\s*$/).test(str);
14640
+ }
14641
+
14642
+ var isWhitespaceAfter = function (textNode, offset) {
14643
+ var str = textNode.data ? textNode.data.slice(offset) : "";
14644
+ return (/^\s*$/).test(str);
14645
+ }
14646
+
14647
+ var trimBlankTextsAndBreaks = function(fragment) {
14648
+ if (fragment) {
14649
+ while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) {
14650
+ fragment.removeChild(fragment.firstChild);
14651
+ }
14652
+
14653
+ while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) {
14654
+ fragment.removeChild(fragment.lastChild);
14655
+ }
14656
+
14657
+ if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
14658
+ fragment.removeChild(fragment.firstChild);
14282
14659
  }
14660
+
14661
+ if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
14662
+ fragment.removeChild(fragment.lastChild);
14663
+ }
14664
+ }
14665
+ }
14666
+
14667
+ // Wrap the range with a block level element
14668
+ // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
14669
+ function wrapRangeWithElement(range, options, closestBlockName, composer) {
14670
+ var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
14671
+ r = range.cloneRange(),
14672
+ rangeStartContainer = r.startContainer,
14673
+ startNode = getRangeNode(r.startContainer, r.startOffset),
14674
+ endNode = getRangeNode(r.endContainer, r.endOffset),
14675
+ prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode : wysihtml5.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
14676
+ nextNode = (
14677
+ (
14678
+ r.endContainer.nodeType === 1 &&
14679
+ r.endContainer.childNodes[r.endOffset] === endNode &&
14680
+ (
14681
+ endNode.nodeType === 1 ||
14682
+ !isWhitespaceAfter(endNode, r.endOffset) &&
14683
+ !wysihtml5.dom.domNode(endNode).is.rangyBookmark()
14684
+ )
14685
+ ) || (
14686
+ r.endContainer === endNode &&
14687
+ endNode.nodeType === 3 &&
14688
+ !isWhitespaceAfter(endNode, r.endOffset)
14689
+ )
14690
+ ) ? endNode : wysihtml5.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
14691
+ content = r.extractContents(),
14692
+ fragment = composer.doc.createDocumentFragment(),
14693
+ similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
14694
+ splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
14695
+ firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
14696
+ wrapper, blocks, children,
14697
+ firstc, lastC;
14698
+
14699
+ if (wysihtml5.dom.domNode(nextNode).is.rangyBookmark()) {
14700
+ endNode = nextNode;
14701
+ nextNode = endNode.nextSibling;
14283
14702
  }
14284
14703
 
14704
+ trimBlankTextsAndBreaks(content);
14705
+
14706
+ if (options && options.nodeName === "BLOCKQUOTE") {
14707
+
14708
+ // If blockquote is to be inserted no quessing just add it as outermost block on line or selection
14709
+ var tmpEl = applyOptionsToElement(null, options, composer);
14710
+ tmpEl.appendChild(content);
14711
+ fragment.appendChild(tmpEl);
14712
+ blocks = [tmpEl];
14713
+
14714
+ } else {
14715
+
14716
+ if (!content.firstChild) {
14717
+ // IF selection is caret (can happen if line is empty) add format around tag
14718
+ fragment.appendChild(applyOptionsToElement(null, options, composer));
14719
+ } else {
14720
+
14721
+ while(content.firstChild) {
14722
+ // Iterate over all selection content first level childNodes
14723
+
14724
+ if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
14725
+
14726
+ // If node is a block element
14727
+ // Escape(split) block formatting at caret
14728
+ applyOptionsToElement(content.firstChild, options, composer);
14729
+ if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
14730
+ unwrapBlocksFromContent(content.firstChild);
14731
+ }
14732
+ fragment.appendChild(content.firstChild);
14733
+
14734
+ } else {
14735
+
14736
+ // Wrap subsequent non-block nodes inside new block element
14737
+ wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer);
14738
+ while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
14739
+ if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
14740
+ unwrapBlocksFromContent(content.firstChild);
14741
+ }
14742
+ wrapper.appendChild(content.firstChild);
14743
+ }
14744
+ fragment.appendChild(wrapper);
14745
+ }
14746
+ }
14747
+ }
14748
+
14749
+ blocks = wysihtml5.lang.array(fragment.childNodes).get();
14750
+ }
14751
+ injectFragmentToRange(fragment, r, composer, firstOuterBlock);
14752
+ removeSurroundingLineBreaks(prevNode, nextNode, composer);
14753
+
14754
+ // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block
14755
+ // (if it contains rangy bookmark, so selection can be restored later correctly)
14756
+ if (blocks.length > 0 &&
14757
+ (
14758
+ typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml5.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark()
14759
+ )
14760
+ ) {
14761
+ blocks[blocks.length - 1].appendChild(composer.doc.createElement('br'));
14762
+ }
14285
14763
  return blocks;
14286
14764
  }
14287
14765
 
@@ -14293,101 +14771,181 @@ wysihtml5.Commands = Base.extend(
14293
14771
 
14294
14772
  return (parentNode) ? parentNode.nodeName : null;
14295
14773
  }
14774
+
14775
+ // Expands caret to cover the closest block that:
14776
+ // * cannot contain other block level elements (h1-6,p, etc)
14777
+ // * Has the same nodeName that is to be inserted
14778
+ // * has insertingNodeName
14779
+ // * is DIV if insertingNodeName is not present
14780
+ //
14781
+ // If nothing found selects the current line
14782
+ function expandCaretToBlock(composer, insertingNodeName) {
14783
+ var parent = wysihtml5.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
14784
+ query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'),
14785
+ }, null, composer.element),
14786
+ range;
14787
+
14788
+ if (parent) {
14789
+ range = composer.selection.createRange();
14790
+ range.selectNode(parent);
14791
+ composer.selection.setSelection(range);
14792
+ } else if (!composer.isEmpty()) {
14793
+ composer.selection.selectLine();
14794
+ }
14795
+ }
14796
+
14797
+ // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element
14798
+ // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
14799
+ function selectElements(newBlockElements, composer) {
14800
+ var range = composer.selection.createRange(),
14801
+ lastEl = newBlockElements[newBlockElements.length - 1],
14802
+ lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
14803
+
14804
+ range.setStart(newBlockElements[0], 0);
14805
+ range.setEnd(lastEl, lastOffset);
14806
+ range.select();
14807
+ }
14808
+
14809
+ // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each
14810
+ // Return created/modified block level elements
14811
+ // Method can be either "apply" or "remove"
14812
+ function formatSelection(method, composer, options) {
14813
+ var ranges = composer.selection.getOwnRanges(),
14814
+ newBlockElements = [],
14815
+ closestBlockName;
14816
+
14817
+ // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th)
14818
+ ranges = fixNotPermittedInsertionPoints(ranges);
14819
+
14820
+ for (var i = ranges.length; i--;) {
14821
+ fixRangeCoverage(ranges[i], composer);
14822
+ closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer);
14823
+ if (method === "remove") {
14824
+ newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer));
14825
+ } else {
14826
+ newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer));
14827
+ }
14828
+ }
14829
+ return newBlockElements;
14830
+ }
14831
+
14832
+ // If properties is passed as a string, look for tag with that tagName/query
14833
+ function parseOptions(options) {
14834
+ if (typeof options === "string") {
14835
+ options = {
14836
+ nodeName: options.toUpperCase()
14837
+ };
14838
+ }
14839
+ return options;
14840
+ }
14841
+
14842
+ function caretIsOnEmptyLine(composer) {
14843
+ var caretInfo;
14844
+ if (composer.selection.isCollapsed()) {
14845
+ caretInfo = composer.selection.getNodesNearCaret();
14846
+ if (caretInfo && caretInfo.caretNode) {
14847
+ if (
14848
+ // caret is allready breaknode
14849
+ wysihtml5.dom.domNode(caretInfo.caretNode).is.lineBreak() ||
14850
+ // caret is textnode
14851
+ (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml5.dom.domNode(caretInfo.prevNode).is.lineBreak())) ||
14852
+ // Caret is temprorary rangy selection marker
14853
+ (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') &&
14854
+ (!caretInfo.prevNode || wysihtml5.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml5.dom.domNode(caretInfo.prevNode).is.block()) &&
14855
+ (!caretInfo.nextNode || wysihtml5.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml5.dom.domNode(caretInfo.nextNode).is.block())
14856
+ )
14857
+ ) {
14858
+ return true;
14859
+ }
14860
+ }
14861
+ }
14862
+ return false;
14863
+ }
14296
14864
 
14297
14865
  wysihtml5.commands.formatBlock = {
14298
14866
  exec: function(composer, command, options) {
14867
+ options = parseOptions(options);
14299
14868
  var newBlockElements = [],
14300
- placeholder, ranges, range, parent, bookmark, state;
14301
-
14302
- // If properties is passed as a string, look for tag with that tagName/query
14303
- if (typeof options === "string") {
14304
- options = {
14305
- nodeName: options.toUpperCase()
14306
- };
14307
- }
14869
+ ranges, range, bookmark, state, closestBlockName;
14308
14870
 
14309
- // Remove state if toggle set and state on and selection is collapsed
14871
+ // Find if current format state is active if options.toggle is set as true
14872
+ // In toggle case active state elemets are formatted instead of working directly on selection
14310
14873
  if (options && options.toggle) {
14311
14874
  state = this.state(composer, command, options);
14312
- if (state) {
14313
- bookmark = rangy.saveSelection(composer.win);
14314
- for (var j = 0, jmax = state.length; j < jmax; j++) {
14315
- removeOptionsFromElement(state[j], options, composer);
14316
- }
14317
- }
14318
14875
  }
14876
+ if (state) {
14877
+ // Remove format from state nodes if toggle set and state on and selection is collapsed
14878
+ bookmark = rangy.saveSelection(composer.win);
14879
+ for (var j = 0, jmax = state.length; j < jmax; j++) {
14880
+ removeOptionsFromElement(state[j], options, composer);
14881
+ }
14319
14882
 
14320
- // Otherwise expand selection so it will cover closest block if option caretSelectsBlock is true and selection is collapsed
14321
- if (!state) {
14322
-
14883
+ } else {
14884
+ // If selection is caret expand it to cover nearest suitable block element or row if none found
14323
14885
  if (composer.selection.isCollapsed()) {
14324
- parent = wysihtml5.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
14325
- query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (options && options.nodeName ? options.nodeName.toLowerCase() : 'div'),
14326
- }, null, composer.element);
14327
- if (parent) {
14328
- bookmark = rangy.saveSelection(composer.win);
14329
- range = composer.selection.createRange();
14330
- range.selectNode(parent);
14331
- composer.selection.setSelection(range);
14332
- } else if (!composer.isEmpty()) {
14333
- bookmark = rangy.saveSelection(composer.win);
14886
+ bookmark = rangy.saveSelection(composer.win);
14887
+ if (caretIsOnEmptyLine(composer)) {
14334
14888
  composer.selection.selectLine();
14889
+ } else {
14890
+ expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
14335
14891
  }
14336
14892
  }
14337
-
14338
- // And get all selection ranges of current composer and iterate
14339
- ranges = composer.selection.getOwnRanges();
14340
- for (var i = ranges.length; i--;) {
14341
- newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, getParentBlockNodeName(ranges[i].startContainer, composer), composer));
14893
+ if (options) {
14894
+ newBlockElements = formatSelection("apply", composer, options);
14895
+ } else {
14896
+ // Options == null means block formatting should be removed from selection
14897
+ newBlockElements = formatSelection("remove", composer);
14342
14898
  }
14343
-
14899
+
14344
14900
  }
14345
14901
 
14346
14902
  // Remove empty block elements that may be left behind
14347
- cleanup(composer);
14348
- // If cleanup removed some new block elements. remove them from array too
14349
- for (var e = newBlockElements.length; e--;) {
14350
- if (!newBlockElements[e].parentNode) {
14351
- newBlockElements.splice(e, 1);
14352
- }
14903
+ // Also remove them from new blocks list
14904
+ newBlockElements = cleanup(composer, newBlockElements);
14905
+
14906
+ // Restore selection
14907
+ if (bookmark) {
14908
+ rangy.restoreSelection(bookmark);
14909
+ } else {
14910
+ selectElements(newBlockElements, composer);
14353
14911
  }
14354
-
14355
- // Restore correct selection
14912
+ },
14913
+
14914
+ // Removes all block formatting from selection
14915
+ remove: function(composer, command, options) {
14916
+ options = parseOptions(options);
14917
+ var newBlockElements, bookmark;
14918
+
14919
+ // If selection is caret expand it to cover nearest suitable block element or row if none found
14920
+ if (composer.selection.isCollapsed()) {
14921
+ bookmark = rangy.saveSelection(composer.win);
14922
+ expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
14923
+ }
14924
+
14925
+ newBlockElements = formatSelection("remove", composer);
14926
+ newBlockElements = cleanup(composer, newBlockElements);
14927
+
14928
+ // Restore selection
14356
14929
  if (bookmark) {
14357
- wysihtml5.dom.removeInvisibleSpaces(composer.element);
14358
14930
  rangy.restoreSelection(bookmark);
14359
14931
  } else {
14360
- wysihtml5.dom.removeInvisibleSpaces(composer.element);
14361
- // Set selection to beging inside first created block element (beginning of it) and end inside (and after content) of last block element
14362
- // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
14363
- range = composer.selection.createRange();
14364
- range.setStart(newBlockElements[0], 0);
14365
- var lastEl = newBlockElements[newBlockElements.length - 1],
14366
- lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
14367
- range.setEnd(lastEl, lastOffset);
14368
- range.select();
14932
+ selectElements(newBlockElements, composer);
14369
14933
  }
14370
14934
  },
14371
14935
 
14372
- // If properties as null is passed returns status describing all block level elements
14373
- state: function(composer, command, properties) {
14374
-
14375
- // If properties is passed as a string, look for tag with that tagName/query
14376
- if (typeof properties === "string") {
14377
- properties = {
14378
- query: properties
14379
- };
14380
- }
14936
+ // If options as null is passed returns status describing all block level elements
14937
+ state: function(composer, command, options) {
14938
+ options = parseOptions(options);
14381
14939
 
14382
14940
  var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
14383
- return wysihtml5.dom.domNode(element).test(properties || { query: BLOCK_ELEMENTS });
14941
+ return wysihtml5.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS });
14384
14942
  }).bind(this)),
14385
14943
  parentNodes = composer.selection.getSelectedOwnNodes(),
14386
14944
  parent;
14387
14945
 
14388
14946
  // Finds matching elements that are parents of selection and adds to nodes list
14389
14947
  for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
14390
- parent = dom.getParentElement(parentNodes[i], properties || { query: BLOCK_ELEMENTS }, null, composer.element);
14948
+ parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element);
14391
14949
  if (parent && nodes.indexOf(parent) === -1) {
14392
14950
  nodes.push(parent);
14393
14951
  }
@@ -14559,7 +15117,7 @@ wysihtml5.Commands = Base.extend(
14559
15117
  }
14560
15118
 
14561
15119
  // If attrbutes and values are the same > remove
14562
- // if attributes or values
15120
+ // if attributes or values
14563
15121
  function updateElementAttributes(element, newAttributes, toggle) {
14564
15122
  var attr = wysihtml5.dom.getAttributes(element),
14565
15123
  fullContain = containsSameAttributes(newAttributes, attr),
@@ -14597,6 +15155,9 @@ wysihtml5.Commands = Base.extend(
14597
15155
  if (options.toggle !== false && element.classList.contains(options.className)) {
14598
15156
  element.classList.remove(options.className);
14599
15157
  } else {
15158
+ if (options.classRegExp) {
15159
+ element.className = element.className.replace(options.classRegExp, '');
15160
+ }
14600
15161
  element.classList.add(options.className);
14601
15162
  }
14602
15163
  if (hasNoClass(element)) {
@@ -14630,7 +15191,7 @@ wysihtml5.Commands = Base.extend(
14630
15191
  // Handle similar semantically same elements (queryAliasMap)
14631
15192
  nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null;
14632
15193
  nodeQueryMatch = nodeNameQuery ? wysihtml5.dom.domNode(element).test({ query: nodeNameQuery }) : false;
14633
-
15194
+
14634
15195
  // Unwrap element if no attributes present and node name given
14635
15196
  // or no attributes and if no nodename set but node is the default
14636
15197
  if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) {
@@ -14703,7 +15264,7 @@ wysihtml5.Commands = Base.extend(
14703
15264
  selection = rangy.getSelection(composer.win);
14704
15265
 
14705
15266
  rangy.getSelection(composer.win).removeAllRanges();
14706
-
15267
+
14707
15268
  // IE looses focus of contenteditable on removeallranges and can not set new selection unless contenteditable is focused again
14708
15269
  try {
14709
15270
  rangy.getSelection(composer.win).addRange(range);
@@ -14725,7 +15286,7 @@ wysihtml5.Commands = Base.extend(
14725
15286
  range.setEnd(lastText, lastText.length);
14726
15287
  selectRange(composer, range);
14727
15288
  }
14728
-
15289
+
14729
15290
  }
14730
15291
 
14731
15292
  function selectTextNode(composer, node, start, end) {
@@ -14777,7 +15338,7 @@ wysihtml5.Commands = Base.extend(
14777
15338
  }
14778
15339
 
14779
15340
  }
14780
-
15341
+
14781
15342
  return {
14782
15343
  nodes: nodes,
14783
15344
  partial: partial
@@ -14801,7 +15362,7 @@ wysihtml5.Commands = Base.extend(
14801
15362
  }
14802
15363
 
14803
15364
  // Returns a range and textnode containing object from caret position covering a whole word
14804
- // wordOffsety describes the original position of caret in the new textNode
15365
+ // wordOffsety describes the original position of caret in the new textNode
14805
15366
  // Caret has to be inside a textNode.
14806
15367
  function getRangeForWord(selection) {
14807
15368
  var anchor, offset, doc, range, offsetStart, offsetEnd, beforeChar, afterChar,
@@ -14849,7 +15410,7 @@ wysihtml5.Commands = Base.extend(
14849
15410
 
14850
15411
  function mergeConsequentSimilarElements(elements) {
14851
15412
  for (var i = elements.length; i--;) {
14852
-
15413
+
14853
15414
  if (elements[i] && elements[i].parentNode) { // Test if node is not allready removed in cleanup
14854
15415
 
14855
15416
  if (elements[i].nextSibling && isSameNode(elements[i], elements[i].nextSibling)) {
@@ -14933,7 +15494,7 @@ wysihtml5.Commands = Base.extend(
14933
15494
  if (options.toggle !== false) {
14934
15495
  if (caretIsInsideWord(selection)) {
14935
15496
 
14936
- // Unformat whole word
15497
+ // Unformat whole word
14937
15498
  wordObj = getRangeForWord(selection);
14938
15499
  textNode = wordObj.textNode;
14939
15500
  unformatTextNode(wordObj.textNode, composer, options);
@@ -14975,13 +15536,13 @@ wysihtml5.Commands = Base.extend(
14975
15536
  }
14976
15537
 
14977
15538
  } else {
14978
-
15539
+
14979
15540
  // Selection is partially in format
14980
15541
  // change it to new if format if textnode allreafy in similar state
14981
15542
  // else just apply
14982
-
15543
+
14983
15544
  for (i = textNodes.length; i--;) {
14984
-
15545
+
14985
15546
  if (findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) {
14986
15547
  unformatTextNode(textNodes[i], composer, options);
14987
15548
  }
@@ -15002,7 +15563,7 @@ wysihtml5.Commands = Base.extend(
15002
15563
  var textNode, textOffset, newNode, i,
15003
15564
  selection = composer.selection.getSelection();
15004
15565
 
15005
- if (!textNodes.length) {
15566
+ if (!textNodes.length) {
15006
15567
  textNode = selection.anchorNode;
15007
15568
  textOffset = selection.anchorOffset;
15008
15569
 
@@ -15023,7 +15584,7 @@ wysihtml5.Commands = Base.extend(
15023
15584
  function applyFormat(composer, textNodes, options) {
15024
15585
  var wordObj, i,
15025
15586
  selection = composer.selection.getSelection();
15026
-
15587
+
15027
15588
  if (!textNodes.length) {
15028
15589
  // Handle collapsed selection caret and return
15029
15590
  if (caretIsInsideWord(selection)) {
@@ -15038,7 +15599,7 @@ wysihtml5.Commands = Base.extend(
15038
15599
  formatTextRange(r, composer, options);
15039
15600
  }
15040
15601
  }
15041
-
15602
+
15042
15603
  } else {
15043
15604
  // Handle textnodes in selection and apply format
15044
15605
  for (i = textNodes.length; i--;) {
@@ -15047,7 +15608,7 @@ wysihtml5.Commands = Base.extend(
15047
15608
  cleanupAndSetSelection(composer, textNodes, options);
15048
15609
  }
15049
15610
  }
15050
-
15611
+
15051
15612
  // If properties is passed as a string, correct options with that nodeName
15052
15613
  function fixOptions(options) {
15053
15614
  options = (typeof options === "string") ? { nodeName: options } : options;
@@ -15090,7 +15651,7 @@ wysihtml5.Commands = Base.extend(
15090
15651
  // Text allready has the format applied
15091
15652
  removeFormat(composer, textNodes, state, options);
15092
15653
  }
15093
-
15654
+
15094
15655
  composer.element.normalize();
15095
15656
  },
15096
15657
 
@@ -15347,7 +15908,9 @@ wysihtml5.Commands = Base.extend(
15347
15908
  for (var i = innerLists.length; i--;) {
15348
15909
  wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
15349
15910
  }
15350
- wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
15911
+ if (innerLists.length === 0) {
15912
+ wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
15913
+ }
15351
15914
  }
15352
15915
  });
15353
15916
  };
@@ -15383,20 +15946,19 @@ wysihtml5.Commands = Base.extend(
15383
15946
  };
15384
15947
 
15385
15948
  var createListFallback = function(nodeName, composer) {
15386
- var sel;
15387
-
15388
- if (!composer.selection.isCollapsed()) {
15389
- sel = rangy.saveSelection(composer.win);
15390
- }
15949
+ var sel = rangy.saveSelection(composer.win);
15391
15950
 
15392
15951
  // Fallback for Create list
15393
15952
  var tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
15394
- tempElement = composer.selection.deblockAndSurround({
15395
- "nodeName": "div",
15396
- "className": tempClassName
15397
- }),
15398
15953
  isEmpty, list;
15399
15954
 
15955
+ composer.commands.exec("formatBlock", {
15956
+ "nodeName": "div",
15957
+ "className": tempClassName
15958
+ });
15959
+
15960
+ var tempElement = composer.element.querySelector("." + tempClassName);
15961
+
15400
15962
  // This space causes new lists to never break on enter
15401
15963
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
15402
15964
  tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
@@ -15416,8 +15978,34 @@ wysihtml5.Commands = Base.extend(
15416
15978
  exec: function(composer, command, nodeName) {
15417
15979
  var doc = composer.doc,
15418
15980
  cmd = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
15419
- selectedNode = composer.selection.getSelectedNode(),
15420
- list = findListEl(selectedNode, nodeName, composer);
15981
+ s = composer.selection.getSelection(),
15982
+ anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
15983
+ fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode,
15984
+ selectedNode, list;
15985
+
15986
+ if (s.isBackwards()) {
15987
+ // swap variables
15988
+ anode = [fnode, fnode = anode][0];
15989
+ }
15990
+
15991
+ if (wysihtml5.dom.domNode(fnode).is.emptyTextNode(true) && fnode) {
15992
+ fnode = wysihtml5.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
15993
+ }
15994
+ if (wysihtml5.dom.domNode(anode).is.emptyTextNode(true) && anode) {
15995
+ anode = wysihtml5.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true});
15996
+ }
15997
+
15998
+ if (anode && fnode) {
15999
+ if (anode === fnode) {
16000
+ selectedNode = anode;
16001
+ } else {
16002
+ selectedNode = wysihtml5.dom.domNode(anode).commonAncestor(fnode, composer.element);
16003
+ }
16004
+ } else {
16005
+ selectedNode = composer.selection.getSelectedNode();
16006
+ }
16007
+
16008
+ list = findListEl(selectedNode, nodeName, composer);
15421
16009
 
15422
16010
  if (!list.el) {
15423
16011
  if (composer.commands.support(cmd)) {
@@ -15442,7 +16030,7 @@ wysihtml5.Commands = Base.extend(
15442
16030
 
15443
16031
  })(wysihtml5);
15444
16032
  ;(function(wysihtml5){
15445
-
16033
+
15446
16034
  var nodeOptions = {
15447
16035
  nodeName: "I",
15448
16036
  toggle: true
@@ -15476,7 +16064,7 @@ wysihtml5.Commands = Base.extend(
15476
16064
  return wysihtml5.commands.formatBlock.state(composer, "formatBlock", nodeOptions);
15477
16065
  }
15478
16066
  };
15479
-
16067
+
15480
16068
  })(wysihtml5);
15481
16069
  ;(function(wysihtml5) {
15482
16070
 
@@ -15533,7 +16121,7 @@ wysihtml5.Commands = Base.extend(
15533
16121
  };
15534
16122
  })(wysihtml5);
15535
16123
  ;(function(wysihtml5) {
15536
-
16124
+
15537
16125
  var nodeOptions = {
15538
16126
  styleProperty: "textAlign",
15539
16127
  styleValue: "right",
@@ -15897,7 +16485,7 @@ wysihtml5.Commands = Base.extend(
15897
16485
  };
15898
16486
  }(wysihtml5));
15899
16487
  ;(function(wysihtml5){
15900
-
16488
+
15901
16489
  var nodeOptions = {
15902
16490
  nodeName: "SUB",
15903
16491
  toggle: true
@@ -16247,7 +16835,7 @@ wysihtml5.views.View = Base.extend(
16247
16835
 
16248
16836
  cleanUp: function(rules) {
16249
16837
  var bookmark;
16250
- if (this.selection) {
16838
+ if (this.selection && this.selection.isInThisEditable()) {
16251
16839
  bookmark = rangy.saveSelection(this.win);
16252
16840
  }
16253
16841
  this.parent.parse(this.element, undefined, rules);
@@ -16419,6 +17007,8 @@ wysihtml5.views.View = Base.extend(
16419
17007
  ]).from(this.textarea.element).to(this.element);
16420
17008
  }
16421
17009
 
17010
+ this._initAutoLinking();
17011
+
16422
17012
  dom.addClass(this.element, this.config.classNames.composer);
16423
17013
  //
16424
17014
  // Make the editor look like the original textarea, by syncing styles
@@ -16451,7 +17041,6 @@ wysihtml5.views.View = Base.extend(
16451
17041
  // Make sure that the browser avoids using inline styles whenever possible
16452
17042
  this.commands.exec("styleWithCSS", false);
16453
17043
 
16454
- this._initAutoLinking();
16455
17044
  this._initObjectResizing();
16456
17045
  this._initUndoManager();
16457
17046
  this._initLineBreaking();
@@ -16485,10 +17074,7 @@ wysihtml5.views.View = Base.extend(
16485
17074
  supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
16486
17075
 
16487
17076
  if (supportsDisablingOfAutoLinking) {
16488
- // I have no idea why IE edge deletes element content here when calling the command,
16489
- var tmpHTML = this.element.innerHTML;
16490
17077
  this.commands.exec("AutoUrlDetect", false, false);
16491
- this.element.innerHTML = tmpHTML;
16492
17078
  }
16493
17079
 
16494
17080
  if (!this.config.autoLink) {
@@ -16612,8 +17198,11 @@ wysihtml5.views.View = Base.extend(
16612
17198
  function adjust(selectedNode) {
16613
17199
  var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2);
16614
17200
  if (parentElement && dom.contains(that.element, parentElement)) {
16615
- that.selection.executeAndRestore(function() {
17201
+ that.selection.executeAndRestoreRangy(function() {
16616
17202
  if (that.config.useLineBreaks) {
17203
+ if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) {
17204
+ parentElement.appendChild(that.doc.createElement('br'));
17205
+ }
16617
17206
  dom.replaceWithChildNodes(parentElement);
16618
17207
  } else if (parentElement.nodeName !== "P") {
16619
17208
  dom.renameElement(parentElement, "p");
@@ -16622,59 +17211,63 @@ wysihtml5.views.View = Base.extend(
16622
17211
  }
16623
17212
  }
16624
17213
 
17214
+ // Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph
16625
17215
  if (!this.config.useLineBreaks) {
16626
- dom.observe(this.element, ["focus", "keydown"], function() {
17216
+ dom.observe(this.element, ["focus"], function() {
16627
17217
  if (that.isEmpty()) {
16628
- var paragraph = that.doc.createElement("P");
16629
- that.element.innerHTML = "";
16630
- that.element.appendChild(paragraph);
16631
- if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
16632
- paragraph.innerHTML = "<br>";
16633
- that.selection.setBefore(paragraph.firstChild);
16634
- } else {
16635
- that.selection.selectNode(paragraph, true);
16636
- }
17218
+ setTimeout(function() {
17219
+ var paragraph = that.doc.createElement("P");
17220
+ that.element.innerHTML = "";
17221
+ that.element.appendChild(paragraph);
17222
+ if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
17223
+ paragraph.innerHTML = "<br>";
17224
+ that.selection.setBefore(paragraph.firstChild);
17225
+ } else {
17226
+ that.selection.selectNode(paragraph, true);
17227
+ }
17228
+ }, 0);
16637
17229
  }
16638
17230
  });
16639
17231
  }
17232
+
16640
17233
  dom.observe(this.element, "keydown", function(event) {
16641
17234
  var keyCode = event.keyCode;
16642
17235
 
16643
- if (event.ctrlKey || event.defaultPrevented) {
17236
+ if (event.shiftKey || event.ctrlKey || event.defaultPrevented) {
16644
17237
  return;
16645
17238
  }
16646
17239
 
16647
17240
  if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
16648
17241
  return;
16649
17242
  }
16650
- if (keyCode === wysihtml5.ENTER_KEY && (that.config.useLineBreaks || event.shiftKey) && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
16651
- event.preventDefault();
16652
- that.commands.exec("insertLineBreak");
16653
- } else {
16654
- var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
16655
- if (blockElement) {
16656
- setTimeout(function() {
16657
- // Unwrap paragraph after leaving a list or a H1-6
16658
- var selectedNode = that.selection.getSelectedNode(),
16659
- list;
16660
-
16661
- if (blockElement.nodeName === "LI") {
16662
- if (!selectedNode) {
16663
- return;
16664
- }
16665
-
16666
- list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2);
17243
+ var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
17244
+ if (blockElement) {
17245
+ setTimeout(function() {
17246
+ // Unwrap paragraph after leaving a list or a H1-6
17247
+ var selectedNode = that.selection.getSelectedNode(),
17248
+ list;
16667
17249
 
16668
- if (!list) {
16669
- adjust(selectedNode);
16670
- }
17250
+ if (blockElement.nodeName === "LI") {
17251
+ if (!selectedNode) {
17252
+ return;
16671
17253
  }
16672
17254
 
16673
- if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
17255
+ list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2);
17256
+
17257
+ if (!list) {
16674
17258
  adjust(selectedNode);
16675
17259
  }
16676
- }, 0);
16677
- }
17260
+ }
17261
+
17262
+ if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
17263
+ adjust(selectedNode);
17264
+ }
17265
+ }, 0);
17266
+ return;
17267
+ }
17268
+ if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
17269
+ event.preventDefault();
17270
+ that.commands.exec("insertLineBreak");
16678
17271
  }
16679
17272
  });
16680
17273
  }
@@ -16891,6 +17484,7 @@ wysihtml5.views.View = Base.extend(
16891
17484
  */
16892
17485
  (function(wysihtml5) {
16893
17486
  var dom = wysihtml5.dom,
17487
+ domNode = dom.domNode,
16894
17488
  browser = wysihtml5.browser,
16895
17489
  /**
16896
17490
  * Map keyCodes to query commands
@@ -16900,95 +17494,253 @@ wysihtml5.views.View = Base.extend(
16900
17494
  "73": "italic", // I
16901
17495
  "85": "underline" // U
16902
17496
  };
17497
+
17498
+ var actions = {
16903
17499
 
16904
- // Adds multiple eventlisteners to target, bound to one callback
16905
- // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
16906
- var addListeners = function (target, events, callback) {
16907
- for(var i = 0, max = events.length; i < max; i++) {
16908
- target.addEventListener(events[i], callback, false);
16909
- }
16910
- };
17500
+ // Adds multiple eventlisteners to target, bound to one callback
17501
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17502
+ addListeners: function (target, events, callback) {
17503
+ for(var i = 0, max = events.length; i < max; i++) {
17504
+ target.addEventListener(events[i], callback, false);
17505
+ }
17506
+ },
16911
17507
 
16912
- // Removes multiple eventlisteners from target, bound to one callback
16913
- // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
16914
- var removeListeners = function (target, events, callback) {
16915
- for(var i = 0, max = events.length; i < max; i++) {
16916
- target.removeEventListener(events[i], callback, false);
16917
- }
16918
- };
17508
+ // Removes multiple eventlisteners from target, bound to one callback
17509
+ // TODO: If needed elsewhere make it part of wysihtml5.dom or sth
17510
+ removeListeners: function (target, events, callback) {
17511
+ for(var i = 0, max = events.length; i < max; i++) {
17512
+ target.removeEventListener(events[i], callback, false);
17513
+ }
17514
+ },
16919
17515
 
16920
- // Override for giving user ability to delete last line break in table cell
16921
- var fixLastBrDeletionInTable = function(composer, force) {
16922
- if (composer.selection.caretIsLastInSelection()) {
16923
- var sel = composer.selection.getSelection(),
16924
- aNode = sel.anchorNode;
16925
- if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
16926
- var nextNode = aNode.childNodes[sel.anchorOffset];
16927
- if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
16928
- nextNode.parentNode.removeChild(nextNode);
16929
- return true;
17516
+ // Override for giving user ability to delete last line break in table cell
17517
+ fixLastBrDeletionInTable: function(composer, force) {
17518
+ if (composer.selection.caretIsLastInSelection()) {
17519
+ var sel = composer.selection.getSelection(),
17520
+ aNode = sel.anchorNode;
17521
+ if (aNode && aNode.nodeType === 1 && (wysihtml5.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
17522
+ var nextNode = aNode.childNodes[sel.anchorOffset];
17523
+ if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
17524
+ nextNode.parentNode.removeChild(nextNode);
17525
+ return true;
17526
+ }
16930
17527
  }
16931
17528
  }
16932
- }
16933
- return false;
16934
- };
17529
+ return false;
17530
+ },
16935
17531
 
16936
- // If found an uneditable before caret then notify it before deletion
16937
- var handleUneditableDeletion = function(composer) {
16938
- var before = composer.selection.getBeforeSelection(true);
16939
- if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
16940
- if (fixLastBrDeletionInTable(composer, true)) {
17532
+ // If found an uneditable before caret then notify it before deletion
17533
+ handleUneditableDeletion: function(composer) {
17534
+ var before = composer.selection.getBeforeSelection(true);
17535
+ if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
17536
+ if (actions.fixLastBrDeletionInTable(composer, true)) {
17537
+ return true;
17538
+ }
17539
+ try {
17540
+ var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
17541
+ before.node.dispatchEvent(ev);
17542
+ } catch (err) {}
17543
+ before.node.parentNode.removeChild(before.node);
16941
17544
  return true;
16942
17545
  }
16943
- try {
16944
- var ev = new CustomEvent("wysihtml5:uneditable:delete");
16945
- before.node.dispatchEvent(ev);
16946
- } catch (err) {}
16947
- before.node.parentNode.removeChild(before.node);
16948
- return true;
16949
- }
16950
- return false;
16951
- };
17546
+ return false;
17547
+ },
16952
17548
 
16953
- // Deletion with caret in the beginning of headings needs special attention
16954
- // Heading does not concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
16955
- var fixDeleteInTheBeginnigOfHeading = function(composer) {
16956
- var selection = composer.selection,
16957
- prevNode = selection.getPreviousNode();
17549
+ // Deletion with caret in the beginning of headings and other block elvel elements needs special attention
17550
+ // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
17551
+ fixDeleteInTheBeginningOfBlock: function(composer) {
17552
+ var selection = composer.selection,
17553
+ prevNode = selection.getPreviousNode();
16958
17554
 
16959
- if (selection.caretIsFirstInSelection() &&
16960
- prevNode &&
16961
- prevNode.nodeType === 1 &&
16962
- (/block/).test(composer.win.getComputedStyle(prevNode).display)
16963
- ) {
16964
- if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
16965
- // If heading is empty remove the heading node
16966
- prevNode.parentNode.removeChild(prevNode);
17555
+ if (selection.caretIsFirstInSelection(wysihtml5.browser.usesControlRanges()) && prevNode) {
17556
+ if (prevNode.nodeType === 1 &&
17557
+ wysihtml5.dom.domNode(prevNode).is.block() &&
17558
+ !domNode(prevNode).test({
17559
+ query: "ol, ul, table, tr, dl"
17560
+ })
17561
+ ) {
17562
+ if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
17563
+ // If heading is empty remove the heading node
17564
+ prevNode.parentNode.removeChild(prevNode);
17565
+ return true;
17566
+ } else {
17567
+ if (prevNode.lastChild) {
17568
+ var selNode = prevNode.lastChild,
17569
+ selectedNode = selection.getSelectedNode(),
17570
+ commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element),
17571
+ curNode = wysihtml5.dom.getParentElement(selectedNode, {
17572
+ query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
17573
+ }, false, commonAncestorNode || composer.element);
17574
+
17575
+ if (curNode) {
17576
+ domNode(curNode).transferContentTo(prevNode, true);
17577
+ selection.setAfter(selNode);
17578
+ return true;
17579
+ } else if (wysihtml5.browser.usesControlRanges()) {
17580
+ selectedNode = selection.getCaretNode();
17581
+ domNode(selectedNode).transferContentTo(prevNode, true);
17582
+ selection.setAfter(selNode);
17583
+ return true;
17584
+ }
17585
+ }
17586
+ }
17587
+ }
17588
+ }
17589
+ return false;
17590
+ },
17591
+
17592
+ /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
17593
+ /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
17594
+ fixDeleteInTheBeginningOfLi: function(composer) {
17595
+ if (wysihtml5.browser.hasLiDeletingProblem()) {
17596
+ var selection = composer.selection.getSelection(),
17597
+ aNode = selection.anchorNode,
17598
+ listNode, prevNode, firstNode,
17599
+ isInBeginnig = composer.selection.caretIsFirstInSelection();
17600
+
17601
+ // Fix caret at the beginnig of first textNode in LI
17602
+ if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
17603
+ aNode = aNode.parentNode;
17604
+ isInBeginnig = true;
17605
+ }
17606
+
17607
+ if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
17608
+ prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17609
+ if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
17610
+ prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
17611
+ }
17612
+ if (prevNode) {
17613
+ firstNode = aNode.firstChild;
17614
+ domNode(aNode).transferContentTo(prevNode, true);
17615
+ if (firstNode) {
17616
+ composer.selection.setBefore(firstNode);
17617
+ } else if (prevNode) {
17618
+ if (prevNode.nodeType === 1) {
17619
+ if (prevNode.lastChild) {
17620
+ composer.selection.setAfter(prevNode.lastChild);
17621
+ } else {
17622
+ composer.selection.selectNode(prevNode);
17623
+ }
17624
+ } else {
17625
+ composer.selection.setAfter(prevNode);
17626
+ }
17627
+ }
17628
+ return true;
17629
+ }
17630
+ }
17631
+ }
17632
+ return false;
17633
+ },
17634
+
17635
+ fixDeleteInTheBeginningOfControlSelection: function(composer) {
17636
+ var selection = composer.selection,
17637
+ prevNode = selection.getPreviousNode(),
17638
+ selectedNode = selection.getSelectedNode(),
17639
+ afterCaretNode;
17640
+
17641
+ if (selection.caretIsFirstInSelection()) {
17642
+ if (selectedNode.nodeType === 3) {
17643
+ selectedNode = selectedNode.parentNode;
17644
+ }
17645
+ afterCaretNode = selectedNode.firstChild;
17646
+ domNode(selectedNode).transferContentTo(prevNode, true);
17647
+ if (afterCaretNode) {
17648
+ composer.selection.setBefore(afterCaretNode);
17649
+ }
16967
17650
  return true;
16968
- } else {
16969
- if (prevNode.lastChild) {
16970
- var selNode = prevNode.lastChild,
16971
- selectedNode = selection.getSelectedNode(),
16972
- commonAncestorNode = wysihtml5.dom.domNode(prevNode).commonAncestor(selectedNode, composer.element);
16973
- curNode = commonAncestorNode ? wysihtml5.dom.getParentElement(selectedNode, {
16974
- query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
16975
- }, false, commonAncestorNode) : null;
16976
-
16977
- if (curNode) {
16978
- while (curNode.firstChild) {
16979
- prevNode.appendChild(curNode.firstChild);
17651
+ }
17652
+ return false;
17653
+ },
17654
+
17655
+ // Table management
17656
+ // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
17657
+ initTableHandling: function() {
17658
+ var hideHandlers = function() {
17659
+ window.removeEventListener('load', hideHandlers);
17660
+ this.doc.execCommand("enableObjectResizing", false, "false");
17661
+ this.doc.execCommand("enableInlineTableEditing", false, "false");
17662
+ }.bind(this),
17663
+ iframeInitiator = (function() {
17664
+ hideHandlers.call(this);
17665
+ actions.removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17666
+ }).bind(this);
17667
+
17668
+ if( this.doc.execCommand &&
17669
+ wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
17670
+ wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
17671
+ {
17672
+ if (this.sandbox.getIframe) {
17673
+ actions.addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17674
+ } else {
17675
+ window.addEventListener('load', hideHandlers);
17676
+ }
17677
+ }
17678
+ this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
17679
+ },
17680
+
17681
+ // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
17682
+ // Returns true if some corrections is applied so events know when to prevent default
17683
+ doLineBreaksModeEnterWithCaret: function(composer) {
17684
+ var breakNodes = "p, pre, div, blockquote",
17685
+ caretInfo, parent, txtNode,
17686
+ ret = false;
17687
+
17688
+ caretInfo = composer.selection.getNodesNearCaret();
17689
+ if (caretInfo) {
17690
+
17691
+ if (caretInfo.caretNode || caretInfo.nextNode) {
17692
+ parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
17693
+ if (parent === composer.element) {
17694
+ parent = undefined;
17695
+ }
17696
+ }
17697
+
17698
+ if (parent && caretInfo.caretNode) {
17699
+ if (domNode(caretInfo.caretNode).is.lineBreak()) {
17700
+
17701
+ if (composer.config.doubleLineBreakEscapesBlock) {
17702
+ // Double enter (enter on blank line) exits block element in useLineBreaks mode.
17703
+ ret = true;
17704
+ caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
17705
+
17706
+ // Ensure surplous line breaks are not added to preceding element
17707
+ if (domNode(caretInfo.nextNode).is.lineBreak()) {
17708
+ caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
17709
+ }
17710
+
17711
+ var brNode = composer.doc.createElement('br');
17712
+ if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
17713
+ parent.parentNode.insertBefore(brNode, parent.nextSibling);
17714
+ } else {
17715
+ composer.selection.splitElementAtCaret(parent, brNode);
16980
17716
  }
16981
- selection.setAfter(selNode);
16982
- return true;
16983
- } else if (selectedNode.nodeType === 3) {
16984
- prevNode.appendChild(selectedNode);
16985
- selection.setAfter(selNode);
16986
- return true;
17717
+
17718
+ // Ensure surplous blank lines are not added to preceding element
17719
+ if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
17720
+ // Replaces blank lines at the beginning of textnode
17721
+ caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
17722
+ }
17723
+ composer.selection.setBefore(brNode);
16987
17724
  }
17725
+
17726
+ } else if (caretInfo.caretNode.nodeType === 3 && wysihtml5.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
17727
+
17728
+ // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
17729
+ // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
17730
+ ret = true;
17731
+ var br1 = composer.doc.createElement('br'),
17732
+ br2 = composer.doc.createElement('br'),
17733
+ f = composer.doc.createDocumentFragment();
17734
+ f.appendChild(br1);
17735
+ f.appendChild(br2);
17736
+ composer.selection.insertNode(f);
17737
+ composer.selection.setBefore(br2);
17738
+
17739
+ }
16988
17740
  }
16989
17741
  }
17742
+ return ret;
16990
17743
  }
16991
- return false;
16992
17744
  };
16993
17745
 
16994
17746
  var handleDeleteKeyPress = function(event, composer) {
@@ -16996,18 +17748,28 @@ wysihtml5.views.View = Base.extend(
16996
17748
  element = composer.element;
16997
17749
 
16998
17750
  if (selection.isCollapsed()) {
16999
- if (fixDeleteInTheBeginnigOfHeading(composer)) {
17751
+ if (actions.handleUneditableDeletion(composer)) {
17752
+ event.preventDefault();
17753
+ return;
17754
+ }
17755
+ if (actions.fixDeleteInTheBeginningOfLi(composer)) {
17000
17756
  event.preventDefault();
17001
17757
  return;
17002
17758
  }
17003
- if (fixLastBrDeletionInTable(composer)) {
17759
+ if (actions.fixDeleteInTheBeginningOfBlock(composer)) {
17004
17760
  event.preventDefault();
17005
17761
  return;
17006
17762
  }
17007
- if (handleUneditableDeletion(composer)) {
17763
+ if (actions.fixLastBrDeletionInTable(composer)) {
17008
17764
  event.preventDefault();
17009
17765
  return;
17010
17766
  }
17767
+ if (wysihtml5.browser.usesControlRanges()) {
17768
+ if (actions.fixDeleteInTheBeginningOfControlSelection(composer)) {
17769
+ event.preventDefault();
17770
+ return;
17771
+ }
17772
+ }
17011
17773
  } else {
17012
17774
  if (selection.containsUneditable()) {
17013
17775
  event.preventDefault();
@@ -17016,6 +17778,21 @@ wysihtml5.views.View = Base.extend(
17016
17778
  }
17017
17779
  };
17018
17780
 
17781
+ var handleEnterKeyPress = function(event, composer) {
17782
+ if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) {
17783
+ // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
17784
+
17785
+ var breakNodes = "p, pre, div, blockquote",
17786
+ caretInfo, parent, txtNode;
17787
+
17788
+ if (composer.selection.isCollapsed()) {
17789
+ if (actions.doLineBreaksModeEnterWithCaret(composer)) {
17790
+ event.preventDefault();
17791
+ }
17792
+ }
17793
+ }
17794
+ };
17795
+
17019
17796
  var handleTabKeyDown = function(composer, element, shiftKey) {
17020
17797
  if (!composer.selection.isCollapsed()) {
17021
17798
  composer.selection.deleteContents();
@@ -17109,25 +17886,29 @@ wysihtml5.views.View = Base.extend(
17109
17886
  this.selection.selectNode(target);
17110
17887
  }
17111
17888
  }
17112
- };
17113
-
17114
- // TODO: mouseover is not actually a foolproof and obvious place for this, must be changed as it modifies dom on random basis
17115
- // Shows url in tooltip when hovering links or images
17116
- var handleMouseOver = function(event) {
17117
- var titlePrefixes = {
17118
- IMG: "Image: ",
17119
- A: "Link: "
17120
- },
17121
- target = event.target,
17122
- nodeName = target.nodeName,
17123
- title;
17124
17889
 
17125
- if (nodeName !== "A" && nodeName !== "IMG") {
17126
- return;
17890
+ // Saves mousedown position for IE controlSelect fix
17891
+ if (wysihtml5.browser.usesControlRanges()) {
17892
+ this.selection.lastMouseDownPos = {x: event.clientX, y: event.clientY};
17893
+ setTimeout(function() {
17894
+ delete this.selection.lastMouseDownPos;
17895
+ }.bind(this), 0);
17127
17896
  }
17128
- if(!target.hasAttribute("title")){
17129
- title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
17130
- target.setAttribute("title", title);
17897
+ };
17898
+
17899
+ // IE has this madness of control selects of overflowed and some other elements (weird box around element on selection and second click selects text)
17900
+ // This fix handles the second click problem by adding cursor to the right position under cursor inside when controlSelection is made
17901
+ var handleIEControlSelect = function(event) {
17902
+ var target = event.target,
17903
+ pos = this.selection.lastMouseDownPos;
17904
+ if (pos) {
17905
+ var caretPosition = document.body.createTextRange();
17906
+ setTimeout(function() {
17907
+ try {
17908
+ caretPosition.moveToPoint(pos.x, pos.y);
17909
+ caretPosition.select();
17910
+ } catch (e) {}
17911
+ }.bind(this), 0);
17131
17912
  }
17132
17913
  };
17133
17914
 
@@ -17197,6 +17978,10 @@ wysihtml5.views.View = Base.extend(
17197
17978
  handleTabKeyDown(this, this.element, event.shiftKey);
17198
17979
  }
17199
17980
 
17981
+ if (keyCode === wysihtml5.ENTER_KEY) {
17982
+ handleEnterKeyPress(event, this);
17983
+ }
17984
+
17200
17985
  };
17201
17986
 
17202
17987
  var handleIframeFocus = function(event) {
@@ -17212,32 +17997,9 @@ wysihtml5.views.View = Base.extend(
17212
17997
  this.selection.getSelection().removeAllRanges();
17213
17998
  }).bind(this), 0);
17214
17999
  };
17215
-
17216
- // Table management
17217
- // If present enableObjectResizing and enableInlineTableEditing command should be called with false to prevent native table handlers
17218
- var initTableHandling = function () {
17219
- var hideHandlers = function () {
17220
- window.removeEventListener('load', hideHandlers);
17221
- this.doc.execCommand("enableObjectResizing", false, "false");
17222
- this.doc.execCommand("enableInlineTableEditing", false, "false");
17223
- }.bind(this),
17224
- iframeInitiator = (function() {
17225
- hideHandlers.call(this);
17226
- removeListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17227
- }).bind(this);
17228
-
17229
- if( this.doc.execCommand &&
17230
- wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") &&
17231
- wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing"))
17232
- {
17233
- if (this.sandbox.getIframe) {
17234
- addListeners(this.sandbox.getIframe(), ["focus", "mouseup", "mouseover"], iframeInitiator);
17235
- } else {
17236
- window.addEventListener('load', hideHandlers);
17237
- }
17238
- }
17239
- this.tableSelection = wysihtml5.quirks.tableCellsSelection(this.element, this.parent);
17240
- };
18000
+
18001
+ // Testing requires actions to be accessible from out of scope
18002
+ wysihtml5.views.Composer.prototype.observeActions = actions;
17241
18003
 
17242
18004
  wysihtml5.views.Composer.prototype.observe = function() {
17243
18005
  var that = this,
@@ -17248,7 +18010,19 @@ wysihtml5.views.View = Base.extend(
17248
18010
  this.focusState = this.getValue(false, false);
17249
18011
 
17250
18012
  // --------- destroy:composer event ---------
17251
- container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false);
18013
+
18014
+ // FIXME: (did) replace the deprecated DOMNodeRemoved event by a call to the MutationObserver
18015
+ const handleRemovedNodes = function(records) {
18016
+ for (const record of records) {
18017
+ if (record.removedNodes.length > 0) {
18018
+ if (record.removedNodes[0] === container) {
18019
+ handleDomNodeRemoved.call(this)
18020
+ }
18021
+ }
18022
+ }
18023
+ }
18024
+ const observer = new MutationObserver(handleRemovedNodes.bind(this));
18025
+ observer.observe(container.parentNode, { childList: true });
17252
18026
 
17253
18027
  // DOMNodeRemoved event is not supported in IE 8
17254
18028
  // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed
@@ -17263,22 +18037,26 @@ wysihtml5.views.View = Base.extend(
17263
18037
  // --------- User interactions --
17264
18038
  if (this.config.handleTables) {
17265
18039
  // If handleTables option is true, table handling functions are bound
17266
- initTableHandling.call(this);
18040
+ actions.initTableHandling.call(this);
17267
18041
  }
17268
18042
 
17269
- addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
18043
+ actions.addListeners(focusBlurElement, ["drop", "paste", "mouseup", "focus", "keyup"], handleUserInteraction.bind(this));
17270
18044
  focusBlurElement.addEventListener("focus", handleFocus.bind(this), false);
17271
18045
  focusBlurElement.addEventListener("blur", handleBlur.bind(this), false);
17272
-
17273
- addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
18046
+
18047
+ actions.addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
17274
18048
  this.element.addEventListener("copy", handleCopy.bind(this), false);
17275
18049
  this.element.addEventListener("mousedown", handleMouseDown.bind(this), false);
17276
- this.element.addEventListener("mouseover", handleMouseOver.bind(this), false);
17277
18050
  this.element.addEventListener("click", handleClick.bind(this), false);
17278
18051
  this.element.addEventListener("drop", handleDrop.bind(this), false);
17279
18052
  this.element.addEventListener("keyup", handleKeyUp.bind(this), false);
17280
18053
  this.element.addEventListener("keydown", handleKeyDown.bind(this), false);
17281
18054
 
18055
+ // IE controlselect madness fix
18056
+ if (wysihtml5.browser.usesControlRanges()) {
18057
+ this.element.addEventListener('mscontrolselect', handleIEControlSelect.bind(this), false);
18058
+ }
18059
+
17282
18060
  this.element.addEventListener("dragenter", (function() {
17283
18061
  this.parent.fire("unset_placeholder");
17284
18062
  }).bind(this), false);
@@ -17569,6 +18347,9 @@ wysihtml5.views.View = Base.extend(
17569
18347
  parser: wysihtml5.dom.parse,
17570
18348
  // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
17571
18349
  useLineBreaks: true,
18350
+ // Double enter (enter on blank line) exits block element in useLineBreaks mode.
18351
+ // It enables a way of escaping out of block elements and splitting block elements
18352
+ doubleLineBreakEscapesBlock: true,
17572
18353
  // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
17573
18354
  stylesheets: [],
17574
18355
  // Placeholder text to use, defaults to the placeholder attribute on the textarea element
@@ -17592,7 +18373,7 @@ wysihtml5.views.View = Base.extend(
17592
18373
  uneditableContainer: "wysihtml5-uneditable-container"
17593
18374
  },
17594
18375
  // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
17595
- // Also copied source is based directly on selection -
18376
+ // Also copied source is based directly on selection -
17596
18377
  // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
17597
18378
  // If falsy value is passed source override is also disabled
17598
18379
  copyedFromMarking: '<meta name="copied-from" content="wysihtml5">'
@@ -18339,6 +19120,7 @@ wysihtml5.views.View = Base.extend(
18339
19120
  },
18340
19121
 
18341
19122
  _updateLinkStates: function() {
19123
+
18342
19124
  var i, state, action, command, displayDialogAttributeValue,
18343
19125
  commandMapping = this.commandMapping,
18344
19126
  composer = this.composer,