tui_editor-rails 1.0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (887) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +20 -0
  6. data/README.md +69 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/example/.gitignore +30 -0
  11. data/example/.ruby-version +1 -0
  12. data/example/Gemfile +64 -0
  13. data/example/Gemfile.lock +224 -0
  14. data/example/README.md +67 -0
  15. data/example/Rakefile +6 -0
  16. data/example/app/assets/config/manifest.js +3 -0
  17. data/example/app/assets/images/.keep +0 -0
  18. data/example/app/assets/images/screen_capture.png +0 -0
  19. data/example/app/assets/javascripts/application.js +23 -0
  20. data/example/app/assets/javascripts/cable.js +13 -0
  21. data/example/app/assets/javascripts/channels/.keep +0 -0
  22. data/example/app/assets/javascripts/home.coffee +3 -0
  23. data/example/app/assets/javascripts/posts.coffee +3 -0
  24. data/example/app/assets/stylesheets/application.scss +4 -0
  25. data/example/app/assets/stylesheets/home.scss +3 -0
  26. data/example/app/assets/stylesheets/posts.scss +3 -0
  27. data/example/app/assets/stylesheets/scaffolds.scss +84 -0
  28. data/example/app/channels/application_cable/channel.rb +4 -0
  29. data/example/app/channels/application_cable/connection.rb +4 -0
  30. data/example/app/controllers/application_controller.rb +2 -0
  31. data/example/app/controllers/concerns/.keep +0 -0
  32. data/example/app/controllers/home_controller.rb +4 -0
  33. data/example/app/controllers/posts_controller.rb +74 -0
  34. data/example/app/helpers/application_helper.rb +2 -0
  35. data/example/app/helpers/home_helper.rb +2 -0
  36. data/example/app/helpers/posts_helper.rb +2 -0
  37. data/example/app/jobs/application_job.rb +2 -0
  38. data/example/app/mailers/application_mailer.rb +4 -0
  39. data/example/app/models/application_record.rb +3 -0
  40. data/example/app/models/concerns/.keep +0 -0
  41. data/example/app/models/post.rb +2 -0
  42. data/example/app/views/home/index.html.erb +11 -0
  43. data/example/app/views/layouts/application.html.erb +14 -0
  44. data/example/app/views/layouts/mailer.html.erb +13 -0
  45. data/example/app/views/layouts/mailer.text.erb +1 -0
  46. data/example/app/views/posts/_form.html.erb +27 -0
  47. data/example/app/views/posts/_post.json.jbuilder +2 -0
  48. data/example/app/views/posts/edit.html.erb +6 -0
  49. data/example/app/views/posts/index.html.erb +29 -0
  50. data/example/app/views/posts/index.json.jbuilder +1 -0
  51. data/example/app/views/posts/new.html.erb +5 -0
  52. data/example/app/views/posts/show.html.erb +14 -0
  53. data/example/app/views/posts/show.json.jbuilder +1 -0
  54. data/example/bin/bundle +3 -0
  55. data/example/bin/rails +9 -0
  56. data/example/bin/rake +9 -0
  57. data/example/bin/setup +36 -0
  58. data/example/bin/spring +17 -0
  59. data/example/bin/update +31 -0
  60. data/example/bin/yarn +11 -0
  61. data/example/config.ru +5 -0
  62. data/example/config/application.rb +19 -0
  63. data/example/config/boot.rb +4 -0
  64. data/example/config/cable.yml +10 -0
  65. data/example/config/credentials.yml.enc +1 -0
  66. data/example/config/database.yml +25 -0
  67. data/example/config/environment.rb +5 -0
  68. data/example/config/environments/development.rb +61 -0
  69. data/example/config/environments/production.rb +94 -0
  70. data/example/config/environments/test.rb +46 -0
  71. data/example/config/initializers/application_controller_renderer.rb +8 -0
  72. data/example/config/initializers/assets.rb +14 -0
  73. data/example/config/initializers/backtrace_silencers.rb +7 -0
  74. data/example/config/initializers/content_security_policy.rb +22 -0
  75. data/example/config/initializers/cookies_serializer.rb +5 -0
  76. data/example/config/initializers/filter_parameter_logging.rb +4 -0
  77. data/example/config/initializers/inflections.rb +16 -0
  78. data/example/config/initializers/mime_types.rb +4 -0
  79. data/example/config/initializers/wrap_parameters.rb +14 -0
  80. data/example/config/locales/en.yml +33 -0
  81. data/example/config/puma.rb +34 -0
  82. data/example/config/routes.rb +6 -0
  83. data/example/config/spring.rb +6 -0
  84. data/example/config/storage.yml +35 -0
  85. data/example/db/migrate/20180208210404_create_posts.rb +10 -0
  86. data/example/db/schema.rb +22 -0
  87. data/example/db/seeds.rb +7 -0
  88. data/example/lib/assets/.keep +0 -0
  89. data/example/lib/tasks/.keep +0 -0
  90. data/example/log/.keep +0 -0
  91. data/example/package.json +5 -0
  92. data/example/public/404.html +67 -0
  93. data/example/public/422.html +67 -0
  94. data/example/public/500.html +66 -0
  95. data/example/public/apple-touch-icon-precomposed.png +0 -0
  96. data/example/public/apple-touch-icon.png +0 -0
  97. data/example/public/favicon.ico +0 -0
  98. data/example/public/robots.txt +1 -0
  99. data/example/test/application_system_test_case.rb +5 -0
  100. data/example/test/controllers/.keep +0 -0
  101. data/example/test/controllers/home_controller_test.rb +9 -0
  102. data/example/test/controllers/posts_controller_test.rb +48 -0
  103. data/example/test/fixtures/.keep +0 -0
  104. data/example/test/fixtures/files/.keep +0 -0
  105. data/example/test/fixtures/posts.yml +9 -0
  106. data/example/test/helpers/.keep +0 -0
  107. data/example/test/integration/.keep +0 -0
  108. data/example/test/mailers/.keep +0 -0
  109. data/example/test/models/.keep +0 -0
  110. data/example/test/models/post_test.rb +7 -0
  111. data/example/test/system/.keep +0 -0
  112. data/example/test/system/posts_test.rb +45 -0
  113. data/example/test/test_helper.rb +10 -0
  114. data/example/tmp/.keep +0 -0
  115. data/example/vendor/.keep +0 -0
  116. data/lib/tui_editor/rails.rb +8 -0
  117. data/lib/tui_editor/rails/version.rb +5 -0
  118. data/tui_editor-rails.gemspec +34 -0
  119. data/vendor/assets/components/codemirror/.bower.json +31 -0
  120. data/vendor/assets/components/codemirror/AUTHORS +714 -0
  121. data/vendor/assets/components/codemirror/CHANGELOG.md +1316 -0
  122. data/vendor/assets/components/codemirror/CONTRIBUTING.md +92 -0
  123. data/vendor/assets/components/codemirror/LICENSE +21 -0
  124. data/vendor/assets/components/codemirror/README.md +35 -0
  125. data/vendor/assets/components/codemirror/addon/comment/comment.js +209 -0
  126. data/vendor/assets/components/codemirror/addon/comment/continuecomment.js +78 -0
  127. data/vendor/assets/components/codemirror/addon/dialog/dialog.css +32 -0
  128. data/vendor/assets/components/codemirror/addon/dialog/dialog.js +157 -0
  129. data/vendor/assets/components/codemirror/addon/display/autorefresh.js +47 -0
  130. data/vendor/assets/components/codemirror/addon/display/fullscreen.css +6 -0
  131. data/vendor/assets/components/codemirror/addon/display/fullscreen.js +41 -0
  132. data/vendor/assets/components/codemirror/addon/display/panel.js +123 -0
  133. data/vendor/assets/components/codemirror/addon/display/placeholder.js +63 -0
  134. data/vendor/assets/components/codemirror/addon/display/rulers.js +51 -0
  135. data/vendor/assets/components/codemirror/addon/edit/closebrackets.js +194 -0
  136. data/vendor/assets/components/codemirror/addon/edit/closetag.js +175 -0
  137. data/vendor/assets/components/codemirror/addon/edit/continuelist.js +89 -0
  138. data/vendor/assets/components/codemirror/addon/edit/matchbrackets.js +145 -0
  139. data/vendor/assets/components/codemirror/addon/edit/matchtags.js +66 -0
  140. data/vendor/assets/components/codemirror/addon/edit/trailingspace.js +27 -0
  141. data/vendor/assets/components/codemirror/addon/fold/brace-fold.js +105 -0
  142. data/vendor/assets/components/codemirror/addon/fold/comment-fold.js +59 -0
  143. data/vendor/assets/components/codemirror/addon/fold/foldcode.js +152 -0
  144. data/vendor/assets/components/codemirror/addon/fold/foldgutter.css +20 -0
  145. data/vendor/assets/components/codemirror/addon/fold/foldgutter.js +146 -0
  146. data/vendor/assets/components/codemirror/addon/fold/indent-fold.js +48 -0
  147. data/vendor/assets/components/codemirror/addon/fold/markdown-fold.js +49 -0
  148. data/vendor/assets/components/codemirror/addon/fold/xml-fold.js +182 -0
  149. data/vendor/assets/components/codemirror/addon/hint/anyword-hint.js +41 -0
  150. data/vendor/assets/components/codemirror/addon/hint/css-hint.js +60 -0
  151. data/vendor/assets/components/codemirror/addon/hint/html-hint.js +348 -0
  152. data/vendor/assets/components/codemirror/addon/hint/javascript-hint.js +155 -0
  153. data/vendor/assets/components/codemirror/addon/hint/show-hint.css +36 -0
  154. data/vendor/assets/components/codemirror/addon/hint/show-hint.js +432 -0
  155. data/vendor/assets/components/codemirror/addon/hint/sql-hint.js +286 -0
  156. data/vendor/assets/components/codemirror/addon/hint/xml-hint.js +110 -0
  157. data/vendor/assets/components/codemirror/addon/lint/coffeescript-lint.js +47 -0
  158. data/vendor/assets/components/codemirror/addon/lint/css-lint.js +40 -0
  159. data/vendor/assets/components/codemirror/addon/lint/html-lint.js +53 -0
  160. data/vendor/assets/components/codemirror/addon/lint/javascript-lint.js +63 -0
  161. data/vendor/assets/components/codemirror/addon/lint/json-lint.js +37 -0
  162. data/vendor/assets/components/codemirror/addon/lint/lint.css +73 -0
  163. data/vendor/assets/components/codemirror/addon/lint/lint.js +252 -0
  164. data/vendor/assets/components/codemirror/addon/lint/yaml-lint.js +41 -0
  165. data/vendor/assets/components/codemirror/addon/merge/merge.css +113 -0
  166. data/vendor/assets/components/codemirror/addon/merge/merge.js +1001 -0
  167. data/vendor/assets/components/codemirror/addon/mode/loadmode.js +64 -0
  168. data/vendor/assets/components/codemirror/addon/mode/multiplex.js +123 -0
  169. data/vendor/assets/components/codemirror/addon/mode/multiplex_test.js +33 -0
  170. data/vendor/assets/components/codemirror/addon/mode/overlay.js +90 -0
  171. data/vendor/assets/components/codemirror/addon/mode/simple.js +216 -0
  172. data/vendor/assets/components/codemirror/addon/runmode/colorize.js +40 -0
  173. data/vendor/assets/components/codemirror/addon/runmode/runmode-standalone.js +158 -0
  174. data/vendor/assets/components/codemirror/addon/runmode/runmode.js +72 -0
  175. data/vendor/assets/components/codemirror/addon/runmode/runmode.node.js +197 -0
  176. data/vendor/assets/components/codemirror/addon/scroll/annotatescrollbar.js +122 -0
  177. data/vendor/assets/components/codemirror/addon/scroll/scrollpastend.js +48 -0
  178. data/vendor/assets/components/codemirror/addon/scroll/simplescrollbars.css +66 -0
  179. data/vendor/assets/components/codemirror/addon/scroll/simplescrollbars.js +152 -0
  180. data/vendor/assets/components/codemirror/addon/search/jump-to-line.js +49 -0
  181. data/vendor/assets/components/codemirror/addon/search/match-highlighter.js +165 -0
  182. data/vendor/assets/components/codemirror/addon/search/matchesonscrollbar.css +8 -0
  183. data/vendor/assets/components/codemirror/addon/search/matchesonscrollbar.js +97 -0
  184. data/vendor/assets/components/codemirror/addon/search/search.js +252 -0
  185. data/vendor/assets/components/codemirror/addon/search/searchcursor.js +289 -0
  186. data/vendor/assets/components/codemirror/addon/selection/active-line.js +72 -0
  187. data/vendor/assets/components/codemirror/addon/selection/mark-selection.js +119 -0
  188. data/vendor/assets/components/codemirror/addon/selection/selection-pointer.js +98 -0
  189. data/vendor/assets/components/codemirror/addon/tern/tern.css +87 -0
  190. data/vendor/assets/components/codemirror/addon/tern/tern.js +718 -0
  191. data/vendor/assets/components/codemirror/addon/tern/worker.js +44 -0
  192. data/vendor/assets/components/codemirror/addon/wrap/hardwrap.js +144 -0
  193. data/vendor/assets/components/codemirror/bower.json +17 -0
  194. data/vendor/assets/components/codemirror/component-tools/bower.json +17 -0
  195. data/vendor/assets/components/codemirror/component-tools/build.sh +31 -0
  196. data/vendor/assets/components/codemirror/component-tools/update.py +38 -0
  197. data/vendor/assets/components/codemirror/keymap/emacs.js +416 -0
  198. data/vendor/assets/components/codemirror/keymap/sublime.js +685 -0
  199. data/vendor/assets/components/codemirror/keymap/vim.js +5219 -0
  200. data/vendor/assets/components/codemirror/lib/codemirror.css +346 -0
  201. data/vendor/assets/components/codemirror/lib/codemirror.js +9669 -0
  202. data/vendor/assets/components/codemirror/mode/apl/apl.js +174 -0
  203. data/vendor/assets/components/codemirror/mode/asciiarmor/asciiarmor.js +74 -0
  204. data/vendor/assets/components/codemirror/mode/asn.1/asn.1.js +204 -0
  205. data/vendor/assets/components/codemirror/mode/asterisk/asterisk.js +196 -0
  206. data/vendor/assets/components/codemirror/mode/brainfuck/brainfuck.js +85 -0
  207. data/vendor/assets/components/codemirror/mode/clike/clike.js +817 -0
  208. data/vendor/assets/components/codemirror/mode/clojure/clojure.js +306 -0
  209. data/vendor/assets/components/codemirror/mode/cmake/cmake.js +97 -0
  210. data/vendor/assets/components/codemirror/mode/cobol/cobol.js +255 -0
  211. data/vendor/assets/components/codemirror/mode/coffeescript/coffeescript.js +359 -0
  212. data/vendor/assets/components/codemirror/mode/commonlisp/commonlisp.js +124 -0
  213. data/vendor/assets/components/codemirror/mode/crystal/crystal.js +433 -0
  214. data/vendor/assets/components/codemirror/mode/css/css.js +832 -0
  215. data/vendor/assets/components/codemirror/mode/cypher/cypher.js +150 -0
  216. data/vendor/assets/components/codemirror/mode/d/d.js +218 -0
  217. data/vendor/assets/components/codemirror/mode/dart/dart.js +157 -0
  218. data/vendor/assets/components/codemirror/mode/diff/diff.js +47 -0
  219. data/vendor/assets/components/codemirror/mode/django/django.js +356 -0
  220. data/vendor/assets/components/codemirror/mode/dockerfile/dockerfile.js +79 -0
  221. data/vendor/assets/components/codemirror/mode/dtd/dtd.js +142 -0
  222. data/vendor/assets/components/codemirror/mode/dylan/dylan.js +352 -0
  223. data/vendor/assets/components/codemirror/mode/ebnf/ebnf.js +195 -0
  224. data/vendor/assets/components/codemirror/mode/ecl/ecl.js +206 -0
  225. data/vendor/assets/components/codemirror/mode/eiffel/eiffel.js +160 -0
  226. data/vendor/assets/components/codemirror/mode/elm/elm.js +205 -0
  227. data/vendor/assets/components/codemirror/mode/erlang/erlang.js +619 -0
  228. data/vendor/assets/components/codemirror/mode/factor/factor.js +85 -0
  229. data/vendor/assets/components/codemirror/mode/fcl/fcl.js +173 -0
  230. data/vendor/assets/components/codemirror/mode/forth/forth.js +180 -0
  231. data/vendor/assets/components/codemirror/mode/fortran/fortran.js +188 -0
  232. data/vendor/assets/components/codemirror/mode/gas/gas.js +345 -0
  233. data/vendor/assets/components/codemirror/mode/gfm/gfm.js +129 -0
  234. data/vendor/assets/components/codemirror/mode/gherkin/gherkin.js +178 -0
  235. data/vendor/assets/components/codemirror/mode/go/go.js +187 -0
  236. data/vendor/assets/components/codemirror/mode/groovy/groovy.js +230 -0
  237. data/vendor/assets/components/codemirror/mode/haml/haml.js +161 -0
  238. data/vendor/assets/components/codemirror/mode/handlebars/handlebars.js +62 -0
  239. data/vendor/assets/components/codemirror/mode/haskell-literate/haskell-literate.js +43 -0
  240. data/vendor/assets/components/codemirror/mode/haskell/haskell.js +267 -0
  241. data/vendor/assets/components/codemirror/mode/haxe/haxe.js +515 -0
  242. data/vendor/assets/components/codemirror/mode/htmlembedded/htmlembedded.js +37 -0
  243. data/vendor/assets/components/codemirror/mode/htmlmixed/htmlmixed.js +152 -0
  244. data/vendor/assets/components/codemirror/mode/http/http.js +113 -0
  245. data/vendor/assets/components/codemirror/mode/idl/idl.js +290 -0
  246. data/vendor/assets/components/codemirror/mode/javascript/javascript.js +865 -0
  247. data/vendor/assets/components/codemirror/mode/jinja2/jinja2.js +142 -0
  248. data/vendor/assets/components/codemirror/mode/jsx/jsx.js +148 -0
  249. data/vendor/assets/components/codemirror/mode/julia/julia.js +418 -0
  250. data/vendor/assets/components/codemirror/mode/livescript/livescript.js +280 -0
  251. data/vendor/assets/components/codemirror/mode/lua/lua.js +159 -0
  252. data/vendor/assets/components/codemirror/mode/markdown/markdown.js +872 -0
  253. data/vendor/assets/components/codemirror/mode/mathematica/mathematica.js +176 -0
  254. data/vendor/assets/components/codemirror/mode/mbox/mbox.js +129 -0
  255. data/vendor/assets/components/codemirror/mode/meta.js +217 -0
  256. data/vendor/assets/components/codemirror/mode/mirc/mirc.js +193 -0
  257. data/vendor/assets/components/codemirror/mode/mllike/mllike.js +356 -0
  258. data/vendor/assets/components/codemirror/mode/modelica/modelica.js +245 -0
  259. data/vendor/assets/components/codemirror/mode/mscgen/mscgen.js +175 -0
  260. data/vendor/assets/components/codemirror/mode/mumps/mumps.js +148 -0
  261. data/vendor/assets/components/codemirror/mode/nginx/nginx.js +178 -0
  262. data/vendor/assets/components/codemirror/mode/nsis/nsis.js +95 -0
  263. data/vendor/assets/components/codemirror/mode/ntriples/ntriples.js +195 -0
  264. data/vendor/assets/components/codemirror/mode/octave/octave.js +139 -0
  265. data/vendor/assets/components/codemirror/mode/oz/oz.js +252 -0
  266. data/vendor/assets/components/codemirror/mode/pascal/pascal.js +109 -0
  267. data/vendor/assets/components/codemirror/mode/pegjs/pegjs.js +114 -0
  268. data/vendor/assets/components/codemirror/mode/perl/perl.js +837 -0
  269. data/vendor/assets/components/codemirror/mode/php/php.js +234 -0
  270. data/vendor/assets/components/codemirror/mode/pig/pig.js +178 -0
  271. data/vendor/assets/components/codemirror/mode/powershell/powershell.js +398 -0
  272. data/vendor/assets/components/codemirror/mode/properties/properties.js +78 -0
  273. data/vendor/assets/components/codemirror/mode/protobuf/protobuf.js +69 -0
  274. data/vendor/assets/components/codemirror/mode/pug/pug.js +591 -0
  275. data/vendor/assets/components/codemirror/mode/puppet/puppet.js +220 -0
  276. data/vendor/assets/components/codemirror/mode/python/python.js +334 -0
  277. data/vendor/assets/components/codemirror/mode/q/q.js +139 -0
  278. data/vendor/assets/components/codemirror/mode/r/r.js +183 -0
  279. data/vendor/assets/components/codemirror/mode/rpm/rpm.js +109 -0
  280. data/vendor/assets/components/codemirror/mode/rst/rst.js +557 -0
  281. data/vendor/assets/components/codemirror/mode/ruby/ruby.js +296 -0
  282. data/vendor/assets/components/codemirror/mode/rust/rust.js +72 -0
  283. data/vendor/assets/components/codemirror/mode/sas/sas.js +303 -0
  284. data/vendor/assets/components/codemirror/mode/sass/sass.js +454 -0
  285. data/vendor/assets/components/codemirror/mode/scheme/scheme.js +249 -0
  286. data/vendor/assets/components/codemirror/mode/shell/shell.js +151 -0
  287. data/vendor/assets/components/codemirror/mode/sieve/sieve.js +193 -0
  288. data/vendor/assets/components/codemirror/mode/slim/slim.js +575 -0
  289. data/vendor/assets/components/codemirror/mode/smalltalk/smalltalk.js +168 -0
  290. data/vendor/assets/components/codemirror/mode/smarty/smarty.js +225 -0
  291. data/vendor/assets/components/codemirror/mode/solr/solr.js +104 -0
  292. data/vendor/assets/components/codemirror/mode/soy/soy.js +354 -0
  293. data/vendor/assets/components/codemirror/mode/sparql/sparql.js +180 -0
  294. data/vendor/assets/components/codemirror/mode/spreadsheet/spreadsheet.js +112 -0
  295. data/vendor/assets/components/codemirror/mode/sql/sql.js +488 -0
  296. data/vendor/assets/components/codemirror/mode/stex/stex.js +251 -0
  297. data/vendor/assets/components/codemirror/mode/stylus/stylus.js +771 -0
  298. data/vendor/assets/components/codemirror/mode/swift/swift.js +219 -0
  299. data/vendor/assets/components/codemirror/mode/tcl/tcl.js +139 -0
  300. data/vendor/assets/components/codemirror/mode/textile/textile.js +469 -0
  301. data/vendor/assets/components/codemirror/mode/tiddlywiki/tiddlywiki.css +14 -0
  302. data/vendor/assets/components/codemirror/mode/tiddlywiki/tiddlywiki.js +308 -0
  303. data/vendor/assets/components/codemirror/mode/tiki/tiki.css +26 -0
  304. data/vendor/assets/components/codemirror/mode/tiki/tiki.js +312 -0
  305. data/vendor/assets/components/codemirror/mode/toml/toml.js +88 -0
  306. data/vendor/assets/components/codemirror/mode/tornado/tornado.js +68 -0
  307. data/vendor/assets/components/codemirror/mode/troff/troff.js +84 -0
  308. data/vendor/assets/components/codemirror/mode/ttcn-cfg/ttcn-cfg.js +214 -0
  309. data/vendor/assets/components/codemirror/mode/ttcn/ttcn.js +283 -0
  310. data/vendor/assets/components/codemirror/mode/turtle/turtle.js +162 -0
  311. data/vendor/assets/components/codemirror/mode/twig/twig.js +141 -0
  312. data/vendor/assets/components/codemirror/mode/vb/vb.js +275 -0
  313. data/vendor/assets/components/codemirror/mode/vbscript/vbscript.js +350 -0
  314. data/vendor/assets/components/codemirror/mode/velocity/velocity.js +201 -0
  315. data/vendor/assets/components/codemirror/mode/verilog/verilog.js +675 -0
  316. data/vendor/assets/components/codemirror/mode/vhdl/vhdl.js +189 -0
  317. data/vendor/assets/components/codemirror/mode/vue/vue.js +77 -0
  318. data/vendor/assets/components/codemirror/mode/webidl/webidl.js +195 -0
  319. data/vendor/assets/components/codemirror/mode/xml/xml.js +401 -0
  320. data/vendor/assets/components/codemirror/mode/xquery/xquery.js +448 -0
  321. data/vendor/assets/components/codemirror/mode/yacas/yacas.js +204 -0
  322. data/vendor/assets/components/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js +68 -0
  323. data/vendor/assets/components/codemirror/mode/yaml/yaml.js +118 -0
  324. data/vendor/assets/components/codemirror/mode/z80/z80.js +116 -0
  325. data/vendor/assets/components/codemirror/rollup.config.js +18 -0
  326. data/vendor/assets/components/codemirror/src/codemirror.js +3 -0
  327. data/vendor/assets/components/codemirror/src/display/Display.js +106 -0
  328. data/vendor/assets/components/codemirror/src/display/focus.js +47 -0
  329. data/vendor/assets/components/codemirror/src/display/gutters.js +34 -0
  330. data/vendor/assets/components/codemirror/src/display/highlight_worker.js +55 -0
  331. data/vendor/assets/components/codemirror/src/display/line_numbers.js +48 -0
  332. data/vendor/assets/components/codemirror/src/display/mode_state.js +22 -0
  333. data/vendor/assets/components/codemirror/src/display/operations.js +205 -0
  334. data/vendor/assets/components/codemirror/src/display/scroll_events.js +115 -0
  335. data/vendor/assets/components/codemirror/src/display/scrollbars.js +192 -0
  336. data/vendor/assets/components/codemirror/src/display/scrolling.js +184 -0
  337. data/vendor/assets/components/codemirror/src/display/selection.js +158 -0
  338. data/vendor/assets/components/codemirror/src/display/update_display.js +260 -0
  339. data/vendor/assets/components/codemirror/src/display/update_line.js +188 -0
  340. data/vendor/assets/components/codemirror/src/display/update_lines.js +64 -0
  341. data/vendor/assets/components/codemirror/src/display/view_tracking.js +153 -0
  342. data/vendor/assets/components/codemirror/src/edit/CodeMirror.js +214 -0
  343. data/vendor/assets/components/codemirror/src/edit/commands.js +178 -0
  344. data/vendor/assets/components/codemirror/src/edit/deleteNearSelection.js +30 -0
  345. data/vendor/assets/components/codemirror/src/edit/drop_events.js +119 -0
  346. data/vendor/assets/components/codemirror/src/edit/fromTextArea.js +61 -0
  347. data/vendor/assets/components/codemirror/src/edit/global_events.js +44 -0
  348. data/vendor/assets/components/codemirror/src/edit/key_events.js +159 -0
  349. data/vendor/assets/components/codemirror/src/edit/legacy.js +62 -0
  350. data/vendor/assets/components/codemirror/src/edit/main.js +69 -0
  351. data/vendor/assets/components/codemirror/src/edit/methods.js +539 -0
  352. data/vendor/assets/components/codemirror/src/edit/mouse_events.js +407 -0
  353. data/vendor/assets/components/codemirror/src/edit/options.js +191 -0
  354. data/vendor/assets/components/codemirror/src/edit/utils.js +7 -0
  355. data/vendor/assets/components/codemirror/src/input/ContentEditableInput.js +517 -0
  356. data/vendor/assets/components/codemirror/src/input/TextareaInput.js +350 -0
  357. data/vendor/assets/components/codemirror/src/input/indent.js +71 -0
  358. data/vendor/assets/components/codemirror/src/input/input.js +135 -0
  359. data/vendor/assets/components/codemirror/src/input/keymap.js +148 -0
  360. data/vendor/assets/components/codemirror/src/input/keynames.js +17 -0
  361. data/vendor/assets/components/codemirror/src/input/movement.js +110 -0
  362. data/vendor/assets/components/codemirror/src/line/highlight.js +284 -0
  363. data/vendor/assets/components/codemirror/src/line/line_data.js +337 -0
  364. data/vendor/assets/components/codemirror/src/line/pos.js +40 -0
  365. data/vendor/assets/components/codemirror/src/line/saw_special_spans.js +10 -0
  366. data/vendor/assets/components/codemirror/src/line/spans.js +372 -0
  367. data/vendor/assets/components/codemirror/src/line/utils_line.js +85 -0
  368. data/vendor/assets/components/codemirror/src/measurement/position_measurement.js +700 -0
  369. data/vendor/assets/components/codemirror/src/measurement/widgets.js +26 -0
  370. data/vendor/assets/components/codemirror/src/model/Doc.js +432 -0
  371. data/vendor/assets/components/codemirror/src/model/change_measurement.js +61 -0
  372. data/vendor/assets/components/codemirror/src/model/changes.js +330 -0
  373. data/vendor/assets/components/codemirror/src/model/chunk.js +167 -0
  374. data/vendor/assets/components/codemirror/src/model/document_data.js +111 -0
  375. data/vendor/assets/components/codemirror/src/model/history.js +228 -0
  376. data/vendor/assets/components/codemirror/src/model/line_widget.js +78 -0
  377. data/vendor/assets/components/codemirror/src/model/mark_text.js +292 -0
  378. data/vendor/assets/components/codemirror/src/model/selection.js +82 -0
  379. data/vendor/assets/components/codemirror/src/model/selection_updates.js +208 -0
  380. data/vendor/assets/components/codemirror/src/modes.js +96 -0
  381. data/vendor/assets/components/codemirror/src/util/StringStream.js +90 -0
  382. data/vendor/assets/components/codemirror/src/util/bidi.js +214 -0
  383. data/vendor/assets/components/codemirror/src/util/browser.js +33 -0
  384. data/vendor/assets/components/codemirror/src/util/dom.js +97 -0
  385. data/vendor/assets/components/codemirror/src/util/event.js +103 -0
  386. data/vendor/assets/components/codemirror/src/util/feature_detection.js +84 -0
  387. data/vendor/assets/components/codemirror/src/util/misc.js +150 -0
  388. data/vendor/assets/components/codemirror/src/util/operation_group.js +72 -0
  389. data/vendor/assets/components/codemirror/theme/3024-day.css +41 -0
  390. data/vendor/assets/components/codemirror/theme/3024-night.css +39 -0
  391. data/vendor/assets/components/codemirror/theme/abcdef.css +32 -0
  392. data/vendor/assets/components/codemirror/theme/ambiance-mobile.css +5 -0
  393. data/vendor/assets/components/codemirror/theme/ambiance.css +74 -0
  394. data/vendor/assets/components/codemirror/theme/base16-dark.css +38 -0
  395. data/vendor/assets/components/codemirror/theme/base16-light.css +38 -0
  396. data/vendor/assets/components/codemirror/theme/bespin.css +34 -0
  397. data/vendor/assets/components/codemirror/theme/blackboard.css +32 -0
  398. data/vendor/assets/components/codemirror/theme/cobalt.css +25 -0
  399. data/vendor/assets/components/codemirror/theme/colorforth.css +33 -0
  400. data/vendor/assets/components/codemirror/theme/dracula.css +40 -0
  401. data/vendor/assets/components/codemirror/theme/duotone-dark.css +35 -0
  402. data/vendor/assets/components/codemirror/theme/duotone-light.css +36 -0
  403. data/vendor/assets/components/codemirror/theme/eclipse.css +23 -0
  404. data/vendor/assets/components/codemirror/theme/elegant.css +13 -0
  405. data/vendor/assets/components/codemirror/theme/erlang-dark.css +34 -0
  406. data/vendor/assets/components/codemirror/theme/hopscotch.css +34 -0
  407. data/vendor/assets/components/codemirror/theme/icecoder.css +43 -0
  408. data/vendor/assets/components/codemirror/theme/isotope.css +34 -0
  409. data/vendor/assets/components/codemirror/theme/lesser-dark.css +47 -0
  410. data/vendor/assets/components/codemirror/theme/liquibyte.css +95 -0
  411. data/vendor/assets/components/codemirror/theme/material.css +53 -0
  412. data/vendor/assets/components/codemirror/theme/mbo.css +37 -0
  413. data/vendor/assets/components/codemirror/theme/mdn-like.css +46 -0
  414. data/vendor/assets/components/codemirror/theme/midnight.css +43 -0
  415. data/vendor/assets/components/codemirror/theme/monokai.css +36 -0
  416. data/vendor/assets/components/codemirror/theme/neat.css +12 -0
  417. data/vendor/assets/components/codemirror/theme/neo.css +43 -0
  418. data/vendor/assets/components/codemirror/theme/night.css +27 -0
  419. data/vendor/assets/components/codemirror/theme/oceanic-next.css +44 -0
  420. data/vendor/assets/components/codemirror/theme/panda-syntax.css +85 -0
  421. data/vendor/assets/components/codemirror/theme/paraiso-dark.css +38 -0
  422. data/vendor/assets/components/codemirror/theme/paraiso-light.css +38 -0
  423. data/vendor/assets/components/codemirror/theme/pastel-on-dark.css +52 -0
  424. data/vendor/assets/components/codemirror/theme/railscasts.css +34 -0
  425. data/vendor/assets/components/codemirror/theme/rubyblue.css +25 -0
  426. data/vendor/assets/components/codemirror/theme/seti.css +44 -0
  427. data/vendor/assets/components/codemirror/theme/shadowfox.css +52 -0
  428. data/vendor/assets/components/codemirror/theme/solarized.css +168 -0
  429. data/vendor/assets/components/codemirror/theme/the-matrix.css +30 -0
  430. data/vendor/assets/components/codemirror/theme/tomorrow-night-bright.css +35 -0
  431. data/vendor/assets/components/codemirror/theme/tomorrow-night-eighties.css +38 -0
  432. data/vendor/assets/components/codemirror/theme/ttcn.css +64 -0
  433. data/vendor/assets/components/codemirror/theme/twilight.css +32 -0
  434. data/vendor/assets/components/codemirror/theme/vibrant-ink.css +34 -0
  435. data/vendor/assets/components/codemirror/theme/xq-dark.css +53 -0
  436. data/vendor/assets/components/codemirror/theme/xq-light.css +43 -0
  437. data/vendor/assets/components/codemirror/theme/yeti.css +44 -0
  438. data/vendor/assets/components/codemirror/theme/zenburn.css +37 -0
  439. data/vendor/assets/components/eve/.bower.json +12 -0
  440. data/vendor/assets/components/eve/LICENSE +202 -0
  441. data/vendor/assets/components/eve/README.md +7 -0
  442. data/vendor/assets/components/eve/component.json +13 -0
  443. data/vendor/assets/components/eve/e.html +66 -0
  444. data/vendor/assets/components/eve/eve.js +371 -0
  445. data/vendor/assets/components/eve/package.json +18 -0
  446. data/vendor/assets/components/highlightjs/.bower.json +24 -0
  447. data/vendor/assets/components/highlightjs/LICENSE +24 -0
  448. data/vendor/assets/components/highlightjs/Makefile +21 -0
  449. data/vendor/assets/components/highlightjs/README.md +12 -0
  450. data/vendor/assets/components/highlightjs/bower.json +14 -0
  451. data/vendor/assets/components/highlightjs/component.json +12 -0
  452. data/vendor/assets/components/highlightjs/composer.json +26 -0
  453. data/vendor/assets/components/highlightjs/highlight.pack.js +16645 -0
  454. data/vendor/assets/components/highlightjs/highlight.pack.min.js +15 -0
  455. data/vendor/assets/components/highlightjs/package.json +8 -0
  456. data/vendor/assets/components/highlightjs/styles/agate.css +108 -0
  457. data/vendor/assets/components/highlightjs/styles/androidstudio.css +66 -0
  458. data/vendor/assets/components/highlightjs/styles/arduino-light.css +88 -0
  459. data/vendor/assets/components/highlightjs/styles/arta.css +73 -0
  460. data/vendor/assets/components/highlightjs/styles/ascetic.css +45 -0
  461. data/vendor/assets/components/highlightjs/styles/atelier-cave-dark.css +83 -0
  462. data/vendor/assets/components/highlightjs/styles/atelier-cave-light.css +85 -0
  463. data/vendor/assets/components/highlightjs/styles/atelier-cave.dark.css +113 -0
  464. data/vendor/assets/components/highlightjs/styles/atelier-cave.light.css +113 -0
  465. data/vendor/assets/components/highlightjs/styles/atelier-dune-dark.css +69 -0
  466. data/vendor/assets/components/highlightjs/styles/atelier-dune-light.css +69 -0
  467. data/vendor/assets/components/highlightjs/styles/atelier-dune.dark.css +94 -0
  468. data/vendor/assets/components/highlightjs/styles/atelier-dune.light.css +94 -0
  469. data/vendor/assets/components/highlightjs/styles/atelier-estuary-dark.css +84 -0
  470. data/vendor/assets/components/highlightjs/styles/atelier-estuary-light.css +84 -0
  471. data/vendor/assets/components/highlightjs/styles/atelier-estuary.dark.css +113 -0
  472. data/vendor/assets/components/highlightjs/styles/atelier-estuary.light.css +113 -0
  473. data/vendor/assets/components/highlightjs/styles/atelier-forest-dark.css +69 -0
  474. data/vendor/assets/components/highlightjs/styles/atelier-forest-light.css +69 -0
  475. data/vendor/assets/components/highlightjs/styles/atelier-forest.dark.css +94 -0
  476. data/vendor/assets/components/highlightjs/styles/atelier-forest.light.css +94 -0
  477. data/vendor/assets/components/highlightjs/styles/atelier-heath-dark.css +69 -0
  478. data/vendor/assets/components/highlightjs/styles/atelier-heath-light.css +69 -0
  479. data/vendor/assets/components/highlightjs/styles/atelier-heath.dark.css +94 -0
  480. data/vendor/assets/components/highlightjs/styles/atelier-heath.light.css +94 -0
  481. data/vendor/assets/components/highlightjs/styles/atelier-lakeside-dark.css +69 -0
  482. data/vendor/assets/components/highlightjs/styles/atelier-lakeside-light.css +69 -0
  483. data/vendor/assets/components/highlightjs/styles/atelier-lakeside.dark.css +94 -0
  484. data/vendor/assets/components/highlightjs/styles/atelier-lakeside.light.css +94 -0
  485. data/vendor/assets/components/highlightjs/styles/atelier-plateau-dark.css +84 -0
  486. data/vendor/assets/components/highlightjs/styles/atelier-plateau-light.css +84 -0
  487. data/vendor/assets/components/highlightjs/styles/atelier-plateau.dark.css +113 -0
  488. data/vendor/assets/components/highlightjs/styles/atelier-plateau.light.css +113 -0
  489. data/vendor/assets/components/highlightjs/styles/atelier-savanna-dark.css +84 -0
  490. data/vendor/assets/components/highlightjs/styles/atelier-savanna-light.css +84 -0
  491. data/vendor/assets/components/highlightjs/styles/atelier-savanna.dark.css +113 -0
  492. data/vendor/assets/components/highlightjs/styles/atelier-savanna.light.css +113 -0
  493. data/vendor/assets/components/highlightjs/styles/atelier-seaside-dark.css +69 -0
  494. data/vendor/assets/components/highlightjs/styles/atelier-seaside-light.css +69 -0
  495. data/vendor/assets/components/highlightjs/styles/atelier-seaside.dark.css +94 -0
  496. data/vendor/assets/components/highlightjs/styles/atelier-seaside.light.css +94 -0
  497. data/vendor/assets/components/highlightjs/styles/atelier-sulphurpool-dark.css +69 -0
  498. data/vendor/assets/components/highlightjs/styles/atelier-sulphurpool-light.css +69 -0
  499. data/vendor/assets/components/highlightjs/styles/atelier-sulphurpool.dark.css +94 -0
  500. data/vendor/assets/components/highlightjs/styles/atelier-sulphurpool.light.css +94 -0
  501. data/vendor/assets/components/highlightjs/styles/atom-one-dark.css +96 -0
  502. data/vendor/assets/components/highlightjs/styles/atom-one-light.css +96 -0
  503. data/vendor/assets/components/highlightjs/styles/brown-paper.css +64 -0
  504. data/vendor/assets/components/highlightjs/styles/brown-papersq.png +0 -0
  505. data/vendor/assets/components/highlightjs/styles/brown_paper.css +103 -0
  506. data/vendor/assets/components/highlightjs/styles/brown_papersq.png +0 -0
  507. data/vendor/assets/components/highlightjs/styles/codepen-embed.css +60 -0
  508. data/vendor/assets/components/highlightjs/styles/color-brewer.css +71 -0
  509. data/vendor/assets/components/highlightjs/styles/darcula.css +77 -0
  510. data/vendor/assets/components/highlightjs/styles/dark.css +63 -0
  511. data/vendor/assets/components/highlightjs/styles/darkula.css +6 -0
  512. data/vendor/assets/components/highlightjs/styles/default.css +99 -0
  513. data/vendor/assets/components/highlightjs/styles/docco.css +97 -0
  514. data/vendor/assets/components/highlightjs/styles/dracula.css +76 -0
  515. data/vendor/assets/components/highlightjs/styles/far.css +71 -0
  516. data/vendor/assets/components/highlightjs/styles/foundation.css +88 -0
  517. data/vendor/assets/components/highlightjs/styles/github-gist.css +71 -0
  518. data/vendor/assets/components/highlightjs/styles/github.css +99 -0
  519. data/vendor/assets/components/highlightjs/styles/googlecode.css +89 -0
  520. data/vendor/assets/components/highlightjs/styles/grayscale.css +101 -0
  521. data/vendor/assets/components/highlightjs/styles/gruvbox-dark.css +108 -0
  522. data/vendor/assets/components/highlightjs/styles/gruvbox-light.css +108 -0
  523. data/vendor/assets/components/highlightjs/styles/hopscotch.css +83 -0
  524. data/vendor/assets/components/highlightjs/styles/hybrid.css +102 -0
  525. data/vendor/assets/components/highlightjs/styles/idea.css +97 -0
  526. data/vendor/assets/components/highlightjs/styles/ir-black.css +73 -0
  527. data/vendor/assets/components/highlightjs/styles/ir_black.css +106 -0
  528. data/vendor/assets/components/highlightjs/styles/kimbie.dark.css +74 -0
  529. data/vendor/assets/components/highlightjs/styles/kimbie.light.css +74 -0
  530. data/vendor/assets/components/highlightjs/styles/magula.css +70 -0
  531. data/vendor/assets/components/highlightjs/styles/mono-blue.css +59 -0
  532. data/vendor/assets/components/highlightjs/styles/monokai-sublime.css +83 -0
  533. data/vendor/assets/components/highlightjs/styles/monokai.css +70 -0
  534. data/vendor/assets/components/highlightjs/styles/monokai_sublime.css +154 -0
  535. data/vendor/assets/components/highlightjs/styles/obsidian.css +88 -0
  536. data/vendor/assets/components/highlightjs/styles/ocean.css +74 -0
  537. data/vendor/assets/components/highlightjs/styles/paraiso-dark.css +72 -0
  538. data/vendor/assets/components/highlightjs/styles/paraiso-light.css +72 -0
  539. data/vendor/assets/components/highlightjs/styles/paraiso.dark.css +96 -0
  540. data/vendor/assets/components/highlightjs/styles/paraiso.light.css +96 -0
  541. data/vendor/assets/components/highlightjs/styles/pojoaque.css +83 -0
  542. data/vendor/assets/components/highlightjs/styles/pojoaque.jpg +0 -0
  543. data/vendor/assets/components/highlightjs/styles/purebasic.css +96 -0
  544. data/vendor/assets/components/highlightjs/styles/qtcreator_dark.css +83 -0
  545. data/vendor/assets/components/highlightjs/styles/qtcreator_light.css +83 -0
  546. data/vendor/assets/components/highlightjs/styles/railscasts.css +106 -0
  547. data/vendor/assets/components/highlightjs/styles/rainbow.css +85 -0
  548. data/vendor/assets/components/highlightjs/styles/routeros.css +108 -0
  549. data/vendor/assets/components/highlightjs/styles/school-book.css +72 -0
  550. data/vendor/assets/components/highlightjs/styles/school-book.png +0 -0
  551. data/vendor/assets/components/highlightjs/styles/school_book.css +111 -0
  552. data/vendor/assets/components/highlightjs/styles/school_book.png +0 -0
  553. data/vendor/assets/components/highlightjs/styles/solarized-dark.css +84 -0
  554. data/vendor/assets/components/highlightjs/styles/solarized-light.css +84 -0
  555. data/vendor/assets/components/highlightjs/styles/solarized_dark.css +107 -0
  556. data/vendor/assets/components/highlightjs/styles/solarized_light.css +107 -0
  557. data/vendor/assets/components/highlightjs/styles/sunburst.css +102 -0
  558. data/vendor/assets/components/highlightjs/styles/tomorrow-night-blue.css +75 -0
  559. data/vendor/assets/components/highlightjs/styles/tomorrow-night-bright.css +74 -0
  560. data/vendor/assets/components/highlightjs/styles/tomorrow-night-eighties.css +74 -0
  561. data/vendor/assets/components/highlightjs/styles/tomorrow-night.css +75 -0
  562. data/vendor/assets/components/highlightjs/styles/tomorrow.css +72 -0
  563. data/vendor/assets/components/highlightjs/styles/vs.css +68 -0
  564. data/vendor/assets/components/highlightjs/styles/vs2015.css +115 -0
  565. data/vendor/assets/components/highlightjs/styles/xcode.css +93 -0
  566. data/vendor/assets/components/highlightjs/styles/xt256.css +92 -0
  567. data/vendor/assets/components/highlightjs/styles/zenburn.css +80 -0
  568. data/vendor/assets/components/jquery/.bower.json +25 -0
  569. data/vendor/assets/components/jquery/AUTHORS.txt +313 -0
  570. data/vendor/assets/components/jquery/LICENSE.txt +36 -0
  571. data/vendor/assets/components/jquery/README.md +67 -0
  572. data/vendor/assets/components/jquery/bower.json +14 -0
  573. data/vendor/assets/components/jquery/dist/core.js +399 -0
  574. data/vendor/assets/components/jquery/dist/jquery.js +10364 -0
  575. data/vendor/assets/components/jquery/dist/jquery.min.js +2 -0
  576. data/vendor/assets/components/jquery/dist/jquery.min.map +1 -0
  577. data/vendor/assets/components/jquery/dist/jquery.slim.js +8269 -0
  578. data/vendor/assets/components/jquery/dist/jquery.slim.min.js +2 -0
  579. data/vendor/assets/components/jquery/dist/jquery.slim.min.map +1 -0
  580. data/vendor/assets/components/jquery/external/sizzle/LICENSE.txt +36 -0
  581. data/vendor/assets/components/jquery/external/sizzle/dist/sizzle.js +2272 -0
  582. data/vendor/assets/components/jquery/external/sizzle/dist/sizzle.min.js +3 -0
  583. data/vendor/assets/components/jquery/external/sizzle/dist/sizzle.min.map +1 -0
  584. data/vendor/assets/components/jquery/src/.eslintrc.json +5 -0
  585. data/vendor/assets/components/jquery/src/ajax.js +856 -0
  586. data/vendor/assets/components/jquery/src/ajax/jsonp.js +103 -0
  587. data/vendor/assets/components/jquery/src/ajax/load.js +77 -0
  588. data/vendor/assets/components/jquery/src/ajax/parseXML.js +30 -0
  589. data/vendor/assets/components/jquery/src/ajax/script.js +77 -0
  590. data/vendor/assets/components/jquery/src/ajax/var/location.js +5 -0
  591. data/vendor/assets/components/jquery/src/ajax/var/nonce.js +5 -0
  592. data/vendor/assets/components/jquery/src/ajax/var/rquery.js +5 -0
  593. data/vendor/assets/components/jquery/src/ajax/xhr.js +170 -0
  594. data/vendor/assets/components/jquery/src/attributes.js +13 -0
  595. data/vendor/assets/components/jquery/src/attributes/attr.js +141 -0
  596. data/vendor/assets/components/jquery/src/attributes/classes.js +186 -0
  597. data/vendor/assets/components/jquery/src/attributes/prop.js +143 -0
  598. data/vendor/assets/components/jquery/src/attributes/support.js +33 -0
  599. data/vendor/assets/components/jquery/src/attributes/val.js +191 -0
  600. data/vendor/assets/components/jquery/src/callbacks.js +236 -0
  601. data/vendor/assets/components/jquery/src/core.js +399 -0
  602. data/vendor/assets/components/jquery/src/core/DOMEval.js +30 -0
  603. data/vendor/assets/components/jquery/src/core/access.js +72 -0
  604. data/vendor/assets/components/jquery/src/core/camelCase.js +23 -0
  605. data/vendor/assets/components/jquery/src/core/init.js +129 -0
  606. data/vendor/assets/components/jquery/src/core/nodeName.js +13 -0
  607. data/vendor/assets/components/jquery/src/core/parseHTML.js +65 -0
  608. data/vendor/assets/components/jquery/src/core/ready-no-deferred.js +97 -0
  609. data/vendor/assets/components/jquery/src/core/ready.js +86 -0
  610. data/vendor/assets/components/jquery/src/core/readyException.js +13 -0
  611. data/vendor/assets/components/jquery/src/core/stripAndCollapse.js +14 -0
  612. data/vendor/assets/components/jquery/src/core/support.js +20 -0
  613. data/vendor/assets/components/jquery/src/core/toType.js +20 -0
  614. data/vendor/assets/components/jquery/src/core/var/rsingleTag.js +6 -0
  615. data/vendor/assets/components/jquery/src/css.js +481 -0
  616. data/vendor/assets/components/jquery/src/css/addGetHookIf.js +26 -0
  617. data/vendor/assets/components/jquery/src/css/adjustCSS.js +73 -0
  618. data/vendor/assets/components/jquery/src/css/curCSS.js +65 -0
  619. data/vendor/assets/components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  620. data/vendor/assets/components/jquery/src/css/showHide.js +105 -0
  621. data/vendor/assets/components/jquery/src/css/support.js +102 -0
  622. data/vendor/assets/components/jquery/src/css/var/cssExpand.js +5 -0
  623. data/vendor/assets/components/jquery/src/css/var/getStyles.js +17 -0
  624. data/vendor/assets/components/jquery/src/css/var/isHiddenWithinTree.js +34 -0
  625. data/vendor/assets/components/jquery/src/css/var/rboxStyle.js +7 -0
  626. data/vendor/assets/components/jquery/src/css/var/rnumnonpx.js +7 -0
  627. data/vendor/assets/components/jquery/src/css/var/swap.js +26 -0
  628. data/vendor/assets/components/jquery/src/data.js +180 -0
  629. data/vendor/assets/components/jquery/src/data/Data.js +162 -0
  630. data/vendor/assets/components/jquery/src/data/var/acceptData.js +19 -0
  631. data/vendor/assets/components/jquery/src/data/var/dataPriv.js +7 -0
  632. data/vendor/assets/components/jquery/src/data/var/dataUser.js +7 -0
  633. data/vendor/assets/components/jquery/src/deferred.js +399 -0
  634. data/vendor/assets/components/jquery/src/deferred/exceptionHook.js +21 -0
  635. data/vendor/assets/components/jquery/src/deprecated.js +98 -0
  636. data/vendor/assets/components/jquery/src/dimensions.js +57 -0
  637. data/vendor/assets/components/jquery/src/effects.js +702 -0
  638. data/vendor/assets/components/jquery/src/effects/Tween.js +123 -0
  639. data/vendor/assets/components/jquery/src/effects/animatedSelector.js +15 -0
  640. data/vendor/assets/components/jquery/src/event.js +748 -0
  641. data/vendor/assets/components/jquery/src/event/ajax.js +22 -0
  642. data/vendor/assets/components/jquery/src/event/alias.js +29 -0
  643. data/vendor/assets/components/jquery/src/event/focusin.js +55 -0
  644. data/vendor/assets/components/jquery/src/event/support.js +11 -0
  645. data/vendor/assets/components/jquery/src/event/trigger.js +199 -0
  646. data/vendor/assets/components/jquery/src/exports/amd.js +26 -0
  647. data/vendor/assets/components/jquery/src/exports/global.js +34 -0
  648. data/vendor/assets/components/jquery/src/jquery.js +40 -0
  649. data/vendor/assets/components/jquery/src/manipulation.js +486 -0
  650. data/vendor/assets/components/jquery/src/manipulation/_evalUrl.js +23 -0
  651. data/vendor/assets/components/jquery/src/manipulation/buildFragment.js +105 -0
  652. data/vendor/assets/components/jquery/src/manipulation/getAll.js +32 -0
  653. data/vendor/assets/components/jquery/src/manipulation/setGlobalEval.js +22 -0
  654. data/vendor/assets/components/jquery/src/manipulation/support.js +35 -0
  655. data/vendor/assets/components/jquery/src/manipulation/var/rcheckableType.js +5 -0
  656. data/vendor/assets/components/jquery/src/manipulation/var/rscriptType.js +5 -0
  657. data/vendor/assets/components/jquery/src/manipulation/var/rtagName.js +5 -0
  658. data/vendor/assets/components/jquery/src/manipulation/wrapMap.js +29 -0
  659. data/vendor/assets/components/jquery/src/offset.js +233 -0
  660. data/vendor/assets/components/jquery/src/queue.js +145 -0
  661. data/vendor/assets/components/jquery/src/queue/delay.js +24 -0
  662. data/vendor/assets/components/jquery/src/selector-native.js +237 -0
  663. data/vendor/assets/components/jquery/src/selector-sizzle.js +19 -0
  664. data/vendor/assets/components/jquery/src/selector.js +3 -0
  665. data/vendor/assets/components/jquery/src/serialize.js +132 -0
  666. data/vendor/assets/components/jquery/src/traversing.js +191 -0
  667. data/vendor/assets/components/jquery/src/traversing/findFilter.js +97 -0
  668. data/vendor/assets/components/jquery/src/traversing/var/dir.js +22 -0
  669. data/vendor/assets/components/jquery/src/traversing/var/rneedsContext.js +8 -0
  670. data/vendor/assets/components/jquery/src/traversing/var/siblings.js +17 -0
  671. data/vendor/assets/components/jquery/src/var/ObjectFunctionString.js +7 -0
  672. data/vendor/assets/components/jquery/src/var/arr.js +5 -0
  673. data/vendor/assets/components/jquery/src/var/class2type.js +6 -0
  674. data/vendor/assets/components/jquery/src/var/concat.js +7 -0
  675. data/vendor/assets/components/jquery/src/var/document.js +5 -0
  676. data/vendor/assets/components/jquery/src/var/documentElement.js +7 -0
  677. data/vendor/assets/components/jquery/src/var/fnToString.js +7 -0
  678. data/vendor/assets/components/jquery/src/var/getProto.js +5 -0
  679. data/vendor/assets/components/jquery/src/var/hasOwn.js +7 -0
  680. data/vendor/assets/components/jquery/src/var/indexOf.js +7 -0
  681. data/vendor/assets/components/jquery/src/var/isFunction.js +13 -0
  682. data/vendor/assets/components/jquery/src/var/isWindow.js +8 -0
  683. data/vendor/assets/components/jquery/src/var/pnum.js +5 -0
  684. data/vendor/assets/components/jquery/src/var/push.js +7 -0
  685. data/vendor/assets/components/jquery/src/var/rcssNum.js +9 -0
  686. data/vendor/assets/components/jquery/src/var/rnothtmlwhite.js +8 -0
  687. data/vendor/assets/components/jquery/src/var/slice.js +7 -0
  688. data/vendor/assets/components/jquery/src/var/support.js +6 -0
  689. data/vendor/assets/components/jquery/src/var/toString.js +7 -0
  690. data/vendor/assets/components/jquery/src/wrap.js +78 -0
  691. data/vendor/assets/components/markdown-it/.bower.json +38 -0
  692. data/vendor/assets/components/markdown-it/CHANGELOG.md +442 -0
  693. data/vendor/assets/components/markdown-it/CONTRIBUTING.md +15 -0
  694. data/vendor/assets/components/markdown-it/LICENSE +22 -0
  695. data/vendor/assets/components/markdown-it/Procfile +1 -0
  696. data/vendor/assets/components/markdown-it/README.md +294 -0
  697. data/vendor/assets/components/markdown-it/bin/markdown-it.js +113 -0
  698. data/vendor/assets/components/markdown-it/bower.json +28 -0
  699. data/vendor/assets/components/markdown-it/dist/markdown-it.js +7963 -0
  700. data/vendor/assets/components/markdown-it/dist/markdown-it.min.js +1 -0
  701. data/vendor/assets/components/markdown-it/package.json +64 -0
  702. data/vendor/assets/components/plantuml-encoder/.bower.json +29 -0
  703. data/vendor/assets/components/plantuml-encoder/LICENSE +19 -0
  704. data/vendor/assets/components/plantuml-encoder/README.md +38 -0
  705. data/vendor/assets/components/plantuml-encoder/bower.json +20 -0
  706. data/vendor/assets/components/plantuml-encoder/dist/plantuml-encoder.js +3994 -0
  707. data/vendor/assets/components/plantuml-encoder/dist/plantuml-encoder.min.js +1 -0
  708. data/vendor/assets/components/plantuml-encoder/package.json +33 -0
  709. data/vendor/assets/components/raphael/.bower.json +45 -0
  710. data/vendor/assets/components/raphael/bower.json +31 -0
  711. data/vendor/assets/components/raphael/dev/banner.txt +8 -0
  712. data/vendor/assets/components/raphael/dev/raphael.amd.js +14 -0
  713. data/vendor/assets/components/raphael/dev/raphael.core.js +5413 -0
  714. data/vendor/assets/components/raphael/dev/raphael.svg.js +1428 -0
  715. data/vendor/assets/components/raphael/dev/raphael.vml.js +1010 -0
  716. data/vendor/assets/components/raphael/dev/test/svg/dom.js +295 -0
  717. data/vendor/assets/components/raphael/dev/test/vml/dom.js +5 -0
  718. data/vendor/assets/components/raphael/license.txt +21 -0
  719. data/vendor/assets/components/raphael/raphael.js +8330 -0
  720. data/vendor/assets/components/raphael/raphael.min.js +3 -0
  721. data/vendor/assets/components/raphael/raphael.no-deps.js +7959 -0
  722. data/vendor/assets/components/raphael/raphael.no-deps.min.js +3 -0
  723. data/vendor/assets/components/raphael/webpack.config.js +62 -0
  724. data/vendor/assets/components/squire-rte/.bower.json +34 -0
  725. data/vendor/assets/components/squire-rte/Demo.html +143 -0
  726. data/vendor/assets/components/squire-rte/LICENSE +21 -0
  727. data/vendor/assets/components/squire-rte/Makefile +22 -0
  728. data/vendor/assets/components/squire-rte/README.md +473 -0
  729. data/vendor/assets/components/squire-rte/bower.json +24 -0
  730. data/vendor/assets/components/squire-rte/build/document.html +54 -0
  731. data/vendor/assets/components/squire-rte/build/squire-raw.js +4722 -0
  732. data/vendor/assets/components/squire-rte/build/squire.js +2 -0
  733. data/vendor/assets/components/squire-rte/package.json +31 -0
  734. data/vendor/assets/components/squire-rte/source/Clean.js +358 -0
  735. data/vendor/assets/components/squire-rte/source/Clipboard.js +331 -0
  736. data/vendor/assets/components/squire-rte/source/Constants.js +60 -0
  737. data/vendor/assets/components/squire-rte/source/Editor.js +2201 -0
  738. data/vendor/assets/components/squire-rte/source/KeyHandlers.js +502 -0
  739. data/vendor/assets/components/squire-rte/source/Node.js +553 -0
  740. data/vendor/assets/components/squire-rte/source/Range.js +540 -0
  741. data/vendor/assets/components/squire-rte/source/TreeWalker.js +118 -0
  742. data/vendor/assets/components/squire-rte/source/document.html +54 -0
  743. data/vendor/assets/components/squire-rte/source/exports.js +42 -0
  744. data/vendor/assets/components/squire-rte/source/intro.js +6 -0
  745. data/vendor/assets/components/squire-rte/source/outro.js +22 -0
  746. data/vendor/assets/components/to-mark/.bower.json +35 -0
  747. data/vendor/assets/components/to-mark/bower.json +22 -0
  748. data/vendor/assets/components/to-mark/demo/demo.html +24 -0
  749. data/vendor/assets/components/to-mark/demo/demo2.html +94 -0
  750. data/vendor/assets/components/to-mark/dist/to-mark.js +1283 -0
  751. data/vendor/assets/components/to-mark/dist/to-mark.min.js +1 -0
  752. data/vendor/assets/components/to-mark/gulpfile.js +112 -0
  753. data/vendor/assets/components/to-mark/karma.conf.js +260 -0
  754. data/vendor/assets/components/to-mark/package-lock.json +13061 -0
  755. data/vendor/assets/components/to-mark/package.json +53 -0
  756. data/vendor/assets/components/tui-chart/.bower.json +44 -0
  757. data/vendor/assets/components/tui-chart/CODE_OF_CONDUCT.md +73 -0
  758. data/vendor/assets/components/tui-chart/CONTRIBUTING.md +91 -0
  759. data/vendor/assets/components/tui-chart/LICENSE +21 -0
  760. data/vendor/assets/components/tui-chart/README.md +142 -0
  761. data/vendor/assets/components/tui-chart/bower.json +33 -0
  762. data/vendor/assets/components/tui-chart/dist/maps/china.js +10 -0
  763. data/vendor/assets/components/tui-chart/dist/maps/japan.js +10 -0
  764. data/vendor/assets/components/tui-chart/dist/maps/singapore.js +10 -0
  765. data/vendor/assets/components/tui-chart/dist/maps/south-korea.js +10 -0
  766. data/vendor/assets/components/tui-chart/dist/maps/taiwan.js +10 -0
  767. data/vendor/assets/components/tui-chart/dist/maps/thailand.js +10 -0
  768. data/vendor/assets/components/tui-chart/dist/maps/usa.js +10 -0
  769. data/vendor/assets/components/tui-chart/dist/maps/world.js +11 -0
  770. data/vendor/assets/components/tui-chart/dist/tui-chart.css +704 -0
  771. data/vendor/assets/components/tui-chart/dist/tui-chart.js +40567 -0
  772. data/vendor/assets/components/tui-chart/dist/tui-chart.min.css +10 -0
  773. data/vendor/assets/components/tui-chart/dist/tui-chart.min.js +20 -0
  774. data/vendor/assets/components/tui-chart/docs/COMMIT_MESSAGE_CONVENTION.md +49 -0
  775. data/vendor/assets/components/tui-chart/docs/ISSUE_TEMPLATE.md +24 -0
  776. data/vendor/assets/components/tui-chart/docs/PULL_REQUEST_TEMPLATE.md +42 -0
  777. data/vendor/assets/components/tui-chart/docs/README.md +11 -0
  778. data/vendor/assets/components/tui-chart/docs/wiki/README.md +27 -0
  779. data/vendor/assets/components/tui-chart/docs/wiki/_Sidebar.md +31 -0
  780. data/vendor/assets/components/tui-chart/docs/wiki/chart-export-menu.md +66 -0
  781. data/vendor/assets/components/tui-chart/docs/wiki/chart-type-radial.md +121 -0
  782. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-bar,column.md +312 -0
  783. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-bubble.md +63 -0
  784. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-column-line-combo.md +104 -0
  785. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-heatmap.md +39 -0
  786. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-line,area.md +333 -0
  787. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-line-area-combo.md +78 -0
  788. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-line-scatter-combo.md +101 -0
  789. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-map.md +153 -0
  790. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-pie-donut-combo.md +98 -0
  791. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-pie.md +186 -0
  792. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-scatter.md +58 -0
  793. data/vendor/assets/components/tui-chart/docs/wiki/chart-types-treemap.md +158 -0
  794. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-china-map.md +38 -0
  795. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-japan-map.md +51 -0
  796. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-singapore-map.md +9 -0
  797. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-south-korea-map.md +21 -0
  798. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-taiwan-map.md +26 -0
  799. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-thailand-map.md +82 -0
  800. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-usa-map.md +55 -0
  801. data/vendor/assets/components/tui-chart/docs/wiki/code-table-of-world-map.md +180 -0
  802. data/vendor/assets/components/tui-chart/docs/wiki/features-axes.md +209 -0
  803. data/vendor/assets/components/tui-chart/docs/wiki/features-chart.md +157 -0
  804. data/vendor/assets/components/tui-chart/docs/wiki/features-circle-legend.md +20 -0
  805. data/vendor/assets/components/tui-chart/docs/wiki/features-legend.md +89 -0
  806. data/vendor/assets/components/tui-chart/docs/wiki/features-plot.md +141 -0
  807. data/vendor/assets/components/tui-chart/docs/wiki/features-series.md +240 -0
  808. data/vendor/assets/components/tui-chart/docs/wiki/features-tooltip.md +223 -0
  809. data/vendor/assets/components/tui-chart/docs/wiki/getting-started.md +74 -0
  810. data/vendor/assets/components/tui-chart/docs/wiki/import-chart-data-from-existing-table-element.md +150 -0
  811. data/vendor/assets/components/tui-chart/docs/wiki/table-of-supported-options.md +344 -0
  812. data/vendor/assets/components/tui-chart/docs/wiki/theme.md +338 -0
  813. data/vendor/assets/components/tui-chart/package-lock.json +8881 -0
  814. data/vendor/assets/components/tui-code-snippet/.bower.json +31 -0
  815. data/vendor/assets/components/tui-code-snippet/LICENSE +21 -0
  816. data/vendor/assets/components/tui-code-snippet/README.md +109 -0
  817. data/vendor/assets/components/tui-code-snippet/bower.json +20 -0
  818. data/vendor/assets/components/tui-code-snippet/demo/postBridge/README.md +26 -0
  819. data/vendor/assets/components/tui-code-snippet/demo/postBridge/public/popup.html +27 -0
  820. data/vendor/assets/components/tui-code-snippet/demo/postBridge/public/postBridge.html +31 -0
  821. data/vendor/assets/components/tui-code-snippet/demo/postBridge/server.js +28 -0
  822. data/vendor/assets/components/tui-code-snippet/dist/tui-code-snippet.js +4228 -0
  823. data/vendor/assets/components/tui-code-snippet/dist/tui-code-snippet.min.js +7 -0
  824. data/vendor/assets/components/tui-code-snippet/package-lock.json +6038 -0
  825. data/vendor/assets/components/tui-color-picker/.bower.json +37 -0
  826. data/vendor/assets/components/tui-color-picker/ISSUE_TEMPLATE.md +26 -0
  827. data/vendor/assets/components/tui-color-picker/LICENSE +34 -0
  828. data/vendor/assets/components/tui-color-picker/README.md +69 -0
  829. data/vendor/assets/components/tui-color-picker/bower.json +26 -0
  830. data/vendor/assets/components/tui-color-picker/dist/tui-color-picker.css +148 -0
  831. data/vendor/assets/components/tui-color-picker/dist/tui-color-picker.js +3146 -0
  832. data/vendor/assets/components/tui-color-picker/dist/tui-color-picker.min.css +7 -0
  833. data/vendor/assets/components/tui-color-picker/dist/tui-color-picker.min.js +9 -0
  834. data/vendor/assets/components/tui-color-picker/package-lock.json +8440 -0
  835. data/vendor/assets/components/tui-editor/.bower.json +71 -0
  836. data/vendor/assets/components/tui-editor/CODE_OF_CONDUCT.md +73 -0
  837. data/vendor/assets/components/tui-editor/CONTRIBUTING.md +92 -0
  838. data/vendor/assets/components/tui-editor/LICENSE +21 -0
  839. data/vendor/assets/components/tui-editor/README.md +204 -0
  840. data/vendor/assets/components/tui-editor/bower.json +59 -0
  841. data/vendor/assets/components/tui-editor/dist/tui-editor-Editor-all.js +36850 -0
  842. data/vendor/assets/components/tui-editor/dist/tui-editor-Editor-all.min.js +13 -0
  843. data/vendor/assets/components/tui-editor/dist/tui-editor-Editor.js +24646 -0
  844. data/vendor/assets/components/tui-editor/dist/tui-editor-Editor.min.js +7 -0
  845. data/vendor/assets/components/tui-editor/dist/tui-editor-Viewer-all.js +14768 -0
  846. data/vendor/assets/components/tui-editor/dist/tui-editor-Viewer-all.min.js +13 -0
  847. data/vendor/assets/components/tui-editor/dist/tui-editor-Viewer.js +3686 -0
  848. data/vendor/assets/components/tui-editor/dist/tui-editor-Viewer.min.js +7 -0
  849. data/vendor/assets/components/tui-editor/dist/tui-editor-contents.css +239 -0
  850. data/vendor/assets/components/tui-editor/dist/tui-editor-contents.min.css +1 -0
  851. data/vendor/assets/components/tui-editor/dist/tui-editor-extChart.js +7040 -0
  852. data/vendor/assets/components/tui-editor/dist/tui-editor-extChart.min.js +13 -0
  853. data/vendor/assets/components/tui-editor/dist/tui-editor-extColorSyntax.js +472 -0
  854. data/vendor/assets/components/tui-editor/dist/tui-editor-extColorSyntax.min.js +7 -0
  855. data/vendor/assets/components/tui-editor/dist/tui-editor-extScrollSync.js +1241 -0
  856. data/vendor/assets/components/tui-editor/dist/tui-editor-extScrollSync.min.js +7 -0
  857. data/vendor/assets/components/tui-editor/dist/tui-editor-extTable.js +3930 -0
  858. data/vendor/assets/components/tui-editor/dist/tui-editor-extTable.min.js +7 -0
  859. data/vendor/assets/components/tui-editor/dist/tui-editor-extUML.js +212 -0
  860. data/vendor/assets/components/tui-editor/dist/tui-editor-extUML.min.js +7 -0
  861. data/vendor/assets/components/tui-editor/dist/tui-editor.css +1166 -0
  862. data/vendor/assets/components/tui-editor/dist/tui-editor.min.css +1 -0
  863. data/vendor/assets/components/tui-editor/docs/COMMIT_MESSAGE_CONVENTION.md +49 -0
  864. data/vendor/assets/components/tui-editor/docs/PULL_REQUEST_TEMPLATE.md +41 -0
  865. data/vendor/assets/components/tui-editor/docs/README.md +13 -0
  866. data/vendor/assets/components/tui-editor/docs/getting-started-with-bower.md +93 -0
  867. data/vendor/assets/components/tui-editor/docs/getting-started.md +76 -0
  868. data/vendor/assets/components/tui-editor/docs/using-extensions.md +91 -0
  869. data/vendor/assets/components/tui-editor/docs/writing-your-own-extension.md +167 -0
  870. data/vendor/assets/components/tui-editor/examples/example00-demo.html +85 -0
  871. data/vendor/assets/components/tui-editor/examples/example01-basic.html +37 -0
  872. data/vendor/assets/components/tui-editor/examples/example02-viewer-basic.html +68 -0
  873. data/vendor/assets/components/tui-editor/examples/example03-jquery.html +36 -0
  874. data/vendor/assets/components/tui-editor/examples/example04-viewer-jquery.html +67 -0
  875. data/vendor/assets/components/tui-editor/examples/example05-scrollsync.html +77 -0
  876. data/vendor/assets/components/tui-editor/examples/example06-colorsyntax.html +44 -0
  877. data/vendor/assets/components/tui-editor/examples/example07-table.html +48 -0
  878. data/vendor/assets/components/tui-editor/examples/example08-uml.html +69 -0
  879. data/vendor/assets/components/tui-editor/examples/example09-multiple-extensions.html +85 -0
  880. data/vendor/assets/components/tui-editor/examples/example10-viewer-multiple-extensions.html +86 -0
  881. data/vendor/assets/components/tui-editor/examples/example11-chart.html +60 -0
  882. data/vendor/assets/components/tui-editor/examples/example12-writing-extension.html +66 -0
  883. data/vendor/assets/components/tui-editor/examples/examples.json +41 -0
  884. data/vendor/assets/components/tui-editor/examples/explain.css +7 -0
  885. data/vendor/assets/components/tui-editor/package-lock.json +13289 -0
  886. data/vendor/assets/components/tui-editor/package.json +90 -0
  887. metadata +956 -0
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "squire-rte",
3
+ "homepage": "https://github.com/neilj/Squire",
4
+ "authors": [
5
+ "Neil Jenkins <neil@nmjenkins.com>"
6
+ ],
7
+ "description": "Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible.",
8
+ "main": "build/squire.js",
9
+ "keywords": [
10
+ "wysiwyg",
11
+ "editor",
12
+ "text",
13
+ "html",
14
+ "squire"
15
+ ],
16
+ "license": "MIT",
17
+ "ignore": [
18
+ "**/.*",
19
+ "node_modules",
20
+ "bower_components",
21
+ "test",
22
+ "tests"
23
+ ]
24
+ }
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-squireinit="true">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <title></title>
7
+ <style type="text/css">
8
+ html {
9
+ height: 100%;
10
+ }
11
+ body {
12
+ -moz-box-sizing: border-box;
13
+ -webkit-box-sizing: border-box;
14
+ box-sizing: border-box;
15
+ height: 100%;
16
+ padding: 1em;
17
+ background: transparent;
18
+ color: #2b2b2b;
19
+ font: 13px/1.35 Helvetica, arial, sans-serif;
20
+ cursor: text;
21
+ }
22
+ a {
23
+ text-decoration: underline;
24
+ }
25
+ h1 {
26
+ font-size: 138.5%;
27
+ }
28
+ h2 {
29
+ font-size: 123.1%;
30
+ }
31
+ h3 {
32
+ font-size: 108%;
33
+ }
34
+ h1,h2,h3,p {
35
+ margin: 1em 0;
36
+ }
37
+ h4,h5,h6 {
38
+ margin: 0;
39
+ }
40
+ ul, ol {
41
+ margin: 0 1em;
42
+ padding: 0 1em;
43
+ }
44
+ blockquote {
45
+ border-left: 2px solid blue;
46
+ margin: 0;
47
+ padding: 0 10px;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <script type="text/javascript" src="squire.js"></script>
53
+ </body>
54
+ </html>
@@ -0,0 +1,4722 @@
1
+ /* Copyright © 2011-2015 by Neil Jenkins. MIT Licensed. */
2
+
3
+ ( function ( doc, undefined ) {
4
+
5
+ "use strict";
6
+
7
+ var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
8
+ var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
9
+ var TEXT_NODE = 3; // Node.TEXT_NODE;
10
+ var DOCUMENT_NODE = 9; // Node.DOCUMENT_NODE;
11
+ var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
12
+ var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
13
+ var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
14
+
15
+ var START_TO_START = 0; // Range.START_TO_START
16
+ var START_TO_END = 1; // Range.START_TO_END
17
+ var END_TO_END = 2; // Range.END_TO_END
18
+ var END_TO_START = 3; // Range.END_TO_START
19
+
20
+ var HIGHLIGHT_CLASS = 'highlight';
21
+ var COLOUR_CLASS = 'colour';
22
+ var FONT_FAMILY_CLASS = 'font';
23
+ var FONT_SIZE_CLASS = 'size';
24
+
25
+ var ZWS = '\u200B';
26
+
27
+ var win = doc.defaultView;
28
+
29
+ var ua = navigator.userAgent;
30
+
31
+ var isAndroid = /Android/.test( ua );
32
+ var isIOS = /iP(?:ad|hone|od)/.test( ua );
33
+ var isMac = /Mac OS X/.test( ua );
34
+ var isWin = /Windows NT/.test( ua );
35
+
36
+ var isGecko = /Gecko\//.test( ua );
37
+ var isIElt11 = /Trident\/[456]\./.test( ua );
38
+ var isPresto = !!win.opera;
39
+ var isEdge = /Edge\//.test( ua );
40
+ var isWebKit = !isEdge && /WebKit\//.test( ua );
41
+ var isIE = /Trident\/[4567]\./.test( ua );
42
+
43
+ var ctrlKey = isMac ? 'meta-' : 'ctrl-';
44
+
45
+ var useTextFixer = isIElt11 || isPresto;
46
+ var cantFocusEmptyTextNodes = isIElt11 || isWebKit;
47
+ var losesSelectionOnBlur = isIElt11;
48
+
49
+ var canObserveMutations = typeof MutationObserver !== 'undefined';
50
+ var canWeakMap = typeof WeakMap !== 'undefined';
51
+
52
+ // Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
53
+ var notWS = /[^ \t\r\n]/;
54
+
55
+ var indexOf = Array.prototype.indexOf;
56
+
57
+ // Polyfill for FF3.5
58
+ if ( !Object.create ) {
59
+ Object.create = function ( proto ) {
60
+ var F = function () {};
61
+ F.prototype = proto;
62
+ return new F();
63
+ };
64
+ }
65
+
66
+ /*
67
+ Native TreeWalker is buggy in IE and Opera:
68
+ * IE9/10 sometimes throw errors when calling TreeWalker#nextNode or
69
+ TreeWalker#previousNode. No way to feature detect this.
70
+ * Some versions of Opera have a bug in TreeWalker#previousNode which makes
71
+ it skip to the wrong node.
72
+
73
+ Rather than risk further bugs, it's easiest just to implement our own
74
+ (subset) of the spec in all browsers.
75
+ */
76
+
77
+ var typeToBitArray = {
78
+ // ELEMENT_NODE
79
+ 1: 1,
80
+ // ATTRIBUTE_NODE
81
+ 2: 2,
82
+ // TEXT_NODE
83
+ 3: 4,
84
+ // COMMENT_NODE
85
+ 8: 128,
86
+ // DOCUMENT_NODE
87
+ 9: 256,
88
+ // DOCUMENT_FRAGMENT_NODE
89
+ 11: 1024
90
+ };
91
+
92
+ function TreeWalker ( root, nodeType, filter ) {
93
+ this.root = this.currentNode = root;
94
+ this.nodeType = nodeType;
95
+ this.filter = filter;
96
+ }
97
+
98
+ TreeWalker.prototype.nextNode = function () {
99
+ var current = this.currentNode,
100
+ root = this.root,
101
+ nodeType = this.nodeType,
102
+ filter = this.filter,
103
+ node;
104
+ while ( true ) {
105
+ node = current.firstChild;
106
+ while ( !node && current ) {
107
+ if ( current === root ) {
108
+ break;
109
+ }
110
+ node = current.nextSibling;
111
+ if ( !node ) { current = current.parentNode; }
112
+ }
113
+ if ( !node ) {
114
+ return null;
115
+ }
116
+ if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
117
+ filter( node ) ) {
118
+ this.currentNode = node;
119
+ return node;
120
+ }
121
+ current = node;
122
+ }
123
+ };
124
+
125
+ TreeWalker.prototype.previousNode = function () {
126
+ var current = this.currentNode,
127
+ root = this.root,
128
+ nodeType = this.nodeType,
129
+ filter = this.filter,
130
+ node;
131
+ while ( true ) {
132
+ if ( current === root ) {
133
+ return null;
134
+ }
135
+ node = current.previousSibling;
136
+ if ( node ) {
137
+ while ( current = node.lastChild ) {
138
+ node = current;
139
+ }
140
+ } else {
141
+ node = current.parentNode;
142
+ }
143
+ if ( !node ) {
144
+ return null;
145
+ }
146
+ if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
147
+ filter( node ) ) {
148
+ this.currentNode = node;
149
+ return node;
150
+ }
151
+ current = node;
152
+ }
153
+ };
154
+
155
+ // Previous node in post-order.
156
+ TreeWalker.prototype.previousPONode = function () {
157
+ var current = this.currentNode,
158
+ root = this.root,
159
+ nodeType = this.nodeType,
160
+ filter = this.filter,
161
+ node;
162
+ while ( true ) {
163
+ node = current.lastChild;
164
+ while ( !node && current ) {
165
+ if ( current === root ) {
166
+ break;
167
+ }
168
+ node = current.previousSibling;
169
+ if ( !node ) { current = current.parentNode; }
170
+ }
171
+ if ( !node ) {
172
+ return null;
173
+ }
174
+ if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
175
+ filter( node ) ) {
176
+ this.currentNode = node;
177
+ return node;
178
+ }
179
+ current = node;
180
+ }
181
+ };
182
+
183
+ var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
184
+
185
+ var leafNodeNames = {
186
+ BR: 1,
187
+ HR: 1,
188
+ IFRAME: 1,
189
+ IMG: 1,
190
+ INPUT: 1
191
+ };
192
+
193
+ function every ( nodeList, fn ) {
194
+ var l = nodeList.length;
195
+ while ( l-- ) {
196
+ if ( !fn( nodeList[l] ) ) {
197
+ return false;
198
+ }
199
+ }
200
+ return true;
201
+ }
202
+
203
+ // ---
204
+
205
+ var UNKNOWN = 0;
206
+ var INLINE = 1;
207
+ var BLOCK = 2;
208
+ var CONTAINER = 3;
209
+
210
+ var nodeCategoryCache = canWeakMap ? new WeakMap() : null;
211
+
212
+ function isLeaf ( node ) {
213
+ return node.nodeType === ELEMENT_NODE && !!leafNodeNames[ node.nodeName ];
214
+ }
215
+ function getNodeCategory ( node ) {
216
+ switch ( node.nodeType ) {
217
+ case TEXT_NODE:
218
+ return INLINE;
219
+ case ELEMENT_NODE:
220
+ case DOCUMENT_FRAGMENT_NODE:
221
+ if ( canWeakMap && nodeCategoryCache.has( node ) ) {
222
+ return nodeCategoryCache.get( node );
223
+ }
224
+ break;
225
+ default:
226
+ return UNKNOWN;
227
+ }
228
+
229
+ var nodeCategory;
230
+ if ( !every( node.childNodes, isInline ) ) {
231
+ // Malformed HTML can have block tags inside inline tags. Need to treat
232
+ // these as containers rather than inline. See #239.
233
+ nodeCategory = CONTAINER;
234
+ } else if ( inlineNodeNames.test( node.nodeName ) ) {
235
+ nodeCategory = INLINE;
236
+ } else {
237
+ nodeCategory = BLOCK;
238
+ }
239
+ if ( canWeakMap ) {
240
+ nodeCategoryCache.set( node, nodeCategory );
241
+ }
242
+ return nodeCategory;
243
+ }
244
+ function isInline ( node ) {
245
+ return getNodeCategory( node ) === INLINE;
246
+ }
247
+ function isBlock ( node ) {
248
+ return getNodeCategory( node ) === BLOCK;
249
+ }
250
+ function isContainer ( node ) {
251
+ return getNodeCategory( node ) === CONTAINER;
252
+ }
253
+
254
+ function getBlockWalker ( node, root ) {
255
+ var walker = new TreeWalker( root, SHOW_ELEMENT, isBlock );
256
+ walker.currentNode = node;
257
+ return walker;
258
+ }
259
+ function getPreviousBlock ( node, root ) {
260
+ node = getBlockWalker( node, root ).previousNode();
261
+ return node !== root ? node : null;
262
+ }
263
+ function getNextBlock ( node, root ) {
264
+ node = getBlockWalker( node, root ).nextNode();
265
+ return node !== root ? node : null;
266
+ }
267
+
268
+ function isEmptyBlock ( block ) {
269
+ return !block.textContent && !block.querySelector( 'IMG' );
270
+ }
271
+
272
+ function areAlike ( node, node2 ) {
273
+ return !isLeaf( node ) && (
274
+ node.nodeType === node2.nodeType &&
275
+ node.nodeName === node2.nodeName &&
276
+ node.nodeName !== 'A' &&
277
+ node.className === node2.className &&
278
+ ( ( !node.style && !node2.style ) ||
279
+ node.style.cssText === node2.style.cssText )
280
+ );
281
+ }
282
+ function hasTagAttributes ( node, tag, attributes ) {
283
+ if ( node.nodeName !== tag ) {
284
+ return false;
285
+ }
286
+ for ( var attr in attributes ) {
287
+ if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
288
+ return false;
289
+ }
290
+ }
291
+ return true;
292
+ }
293
+ function getNearest ( node, root, tag, attributes ) {
294
+ while ( node && node !== root ) {
295
+ if ( hasTagAttributes( node, tag, attributes ) ) {
296
+ return node;
297
+ }
298
+ node = node.parentNode;
299
+ }
300
+ return null;
301
+ }
302
+ function isOrContains ( parent, node ) {
303
+ while ( node ) {
304
+ if ( node === parent ) {
305
+ return true;
306
+ }
307
+ node = node.parentNode;
308
+ }
309
+ return false;
310
+ }
311
+
312
+ function getPath ( node, root ) {
313
+ var path = '';
314
+ var id, className, classNames, dir;
315
+ if ( node && node !== root ) {
316
+ path = getPath( node.parentNode, root );
317
+ if ( node.nodeType === ELEMENT_NODE ) {
318
+ path += ( path ? '>' : '' ) + node.nodeName;
319
+ if ( id = node.id ) {
320
+ path += '#' + id;
321
+ }
322
+ if ( className = node.className.trim() ) {
323
+ classNames = className.split( /\s\s*/ );
324
+ classNames.sort();
325
+ path += '.';
326
+ path += classNames.join( '.' );
327
+ }
328
+ if ( dir = node.dir ) {
329
+ path += '[dir=' + dir + ']';
330
+ }
331
+ if ( classNames ) {
332
+ if ( indexOf.call( classNames, HIGHLIGHT_CLASS ) > -1 ) {
333
+ path += '[backgroundColor=' +
334
+ node.style.backgroundColor.replace( / /g,'' ) + ']';
335
+ }
336
+ if ( indexOf.call( classNames, COLOUR_CLASS ) > -1 ) {
337
+ path += '[color=' +
338
+ node.style.color.replace( / /g,'' ) + ']';
339
+ }
340
+ if ( indexOf.call( classNames, FONT_FAMILY_CLASS ) > -1 ) {
341
+ path += '[fontFamily=' +
342
+ node.style.fontFamily.replace( / /g,'' ) + ']';
343
+ }
344
+ if ( indexOf.call( classNames, FONT_SIZE_CLASS ) > -1 ) {
345
+ path += '[fontSize=' + node.style.fontSize + ']';
346
+ }
347
+ }
348
+ }
349
+ }
350
+ return path;
351
+ }
352
+
353
+ function getLength ( node ) {
354
+ var nodeType = node.nodeType;
355
+ return nodeType === ELEMENT_NODE || nodeType === DOCUMENT_FRAGMENT_NODE ?
356
+ node.childNodes.length : node.length || 0;
357
+ }
358
+
359
+ function detach ( node ) {
360
+ var parent = node.parentNode;
361
+ if ( parent ) {
362
+ parent.removeChild( node );
363
+ }
364
+ return node;
365
+ }
366
+ function replaceWith ( node, node2 ) {
367
+ var parent = node.parentNode;
368
+ if ( parent ) {
369
+ parent.replaceChild( node2, node );
370
+ }
371
+ }
372
+ function empty ( node ) {
373
+ var frag = node.ownerDocument.createDocumentFragment(),
374
+ childNodes = node.childNodes,
375
+ l = childNodes ? childNodes.length : 0;
376
+ while ( l-- ) {
377
+ frag.appendChild( node.firstChild );
378
+ }
379
+ return frag;
380
+ }
381
+
382
+ function createElement ( doc, tag, props, children ) {
383
+ var el = doc.createElement( tag ),
384
+ attr, value, i, l;
385
+ if ( props instanceof Array ) {
386
+ children = props;
387
+ props = null;
388
+ }
389
+ if ( props ) {
390
+ for ( attr in props ) {
391
+ value = props[ attr ];
392
+ if ( value !== undefined ) {
393
+ el.setAttribute( attr, props[ attr ] );
394
+ }
395
+ }
396
+ }
397
+ if ( children ) {
398
+ for ( i = 0, l = children.length; i < l; i += 1 ) {
399
+ el.appendChild( children[i] );
400
+ }
401
+ }
402
+ return el;
403
+ }
404
+
405
+ function fixCursor ( node, root ) {
406
+ // In Webkit and Gecko, block level elements are collapsed and
407
+ // unfocussable if they have no content. To remedy this, a <BR> must be
408
+ // inserted. In Opera and IE, we just need a textnode in order for the
409
+ // cursor to appear.
410
+ var self = root.__squire__;
411
+ var doc = node.ownerDocument;
412
+ var originalNode = node;
413
+ var fixer, child;
414
+
415
+ if ( node === root ) {
416
+ if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
417
+ fixer = self.createDefaultBlock();
418
+ if ( child ) {
419
+ node.replaceChild( fixer, child );
420
+ }
421
+ else {
422
+ node.appendChild( fixer );
423
+ }
424
+ node = fixer;
425
+ fixer = null;
426
+ }
427
+ }
428
+
429
+ if ( node.nodeType === TEXT_NODE ) {
430
+ return originalNode;
431
+ }
432
+
433
+ if ( isInline( node ) ) {
434
+ child = node.firstChild;
435
+ while ( cantFocusEmptyTextNodes && child &&
436
+ child.nodeType === TEXT_NODE && !child.data ) {
437
+ node.removeChild( child );
438
+ child = node.firstChild;
439
+ }
440
+ if ( !child ) {
441
+ if ( cantFocusEmptyTextNodes ) {
442
+ fixer = doc.createTextNode( ZWS );
443
+ self._didAddZWS();
444
+ } else {
445
+ fixer = doc.createTextNode( '' );
446
+ }
447
+ }
448
+ } else {
449
+ if ( useTextFixer ) {
450
+ while ( node.nodeType !== TEXT_NODE && !isLeaf( node ) ) {
451
+ child = node.firstChild;
452
+ if ( !child ) {
453
+ fixer = doc.createTextNode( '' );
454
+ break;
455
+ }
456
+ node = child;
457
+ }
458
+ if ( node.nodeType === TEXT_NODE ) {
459
+ // Opera will collapse the block element if it contains
460
+ // just spaces (but not if it contains no data at all).
461
+ if ( /^ +$/.test( node.data ) ) {
462
+ node.data = '';
463
+ }
464
+ } else if ( isLeaf( node ) ) {
465
+ node.parentNode.insertBefore( doc.createTextNode( '' ), node );
466
+ }
467
+ }
468
+ else if ( !node.querySelector( 'BR' ) ) {
469
+ fixer = createElement( doc, 'BR' );
470
+ while ( ( child = node.lastElementChild ) && !isInline( child ) ) {
471
+ node = child;
472
+ }
473
+ }
474
+ }
475
+ if ( fixer ) {
476
+ try {
477
+ node.appendChild( fixer );
478
+ } catch ( error ) {
479
+ self.didError({
480
+ name: 'Squire: fixCursor – ' + error,
481
+ message: 'Parent: ' + node.nodeName + '/' + node.innerHTML +
482
+ ' appendChild: ' + fixer.nodeName
483
+ });
484
+ }
485
+ }
486
+
487
+ return originalNode;
488
+ }
489
+
490
+ // Recursively examine container nodes and wrap any inline children.
491
+ function fixContainer ( container, root ) {
492
+ var children = container.childNodes;
493
+ var doc = container.ownerDocument;
494
+ var wrapper = null;
495
+ var i, l, child, isBR;
496
+ var config = root.__squire__._config;
497
+
498
+ for ( i = 0, l = children.length; i < l; i += 1 ) {
499
+ child = children[i];
500
+ isBR = child.nodeName === 'BR';
501
+ if ( !isBR && isInline( child ) ) {
502
+ if ( !wrapper ) {
503
+ wrapper = createElement( doc,
504
+ config.blockTag, config.blockAttributes );
505
+ }
506
+ wrapper.appendChild( child );
507
+ i -= 1;
508
+ l -= 1;
509
+ } else if ( isBR || wrapper ) {
510
+ if ( !wrapper ) {
511
+ wrapper = createElement( doc,
512
+ config.blockTag, config.blockAttributes );
513
+ }
514
+ fixCursor( wrapper, root );
515
+ if ( isBR ) {
516
+ container.replaceChild( wrapper, child );
517
+ } else {
518
+ container.insertBefore( wrapper, child );
519
+ i += 1;
520
+ l += 1;
521
+ }
522
+ wrapper = null;
523
+ }
524
+ if ( isContainer( child ) ) {
525
+ fixContainer( child, root );
526
+ }
527
+ }
528
+ if ( wrapper ) {
529
+ container.appendChild( fixCursor( wrapper, root ) );
530
+ }
531
+ return container;
532
+ }
533
+
534
+ function split ( node, offset, stopNode, root ) {
535
+ var nodeType = node.nodeType,
536
+ parent, clone, next;
537
+ if ( nodeType === TEXT_NODE && node !== stopNode ) {
538
+ return split(
539
+ node.parentNode, node.splitText( offset ), stopNode, root );
540
+ }
541
+ if ( nodeType === ELEMENT_NODE ) {
542
+ if ( typeof( offset ) === 'number' ) {
543
+ offset = offset < node.childNodes.length ?
544
+ node.childNodes[ offset ] : null;
545
+ }
546
+ if ( node === stopNode ) {
547
+ return offset;
548
+ }
549
+
550
+ // Clone node without children
551
+ parent = node.parentNode;
552
+ clone = node.cloneNode( false );
553
+
554
+ // Add right-hand siblings to the clone
555
+ while ( offset ) {
556
+ next = offset.nextSibling;
557
+ clone.appendChild( offset );
558
+ offset = next;
559
+ }
560
+
561
+ // Maintain li numbering if inside a quote.
562
+ if ( node.nodeName === 'OL' &&
563
+ getNearest( node, root, 'BLOCKQUOTE' ) ) {
564
+ clone.start = ( +node.start || 1 ) + node.childNodes.length - 1;
565
+ }
566
+
567
+ // DO NOT NORMALISE. This may undo the fixCursor() call
568
+ // of a node lower down the tree!
569
+
570
+ // We need something in the element in order for the cursor to appear.
571
+ fixCursor( node, root );
572
+ fixCursor( clone, root );
573
+
574
+ // Inject clone after original node
575
+ if ( next = node.nextSibling ) {
576
+ parent.insertBefore( clone, next );
577
+ } else {
578
+ parent.appendChild( clone );
579
+ }
580
+
581
+ // Keep on splitting up the tree
582
+ return split( parent, clone, stopNode, root );
583
+ }
584
+ return offset;
585
+ }
586
+
587
+ function _mergeInlines ( node, fakeRange ) {
588
+ var children = node.childNodes,
589
+ l = children.length,
590
+ frags = [],
591
+ child, prev, len;
592
+ while ( l-- ) {
593
+ child = children[l];
594
+ prev = l && children[ l - 1 ];
595
+ if ( l && isInline( child ) && areAlike( child, prev ) &&
596
+ !leafNodeNames[ child.nodeName ] ) {
597
+ if ( fakeRange.startContainer === child ) {
598
+ fakeRange.startContainer = prev;
599
+ fakeRange.startOffset += getLength( prev );
600
+ }
601
+ if ( fakeRange.endContainer === child ) {
602
+ fakeRange.endContainer = prev;
603
+ fakeRange.endOffset += getLength( prev );
604
+ }
605
+ if ( fakeRange.startContainer === node ) {
606
+ if ( fakeRange.startOffset > l ) {
607
+ fakeRange.startOffset -= 1;
608
+ }
609
+ else if ( fakeRange.startOffset === l ) {
610
+ fakeRange.startContainer = prev;
611
+ fakeRange.startOffset = getLength( prev );
612
+ }
613
+ }
614
+ if ( fakeRange.endContainer === node ) {
615
+ if ( fakeRange.endOffset > l ) {
616
+ fakeRange.endOffset -= 1;
617
+ }
618
+ else if ( fakeRange.endOffset === l ) {
619
+ fakeRange.endContainer = prev;
620
+ fakeRange.endOffset = getLength( prev );
621
+ }
622
+ }
623
+ detach( child );
624
+ if ( child.nodeType === TEXT_NODE ) {
625
+ prev.appendData( child.data );
626
+ }
627
+ else {
628
+ frags.push( empty( child ) );
629
+ }
630
+ }
631
+ else if ( child.nodeType === ELEMENT_NODE ) {
632
+ len = frags.length;
633
+ while ( len-- ) {
634
+ child.appendChild( frags.pop() );
635
+ }
636
+ _mergeInlines( child, fakeRange );
637
+ }
638
+ }
639
+ }
640
+
641
+ function mergeInlines ( node, range ) {
642
+ if ( node.nodeType === TEXT_NODE ) {
643
+ node = node.parentNode;
644
+ }
645
+ if ( node.nodeType === ELEMENT_NODE ) {
646
+ var fakeRange = {
647
+ startContainer: range.startContainer,
648
+ startOffset: range.startOffset,
649
+ endContainer: range.endContainer,
650
+ endOffset: range.endOffset
651
+ };
652
+ _mergeInlines( node, fakeRange );
653
+ range.setStart( fakeRange.startContainer, fakeRange.startOffset );
654
+ range.setEnd( fakeRange.endContainer, fakeRange.endOffset );
655
+ }
656
+ }
657
+
658
+ function mergeWithBlock ( block, next, range, root ) {
659
+ var container = next;
660
+ var parent, last, offset;
661
+ while ( ( parent = container.parentNode ) &&
662
+ parent !== root &&
663
+ parent.nodeType === ELEMENT_NODE &&
664
+ parent.childNodes.length === 1 ) {
665
+ container = parent;
666
+ }
667
+ detach( container );
668
+
669
+ offset = block.childNodes.length;
670
+
671
+ // Remove extra <BR> fixer if present.
672
+ last = block.lastChild;
673
+ if ( last && last.nodeName === 'BR' ) {
674
+ block.removeChild( last );
675
+ offset -= 1;
676
+ }
677
+
678
+ block.appendChild( empty( next ) );
679
+
680
+ range.setStart( block, offset );
681
+ range.collapse( true );
682
+ mergeInlines( block, range );
683
+
684
+ // Opera inserts a BR if you delete the last piece of text
685
+ // in a block-level element. Unfortunately, it then gets
686
+ // confused when setting the selection subsequently and
687
+ // refuses to accept the range that finishes just before the
688
+ // BR. Removing the BR fixes the bug.
689
+ // Steps to reproduce bug: Type "a-b-c" (where - is return)
690
+ // then backspace twice. The cursor goes to the top instead
691
+ // of after "b".
692
+ if ( isPresto && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
693
+ block.removeChild( last );
694
+ }
695
+ }
696
+
697
+ function mergeContainers ( node, root ) {
698
+ var prev = node.previousSibling,
699
+ first = node.firstChild,
700
+ doc = node.ownerDocument,
701
+ isListItem = ( node.nodeName === 'LI' ),
702
+ needsFix, block;
703
+
704
+ // Do not merge LIs, unless it only contains a UL
705
+ if ( isListItem && ( !first || !/^[OU]L$/.test( first.nodeName ) ) ) {
706
+ return;
707
+ }
708
+
709
+ if ( prev && areAlike( prev, node ) ) {
710
+ if ( !isContainer( prev ) ) {
711
+ if ( isListItem ) {
712
+ block = createElement( doc, 'DIV' );
713
+ block.appendChild( empty( prev ) );
714
+ prev.appendChild( block );
715
+ } else {
716
+ return;
717
+ }
718
+ }
719
+ detach( node );
720
+ needsFix = !isContainer( node );
721
+ prev.appendChild( empty( node ) );
722
+ if ( needsFix ) {
723
+ fixContainer( prev, root );
724
+ }
725
+ if ( first ) {
726
+ mergeContainers( first, root );
727
+ }
728
+ } else if ( isListItem ) {
729
+ prev = createElement( doc, 'DIV' );
730
+ node.insertBefore( prev, first );
731
+ fixCursor( prev, root );
732
+ }
733
+ }
734
+
735
+ var getNodeBefore = function ( node, offset ) {
736
+ var children = node.childNodes;
737
+ while ( offset && node.nodeType === ELEMENT_NODE ) {
738
+ node = children[ offset - 1 ];
739
+ children = node.childNodes;
740
+ offset = children.length;
741
+ }
742
+ return node;
743
+ };
744
+
745
+ var getNodeAfter = function ( node, offset ) {
746
+ if ( node.nodeType === ELEMENT_NODE ) {
747
+ var children = node.childNodes;
748
+ if ( offset < children.length ) {
749
+ node = children[ offset ];
750
+ } else {
751
+ while ( node && !node.nextSibling ) {
752
+ node = node.parentNode;
753
+ }
754
+ if ( node ) { node = node.nextSibling; }
755
+ }
756
+ }
757
+ return node;
758
+ };
759
+
760
+ // ---
761
+
762
+ var insertNodeInRange = function ( range, node ) {
763
+ // Insert at start.
764
+ var startContainer = range.startContainer,
765
+ startOffset = range.startOffset,
766
+ endContainer = range.endContainer,
767
+ endOffset = range.endOffset,
768
+ parent, children, childCount, afterSplit;
769
+
770
+ // If part way through a text node, split it.
771
+ if ( startContainer.nodeType === TEXT_NODE ) {
772
+ parent = startContainer.parentNode;
773
+ children = parent.childNodes;
774
+ if ( startOffset === startContainer.length ) {
775
+ startOffset = indexOf.call( children, startContainer ) + 1;
776
+ if ( range.collapsed ) {
777
+ endContainer = parent;
778
+ endOffset = startOffset;
779
+ }
780
+ } else {
781
+ if ( startOffset ) {
782
+ afterSplit = startContainer.splitText( startOffset );
783
+ if ( endContainer === startContainer ) {
784
+ endOffset -= startOffset;
785
+ endContainer = afterSplit;
786
+ }
787
+ else if ( endContainer === parent ) {
788
+ endOffset += 1;
789
+ }
790
+ startContainer = afterSplit;
791
+ }
792
+ startOffset = indexOf.call( children, startContainer );
793
+ }
794
+ startContainer = parent;
795
+ } else {
796
+ children = startContainer.childNodes;
797
+ }
798
+
799
+ childCount = children.length;
800
+
801
+ if ( startOffset === childCount ) {
802
+ startContainer.appendChild( node );
803
+ } else {
804
+ startContainer.insertBefore( node, children[ startOffset ] );
805
+ }
806
+
807
+ if ( startContainer === endContainer ) {
808
+ endOffset += children.length - childCount;
809
+ }
810
+
811
+ range.setStart( startContainer, startOffset );
812
+ range.setEnd( endContainer, endOffset );
813
+ };
814
+
815
+ var extractContentsOfRange = function ( range, common, root ) {
816
+ var startContainer = range.startContainer,
817
+ startOffset = range.startOffset,
818
+ endContainer = range.endContainer,
819
+ endOffset = range.endOffset;
820
+
821
+ if ( !common ) {
822
+ common = range.commonAncestorContainer;
823
+ }
824
+
825
+ if ( common.nodeType === TEXT_NODE ) {
826
+ common = common.parentNode;
827
+ }
828
+
829
+ var endNode = split( endContainer, endOffset, common, root ),
830
+ startNode = split( startContainer, startOffset, common, root ),
831
+ frag = common.ownerDocument.createDocumentFragment(),
832
+ next, before, after;
833
+
834
+ // End node will be null if at end of child nodes list.
835
+ while ( startNode !== endNode ) {
836
+ next = startNode.nextSibling;
837
+ frag.appendChild( startNode );
838
+ startNode = next;
839
+ }
840
+
841
+ startContainer = common;
842
+ startOffset = endNode ?
843
+ indexOf.call( common.childNodes, endNode ) :
844
+ common.childNodes.length;
845
+
846
+ // Merge text nodes if adjacent. IE10 in particular will not focus
847
+ // between two text nodes
848
+ after = common.childNodes[ startOffset ];
849
+ before = after && after.previousSibling;
850
+ if ( before &&
851
+ before.nodeType === TEXT_NODE &&
852
+ after.nodeType === TEXT_NODE ) {
853
+ startContainer = before;
854
+ startOffset = before.length;
855
+ before.appendData( after.data );
856
+ detach( after );
857
+ }
858
+
859
+ range.setStart( startContainer, startOffset );
860
+ range.collapse( true );
861
+
862
+ fixCursor( common, root );
863
+
864
+ return frag;
865
+ };
866
+
867
+ var deleteContentsOfRange = function ( range, root ) {
868
+ var startBlock = getStartBlockOfRange( range, root );
869
+ var endBlock = getEndBlockOfRange( range, root );
870
+ var needsMerge = ( startBlock !== endBlock );
871
+ var frag, child;
872
+
873
+ // Move boundaries up as much as possible without exiting block,
874
+ // to reduce need to split.
875
+ moveRangeBoundariesDownTree( range );
876
+ moveRangeBoundariesUpTree( range, startBlock, endBlock, root );
877
+
878
+ // Remove selected range
879
+ frag = extractContentsOfRange( range, null, root );
880
+
881
+ // Move boundaries back down tree as far as possible.
882
+ moveRangeBoundariesDownTree( range );
883
+
884
+ // If we split into two different blocks, merge the blocks.
885
+ if ( needsMerge ) {
886
+ // endBlock will have been split, so need to refetch
887
+ endBlock = getEndBlockOfRange( range, root );
888
+ if ( startBlock && endBlock && startBlock !== endBlock ) {
889
+ mergeWithBlock( startBlock, endBlock, range, root );
890
+ }
891
+ }
892
+
893
+ // Ensure block has necessary children
894
+ if ( startBlock ) {
895
+ fixCursor( startBlock, root );
896
+ }
897
+
898
+ // Ensure root has a block-level element in it.
899
+ child = root.firstChild;
900
+ if ( !child || child.nodeName === 'BR' ) {
901
+ fixCursor( root, root );
902
+ range.selectNodeContents( root.firstChild );
903
+ } else {
904
+ range.collapse( true );
905
+ }
906
+ return frag;
907
+ };
908
+
909
+ // ---
910
+
911
+ // Contents of range will be deleted.
912
+ // After method, range will be around inserted content
913
+ var insertTreeFragmentIntoRange = function ( range, frag, root ) {
914
+ var node, block, blockContentsAfterSplit, stopPoint, container, offset;
915
+ var replaceBlock, firstBlockInFrag, nodeAfterSplit, nodeBeforeSplit;
916
+ var tempRange;
917
+
918
+ // Fixup content: ensure no top-level inline, and add cursor fix elements.
919
+ fixContainer( frag, root );
920
+ node = frag;
921
+ while ( ( node = getNextBlock( node, root ) ) ) {
922
+ fixCursor( node, root );
923
+ }
924
+
925
+ // Delete any selected content.
926
+ if ( !range.collapsed ) {
927
+ deleteContentsOfRange( range, root );
928
+ }
929
+
930
+ // Move range down into text nodes.
931
+ moveRangeBoundariesDownTree( range );
932
+ range.collapse( false ); // collapse to end
933
+
934
+ // Where will we split up to? First blockquote parent, otherwise root.
935
+ stopPoint = getNearest( range.endContainer, root, 'BLOCKQUOTE' ) || root;
936
+
937
+ // Merge the contents of the first block in the frag with the focused block.
938
+ // If there are contents in the block after the focus point, collect this
939
+ // up to insert in the last block later. If the block is empty, replace
940
+ // it instead of merging.
941
+ block = getStartBlockOfRange( range, root );
942
+ firstBlockInFrag = getNextBlock( frag, frag );
943
+ replaceBlock = !!block && isEmptyBlock( block );
944
+ if ( block && firstBlockInFrag && !replaceBlock &&
945
+ // Don't merge table cells or PRE elements into block
946
+ !getNearest( firstBlockInFrag, frag, 'PRE' ) &&
947
+ !getNearest( firstBlockInFrag, frag, 'TABLE' ) ) {
948
+ moveRangeBoundariesUpTree( range, block, block, root );
949
+ range.collapse( true ); // collapse to start
950
+ container = range.endContainer;
951
+ offset = range.endOffset;
952
+ // Remove trailing <br> – we don't want this considered content to be
953
+ // inserted again later
954
+ cleanupBRs( block, root, false );
955
+ if ( isInline( container ) ) {
956
+ // Split up to block parent.
957
+ nodeAfterSplit = split(
958
+ container, offset, getPreviousBlock( container, root ), root );
959
+ container = nodeAfterSplit.parentNode;
960
+ offset = indexOf.call( container.childNodes, nodeAfterSplit );
961
+ }
962
+ if ( /*isBlock( container ) && */offset !== getLength( container ) ) {
963
+ // Collect any inline contents of the block after the range point
964
+ blockContentsAfterSplit =
965
+ root.ownerDocument.createDocumentFragment();
966
+ while ( ( node = container.childNodes[ offset ] ) ) {
967
+ blockContentsAfterSplit.appendChild( node );
968
+ }
969
+ }
970
+ // And merge the first block in.
971
+ mergeWithBlock( container, firstBlockInFrag, range, root );
972
+
973
+ // And where we will insert
974
+ offset = indexOf.call( container.parentNode.childNodes, container ) + 1;
975
+ container = container.parentNode;
976
+ range.setEnd( container, offset );
977
+ }
978
+
979
+ // Is there still any content in the fragment?
980
+ if ( getLength( frag ) ) {
981
+ if ( replaceBlock ) {
982
+ range.setEndBefore( block );
983
+ range.collapse( false );
984
+ detach( block );
985
+ }
986
+ moveRangeBoundariesUpTree( range, stopPoint, stopPoint, root );
987
+ // Now split after block up to blockquote (if a parent) or root
988
+ nodeAfterSplit = split(
989
+ range.endContainer, range.endOffset, stopPoint, root );
990
+ nodeBeforeSplit = nodeAfterSplit ?
991
+ nodeAfterSplit.previousSibling :
992
+ stopPoint.lastChild;
993
+ stopPoint.insertBefore( frag, nodeAfterSplit );
994
+ if ( nodeAfterSplit ) {
995
+ range.setEndBefore( nodeAfterSplit );
996
+ } else {
997
+ range.setEnd( stopPoint, getLength( stopPoint ) );
998
+ }
999
+ block = getEndBlockOfRange( range, root );
1000
+
1001
+ // Get a reference that won't be invalidated if we merge containers.
1002
+ moveRangeBoundariesDownTree( range );
1003
+ container = range.endContainer;
1004
+ offset = range.endOffset;
1005
+
1006
+ // Merge inserted containers with edges of split
1007
+ if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
1008
+ mergeContainers( nodeAfterSplit, root );
1009
+ }
1010
+ nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling;
1011
+ if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
1012
+ mergeContainers( nodeAfterSplit, root );
1013
+ }
1014
+ range.setEnd( container, offset );
1015
+ }
1016
+
1017
+ // Insert inline content saved from before.
1018
+ if ( blockContentsAfterSplit ) {
1019
+ tempRange = range.cloneRange();
1020
+ mergeWithBlock( block, blockContentsAfterSplit, tempRange, root );
1021
+ range.setEnd( tempRange.endContainer, tempRange.endOffset );
1022
+ }
1023
+ moveRangeBoundariesDownTree( range );
1024
+ };
1025
+
1026
+ // ---
1027
+
1028
+ var isNodeContainedInRange = function ( range, node, partial ) {
1029
+ var nodeRange = node.ownerDocument.createRange();
1030
+
1031
+ nodeRange.selectNode( node );
1032
+
1033
+ if ( partial ) {
1034
+ // Node must not finish before range starts or start after range
1035
+ // finishes.
1036
+ var nodeEndBeforeStart = ( range.compareBoundaryPoints(
1037
+ END_TO_START, nodeRange ) > -1 ),
1038
+ nodeStartAfterEnd = ( range.compareBoundaryPoints(
1039
+ START_TO_END, nodeRange ) < 1 );
1040
+ return ( !nodeEndBeforeStart && !nodeStartAfterEnd );
1041
+ }
1042
+ else {
1043
+ // Node must start after range starts and finish before range
1044
+ // finishes
1045
+ var nodeStartAfterStart = ( range.compareBoundaryPoints(
1046
+ START_TO_START, nodeRange ) < 1 ),
1047
+ nodeEndBeforeEnd = ( range.compareBoundaryPoints(
1048
+ END_TO_END, nodeRange ) > -1 );
1049
+ return ( nodeStartAfterStart && nodeEndBeforeEnd );
1050
+ }
1051
+ };
1052
+
1053
+ var moveRangeBoundariesDownTree = function ( range ) {
1054
+ var startContainer = range.startContainer,
1055
+ startOffset = range.startOffset,
1056
+ endContainer = range.endContainer,
1057
+ endOffset = range.endOffset,
1058
+ maySkipBR = true,
1059
+ child;
1060
+
1061
+ while ( startContainer.nodeType !== TEXT_NODE ) {
1062
+ child = startContainer.childNodes[ startOffset ];
1063
+ if ( !child || isLeaf( child ) ) {
1064
+ break;
1065
+ }
1066
+ startContainer = child;
1067
+ startOffset = 0;
1068
+ }
1069
+ if ( endOffset ) {
1070
+ while ( endContainer.nodeType !== TEXT_NODE ) {
1071
+ child = endContainer.childNodes[ endOffset - 1 ];
1072
+ if ( !child || isLeaf( child ) ) {
1073
+ if ( maySkipBR && child && child.nodeName === 'BR' ) {
1074
+ endOffset -= 1;
1075
+ maySkipBR = false;
1076
+ continue;
1077
+ }
1078
+ break;
1079
+ }
1080
+ endContainer = child;
1081
+ endOffset = getLength( endContainer );
1082
+ }
1083
+ } else {
1084
+ while ( endContainer.nodeType !== TEXT_NODE ) {
1085
+ child = endContainer.firstChild;
1086
+ if ( !child || isLeaf( child ) ) {
1087
+ break;
1088
+ }
1089
+ endContainer = child;
1090
+ }
1091
+ }
1092
+
1093
+ // If collapsed, this algorithm finds the nearest text node positions
1094
+ // *outside* the range rather than inside, but also it flips which is
1095
+ // assigned to which.
1096
+ if ( range.collapsed ) {
1097
+ range.setStart( endContainer, endOffset );
1098
+ range.setEnd( startContainer, startOffset );
1099
+ } else {
1100
+ range.setStart( startContainer, startOffset );
1101
+ range.setEnd( endContainer, endOffset );
1102
+ }
1103
+ };
1104
+
1105
+ var moveRangeBoundariesUpTree = function ( range, startMax, endMax, root ) {
1106
+ var startContainer = range.startContainer;
1107
+ var startOffset = range.startOffset;
1108
+ var endContainer = range.endContainer;
1109
+ var endOffset = range.endOffset;
1110
+ var maySkipBR = true;
1111
+ var parent;
1112
+
1113
+ if ( !startMax ) {
1114
+ startMax = range.commonAncestorContainer;
1115
+ }
1116
+ if ( !endMax ) {
1117
+ endMax = startMax;
1118
+ }
1119
+
1120
+ while ( !startOffset &&
1121
+ startContainer !== startMax &&
1122
+ startContainer !== root ) {
1123
+ parent = startContainer.parentNode;
1124
+ startOffset = indexOf.call( parent.childNodes, startContainer );
1125
+ startContainer = parent;
1126
+ }
1127
+
1128
+ while ( true ) {
1129
+ if ( maySkipBR &&
1130
+ endContainer.nodeType !== TEXT_NODE &&
1131
+ endContainer.childNodes[ endOffset ] &&
1132
+ endContainer.childNodes[ endOffset ].nodeName === 'BR' ) {
1133
+ endOffset += 1;
1134
+ maySkipBR = false;
1135
+ }
1136
+ if ( endContainer === endMax ||
1137
+ endContainer === root ||
1138
+ endOffset !== getLength( endContainer ) ) {
1139
+ break;
1140
+ }
1141
+ parent = endContainer.parentNode;
1142
+ endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
1143
+ endContainer = parent;
1144
+ }
1145
+
1146
+ range.setStart( startContainer, startOffset );
1147
+ range.setEnd( endContainer, endOffset );
1148
+ };
1149
+
1150
+ // Returns the first block at least partially contained by the range,
1151
+ // or null if no block is contained by the range.
1152
+ var getStartBlockOfRange = function ( range, root ) {
1153
+ var container = range.startContainer,
1154
+ block;
1155
+
1156
+ // If inline, get the containing block.
1157
+ if ( isInline( container ) ) {
1158
+ block = getPreviousBlock( container, root );
1159
+ } else if ( container !== root && isBlock( container ) ) {
1160
+ block = container;
1161
+ } else {
1162
+ block = getNodeBefore( container, range.startOffset );
1163
+ block = getNextBlock( block, root );
1164
+ }
1165
+ // Check the block actually intersects the range
1166
+ return block && isNodeContainedInRange( range, block, true ) ? block : null;
1167
+ };
1168
+
1169
+ // Returns the last block at least partially contained by the range,
1170
+ // or null if no block is contained by the range.
1171
+ var getEndBlockOfRange = function ( range, root ) {
1172
+ var container = range.endContainer,
1173
+ block, child;
1174
+
1175
+ // If inline, get the containing block.
1176
+ if ( isInline( container ) ) {
1177
+ block = getPreviousBlock( container, root );
1178
+ } else if ( container !== root && isBlock( container ) ) {
1179
+ block = container;
1180
+ } else {
1181
+ block = getNodeAfter( container, range.endOffset );
1182
+ if ( !block || !isOrContains( root, block ) ) {
1183
+ block = root;
1184
+ while ( child = block.lastChild ) {
1185
+ block = child;
1186
+ }
1187
+ }
1188
+ block = getPreviousBlock( block, root );
1189
+ }
1190
+ // Check the block actually intersects the range
1191
+ return block && isNodeContainedInRange( range, block, true ) ? block : null;
1192
+ };
1193
+
1194
+ var contentWalker = new TreeWalker( null,
1195
+ SHOW_TEXT|SHOW_ELEMENT,
1196
+ function ( node ) {
1197
+ return node.nodeType === TEXT_NODE ?
1198
+ notWS.test( node.data ) :
1199
+ node.nodeName === 'IMG';
1200
+ }
1201
+ );
1202
+
1203
+ var rangeDoesStartAtBlockBoundary = function ( range, root ) {
1204
+ var startContainer = range.startContainer;
1205
+ var startOffset = range.startOffset;
1206
+ var nodeAfterCursor;
1207
+
1208
+ // If in the middle or end of a text node, we're not at the boundary.
1209
+ contentWalker.root = null;
1210
+ if ( startContainer.nodeType === TEXT_NODE ) {
1211
+ if ( startOffset ) {
1212
+ return false;
1213
+ }
1214
+ nodeAfterCursor = startContainer;
1215
+ } else {
1216
+ nodeAfterCursor = getNodeAfter( startContainer, startOffset );
1217
+ if ( nodeAfterCursor && !isOrContains( root, nodeAfterCursor ) ) {
1218
+ nodeAfterCursor = null;
1219
+ }
1220
+ // The cursor was right at the end of the document
1221
+ if ( !nodeAfterCursor ) {
1222
+ nodeAfterCursor = getNodeBefore( startContainer, startOffset );
1223
+ if ( nodeAfterCursor.nodeType === TEXT_NODE &&
1224
+ nodeAfterCursor.length ) {
1225
+ return false;
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // Otherwise, look for any previous content in the same block.
1231
+ contentWalker.currentNode = nodeAfterCursor;
1232
+ contentWalker.root = getStartBlockOfRange( range, root );
1233
+
1234
+ return !contentWalker.previousNode();
1235
+ };
1236
+
1237
+ var rangeDoesEndAtBlockBoundary = function ( range, root ) {
1238
+ var endContainer = range.endContainer,
1239
+ endOffset = range.endOffset,
1240
+ length;
1241
+
1242
+ // If in a text node with content, and not at the end, we're not
1243
+ // at the boundary
1244
+ contentWalker.root = null;
1245
+ if ( endContainer.nodeType === TEXT_NODE ) {
1246
+ length = endContainer.data.length;
1247
+ if ( length && endOffset < length ) {
1248
+ return false;
1249
+ }
1250
+ contentWalker.currentNode = endContainer;
1251
+ } else {
1252
+ contentWalker.currentNode = getNodeBefore( endContainer, endOffset );
1253
+ }
1254
+
1255
+ // Otherwise, look for any further content in the same block.
1256
+ contentWalker.root = getEndBlockOfRange( range, root );
1257
+
1258
+ return !contentWalker.nextNode();
1259
+ };
1260
+
1261
+ var expandRangeToBlockBoundaries = function ( range, root ) {
1262
+ var start = getStartBlockOfRange( range, root ),
1263
+ end = getEndBlockOfRange( range, root ),
1264
+ parent;
1265
+
1266
+ if ( start && end ) {
1267
+ parent = start.parentNode;
1268
+ range.setStart( parent, indexOf.call( parent.childNodes, start ) );
1269
+ parent = end.parentNode;
1270
+ range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
1271
+ }
1272
+ };
1273
+
1274
+ var keys = {
1275
+ 8: 'backspace',
1276
+ 9: 'tab',
1277
+ 13: 'enter',
1278
+ 32: 'space',
1279
+ 33: 'pageup',
1280
+ 34: 'pagedown',
1281
+ 37: 'left',
1282
+ 39: 'right',
1283
+ 46: 'delete',
1284
+ 219: '[',
1285
+ 221: ']'
1286
+ };
1287
+
1288
+ // Ref: http://unixpapa.com/js/key.html
1289
+ var onKey = function ( event ) {
1290
+ var code = event.keyCode,
1291
+ key = keys[ code ],
1292
+ modifiers = '',
1293
+ range = this.getSelection();
1294
+
1295
+ if ( event.defaultPrevented ) {
1296
+ return;
1297
+ }
1298
+
1299
+ if ( !key ) {
1300
+ key = String.fromCharCode( code ).toLowerCase();
1301
+ // Only reliable for letters and numbers
1302
+ if ( !/^[A-Za-z0-9]$/.test( key ) ) {
1303
+ key = '';
1304
+ }
1305
+ }
1306
+
1307
+ // On keypress, delete and '.' both have event.keyCode 46
1308
+ // Must check event.which to differentiate.
1309
+ if ( isPresto && event.which === 46 ) {
1310
+ key = '.';
1311
+ }
1312
+
1313
+ // Function keys
1314
+ if ( 111 < code && code < 124 ) {
1315
+ key = 'f' + ( code - 111 );
1316
+ }
1317
+
1318
+ // We need to apply the backspace/delete handlers regardless of
1319
+ // control key modifiers.
1320
+ if ( key !== 'backspace' && key !== 'delete' ) {
1321
+ if ( event.altKey ) { modifiers += 'alt-'; }
1322
+ if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
1323
+ if ( event.metaKey ) { modifiers += 'meta-'; }
1324
+ }
1325
+ // However, on Windows, shift-delete is apparently "cut" (WTF right?), so
1326
+ // we want to let the browser handle shift-delete.
1327
+ if ( event.shiftKey ) { modifiers += 'shift-'; }
1328
+
1329
+ key = modifiers + key;
1330
+
1331
+ if ( this._keyHandlers[ key ] ) {
1332
+ this._keyHandlers[ key ]( this, event, range );
1333
+ } else if ( key.length === 1 && !range.collapsed ) {
1334
+ // Record undo checkpoint.
1335
+ this.saveUndoState( range );
1336
+ // Delete the selection
1337
+ deleteContentsOfRange( range, this._root );
1338
+ this._ensureBottomLine();
1339
+ this.setSelection( range );
1340
+ this._updatePath( range, true );
1341
+ }
1342
+ };
1343
+
1344
+ var mapKeyTo = function ( method ) {
1345
+ return function ( self, event ) {
1346
+ event.preventDefault();
1347
+ self[ method ]();
1348
+ };
1349
+ };
1350
+
1351
+ var mapKeyToFormat = function ( tag, remove ) {
1352
+ remove = remove || null;
1353
+ return function ( self, event ) {
1354
+ event.preventDefault();
1355
+ var range = self.getSelection();
1356
+ if ( self.hasFormat( tag, null, range ) ) {
1357
+ self.changeFormat( null, { tag: tag }, range );
1358
+ } else {
1359
+ self.changeFormat( { tag: tag }, remove, range );
1360
+ }
1361
+ };
1362
+ };
1363
+
1364
+ // If you delete the content inside a span with a font styling, Webkit will
1365
+ // replace it with a <font> tag (!). If you delete all the text inside a
1366
+ // link in Opera, it won't delete the link. Let's make things consistent. If
1367
+ // you delete all text inside an inline tag, remove the inline tag.
1368
+ var afterDelete = function ( self, range ) {
1369
+ try {
1370
+ if ( !range ) { range = self.getSelection(); }
1371
+ var node = range.startContainer,
1372
+ parent;
1373
+ // Climb the tree from the focus point while we are inside an empty
1374
+ // inline element
1375
+ if ( node.nodeType === TEXT_NODE ) {
1376
+ node = node.parentNode;
1377
+ }
1378
+ parent = node;
1379
+ while ( isInline( parent ) &&
1380
+ ( !parent.textContent || parent.textContent === ZWS ) ) {
1381
+ node = parent;
1382
+ parent = node.parentNode;
1383
+ }
1384
+ // If focused in empty inline element
1385
+ if ( node !== parent ) {
1386
+ // Move focus to just before empty inline(s)
1387
+ range.setStart( parent,
1388
+ indexOf.call( parent.childNodes, node ) );
1389
+ range.collapse( true );
1390
+ // Remove empty inline(s)
1391
+ parent.removeChild( node );
1392
+ // Fix cursor in block
1393
+ if ( !isBlock( parent ) ) {
1394
+ parent = getPreviousBlock( parent, self._root );
1395
+ }
1396
+ fixCursor( parent, self._root );
1397
+ // Move cursor into text node
1398
+ moveRangeBoundariesDownTree( range );
1399
+ }
1400
+ // If you delete the last character in the sole <div> in Chrome,
1401
+ // it removes the div and replaces it with just a <br> inside the
1402
+ // root. Detach the <br>; the _ensureBottomLine call will insert a new
1403
+ // block.
1404
+ if ( node === self._root &&
1405
+ ( node = node.firstChild ) && node.nodeName === 'BR' ) {
1406
+ detach( node );
1407
+ }
1408
+ self._ensureBottomLine();
1409
+ self.setSelection( range );
1410
+ self._updatePath( range, true );
1411
+ } catch ( error ) {
1412
+ self.didError( error );
1413
+ }
1414
+ };
1415
+
1416
+ var keyHandlers = {
1417
+ enter: function ( self, event, range ) {
1418
+ var root = self._root;
1419
+ var block, parent, nodeAfterSplit;
1420
+
1421
+ // We handle this ourselves
1422
+ event.preventDefault();
1423
+
1424
+ // Save undo checkpoint and add any links in the preceding section.
1425
+ // Remove any zws so we don't think there's content in an empty
1426
+ // block.
1427
+ self._recordUndoState( range );
1428
+ addLinks( range.startContainer, root, self );
1429
+ self._removeZWS();
1430
+ self._getRangeAndRemoveBookmark( range );
1431
+
1432
+ // Selected text is overwritten, therefore delete the contents
1433
+ // to collapse selection.
1434
+ if ( !range.collapsed ) {
1435
+ deleteContentsOfRange( range, root );
1436
+ }
1437
+
1438
+ block = getStartBlockOfRange( range, root );
1439
+
1440
+ // If this is a malformed bit of document or in a table;
1441
+ // just play it safe and insert a <br>.
1442
+ if ( !block || /^T[HD]$/.test( block.nodeName ) ) {
1443
+ // If inside an <a>, move focus out
1444
+ parent = getNearest( range.endContainer, root, 'A' );
1445
+ if ( parent ) {
1446
+ parent = parent.parentNode;
1447
+ moveRangeBoundariesUpTree( range, parent, parent, root );
1448
+ range.collapse( false );
1449
+ }
1450
+ insertNodeInRange( range, self.createElement( 'BR' ) );
1451
+ range.collapse( false );
1452
+ self.setSelection( range );
1453
+ self._updatePath( range, true );
1454
+ return;
1455
+ }
1456
+
1457
+ // If in a list, we'll split the LI instead.
1458
+ if ( parent = getNearest( block, root, 'LI' ) ) {
1459
+ block = parent;
1460
+ }
1461
+
1462
+ if ( isEmptyBlock( block ) ) {
1463
+ // Break list
1464
+ if ( getNearest( block, root, 'UL' ) ||
1465
+ getNearest( block, root, 'OL' ) ) {
1466
+ return self.decreaseListLevel( range );
1467
+ }
1468
+ // Break blockquote
1469
+ else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) {
1470
+ return self.modifyBlocks( removeBlockQuote, range );
1471
+ }
1472
+ }
1473
+
1474
+ // Otherwise, split at cursor point.
1475
+ nodeAfterSplit = splitBlock( self, block,
1476
+ range.startContainer, range.startOffset );
1477
+
1478
+ // Clean up any empty inlines if we hit enter at the beginning of the
1479
+ // block
1480
+ removeZWS( block );
1481
+ removeEmptyInlines( block );
1482
+ fixCursor( block, root );
1483
+
1484
+ // Focus cursor
1485
+ // If there's a <b>/<i> etc. at the beginning of the split
1486
+ // make sure we focus inside it.
1487
+ while ( nodeAfterSplit.nodeType === ELEMENT_NODE ) {
1488
+ var child = nodeAfterSplit.firstChild,
1489
+ next;
1490
+
1491
+ // Don't continue links over a block break; unlikely to be the
1492
+ // desired outcome.
1493
+ if ( nodeAfterSplit.nodeName === 'A' &&
1494
+ ( !nodeAfterSplit.textContent ||
1495
+ nodeAfterSplit.textContent === ZWS ) ) {
1496
+ child = self._doc.createTextNode( '' );
1497
+ replaceWith( nodeAfterSplit, child );
1498
+ nodeAfterSplit = child;
1499
+ break;
1500
+ }
1501
+
1502
+ while ( child && child.nodeType === TEXT_NODE && !child.data ) {
1503
+ next = child.nextSibling;
1504
+ if ( !next || next.nodeName === 'BR' ) {
1505
+ break;
1506
+ }
1507
+ detach( child );
1508
+ child = next;
1509
+ }
1510
+
1511
+ // 'BR's essentially don't count; they're a browser hack.
1512
+ // If you try to select the contents of a 'BR', FF will not let
1513
+ // you type anything!
1514
+ if ( !child || child.nodeName === 'BR' ||
1515
+ ( child.nodeType === TEXT_NODE && !isPresto ) ) {
1516
+ break;
1517
+ }
1518
+ nodeAfterSplit = child;
1519
+ }
1520
+ range = self._createRange( nodeAfterSplit, 0 );
1521
+ self.setSelection( range );
1522
+ self._updatePath( range, true );
1523
+ },
1524
+ backspace: function ( self, event, range ) {
1525
+ var root = self._root;
1526
+ self._removeZWS();
1527
+ // Record undo checkpoint.
1528
+ self.saveUndoState( range );
1529
+ // If not collapsed, delete contents
1530
+ if ( !range.collapsed ) {
1531
+ event.preventDefault();
1532
+ deleteContentsOfRange( range, root );
1533
+ afterDelete( self, range );
1534
+ }
1535
+ // If at beginning of block, merge with previous
1536
+ else if ( rangeDoesStartAtBlockBoundary( range, root ) ) {
1537
+ event.preventDefault();
1538
+ var current = getStartBlockOfRange( range, root );
1539
+ var previous;
1540
+ if ( !current ) {
1541
+ return;
1542
+ }
1543
+ // In case inline data has somehow got between blocks.
1544
+ fixContainer( current.parentNode, root );
1545
+ // Now get previous block
1546
+ previous = getPreviousBlock( current, root );
1547
+ // Must not be at the very beginning of the text area.
1548
+ if ( previous ) {
1549
+ // If not editable, just delete whole block.
1550
+ if ( !previous.isContentEditable ) {
1551
+ detach( previous );
1552
+ return;
1553
+ }
1554
+ // Otherwise merge.
1555
+ mergeWithBlock( previous, current, range, root );
1556
+ // If deleted line between containers, merge newly adjacent
1557
+ // containers.
1558
+ current = previous.parentNode;
1559
+ while ( current !== root && !current.nextSibling ) {
1560
+ current = current.parentNode;
1561
+ }
1562
+ if ( current !== root && ( current = current.nextSibling ) ) {
1563
+ mergeContainers( current, root );
1564
+ }
1565
+ self.setSelection( range );
1566
+ }
1567
+ // If at very beginning of text area, allow backspace
1568
+ // to break lists/blockquote.
1569
+ else if ( current ) {
1570
+ // Break list
1571
+ if ( getNearest( current, root, 'UL' ) ||
1572
+ getNearest( current, root, 'OL' ) ) {
1573
+ return self.decreaseListLevel( range );
1574
+ }
1575
+ // Break blockquote
1576
+ else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) {
1577
+ return self.modifyBlocks( decreaseBlockQuoteLevel, range );
1578
+ }
1579
+ self.setSelection( range );
1580
+ self._updatePath( range, true );
1581
+ }
1582
+ }
1583
+ // Otherwise, leave to browser but check afterwards whether it has
1584
+ // left behind an empty inline tag.
1585
+ else {
1586
+ self.setSelection( range );
1587
+ setTimeout( function () { afterDelete( self ); }, 0 );
1588
+ }
1589
+ },
1590
+ 'delete': function ( self, event, range ) {
1591
+ var root = self._root;
1592
+ var current, next, originalRange,
1593
+ cursorContainer, cursorOffset, nodeAfterCursor;
1594
+ self._removeZWS();
1595
+ // Record undo checkpoint.
1596
+ self.saveUndoState( range );
1597
+ // If not collapsed, delete contents
1598
+ if ( !range.collapsed ) {
1599
+ event.preventDefault();
1600
+ deleteContentsOfRange( range, root );
1601
+ afterDelete( self, range );
1602
+ }
1603
+ // If at end of block, merge next into this block
1604
+ else if ( rangeDoesEndAtBlockBoundary( range, root ) ) {
1605
+ event.preventDefault();
1606
+ current = getStartBlockOfRange( range, root );
1607
+ if ( !current ) {
1608
+ return;
1609
+ }
1610
+ // In case inline data has somehow got between blocks.
1611
+ fixContainer( current.parentNode, root );
1612
+ // Now get next block
1613
+ next = getNextBlock( current, root );
1614
+ // Must not be at the very end of the text area.
1615
+ if ( next ) {
1616
+ // If not editable, just delete whole block.
1617
+ if ( !next.isContentEditable ) {
1618
+ detach( next );
1619
+ return;
1620
+ }
1621
+ // Otherwise merge.
1622
+ mergeWithBlock( current, next, range, root );
1623
+ // If deleted line between containers, merge newly adjacent
1624
+ // containers.
1625
+ next = current.parentNode;
1626
+ while ( next !== root && !next.nextSibling ) {
1627
+ next = next.parentNode;
1628
+ }
1629
+ if ( next !== root && ( next = next.nextSibling ) ) {
1630
+ mergeContainers( next, root );
1631
+ }
1632
+ self.setSelection( range );
1633
+ self._updatePath( range, true );
1634
+ }
1635
+ }
1636
+ // Otherwise, leave to browser but check afterwards whether it has
1637
+ // left behind an empty inline tag.
1638
+ else {
1639
+ // But first check if the cursor is just before an IMG tag. If so,
1640
+ // delete it ourselves, because the browser won't if it is not
1641
+ // inline.
1642
+ originalRange = range.cloneRange();
1643
+ moveRangeBoundariesUpTree( range, root, root, root );
1644
+ cursorContainer = range.endContainer;
1645
+ cursorOffset = range.endOffset;
1646
+ if ( cursorContainer.nodeType === ELEMENT_NODE ) {
1647
+ nodeAfterCursor = cursorContainer.childNodes[ cursorOffset ];
1648
+ if ( nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG' ) {
1649
+ event.preventDefault();
1650
+ detach( nodeAfterCursor );
1651
+ moveRangeBoundariesDownTree( range );
1652
+ afterDelete( self, range );
1653
+ return;
1654
+ }
1655
+ }
1656
+ self.setSelection( originalRange );
1657
+ setTimeout( function () { afterDelete( self ); }, 0 );
1658
+ }
1659
+ },
1660
+ tab: function ( self, event, range ) {
1661
+ var root = self._root;
1662
+ var node, parent;
1663
+ self._removeZWS();
1664
+ // If no selection and at start of block
1665
+ if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
1666
+ node = getStartBlockOfRange( range, root );
1667
+ // Iterate through the block's parents
1668
+ while ( ( parent = node.parentNode ) ) {
1669
+ // If we find a UL or OL (so are in a list, node must be an LI)
1670
+ if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) {
1671
+ // Then increase the list level
1672
+ event.preventDefault();
1673
+ self.increaseListLevel( range );
1674
+ break;
1675
+ }
1676
+ node = parent;
1677
+ }
1678
+ }
1679
+ },
1680
+ 'shift-tab': function ( self, event, range ) {
1681
+ var root = self._root;
1682
+ var node;
1683
+ self._removeZWS();
1684
+ // If no selection and at start of block
1685
+ if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
1686
+ // Break list
1687
+ node = range.startContainer;
1688
+ if ( getNearest( node, root, 'UL' ) ||
1689
+ getNearest( node, root, 'OL' ) ) {
1690
+ event.preventDefault();
1691
+ self.decreaseListLevel( range );
1692
+ }
1693
+ }
1694
+ },
1695
+ space: function ( self, _, range ) {
1696
+ var node, parent;
1697
+ self._recordUndoState( range );
1698
+ addLinks( range.startContainer, self._root, self );
1699
+ self._getRangeAndRemoveBookmark( range );
1700
+
1701
+ // If the cursor is at the end of a link (<a>foo|</a>) then move it
1702
+ // outside of the link (<a>foo</a>|) so that the space is not part of
1703
+ // the link text.
1704
+ node = range.endContainer;
1705
+ parent = node.parentNode;
1706
+ if ( range.collapsed && parent.nodeName === 'A' &&
1707
+ !node.nextSibling && range.endOffset === getLength( node ) ) {
1708
+ range.setStartAfter( parent );
1709
+ }
1710
+ // Delete the selection if not collapsed
1711
+ else if ( !range.collapsed ) {
1712
+ deleteContentsOfRange( range, self._root );
1713
+ self._ensureBottomLine();
1714
+ self.setSelection( range );
1715
+ self._updatePath( range, true );
1716
+ }
1717
+
1718
+ self.setSelection( range );
1719
+ },
1720
+ left: function ( self ) {
1721
+ self._removeZWS();
1722
+ },
1723
+ right: function ( self ) {
1724
+ self._removeZWS();
1725
+ }
1726
+ };
1727
+
1728
+ // Firefox pre v29 incorrectly handles Cmd-left/Cmd-right on Mac:
1729
+ // it goes back/forward in history! Override to do the right
1730
+ // thing.
1731
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=289384
1732
+ if ( isMac && isGecko ) {
1733
+ keyHandlers[ 'meta-left' ] = function ( self, event ) {
1734
+ event.preventDefault();
1735
+ var sel = getWindowSelection( self );
1736
+ if ( sel && sel.modify ) {
1737
+ sel.modify( 'move', 'backward', 'lineboundary' );
1738
+ }
1739
+ };
1740
+ keyHandlers[ 'meta-right' ] = function ( self, event ) {
1741
+ event.preventDefault();
1742
+ var sel = getWindowSelection( self );
1743
+ if ( sel && sel.modify ) {
1744
+ sel.modify( 'move', 'forward', 'lineboundary' );
1745
+ }
1746
+ };
1747
+ }
1748
+
1749
+ // System standard for page up/down on Mac is to just scroll, not move the
1750
+ // cursor. On Linux/Windows, it should move the cursor, but some browsers don't
1751
+ // implement this natively. Override to support it.
1752
+ if ( !isMac ) {
1753
+ keyHandlers.pageup = function ( self ) {
1754
+ self.moveCursorToStart();
1755
+ };
1756
+ keyHandlers.pagedown = function ( self ) {
1757
+ self.moveCursorToEnd();
1758
+ };
1759
+ }
1760
+
1761
+ keyHandlers[ ctrlKey + 'b' ] = mapKeyToFormat( 'B' );
1762
+ keyHandlers[ ctrlKey + 'i' ] = mapKeyToFormat( 'I' );
1763
+ keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' );
1764
+ keyHandlers[ ctrlKey + 'shift-7' ] = mapKeyToFormat( 'S' );
1765
+ keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } );
1766
+ keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } );
1767
+ keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
1768
+ keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
1769
+ keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
1770
+ keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
1771
+ keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
1772
+ keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
1773
+ keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
1774
+
1775
+ var fontSizes = {
1776
+ 1: 10,
1777
+ 2: 13,
1778
+ 3: 16,
1779
+ 4: 18,
1780
+ 5: 24,
1781
+ 6: 32,
1782
+ 7: 48
1783
+ };
1784
+
1785
+ var styleToSemantic = {
1786
+ backgroundColor: {
1787
+ regexp: notWS,
1788
+ replace: function ( doc, colour ) {
1789
+ return createElement( doc, 'SPAN', {
1790
+ 'class': HIGHLIGHT_CLASS,
1791
+ style: 'background-color:' + colour
1792
+ });
1793
+ }
1794
+ },
1795
+ color: {
1796
+ regexp: notWS,
1797
+ replace: function ( doc, colour ) {
1798
+ return createElement( doc, 'SPAN', {
1799
+ 'class': COLOUR_CLASS,
1800
+ style: 'color:' + colour
1801
+ });
1802
+ }
1803
+ },
1804
+ fontWeight: {
1805
+ regexp: /^bold|^700/i,
1806
+ replace: function ( doc ) {
1807
+ return createElement( doc, 'B' );
1808
+ }
1809
+ },
1810
+ fontStyle: {
1811
+ regexp: /^italic/i,
1812
+ replace: function ( doc ) {
1813
+ return createElement( doc, 'I' );
1814
+ }
1815
+ },
1816
+ fontFamily: {
1817
+ regexp: notWS,
1818
+ replace: function ( doc, family ) {
1819
+ return createElement( doc, 'SPAN', {
1820
+ 'class': FONT_FAMILY_CLASS,
1821
+ style: 'font-family:' + family
1822
+ });
1823
+ }
1824
+ },
1825
+ fontSize: {
1826
+ regexp: notWS,
1827
+ replace: function ( doc, size ) {
1828
+ return createElement( doc, 'SPAN', {
1829
+ 'class': FONT_SIZE_CLASS,
1830
+ style: 'font-size:' + size
1831
+ });
1832
+ }
1833
+ },
1834
+ textDecoration: {
1835
+ regexp: /^underline/i,
1836
+ replace: function ( doc ) {
1837
+ return createElement( doc, 'U' );
1838
+ }
1839
+ }
1840
+ };
1841
+
1842
+ var replaceWithTag = function ( tag ) {
1843
+ return function ( node, parent ) {
1844
+ var el = createElement( node.ownerDocument, tag );
1845
+ parent.replaceChild( el, node );
1846
+ el.appendChild( empty( node ) );
1847
+ return el;
1848
+ };
1849
+ };
1850
+
1851
+ var replaceStyles = function ( node, parent ) {
1852
+ var style = node.style;
1853
+ var doc = node.ownerDocument;
1854
+ var attr, converter, css, newTreeBottom, newTreeTop, el;
1855
+
1856
+ for ( attr in styleToSemantic ) {
1857
+ converter = styleToSemantic[ attr ];
1858
+ css = style[ attr ];
1859
+ if ( css && converter.regexp.test( css ) ) {
1860
+ el = converter.replace( doc, css );
1861
+ if ( !newTreeTop ) {
1862
+ newTreeTop = el;
1863
+ }
1864
+ if ( newTreeBottom ) {
1865
+ newTreeBottom.appendChild( el );
1866
+ }
1867
+ newTreeBottom = el;
1868
+ node.style[ attr ] = '';
1869
+ }
1870
+ }
1871
+
1872
+ if ( newTreeTop ) {
1873
+ newTreeBottom.appendChild( empty( node ) );
1874
+ if ( node.nodeName === 'SPAN' ) {
1875
+ parent.replaceChild( newTreeTop, node );
1876
+ } else {
1877
+ node.appendChild( newTreeTop );
1878
+ }
1879
+ }
1880
+
1881
+ return newTreeBottom || node;
1882
+ };
1883
+
1884
+ var stylesRewriters = {
1885
+ P: replaceStyles,
1886
+ SPAN: replaceStyles,
1887
+ STRONG: replaceWithTag( 'B' ),
1888
+ EM: replaceWithTag( 'I' ),
1889
+ INS: replaceWithTag( 'U' ),
1890
+ STRIKE: replaceWithTag( 'S' ),
1891
+ FONT: function ( node, parent ) {
1892
+ var face = node.face,
1893
+ size = node.size,
1894
+ colour = node.color,
1895
+ doc = node.ownerDocument,
1896
+ fontSpan, sizeSpan, colourSpan,
1897
+ newTreeBottom, newTreeTop;
1898
+ if ( face ) {
1899
+ fontSpan = createElement( doc, 'SPAN', {
1900
+ 'class': FONT_FAMILY_CLASS,
1901
+ style: 'font-family:' + face
1902
+ });
1903
+ newTreeTop = fontSpan;
1904
+ newTreeBottom = fontSpan;
1905
+ }
1906
+ if ( size ) {
1907
+ sizeSpan = createElement( doc, 'SPAN', {
1908
+ 'class': FONT_SIZE_CLASS,
1909
+ style: 'font-size:' + fontSizes[ size ] + 'px'
1910
+ });
1911
+ if ( !newTreeTop ) {
1912
+ newTreeTop = sizeSpan;
1913
+ }
1914
+ if ( newTreeBottom ) {
1915
+ newTreeBottom.appendChild( sizeSpan );
1916
+ }
1917
+ newTreeBottom = sizeSpan;
1918
+ }
1919
+ if ( colour && /^#?([\dA-F]{3}){1,2}$/i.test( colour ) ) {
1920
+ if ( colour.charAt( 0 ) !== '#' ) {
1921
+ colour = '#' + colour;
1922
+ }
1923
+ colourSpan = createElement( doc, 'SPAN', {
1924
+ 'class': COLOUR_CLASS,
1925
+ style: 'color:' + colour
1926
+ });
1927
+ if ( !newTreeTop ) {
1928
+ newTreeTop = colourSpan;
1929
+ }
1930
+ if ( newTreeBottom ) {
1931
+ newTreeBottom.appendChild( colourSpan );
1932
+ }
1933
+ newTreeBottom = colourSpan;
1934
+ }
1935
+ if ( !newTreeTop ) {
1936
+ newTreeTop = newTreeBottom = createElement( doc, 'SPAN' );
1937
+ }
1938
+ parent.replaceChild( newTreeTop, node );
1939
+ newTreeBottom.appendChild( empty( node ) );
1940
+ return newTreeBottom;
1941
+ },
1942
+ TT: function ( node, parent ) {
1943
+ var el = createElement( node.ownerDocument, 'SPAN', {
1944
+ 'class': FONT_FAMILY_CLASS,
1945
+ style: 'font-family:menlo,consolas,"courier new",monospace'
1946
+ });
1947
+ parent.replaceChild( el, node );
1948
+ el.appendChild( empty( node ) );
1949
+ return el;
1950
+ }
1951
+ };
1952
+
1953
+ var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;
1954
+
1955
+ var blacklist = /^(?:HEAD|META|STYLE)/;
1956
+
1957
+ var walker = new TreeWalker( null, SHOW_TEXT|SHOW_ELEMENT, function () {
1958
+ return true;
1959
+ });
1960
+
1961
+ /*
1962
+ Two purposes:
1963
+
1964
+ 1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes
1965
+ and whitespace nodes.
1966
+ 2. Convert inline tags into our preferred format.
1967
+ */
1968
+ var cleanTree = function cleanTree ( node, preserveWS ) {
1969
+ var children = node.childNodes,
1970
+ nonInlineParent, i, l, child, nodeName, nodeType, rewriter, childLength,
1971
+ startsWithWS, endsWithWS, data, sibling;
1972
+
1973
+ nonInlineParent = node;
1974
+ while ( isInline( nonInlineParent ) ) {
1975
+ nonInlineParent = nonInlineParent.parentNode;
1976
+ }
1977
+ walker.root = nonInlineParent;
1978
+
1979
+ for ( i = 0, l = children.length; i < l; i += 1 ) {
1980
+ child = children[i];
1981
+ nodeName = child.nodeName;
1982
+ nodeType = child.nodeType;
1983
+ rewriter = stylesRewriters[ nodeName ];
1984
+ if ( nodeType === ELEMENT_NODE ) {
1985
+ childLength = child.childNodes.length;
1986
+ if ( rewriter ) {
1987
+ child = rewriter( child, node );
1988
+ } else if ( blacklist.test( nodeName ) ) {
1989
+ node.removeChild( child );
1990
+ i -= 1;
1991
+ l -= 1;
1992
+ continue;
1993
+ } else if ( !allowedBlock.test( nodeName ) && !isInline( child ) ) {
1994
+ i -= 1;
1995
+ l += childLength - 1;
1996
+ node.replaceChild( empty( child ), child );
1997
+ continue;
1998
+ }
1999
+ if ( childLength ) {
2000
+ cleanTree( child, preserveWS || ( nodeName === 'PRE' ) );
2001
+ }
2002
+ } else {
2003
+ if ( nodeType === TEXT_NODE ) {
2004
+ data = child.data;
2005
+ startsWithWS = !notWS.test( data.charAt( 0 ) );
2006
+ endsWithWS = !notWS.test( data.charAt( data.length - 1 ) );
2007
+ if ( preserveWS || ( !startsWithWS && !endsWithWS ) ) {
2008
+ continue;
2009
+ }
2010
+ // Iterate through the nodes; if we hit some other content
2011
+ // before the start of a new block we don't trim
2012
+ if ( startsWithWS ) {
2013
+ walker.currentNode = child;
2014
+ while ( sibling = walker.previousPONode() ) {
2015
+ nodeName = sibling.nodeName;
2016
+ if ( nodeName === 'IMG' ||
2017
+ ( nodeName === '#text' &&
2018
+ notWS.test( sibling.data ) ) ) {
2019
+ break;
2020
+ }
2021
+ if ( !isInline( sibling ) ) {
2022
+ sibling = null;
2023
+ break;
2024
+ }
2025
+ }
2026
+ data = data.replace( /^[ \t\r\n]+/g, sibling ? ' ' : '' );
2027
+ }
2028
+ if ( endsWithWS ) {
2029
+ walker.currentNode = child;
2030
+ while ( sibling = walker.nextNode() ) {
2031
+ if ( nodeName === 'IMG' ||
2032
+ ( nodeName === '#text' &&
2033
+ notWS.test( sibling.data ) ) ) {
2034
+ break;
2035
+ }
2036
+ if ( !isInline( sibling ) ) {
2037
+ sibling = null;
2038
+ break;
2039
+ }
2040
+ }
2041
+ data = data.replace( /[ \t\r\n]+$/g, sibling ? ' ' : '' );
2042
+ }
2043
+ if ( data ) {
2044
+ child.data = data;
2045
+ continue;
2046
+ }
2047
+ }
2048
+ node.removeChild( child );
2049
+ i -= 1;
2050
+ l -= 1;
2051
+ }
2052
+ }
2053
+ return node;
2054
+ };
2055
+
2056
+ // ---
2057
+
2058
+ var removeEmptyInlines = function removeEmptyInlines ( node ) {
2059
+ var children = node.childNodes,
2060
+ l = children.length,
2061
+ child;
2062
+ while ( l-- ) {
2063
+ child = children[l];
2064
+ if ( child.nodeType === ELEMENT_NODE && !isLeaf( child ) ) {
2065
+ removeEmptyInlines( child );
2066
+ if ( isInline( child ) && !child.firstChild ) {
2067
+ node.removeChild( child );
2068
+ }
2069
+ } else if ( child.nodeType === TEXT_NODE && !child.data ) {
2070
+ node.removeChild( child );
2071
+ }
2072
+ }
2073
+ };
2074
+
2075
+ // ---
2076
+
2077
+ var notWSTextNode = function ( node ) {
2078
+ return node.nodeType === ELEMENT_NODE ?
2079
+ node.nodeName === 'BR' :
2080
+ notWS.test( node.data );
2081
+ };
2082
+ var isLineBreak = function ( br, isLBIfEmptyBlock ) {
2083
+ var block = br.parentNode;
2084
+ var walker;
2085
+ while ( isInline( block ) ) {
2086
+ block = block.parentNode;
2087
+ }
2088
+ walker = new TreeWalker(
2089
+ block, SHOW_ELEMENT|SHOW_TEXT, notWSTextNode );
2090
+ walker.currentNode = br;
2091
+ return !!walker.nextNode() ||
2092
+ ( isLBIfEmptyBlock && !walker.previousNode() );
2093
+ };
2094
+
2095
+ // <br> elements are treated specially, and differently depending on the
2096
+ // browser, when in rich text editor mode. When adding HTML from external
2097
+ // sources, we must remove them, replacing the ones that actually affect
2098
+ // line breaks by wrapping the inline text in a <div>. Browsers that want <br>
2099
+ // elements at the end of each block will then have them added back in a later
2100
+ // fixCursor method call.
2101
+ var cleanupBRs = function ( node, root, keepForBlankLine ) {
2102
+ var brs = node.querySelectorAll( 'BR' );
2103
+ var brBreaksLine = [];
2104
+ var l = brs.length;
2105
+ var i, br, parent;
2106
+
2107
+ // Must calculate whether the <br> breaks a line first, because if we
2108
+ // have two <br>s next to each other, after the first one is converted
2109
+ // to a block split, the second will be at the end of a block and
2110
+ // therefore seem to not be a line break. But in its original context it
2111
+ // was, so we should also convert it to a block split.
2112
+ for ( i = 0; i < l; i += 1 ) {
2113
+ brBreaksLine[i] = isLineBreak( brs[i], keepForBlankLine );
2114
+ }
2115
+ while ( l-- ) {
2116
+ br = brs[l];
2117
+ // Cleanup may have removed it
2118
+ parent = br.parentNode;
2119
+ if ( !parent ) { continue; }
2120
+ // If it doesn't break a line, just remove it; it's not doing
2121
+ // anything useful. We'll add it back later if required by the
2122
+ // browser. If it breaks a line, wrap the content in div tags
2123
+ // and replace the brs.
2124
+ if ( !brBreaksLine[l] ) {
2125
+ detach( br );
2126
+ } else if ( !isInline( parent ) ) {
2127
+ fixContainer( parent, root );
2128
+ }
2129
+ }
2130
+ };
2131
+
2132
+ // The (non-standard but supported enough) innerText property is based on the
2133
+ // render tree in Firefox and possibly other browsers, so we must insert the
2134
+ // DOM node into the document to ensure the text part is correct.
2135
+ var setClipboardData = function ( clipboardData, node, root ) {
2136
+ var body = node.ownerDocument.body;
2137
+ var html, text;
2138
+
2139
+ // Firefox will add an extra new line for BRs at the end of block when
2140
+ // calculating innerText, even though they don't actually affect display.
2141
+ // So we need to remove them first.
2142
+ cleanupBRs( node, root, true );
2143
+
2144
+ node.setAttribute( 'style',
2145
+ 'position:fixed;overflow:hidden;bottom:100%;right:100%;' );
2146
+ body.appendChild( node );
2147
+ html = node.innerHTML;
2148
+ text = node.innerText || node.textContent;
2149
+
2150
+ // Firefox (and others?) returns unix line endings (\n) even on Windows.
2151
+ // If on Windows, normalise to \r\n, since Notepad and some other crappy
2152
+ // apps do not understand just \n.
2153
+ if ( isWin ) {
2154
+ text = text.replace( /\r?\n/g, '\r\n' );
2155
+ }
2156
+
2157
+ clipboardData.setData( 'text/html', html );
2158
+ clipboardData.setData( 'text/plain', text );
2159
+
2160
+ body.removeChild( node );
2161
+ };
2162
+
2163
+ var onCut = function ( event ) {
2164
+ var clipboardData = event.clipboardData;
2165
+ var range = this.getSelection();
2166
+ var root = this._root;
2167
+ var self = this;
2168
+ var startBlock, endBlock, copyRoot, contents, parent, newContents, node;
2169
+
2170
+ // Nothing to do
2171
+ if ( range.collapsed ) {
2172
+ event.preventDefault();
2173
+ return;
2174
+ }
2175
+
2176
+ // Save undo checkpoint
2177
+ this.saveUndoState( range );
2178
+
2179
+ // Edge only seems to support setting plain text as of 2016-03-11.
2180
+ // Mobile Safari flat out doesn't work:
2181
+ // https://bugs.webkit.org/show_bug.cgi?id=143776
2182
+ if ( !isEdge && !isIOS && clipboardData ) {
2183
+ // Clipboard content should include all parents within block, or all
2184
+ // parents up to root if selection across blocks
2185
+ startBlock = getStartBlockOfRange( range, root );
2186
+ endBlock = getEndBlockOfRange( range, root );
2187
+ copyRoot = ( ( startBlock === endBlock ) && startBlock ) || root;
2188
+ // Extract the contents
2189
+ contents = deleteContentsOfRange( range, root );
2190
+ // Add any other parents not in extracted content, up to copy root
2191
+ parent = range.commonAncestorContainer;
2192
+ if ( parent.nodeType === TEXT_NODE ) {
2193
+ parent = parent.parentNode;
2194
+ }
2195
+ while ( parent && parent !== copyRoot ) {
2196
+ newContents = parent.cloneNode( false );
2197
+ newContents.appendChild( contents );
2198
+ contents = newContents;
2199
+ parent = parent.parentNode;
2200
+ }
2201
+ // Set clipboard data
2202
+ node = this.createElement( 'div' );
2203
+ node.appendChild( contents );
2204
+ setClipboardData( clipboardData, node, root );
2205
+ event.preventDefault();
2206
+ } else {
2207
+ setTimeout( function () {
2208
+ try {
2209
+ // If all content removed, ensure div at start of root.
2210
+ self._ensureBottomLine();
2211
+ } catch ( error ) {
2212
+ self.didError( error );
2213
+ }
2214
+ }, 0 );
2215
+ }
2216
+
2217
+ this.setSelection( range );
2218
+ };
2219
+
2220
+ var onCopy = function ( event ) {
2221
+ var clipboardData = event.clipboardData;
2222
+ var range = this.getSelection();
2223
+ var root = this._root;
2224
+ var startBlock, endBlock, copyRoot, contents, parent, newContents, node;
2225
+
2226
+ // Edge only seems to support setting plain text as of 2016-03-11.
2227
+ // Mobile Safari flat out doesn't work:
2228
+ // https://bugs.webkit.org/show_bug.cgi?id=143776
2229
+ if ( !isEdge && !isIOS && clipboardData ) {
2230
+ // Clipboard content should include all parents within block, or all
2231
+ // parents up to root if selection across blocks
2232
+ startBlock = getStartBlockOfRange( range, root );
2233
+ endBlock = getEndBlockOfRange( range, root );
2234
+ copyRoot = ( ( startBlock === endBlock ) && startBlock ) || root;
2235
+ // Clone range to mutate, then move up as high as possible without
2236
+ // passing the copy root node.
2237
+ range = range.cloneRange();
2238
+ moveRangeBoundariesDownTree( range );
2239
+ moveRangeBoundariesUpTree( range, copyRoot, copyRoot, root );
2240
+ // Extract the contents
2241
+ contents = range.cloneContents();
2242
+ // Add any other parents not in extracted content, up to copy root
2243
+ parent = range.commonAncestorContainer;
2244
+ if ( parent.nodeType === TEXT_NODE ) {
2245
+ parent = parent.parentNode;
2246
+ }
2247
+ while ( parent && parent !== copyRoot ) {
2248
+ newContents = parent.cloneNode( false );
2249
+ newContents.appendChild( contents );
2250
+ contents = newContents;
2251
+ parent = parent.parentNode;
2252
+ }
2253
+ // Set clipboard data
2254
+ node = this.createElement( 'div' );
2255
+ node.appendChild( contents );
2256
+ setClipboardData( clipboardData, node, root );
2257
+ event.preventDefault();
2258
+ }
2259
+ };
2260
+
2261
+ // Need to monitor for shift key like this, as event.shiftKey is not available
2262
+ // in paste event.
2263
+ function monitorShiftKey ( event ) {
2264
+ this.isShiftDown = event.shiftKey;
2265
+ }
2266
+
2267
+ var onPaste = function ( event ) {
2268
+ var clipboardData = event.clipboardData;
2269
+ var items = clipboardData && clipboardData.items;
2270
+ var choosePlain = this.isShiftDown;
2271
+ var fireDrop = false;
2272
+ var hasImage = false;
2273
+ var plainItem = null;
2274
+ var self = this;
2275
+ var l, item, type, types, data;
2276
+
2277
+ // Current HTML5 Clipboard interface
2278
+ // ---------------------------------
2279
+ // https://html.spec.whatwg.org/multipage/interaction.html
2280
+
2281
+ // Edge only provides access to plain text as of 2016-03-11 and gives no
2282
+ // indication there should be an HTML part. However, it does support access
2283
+ // to image data, so check if this is present and use if so.
2284
+ if ( isEdge && items ) {
2285
+ l = items.length;
2286
+ while ( l-- ) {
2287
+ if ( !choosePlain && /^image\/.*/.test( items[l].type ) ) {
2288
+ hasImage = true;
2289
+ }
2290
+ }
2291
+ if ( !hasImage ) {
2292
+ items = null;
2293
+ }
2294
+ }
2295
+ if ( items ) {
2296
+ event.preventDefault();
2297
+ l = items.length;
2298
+ while ( l-- ) {
2299
+ item = items[l];
2300
+ type = item.type;
2301
+ if ( !choosePlain && type === 'text/html' ) {
2302
+ /*jshint loopfunc: true */
2303
+ item.getAsString( function ( html ) {
2304
+ self.insertHTML( html, true );
2305
+ });
2306
+ /*jshint loopfunc: false */
2307
+ return;
2308
+ }
2309
+ if ( type === 'text/plain' ) {
2310
+ plainItem = item;
2311
+ }
2312
+ if ( !choosePlain && /^image\/.*/.test( type ) ) {
2313
+ hasImage = true;
2314
+ }
2315
+ }
2316
+ // Treat image paste as a drop of an image file.
2317
+ if ( hasImage ) {
2318
+ this.fireEvent( 'dragover', {
2319
+ dataTransfer: clipboardData,
2320
+ /*jshint loopfunc: true */
2321
+ preventDefault: function () {
2322
+ fireDrop = true;
2323
+ }
2324
+ /*jshint loopfunc: false */
2325
+ });
2326
+ if ( fireDrop ) {
2327
+ this.fireEvent( 'drop', {
2328
+ dataTransfer: clipboardData
2329
+ });
2330
+ }
2331
+ } else if ( plainItem ) {
2332
+ plainItem.getAsString( function ( text ) {
2333
+ self.insertPlainText( text, true );
2334
+ });
2335
+ }
2336
+ return;
2337
+ }
2338
+
2339
+ // Old interface
2340
+ // -------------
2341
+
2342
+ // Safari (and indeed many other OS X apps) copies stuff as text/rtf
2343
+ // rather than text/html; even from a webpage in Safari. The only way
2344
+ // to get an HTML version is to fallback to letting the browser insert
2345
+ // the content. Same for getting image data. *Sigh*.
2346
+ //
2347
+ // Firefox is even worse: it doesn't even let you know that there might be
2348
+ // an RTF version on the clipboard, but it will also convert to HTML if you
2349
+ // let the browser insert the content. I've filed
2350
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
2351
+ types = clipboardData && clipboardData.types;
2352
+ if ( !isEdge && types && (
2353
+ indexOf.call( types, 'text/html' ) > -1 || (
2354
+ !isGecko &&
2355
+ indexOf.call( types, 'text/plain' ) > -1 &&
2356
+ indexOf.call( types, 'text/rtf' ) < 0 )
2357
+ )) {
2358
+ event.preventDefault();
2359
+ // Abiword on Linux copies a plain text and html version, but the HTML
2360
+ // version is the empty string! So always try to get HTML, but if none,
2361
+ // insert plain text instead. On iOS, Facebook (and possibly other
2362
+ // apps?) copy links as type text/uri-list, but also insert a **blank**
2363
+ // text/plain item onto the clipboard. Why? Who knows.
2364
+ if ( !choosePlain && ( data = clipboardData.getData( 'text/html' ) ) ) {
2365
+ this.insertHTML( data, true );
2366
+ } else if (
2367
+ ( data = clipboardData.getData( 'text/plain' ) ) ||
2368
+ ( data = clipboardData.getData( 'text/uri-list' ) ) ) {
2369
+ this.insertPlainText( data, true );
2370
+ }
2371
+ return;
2372
+ }
2373
+
2374
+ // No interface. Includes all versions of IE :(
2375
+ // --------------------------------------------
2376
+
2377
+ this._awaitingPaste = true;
2378
+
2379
+ var body = this._doc.body,
2380
+ range = this.getSelection(),
2381
+ startContainer = range.startContainer,
2382
+ startOffset = range.startOffset,
2383
+ endContainer = range.endContainer,
2384
+ endOffset = range.endOffset;
2385
+
2386
+ // We need to position the pasteArea in the visible portion of the screen
2387
+ // to stop the browser auto-scrolling.
2388
+ var pasteArea = this.createElement( 'DIV', {
2389
+ contenteditable: 'true',
2390
+ style: 'position:fixed; overflow:hidden; top:0; right:100%; width:1px; height:1px;'
2391
+ });
2392
+ body.appendChild( pasteArea );
2393
+ range.selectNodeContents( pasteArea );
2394
+ this.setSelection( range );
2395
+
2396
+ // A setTimeout of 0 means this is added to the back of the
2397
+ // single javascript thread, so it will be executed after the
2398
+ // paste event.
2399
+ setTimeout( function () {
2400
+ try {
2401
+ // IE sometimes fires the beforepaste event twice; make sure it is
2402
+ // not run again before our after paste function is called.
2403
+ self._awaitingPaste = false;
2404
+
2405
+ // Get the pasted content and clean
2406
+ var html = '',
2407
+ next = pasteArea,
2408
+ first, range;
2409
+
2410
+ // #88: Chrome can apparently split the paste area if certain
2411
+ // content is inserted; gather them all up.
2412
+ while ( pasteArea = next ) {
2413
+ next = pasteArea.nextSibling;
2414
+ detach( pasteArea );
2415
+ // Safari and IE like putting extra divs around things.
2416
+ first = pasteArea.firstChild;
2417
+ if ( first && first === pasteArea.lastChild &&
2418
+ first.nodeName === 'DIV' ) {
2419
+ pasteArea = first;
2420
+ }
2421
+ html += pasteArea.innerHTML;
2422
+ }
2423
+
2424
+ range = self._createRange(
2425
+ startContainer, startOffset, endContainer, endOffset );
2426
+ self.setSelection( range );
2427
+
2428
+ if ( html ) {
2429
+ self.insertHTML( html, true );
2430
+ }
2431
+ } catch ( error ) {
2432
+ self.didError( error );
2433
+ }
2434
+ }, 0 );
2435
+ };
2436
+
2437
+ // On Windows you can drag an drop text. We can't handle this ourselves, because
2438
+ // as far as I can see, there's no way to get the drop insertion point. So just
2439
+ // save an undo state and hope for the best.
2440
+ var onDrop = function ( event ) {
2441
+ var types = event.dataTransfer.types;
2442
+ var l = types.length;
2443
+ var hasPlain = false;
2444
+ var hasHTML = false;
2445
+ while ( l-- ) {
2446
+ switch ( types[l] ) {
2447
+ case 'text/plain':
2448
+ hasPlain = true;
2449
+ break;
2450
+ case 'text/html':
2451
+ hasHTML = true;
2452
+ break;
2453
+ default:
2454
+ return;
2455
+ }
2456
+ }
2457
+ if ( hasHTML || hasPlain ) {
2458
+ this.saveUndoState();
2459
+ }
2460
+ };
2461
+
2462
+ function mergeObjects ( base, extras, mayOverride ) {
2463
+ var prop, value;
2464
+ if ( !base ) {
2465
+ base = {};
2466
+ }
2467
+ if ( extras ) {
2468
+ for ( prop in extras ) {
2469
+ if ( mayOverride || !( prop in base ) ) {
2470
+ value = extras[ prop ];
2471
+ base[ prop ] = ( value && value.constructor === Object ) ?
2472
+ mergeObjects( base[ prop ], value, mayOverride ) :
2473
+ value;
2474
+ }
2475
+ }
2476
+ }
2477
+ return base;
2478
+ }
2479
+
2480
+ function Squire ( root, config ) {
2481
+ if ( root.nodeType === DOCUMENT_NODE ) {
2482
+ root = root.body;
2483
+ }
2484
+ var doc = root.ownerDocument;
2485
+ var win = doc.defaultView;
2486
+ var mutation;
2487
+
2488
+ this._win = win;
2489
+ this._doc = doc;
2490
+ this._root = root;
2491
+
2492
+ this._events = {};
2493
+
2494
+ this._isFocused = false;
2495
+ this._lastSelection = null;
2496
+
2497
+ // IE loses selection state of iframe on blur, so make sure we
2498
+ // cache it just before it loses focus.
2499
+ if ( losesSelectionOnBlur ) {
2500
+ this.addEventListener( 'beforedeactivate', this.getSelection );
2501
+ }
2502
+
2503
+ this._hasZWS = false;
2504
+
2505
+ this._lastAnchorNode = null;
2506
+ this._lastFocusNode = null;
2507
+ this._path = '';
2508
+ this._willUpdatePath = false;
2509
+
2510
+ if ( 'onselectionchange' in doc ) {
2511
+ this.addEventListener( 'selectionchange', this._updatePathOnEvent );
2512
+ } else {
2513
+ this.addEventListener( 'keyup', this._updatePathOnEvent );
2514
+ this.addEventListener( 'mouseup', this._updatePathOnEvent );
2515
+ }
2516
+
2517
+ this._undoIndex = -1;
2518
+ this._undoStack = [];
2519
+ this._undoStackLength = 0;
2520
+ this._isInUndoState = false;
2521
+ this._ignoreChange = false;
2522
+ this._ignoreAllChanges = false;
2523
+
2524
+ if ( canObserveMutations ) {
2525
+ mutation = new MutationObserver( this._docWasChanged.bind( this ) );
2526
+ mutation.observe( root, {
2527
+ childList: true,
2528
+ attributes: true,
2529
+ characterData: true,
2530
+ subtree: true
2531
+ });
2532
+ this._mutation = mutation;
2533
+ } else {
2534
+ this.addEventListener( 'keyup', this._keyUpDetectChange );
2535
+ }
2536
+
2537
+ // On blur, restore focus except if the user taps or clicks to focus a
2538
+ // specific point. Can't actually use click event because focus happens
2539
+ // before click, so use mousedown/touchstart
2540
+ this._restoreSelection = false;
2541
+ this.addEventListener( 'blur', enableRestoreSelection );
2542
+ this.addEventListener( 'mousedown', disableRestoreSelection );
2543
+ this.addEventListener( 'touchstart', disableRestoreSelection );
2544
+ this.addEventListener( 'focus', restoreSelection );
2545
+
2546
+ // IE sometimes fires the beforepaste event twice; make sure it is not run
2547
+ // again before our after paste function is called.
2548
+ this._awaitingPaste = false;
2549
+ this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
2550
+ this.addEventListener( 'copy', onCopy );
2551
+ this.addEventListener( 'keydown', monitorShiftKey );
2552
+ this.addEventListener( 'keyup', monitorShiftKey );
2553
+ this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
2554
+ this.addEventListener( 'drop', onDrop );
2555
+
2556
+ // Opera does not fire keydown repeatedly.
2557
+ this.addEventListener( isPresto ? 'keypress' : 'keydown', onKey );
2558
+
2559
+ // Add key handlers
2560
+ this._keyHandlers = Object.create( keyHandlers );
2561
+
2562
+ // Override default properties
2563
+ this.setConfig( config );
2564
+
2565
+ // Fix IE<10's buggy implementation of Text#splitText.
2566
+ // If the split is at the end of the node, it doesn't insert the newly split
2567
+ // node into the document, and sets its value to undefined rather than ''.
2568
+ // And even if the split is not at the end, the original node is removed
2569
+ // from the document and replaced by another, rather than just having its
2570
+ // data shortened.
2571
+ // We used to feature test for this, but then found the feature test would
2572
+ // sometimes pass, but later on the buggy behaviour would still appear.
2573
+ // I think IE10 does not have the same bug, but it doesn't hurt to replace
2574
+ // its native fn too and then we don't need yet another UA category.
2575
+ if ( isIElt11 ) {
2576
+ win.Text.prototype.splitText = function ( offset ) {
2577
+ var afterSplit = this.ownerDocument.createTextNode(
2578
+ this.data.slice( offset ) ),
2579
+ next = this.nextSibling,
2580
+ parent = this.parentNode,
2581
+ toDelete = this.length - offset;
2582
+ if ( next ) {
2583
+ parent.insertBefore( afterSplit, next );
2584
+ } else {
2585
+ parent.appendChild( afterSplit );
2586
+ }
2587
+ if ( toDelete ) {
2588
+ this.deleteData( offset, toDelete );
2589
+ }
2590
+ return afterSplit;
2591
+ };
2592
+ }
2593
+
2594
+ root.setAttribute( 'contenteditable', 'true' );
2595
+
2596
+ // Remove Firefox's built-in controls
2597
+ try {
2598
+ doc.execCommand( 'enableObjectResizing', false, 'false' );
2599
+ doc.execCommand( 'enableInlineTableEditing', false, 'false' );
2600
+ } catch ( error ) {}
2601
+
2602
+ root.__squire__ = this;
2603
+
2604
+ // Need to register instance before calling setHTML, so that the fixCursor
2605
+ // function can lookup any default block tag options set.
2606
+ this.setHTML( '' );
2607
+ }
2608
+
2609
+ var proto = Squire.prototype;
2610
+
2611
+ var sanitizeToDOMFragment = function ( html, isPaste, self ) {
2612
+ var doc = self._doc;
2613
+ var frag = html ? DOMPurify.sanitize( html, {
2614
+ ALLOW_UNKNOWN_PROTOCOLS: true,
2615
+ WHOLE_DOCUMENT: false,
2616
+ RETURN_DOM: true,
2617
+ RETURN_DOM_FRAGMENT: true
2618
+ }) : null;
2619
+ return frag ? doc.importNode( frag, true ) : doc.createDocumentFragment();
2620
+ };
2621
+
2622
+ proto.setConfig = function ( config ) {
2623
+ config = mergeObjects({
2624
+ blockTag: 'DIV',
2625
+ blockAttributes: null,
2626
+ tagAttributes: {
2627
+ blockquote: null,
2628
+ ul: null,
2629
+ ol: null,
2630
+ li: null,
2631
+ a: null
2632
+ },
2633
+ leafNodeNames: leafNodeNames,
2634
+ undo: {
2635
+ documentSizeThreshold: -1, // -1 means no threshold
2636
+ undoLimit: -1 // -1 means no limit
2637
+ },
2638
+ isInsertedHTMLSanitized: true,
2639
+ isSetHTMLSanitized: true,
2640
+ sanitizeToDOMFragment:
2641
+ typeof DOMPurify !== 'undefined' && DOMPurify.isSupported ?
2642
+ sanitizeToDOMFragment : null
2643
+
2644
+ }, config, true );
2645
+
2646
+ // Users may specify block tag in lower case
2647
+ config.blockTag = config.blockTag.toUpperCase();
2648
+
2649
+ this._config = config;
2650
+
2651
+ return this;
2652
+ };
2653
+
2654
+ proto.createElement = function ( tag, props, children ) {
2655
+ return createElement( this._doc, tag, props, children );
2656
+ };
2657
+
2658
+ proto.createDefaultBlock = function ( children ) {
2659
+ var config = this._config;
2660
+ return fixCursor(
2661
+ this.createElement( config.blockTag, config.blockAttributes, children ),
2662
+ this._root
2663
+ );
2664
+ };
2665
+
2666
+ proto.didError = function ( error ) {
2667
+ console.log( error );
2668
+ };
2669
+
2670
+ proto.getDocument = function () {
2671
+ return this._doc;
2672
+ };
2673
+ proto.getRoot = function () {
2674
+ return this._root;
2675
+ };
2676
+
2677
+ proto.modifyDocument = function ( modificationCallback ) {
2678
+ var mutation = this._mutation;
2679
+ if ( mutation ) {
2680
+ if ( mutation.takeRecords().length ) {
2681
+ this._docWasChanged();
2682
+ }
2683
+ mutation.disconnect();
2684
+ }
2685
+
2686
+ this._ignoreAllChanges = true;
2687
+ modificationCallback();
2688
+ this._ignoreAllChanges = false;
2689
+
2690
+ if ( mutation ) {
2691
+ mutation.observe( this._root, {
2692
+ childList: true,
2693
+ attributes: true,
2694
+ characterData: true,
2695
+ subtree: true
2696
+ });
2697
+ this._ignoreChange = false;
2698
+ }
2699
+ };
2700
+
2701
+ // --- Events ---
2702
+
2703
+ // Subscribing to these events won't automatically add a listener to the
2704
+ // document node, since these events are fired in a custom manner by the
2705
+ // editor code.
2706
+ var customEvents = {
2707
+ pathChange: 1, select: 1, input: 1, undoStateChange: 1
2708
+ };
2709
+
2710
+ proto.fireEvent = function ( type, event ) {
2711
+ var handlers = this._events[ type ];
2712
+ var isFocused, l, obj;
2713
+ // UI code, especially modal views, may be monitoring for focus events and
2714
+ // immediately removing focus. In certain conditions, this can cause the
2715
+ // focus event to fire after the blur event, which can cause an infinite
2716
+ // loop. So we detect whether we're actually focused/blurred before firing.
2717
+ if ( /^(?:focus|blur)/.test( type ) ) {
2718
+ isFocused = this._root === this._doc.activeElement;
2719
+ if ( type === 'focus' ) {
2720
+ if ( !isFocused || this._isFocused ) {
2721
+ return this;
2722
+ }
2723
+ this._isFocused = true;
2724
+ } else {
2725
+ if ( isFocused || !this._isFocused ) {
2726
+ return this;
2727
+ }
2728
+ this._isFocused = false;
2729
+ }
2730
+ }
2731
+ if ( handlers ) {
2732
+ if ( !event ) {
2733
+ event = {};
2734
+ }
2735
+ if ( event.type !== type ) {
2736
+ event.type = type;
2737
+ }
2738
+ // Clone handlers array, so any handlers added/removed do not affect it.
2739
+ handlers = handlers.slice();
2740
+ l = handlers.length;
2741
+ while ( l-- ) {
2742
+ obj = handlers[l];
2743
+ try {
2744
+ if ( obj.handleEvent ) {
2745
+ obj.handleEvent( event );
2746
+ } else {
2747
+ obj.call( this, event );
2748
+ }
2749
+ } catch ( error ) {
2750
+ error.details = 'Squire: fireEvent error. Event type: ' + type;
2751
+ this.didError( error );
2752
+ }
2753
+ }
2754
+ }
2755
+ return this;
2756
+ };
2757
+
2758
+ proto.destroy = function () {
2759
+ var events = this._events;
2760
+ var type;
2761
+
2762
+ for ( type in events ) {
2763
+ this.removeEventListener( type );
2764
+ }
2765
+ if ( this._mutation ) {
2766
+ this._mutation.disconnect();
2767
+ }
2768
+ delete this._root.__squire__;
2769
+
2770
+ // Destroy undo stack
2771
+ this._undoIndex = -1;
2772
+ this._undoStack = [];
2773
+ this._undoStackLength = 0;
2774
+ };
2775
+
2776
+ proto.handleEvent = function ( event ) {
2777
+ this.fireEvent( event.type, event );
2778
+ };
2779
+
2780
+ proto.addEventListener = function ( type, fn ) {
2781
+ var handlers = this._events[ type ];
2782
+ var target = this._root;
2783
+ if ( !fn ) {
2784
+ this.didError({
2785
+ name: 'Squire: addEventListener with null or undefined fn',
2786
+ message: 'Event type: ' + type
2787
+ });
2788
+ return this;
2789
+ }
2790
+ if ( !handlers ) {
2791
+ handlers = this._events[ type ] = [];
2792
+ if ( !customEvents[ type ] ) {
2793
+ if ( type === 'selectionchange' ) {
2794
+ target = this._doc;
2795
+ }
2796
+ target.addEventListener( type, this, true );
2797
+ }
2798
+ }
2799
+ handlers.push( fn );
2800
+ return this;
2801
+ };
2802
+
2803
+ proto.removeEventListener = function ( type, fn ) {
2804
+ var handlers = this._events[ type ];
2805
+ var target = this._root;
2806
+ var l;
2807
+ if ( handlers ) {
2808
+ if ( fn ) {
2809
+ l = handlers.length;
2810
+ while ( l-- ) {
2811
+ if ( handlers[l] === fn ) {
2812
+ handlers.splice( l, 1 );
2813
+ }
2814
+ }
2815
+ } else {
2816
+ handlers.length = 0;
2817
+ }
2818
+ if ( !handlers.length ) {
2819
+ delete this._events[ type ];
2820
+ if ( !customEvents[ type ] ) {
2821
+ if ( type === 'selectionchange' ) {
2822
+ target = this._doc;
2823
+ }
2824
+ target.removeEventListener( type, this, true );
2825
+ }
2826
+ }
2827
+ }
2828
+ return this;
2829
+ };
2830
+
2831
+ // --- Selection and Path ---
2832
+
2833
+ proto._createRange =
2834
+ function ( range, startOffset, endContainer, endOffset ) {
2835
+ if ( range instanceof this._win.Range ) {
2836
+ return range.cloneRange();
2837
+ }
2838
+ var domRange = this._doc.createRange();
2839
+ domRange.setStart( range, startOffset );
2840
+ if ( endContainer ) {
2841
+ domRange.setEnd( endContainer, endOffset );
2842
+ } else {
2843
+ domRange.setEnd( range, startOffset );
2844
+ }
2845
+ return domRange;
2846
+ };
2847
+
2848
+ proto.getCursorPosition = function ( range ) {
2849
+ if ( ( !range && !( range = this.getSelection() ) ) ||
2850
+ !range.getBoundingClientRect ) {
2851
+ return null;
2852
+ }
2853
+ // Get the bounding rect
2854
+ var rect = range.getBoundingClientRect();
2855
+ var node, parent;
2856
+ if ( rect && !rect.top ) {
2857
+ this._ignoreChange = true;
2858
+ node = this._doc.createElement( 'SPAN' );
2859
+ node.textContent = ZWS;
2860
+ insertNodeInRange( range, node );
2861
+ rect = node.getBoundingClientRect();
2862
+ parent = node.parentNode;
2863
+ parent.removeChild( node );
2864
+ mergeInlines( parent, range );
2865
+ }
2866
+ return rect;
2867
+ };
2868
+
2869
+ proto._moveCursorTo = function ( toStart ) {
2870
+ var root = this._root,
2871
+ range = this._createRange( root, toStart ? 0 : root.childNodes.length );
2872
+ moveRangeBoundariesDownTree( range );
2873
+ this.setSelection( range );
2874
+ return this;
2875
+ };
2876
+ proto.moveCursorToStart = function () {
2877
+ return this._moveCursorTo( true );
2878
+ };
2879
+ proto.moveCursorToEnd = function () {
2880
+ return this._moveCursorTo( false );
2881
+ };
2882
+
2883
+ var getWindowSelection = function ( self ) {
2884
+ return self._win.getSelection() || null;
2885
+ };
2886
+
2887
+ proto.setSelection = function ( range ) {
2888
+ if ( range ) {
2889
+ this._lastSelection = range;
2890
+ // If we're setting selection, that automatically, and synchronously, // triggers a focus event. So just store the selection and mark it as
2891
+ // needing restore on focus.
2892
+ if ( !this._isFocused ) {
2893
+ enableRestoreSelection.call( this );
2894
+ } else if ( isAndroid && !this._restoreSelection ) {
2895
+ // Android closes the keyboard on removeAllRanges() and doesn't
2896
+ // open it again when addRange() is called, sigh.
2897
+ // Since Android doesn't trigger a focus event in setSelection(),
2898
+ // use a blur/focus dance to work around this by letting the
2899
+ // selection be restored on focus.
2900
+ // Need to check for !this._restoreSelection to avoid infinite loop
2901
+ enableRestoreSelection.call( this );
2902
+ this.blur();
2903
+ this.focus();
2904
+ } else {
2905
+ // iOS bug: if you don't focus the iframe before setting the
2906
+ // selection, you can end up in a state where you type but the input
2907
+ // doesn't get directed into the contenteditable area but is instead
2908
+ // lost in a black hole. Very strange.
2909
+ if ( isIOS ) {
2910
+ this._win.focus();
2911
+ }
2912
+ var sel = getWindowSelection( this );
2913
+ if ( sel ) {
2914
+ sel.removeAllRanges();
2915
+ sel.addRange( range );
2916
+ }
2917
+ }
2918
+ }
2919
+ return this;
2920
+ };
2921
+
2922
+ proto.getSelection = function () {
2923
+ var sel = getWindowSelection( this );
2924
+ var root = this._root;
2925
+ var selection, startContainer, endContainer, node;
2926
+ // If not focused, always rely on cached selection; another function may
2927
+ // have set it but the DOM is not modified until focus again
2928
+ if ( this._isFocused && sel && sel.rangeCount ) {
2929
+ selection = sel.getRangeAt( 0 ).cloneRange();
2930
+ startContainer = selection.startContainer;
2931
+ endContainer = selection.endContainer;
2932
+ // FF can return the selection as being inside an <img>. WTF?
2933
+ if ( startContainer && isLeaf( startContainer ) ) {
2934
+ selection.setStartBefore( startContainer );
2935
+ }
2936
+ if ( endContainer && isLeaf( endContainer ) ) {
2937
+ selection.setEndBefore( endContainer );
2938
+ }
2939
+ }
2940
+ if ( selection &&
2941
+ isOrContains( root, selection.commonAncestorContainer ) ) {
2942
+ this._lastSelection = selection;
2943
+ } else {
2944
+ selection = this._lastSelection;
2945
+ node = selection.commonAncestorContainer;
2946
+ // Check the editor is in the live document; if not, the range has
2947
+ // probably been rewritten by the browser and is bogus
2948
+ if ( !isOrContains( node.ownerDocument, node ) ) {
2949
+ selection = null;
2950
+ }
2951
+ }
2952
+ if ( !selection ) {
2953
+ selection = this._createRange( root.firstChild, 0 );
2954
+ }
2955
+ return selection;
2956
+ };
2957
+
2958
+ function enableRestoreSelection () {
2959
+ this._restoreSelection = true;
2960
+ }
2961
+ function disableRestoreSelection () {
2962
+ this._restoreSelection = false;
2963
+ }
2964
+ function restoreSelection () {
2965
+ if ( this._restoreSelection ) {
2966
+ this.setSelection( this._lastSelection );
2967
+ }
2968
+ }
2969
+
2970
+ proto.getSelectedText = function () {
2971
+ var range = this.getSelection();
2972
+ if ( !range || range.collapsed ) {
2973
+ return '';
2974
+ }
2975
+ var walker = new TreeWalker(
2976
+ range.commonAncestorContainer,
2977
+ SHOW_TEXT|SHOW_ELEMENT,
2978
+ function ( node ) {
2979
+ return isNodeContainedInRange( range, node, true );
2980
+ }
2981
+ );
2982
+ var startContainer = range.startContainer;
2983
+ var endContainer = range.endContainer;
2984
+ var node = walker.currentNode = startContainer;
2985
+ var textContent = '';
2986
+ var addedTextInBlock = false;
2987
+ var value;
2988
+
2989
+ if ( !walker.filter( node ) ) {
2990
+ node = walker.nextNode();
2991
+ }
2992
+
2993
+ while ( node ) {
2994
+ if ( node.nodeType === TEXT_NODE ) {
2995
+ value = node.data;
2996
+ if ( value && ( /\S/.test( value ) ) ) {
2997
+ if ( node === endContainer ) {
2998
+ value = value.slice( 0, range.endOffset );
2999
+ }
3000
+ if ( node === startContainer ) {
3001
+ value = value.slice( range.startOffset );
3002
+ }
3003
+ textContent += value;
3004
+ addedTextInBlock = true;
3005
+ }
3006
+ } else if ( node.nodeName === 'BR' ||
3007
+ addedTextInBlock && !isInline( node ) ) {
3008
+ textContent += '\n';
3009
+ addedTextInBlock = false;
3010
+ }
3011
+ node = walker.nextNode();
3012
+ }
3013
+
3014
+ return textContent;
3015
+ };
3016
+
3017
+ proto.getPath = function () {
3018
+ return this._path;
3019
+ };
3020
+
3021
+ // --- Workaround for browsers that can't focus empty text nodes ---
3022
+
3023
+ // WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
3024
+
3025
+ // Walk down the tree starting at the root and remove any ZWS. If the node only
3026
+ // contained ZWS space then remove it too. We may want to keep one ZWS node at
3027
+ // the bottom of the tree so the block can be selected. Define that node as the
3028
+ // keepNode.
3029
+ var removeZWS = function ( root, keepNode ) {
3030
+ var walker = new TreeWalker( root, SHOW_TEXT, function () {
3031
+ return true;
3032
+ }, false ),
3033
+ parent, node, index;
3034
+ while ( node = walker.nextNode() ) {
3035
+ while ( ( index = node.data.indexOf( ZWS ) ) > -1 &&
3036
+ ( !keepNode || node.parentNode !== keepNode ) ) {
3037
+ if ( node.length === 1 ) {
3038
+ do {
3039
+ parent = node.parentNode;
3040
+ parent.removeChild( node );
3041
+ node = parent;
3042
+ walker.currentNode = parent;
3043
+ } while ( isInline( node ) && !getLength( node ) );
3044
+ break;
3045
+ } else {
3046
+ node.deleteData( index, 1 );
3047
+ }
3048
+ }
3049
+ }
3050
+ };
3051
+
3052
+ proto._didAddZWS = function () {
3053
+ this._hasZWS = true;
3054
+ };
3055
+ proto._removeZWS = function () {
3056
+ if ( !this._hasZWS ) {
3057
+ return;
3058
+ }
3059
+ removeZWS( this._root );
3060
+ this._hasZWS = false;
3061
+ };
3062
+
3063
+ // --- Path change events ---
3064
+
3065
+ proto._updatePath = function ( range, force ) {
3066
+ if ( !range ) {
3067
+ return;
3068
+ }
3069
+ var anchor = range.startContainer,
3070
+ focus = range.endContainer,
3071
+ newPath;
3072
+ if ( force || anchor !== this._lastAnchorNode ||
3073
+ focus !== this._lastFocusNode ) {
3074
+ this._lastAnchorNode = anchor;
3075
+ this._lastFocusNode = focus;
3076
+ newPath = ( anchor && focus ) ? ( anchor === focus ) ?
3077
+ getPath( focus, this._root ) : '(selection)' : '';
3078
+ if ( this._path !== newPath ) {
3079
+ this._path = newPath;
3080
+ this.fireEvent( 'pathChange', { path: newPath } );
3081
+ }
3082
+ }
3083
+ this.fireEvent( range.collapsed ? 'cursor' : 'select', {
3084
+ range: range
3085
+ });
3086
+ };
3087
+
3088
+ // selectionchange is fired synchronously in IE when removing current selection
3089
+ // and when setting new selection; keyup/mouseup may have processing we want
3090
+ // to do first. Either way, send to next event loop.
3091
+ proto._updatePathOnEvent = function ( event ) {
3092
+ var self = this;
3093
+ if ( self._isFocused && !self._willUpdatePath ) {
3094
+ self._willUpdatePath = true;
3095
+ setTimeout( function () {
3096
+ self._willUpdatePath = false;
3097
+ self._updatePath( self.getSelection() );
3098
+ }, 0 );
3099
+ }
3100
+ };
3101
+
3102
+ // --- Focus ---
3103
+
3104
+ proto.focus = function () {
3105
+ this._root.focus();
3106
+
3107
+ if ( isIE ) {
3108
+ this.fireEvent( 'focus' );
3109
+ }
3110
+
3111
+ return this;
3112
+ };
3113
+
3114
+ proto.blur = function () {
3115
+ this._root.blur();
3116
+
3117
+ if ( isIE ) {
3118
+ this.fireEvent( 'blur' );
3119
+ }
3120
+
3121
+ return this;
3122
+ };
3123
+
3124
+ // --- Bookmarking ---
3125
+
3126
+ var startSelectionId = 'squire-selection-start';
3127
+ var endSelectionId = 'squire-selection-end';
3128
+
3129
+ proto._saveRangeToBookmark = function ( range ) {
3130
+ var startNode = this.createElement( 'INPUT', {
3131
+ id: startSelectionId,
3132
+ type: 'hidden'
3133
+ }),
3134
+ endNode = this.createElement( 'INPUT', {
3135
+ id: endSelectionId,
3136
+ type: 'hidden'
3137
+ }),
3138
+ temp;
3139
+
3140
+ insertNodeInRange( range, startNode );
3141
+ range.collapse( false );
3142
+ insertNodeInRange( range, endNode );
3143
+
3144
+ // In a collapsed range, the start is sometimes inserted after the end!
3145
+ if ( startNode.compareDocumentPosition( endNode ) &
3146
+ DOCUMENT_POSITION_PRECEDING ) {
3147
+ startNode.id = endSelectionId;
3148
+ endNode.id = startSelectionId;
3149
+ temp = startNode;
3150
+ startNode = endNode;
3151
+ endNode = temp;
3152
+ }
3153
+
3154
+ range.setStartAfter( startNode );
3155
+ range.setEndBefore( endNode );
3156
+ };
3157
+
3158
+ proto._getRangeAndRemoveBookmark = function ( range ) {
3159
+ var root = this._root,
3160
+ start = root.querySelector( '#' + startSelectionId ),
3161
+ end = root.querySelector( '#' + endSelectionId );
3162
+
3163
+ if ( start && end ) {
3164
+ var startContainer = start.parentNode,
3165
+ endContainer = end.parentNode,
3166
+ startOffset = indexOf.call( startContainer.childNodes, start ),
3167
+ endOffset = indexOf.call( endContainer.childNodes, end );
3168
+
3169
+ if ( startContainer === endContainer ) {
3170
+ endOffset -= 1;
3171
+ }
3172
+
3173
+ detach( start );
3174
+ detach( end );
3175
+
3176
+ if ( !range ) {
3177
+ range = this._doc.createRange();
3178
+ }
3179
+ range.setStart( startContainer, startOffset );
3180
+ range.setEnd( endContainer, endOffset );
3181
+
3182
+ // Merge any text nodes we split
3183
+ mergeInlines( startContainer, range );
3184
+ if ( startContainer !== endContainer ) {
3185
+ mergeInlines( endContainer, range );
3186
+ }
3187
+
3188
+ // If we didn't split a text node, we should move into any adjacent
3189
+ // text node to current selection point
3190
+ if ( range.collapsed ) {
3191
+ startContainer = range.startContainer;
3192
+ if ( startContainer.nodeType === TEXT_NODE ) {
3193
+ endContainer = startContainer.childNodes[ range.startOffset ];
3194
+ if ( !endContainer || endContainer.nodeType !== TEXT_NODE ) {
3195
+ endContainer =
3196
+ startContainer.childNodes[ range.startOffset - 1 ];
3197
+ }
3198
+ if ( endContainer && endContainer.nodeType === TEXT_NODE ) {
3199
+ range.setStart( endContainer, 0 );
3200
+ range.collapse( true );
3201
+ }
3202
+ }
3203
+ }
3204
+ }
3205
+ return range || null;
3206
+ };
3207
+
3208
+ // --- Undo ---
3209
+
3210
+ proto._keyUpDetectChange = function ( event ) {
3211
+ var code = event.keyCode;
3212
+ // Presume document was changed if:
3213
+ // 1. A modifier key (other than shift) wasn't held down
3214
+ // 2. The key pressed is not in range 16<=x<=20 (control keys)
3215
+ // 3. The key pressed is not in range 33<=x<=45 (navigation keys)
3216
+ if ( !event.ctrlKey && !event.metaKey && !event.altKey &&
3217
+ ( code < 16 || code > 20 ) &&
3218
+ ( code < 33 || code > 45 ) ) {
3219
+ this._docWasChanged();
3220
+ }
3221
+ };
3222
+
3223
+ proto._docWasChanged = function () {
3224
+ if ( canWeakMap ) {
3225
+ nodeCategoryCache = new WeakMap();
3226
+ }
3227
+ if ( this._ignoreAllChanges ) {
3228
+ return;
3229
+ }
3230
+
3231
+ if ( canObserveMutations && this._ignoreChange ) {
3232
+ this._ignoreChange = false;
3233
+ return;
3234
+ }
3235
+ if ( this._isInUndoState ) {
3236
+ this._isInUndoState = false;
3237
+ this.fireEvent( 'undoStateChange', {
3238
+ canUndo: true,
3239
+ canRedo: false
3240
+ });
3241
+ }
3242
+ this.fireEvent( 'input' );
3243
+ };
3244
+
3245
+ // Leaves bookmark
3246
+ proto._recordUndoState = function ( range, replace ) {
3247
+ // Don't record if we're already in an undo state
3248
+ if ( !this._isInUndoState|| replace ) {
3249
+ // Advance pointer to new position
3250
+ var undoIndex = this._undoIndex;
3251
+ var undoStack = this._undoStack;
3252
+ var undoConfig = this._config.undo;
3253
+ var undoThreshold = undoConfig.documentSizeThreshold;
3254
+ var undoLimit = undoConfig.undoLimit;
3255
+ var html;
3256
+
3257
+ if ( !replace ) {
3258
+ undoIndex += 1;
3259
+ }
3260
+
3261
+ // Truncate stack if longer (i.e. if has been previously undone)
3262
+ if ( undoIndex < this._undoStackLength ) {
3263
+ undoStack.length = this._undoStackLength = undoIndex;
3264
+ }
3265
+
3266
+ // Get data
3267
+ if ( range ) {
3268
+ this._saveRangeToBookmark( range );
3269
+ }
3270
+ html = this._getHTML();
3271
+
3272
+ // If this document is above the configured size threshold,
3273
+ // limit the number of saved undo states.
3274
+ // Threshold is in bytes, JS uses 2 bytes per character
3275
+ if ( undoThreshold > -1 && html.length * 2 > undoThreshold ) {
3276
+ if ( undoLimit > -1 && undoIndex > undoLimit ) {
3277
+ undoStack.splice( 0, undoIndex - undoLimit );
3278
+ undoIndex = undoLimit;
3279
+ this._undoStackLength = undoLimit;
3280
+ }
3281
+ }
3282
+
3283
+ // Save data
3284
+ undoStack[ undoIndex ] = html;
3285
+ this._undoIndex = undoIndex;
3286
+ this._undoStackLength += 1;
3287
+ this._isInUndoState = true;
3288
+ }
3289
+ };
3290
+
3291
+ proto.saveUndoState = function ( range ) {
3292
+ if ( range === undefined ) {
3293
+ range = this.getSelection();
3294
+ }
3295
+ this._recordUndoState( range, this._isInUndoState );
3296
+ this._getRangeAndRemoveBookmark( range );
3297
+
3298
+ return this;
3299
+ };
3300
+
3301
+ proto.undo = function () {
3302
+ // Sanity check: must not be at beginning of the history stack
3303
+ if ( this._undoIndex !== 0 || !this._isInUndoState ) {
3304
+ // Make sure any changes since last checkpoint are saved.
3305
+ this._recordUndoState( this.getSelection(), false );
3306
+
3307
+ this._undoIndex -= 1;
3308
+ this._setHTML( this._undoStack[ this._undoIndex ] );
3309
+ var range = this._getRangeAndRemoveBookmark();
3310
+ if ( range ) {
3311
+ this.setSelection( range );
3312
+ }
3313
+ this._isInUndoState = true;
3314
+ this.fireEvent( 'undoStateChange', {
3315
+ canUndo: this._undoIndex !== 0,
3316
+ canRedo: true
3317
+ });
3318
+ this.fireEvent( 'input' );
3319
+ }
3320
+ return this;
3321
+ };
3322
+
3323
+ proto.redo = function () {
3324
+ // Sanity check: must not be at end of stack and must be in an undo
3325
+ // state.
3326
+ var undoIndex = this._undoIndex,
3327
+ undoStackLength = this._undoStackLength;
3328
+ if ( undoIndex + 1 < undoStackLength && this._isInUndoState ) {
3329
+ this._undoIndex += 1;
3330
+ this._setHTML( this._undoStack[ this._undoIndex ] );
3331
+ var range = this._getRangeAndRemoveBookmark();
3332
+ if ( range ) {
3333
+ this.setSelection( range );
3334
+ }
3335
+ this.fireEvent( 'undoStateChange', {
3336
+ canUndo: true,
3337
+ canRedo: undoIndex + 2 < undoStackLength
3338
+ });
3339
+ this.fireEvent( 'input' );
3340
+ }
3341
+ return this;
3342
+ };
3343
+
3344
+ // --- Inline formatting ---
3345
+
3346
+ // Looks for matching tag and attributes, so won't work
3347
+ // if <strong> instead of <b> etc.
3348
+ proto.hasFormat = function ( tag, attributes, range ) {
3349
+ // 1. Normalise the arguments and get selection
3350
+ tag = tag.toUpperCase();
3351
+ if ( !attributes ) { attributes = {}; }
3352
+ if ( !range && !( range = this.getSelection() ) ) {
3353
+ return false;
3354
+ }
3355
+
3356
+ // Sanitize range to prevent weird IE artifacts
3357
+ if ( !range.collapsed &&
3358
+ range.startContainer.nodeType === TEXT_NODE &&
3359
+ range.startOffset === range.startContainer.length &&
3360
+ range.startContainer.nextSibling ) {
3361
+ range.setStartBefore( range.startContainer.nextSibling );
3362
+ }
3363
+ if ( !range.collapsed &&
3364
+ range.endContainer.nodeType === TEXT_NODE &&
3365
+ range.endOffset === 0 &&
3366
+ range.endContainer.previousSibling ) {
3367
+ range.setEndAfter( range.endContainer.previousSibling );
3368
+ }
3369
+
3370
+ // If the common ancestor is inside the tag we require, we definitely
3371
+ // have the format.
3372
+ var root = this._root;
3373
+ var common = range.commonAncestorContainer;
3374
+ var walker, node;
3375
+ if ( getNearest( common, root, tag, attributes ) ) {
3376
+ return true;
3377
+ }
3378
+
3379
+ // If common ancestor is a text node and doesn't have the format, we
3380
+ // definitely don't have it.
3381
+ if ( common.nodeType === TEXT_NODE ) {
3382
+ return false;
3383
+ }
3384
+
3385
+ // Otherwise, check each text node at least partially contained within
3386
+ // the selection and make sure all of them have the format we want.
3387
+ walker = new TreeWalker( common, SHOW_TEXT, function ( node ) {
3388
+ return isNodeContainedInRange( range, node, true );
3389
+ }, false );
3390
+
3391
+ var seenNode = false;
3392
+ while ( node = walker.nextNode() ) {
3393
+ if ( !getNearest( node, root, tag, attributes ) ) {
3394
+ return false;
3395
+ }
3396
+ seenNode = true;
3397
+ }
3398
+
3399
+ return seenNode;
3400
+ };
3401
+
3402
+ // Extracts the font-family and font-size (if any) of the element
3403
+ // holding the cursor. If there's a selection, returns an empty object.
3404
+ proto.getFontInfo = function ( range ) {
3405
+ var fontInfo = {
3406
+ color: undefined,
3407
+ backgroundColor: undefined,
3408
+ family: undefined,
3409
+ size: undefined
3410
+ };
3411
+ var seenAttributes = 0;
3412
+ var element, style, attr;
3413
+
3414
+ if ( !range && !( range = this.getSelection() ) ) {
3415
+ return fontInfo;
3416
+ }
3417
+
3418
+ element = range.commonAncestorContainer;
3419
+ if ( range.collapsed || element.nodeType === TEXT_NODE ) {
3420
+ if ( element.nodeType === TEXT_NODE ) {
3421
+ element = element.parentNode;
3422
+ }
3423
+ while ( seenAttributes < 4 && element ) {
3424
+ if ( style = element.style ) {
3425
+ if ( !fontInfo.color && ( attr = style.color ) ) {
3426
+ fontInfo.color = attr;
3427
+ seenAttributes += 1;
3428
+ }
3429
+ if ( !fontInfo.backgroundColor &&
3430
+ ( attr = style.backgroundColor ) ) {
3431
+ fontInfo.backgroundColor = attr;
3432
+ seenAttributes += 1;
3433
+ }
3434
+ if ( !fontInfo.family && ( attr = style.fontFamily ) ) {
3435
+ fontInfo.family = attr;
3436
+ seenAttributes += 1;
3437
+ }
3438
+ if ( !fontInfo.size && ( attr = style.fontSize ) ) {
3439
+ fontInfo.size = attr;
3440
+ seenAttributes += 1;
3441
+ }
3442
+ }
3443
+ element = element.parentNode;
3444
+ }
3445
+ }
3446
+ return fontInfo;
3447
+ };
3448
+
3449
+ proto._addFormat = function ( tag, attributes, range ) {
3450
+ // If the range is collapsed we simply insert the node by wrapping
3451
+ // it round the range and focus it.
3452
+ var root = this._root;
3453
+ var el, walker, startContainer, endContainer, startOffset, endOffset,
3454
+ node, needsFormat, block;
3455
+
3456
+ if ( range.collapsed ) {
3457
+ el = fixCursor( this.createElement( tag, attributes ), root );
3458
+ insertNodeInRange( range, el );
3459
+ range.setStart( el.firstChild, el.firstChild.length );
3460
+ range.collapse( true );
3461
+
3462
+ // Clean up any previous formats that may have been set on this block
3463
+ // that are unused.
3464
+ block = el;
3465
+ while ( isInline( block ) ) {
3466
+ block = block.parentNode;
3467
+ }
3468
+ removeZWS( block, el );
3469
+ }
3470
+ // Otherwise we find all the textnodes in the range (splitting
3471
+ // partially selected nodes) and if they're not already formatted
3472
+ // correctly we wrap them in the appropriate tag.
3473
+ else {
3474
+ // Create an iterator to walk over all the text nodes under this
3475
+ // ancestor which are in the range and not already formatted
3476
+ // correctly.
3477
+ //
3478
+ // In Blink/WebKit, empty blocks may have no text nodes, just a <br>.
3479
+ // Therefore we wrap this in the tag as well, as this will then cause it
3480
+ // to apply when the user types something in the block, which is
3481
+ // presumably what was intended.
3482
+ //
3483
+ // IMG tags are included because we may want to create a link around
3484
+ // them, and adding other styles is harmless.
3485
+ walker = new TreeWalker(
3486
+ range.commonAncestorContainer,
3487
+ SHOW_TEXT|SHOW_ELEMENT,
3488
+ function ( node ) {
3489
+ return ( node.nodeType === TEXT_NODE ||
3490
+ node.nodeName === 'BR' ||
3491
+ node.nodeName === 'IMG'
3492
+ ) && isNodeContainedInRange( range, node, true );
3493
+ },
3494
+ false
3495
+ );
3496
+
3497
+ // Start at the beginning node of the range and iterate through
3498
+ // all the nodes in the range that need formatting.
3499
+ startContainer = range.startContainer;
3500
+ startOffset = range.startOffset;
3501
+ endContainer = range.endContainer;
3502
+ endOffset = range.endOffset;
3503
+
3504
+ // Make sure we start with a valid node.
3505
+ walker.currentNode = startContainer;
3506
+ if ( !walker.filter( startContainer ) ) {
3507
+ startContainer = walker.nextNode();
3508
+ startOffset = 0;
3509
+ }
3510
+
3511
+ // If there are no interesting nodes in the selection, abort
3512
+ if ( !startContainer ) {
3513
+ return range;
3514
+ }
3515
+
3516
+ do {
3517
+ node = walker.currentNode;
3518
+ needsFormat = !getNearest( node, root, tag, attributes );
3519
+ if ( needsFormat ) {
3520
+ // <br> can never be a container node, so must have a text node
3521
+ // if node == (end|start)Container
3522
+ if ( node === endContainer && node.length > endOffset ) {
3523
+ node.splitText( endOffset );
3524
+ }
3525
+ if ( node === startContainer && startOffset ) {
3526
+ node = node.splitText( startOffset );
3527
+ if ( endContainer === startContainer ) {
3528
+ endContainer = node;
3529
+ endOffset -= startOffset;
3530
+ }
3531
+ startContainer = node;
3532
+ startOffset = 0;
3533
+ }
3534
+ el = this.createElement( tag, attributes );
3535
+ replaceWith( node, el );
3536
+ el.appendChild( node );
3537
+ }
3538
+ } while ( walker.nextNode() );
3539
+
3540
+ // If we don't finish inside a text node, offset may have changed.
3541
+ if ( endContainer.nodeType !== TEXT_NODE ) {
3542
+ if ( node.nodeType === TEXT_NODE ) {
3543
+ endContainer = node;
3544
+ endOffset = node.length;
3545
+ } else {
3546
+ // If <br>, we must have just wrapped it, so it must have only
3547
+ // one child
3548
+ endContainer = node.parentNode;
3549
+ endOffset = 1;
3550
+ }
3551
+ }
3552
+
3553
+ // Now set the selection to as it was before
3554
+ range = this._createRange(
3555
+ startContainer, startOffset, endContainer, endOffset );
3556
+ }
3557
+ return range;
3558
+ };
3559
+
3560
+ proto._removeFormat = function ( tag, attributes, range, partial ) {
3561
+ // Add bookmark
3562
+ this._saveRangeToBookmark( range );
3563
+
3564
+ // We need a node in the selection to break the surrounding
3565
+ // formatted text.
3566
+ var doc = this._doc,
3567
+ fixer;
3568
+ if ( range.collapsed ) {
3569
+ if ( cantFocusEmptyTextNodes ) {
3570
+ fixer = doc.createTextNode( ZWS );
3571
+ this._didAddZWS();
3572
+ } else {
3573
+ fixer = doc.createTextNode( '' );
3574
+ }
3575
+ insertNodeInRange( range, fixer );
3576
+ }
3577
+
3578
+ // Find block-level ancestor of selection
3579
+ var root = range.commonAncestorContainer;
3580
+ while ( isInline( root ) ) {
3581
+ root = root.parentNode;
3582
+ }
3583
+
3584
+ // Find text nodes inside formatTags that are not in selection and
3585
+ // add an extra tag with the same formatting.
3586
+ var startContainer = range.startContainer,
3587
+ startOffset = range.startOffset,
3588
+ endContainer = range.endContainer,
3589
+ endOffset = range.endOffset,
3590
+ toWrap = [],
3591
+ examineNode = function ( node, exemplar ) {
3592
+ // If the node is completely contained by the range then
3593
+ // we're going to remove all formatting so ignore it.
3594
+ if ( isNodeContainedInRange( range, node, false ) ) {
3595
+ return;
3596
+ }
3597
+
3598
+ var isText = ( node.nodeType === TEXT_NODE ),
3599
+ child, next;
3600
+
3601
+ // If not at least partially contained, wrap entire contents
3602
+ // in a clone of the tag we're removing and we're done.
3603
+ if ( !isNodeContainedInRange( range, node, true ) ) {
3604
+ // Ignore bookmarks and empty text nodes
3605
+ if ( node.nodeName !== 'INPUT' &&
3606
+ ( !isText || node.data ) ) {
3607
+ toWrap.push([ exemplar, node ]);
3608
+ }
3609
+ return;
3610
+ }
3611
+
3612
+ // Split any partially selected text nodes.
3613
+ if ( isText ) {
3614
+ if ( node === endContainer && endOffset !== node.length ) {
3615
+ toWrap.push([ exemplar, node.splitText( endOffset ) ]);
3616
+ }
3617
+ if ( node === startContainer && startOffset ) {
3618
+ node.splitText( startOffset );
3619
+ toWrap.push([ exemplar, node ]);
3620
+ }
3621
+ }
3622
+ // If not a text node, recurse onto all children.
3623
+ // Beware, the tree may be rewritten with each call
3624
+ // to examineNode, hence find the next sibling first.
3625
+ else {
3626
+ for ( child = node.firstChild; child; child = next ) {
3627
+ next = child.nextSibling;
3628
+ examineNode( child, exemplar );
3629
+ }
3630
+ }
3631
+ },
3632
+ formatTags = Array.prototype.filter.call(
3633
+ root.getElementsByTagName( tag ), function ( el ) {
3634
+ return isNodeContainedInRange( range, el, true ) &&
3635
+ hasTagAttributes( el, tag, attributes );
3636
+ }
3637
+ );
3638
+
3639
+ if ( !partial ) {
3640
+ formatTags.forEach( function ( node ) {
3641
+ examineNode( node, node );
3642
+ });
3643
+ }
3644
+
3645
+ // Now wrap unselected nodes in the tag
3646
+ toWrap.forEach( function ( item ) {
3647
+ // [ exemplar, node ] tuple
3648
+ var el = item[0].cloneNode( false ),
3649
+ node = item[1];
3650
+ replaceWith( node, el );
3651
+ el.appendChild( node );
3652
+ });
3653
+ // and remove old formatting tags.
3654
+ formatTags.forEach( function ( el ) {
3655
+ replaceWith( el, empty( el ) );
3656
+ });
3657
+
3658
+ // Merge adjacent inlines:
3659
+ this._getRangeAndRemoveBookmark( range );
3660
+ if ( fixer ) {
3661
+ range.collapse( false );
3662
+ }
3663
+ mergeInlines( root, range );
3664
+
3665
+ return range;
3666
+ };
3667
+
3668
+ proto.changeFormat = function ( add, remove, range, partial ) {
3669
+ // Normalise the arguments and get selection
3670
+ if ( !range && !( range = this.getSelection() ) ) {
3671
+ return this;
3672
+ }
3673
+
3674
+ // Save undo checkpoint
3675
+ this.saveUndoState( range );
3676
+
3677
+ if ( remove ) {
3678
+ range = this._removeFormat( remove.tag.toUpperCase(),
3679
+ remove.attributes || {}, range, partial );
3680
+ }
3681
+ if ( add ) {
3682
+ range = this._addFormat( add.tag.toUpperCase(),
3683
+ add.attributes || {}, range );
3684
+ }
3685
+
3686
+ this.setSelection( range );
3687
+ this._updatePath( range, true );
3688
+
3689
+ // We're not still in an undo state
3690
+ if ( !canObserveMutations ) {
3691
+ this._docWasChanged();
3692
+ }
3693
+
3694
+ return this;
3695
+ };
3696
+
3697
+ // --- Block formatting ---
3698
+
3699
+ var tagAfterSplit = {
3700
+ DT: 'DD',
3701
+ DD: 'DT',
3702
+ LI: 'LI',
3703
+ PRE: 'PRE'
3704
+ };
3705
+
3706
+ var splitBlock = function ( self, block, node, offset ) {
3707
+ var splitTag = tagAfterSplit[ block.nodeName ],
3708
+ splitProperties = null,
3709
+ nodeAfterSplit = split( node, offset, block.parentNode, self._root ),
3710
+ config = self._config;
3711
+
3712
+ if ( !splitTag ) {
3713
+ splitTag = config.blockTag;
3714
+ splitProperties = config.blockAttributes;
3715
+ }
3716
+
3717
+ // Make sure the new node is the correct type.
3718
+ if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) {
3719
+ block = createElement( nodeAfterSplit.ownerDocument,
3720
+ splitTag, splitProperties );
3721
+ if ( nodeAfterSplit.dir ) {
3722
+ block.dir = nodeAfterSplit.dir;
3723
+ }
3724
+ replaceWith( nodeAfterSplit, block );
3725
+ block.appendChild( empty( nodeAfterSplit ) );
3726
+ nodeAfterSplit = block;
3727
+ }
3728
+ return nodeAfterSplit;
3729
+ };
3730
+
3731
+ proto.forEachBlock = function ( fn, mutates, range ) {
3732
+ if ( !range && !( range = this.getSelection() ) ) {
3733
+ return this;
3734
+ }
3735
+
3736
+ // Save undo checkpoint
3737
+ if ( mutates ) {
3738
+ this.saveUndoState( range );
3739
+ }
3740
+
3741
+ var root = this._root;
3742
+ var start = getStartBlockOfRange( range, root );
3743
+ var end = getEndBlockOfRange( range, root );
3744
+ if ( start && end ) {
3745
+ do {
3746
+ if ( fn( start ) || start === end ) { break; }
3747
+ } while ( start = getNextBlock( start, root ) );
3748
+ }
3749
+
3750
+ if ( mutates ) {
3751
+ this.setSelection( range );
3752
+
3753
+ // Path may have changed
3754
+ this._updatePath( range, true );
3755
+
3756
+ // We're not still in an undo state
3757
+ if ( !canObserveMutations ) {
3758
+ this._docWasChanged();
3759
+ }
3760
+ }
3761
+ return this;
3762
+ };
3763
+
3764
+ proto.modifyBlocks = function ( modify, range ) {
3765
+ if ( !range && !( range = this.getSelection() ) ) {
3766
+ return this;
3767
+ }
3768
+
3769
+ // 1. Save undo checkpoint and bookmark selection
3770
+ this._recordUndoState( range, this._isInUndoState );
3771
+
3772
+ var root = this._root;
3773
+ var frag;
3774
+
3775
+ // 2. Expand range to block boundaries
3776
+ expandRangeToBlockBoundaries( range, root );
3777
+
3778
+ // 3. Remove range.
3779
+ moveRangeBoundariesUpTree( range, root, root, root );
3780
+ frag = extractContentsOfRange( range, root, root );
3781
+
3782
+ // 4. Modify tree of fragment and reinsert.
3783
+ insertNodeInRange( range, modify.call( this, frag ) );
3784
+
3785
+ // 5. Merge containers at edges
3786
+ if ( range.endOffset < range.endContainer.childNodes.length ) {
3787
+ mergeContainers( range.endContainer.childNodes[ range.endOffset ], root );
3788
+ }
3789
+ mergeContainers( range.startContainer.childNodes[ range.startOffset ], root );
3790
+
3791
+ // 6. Restore selection
3792
+ this._getRangeAndRemoveBookmark( range );
3793
+ this.setSelection( range );
3794
+ this._updatePath( range, true );
3795
+
3796
+ // 7. We're not still in an undo state
3797
+ if ( !canObserveMutations ) {
3798
+ this._docWasChanged();
3799
+ }
3800
+
3801
+ return this;
3802
+ };
3803
+
3804
+ var increaseBlockQuoteLevel = function ( frag ) {
3805
+ return this.createElement( 'BLOCKQUOTE',
3806
+ this._config.tagAttributes.blockquote, [
3807
+ frag
3808
+ ]);
3809
+ };
3810
+
3811
+ var decreaseBlockQuoteLevel = function ( frag ) {
3812
+ var root = this._root;
3813
+ var blockquotes = frag.querySelectorAll( 'blockquote' );
3814
+ Array.prototype.filter.call( blockquotes, function ( el ) {
3815
+ return !getNearest( el.parentNode, root, 'BLOCKQUOTE' );
3816
+ }).forEach( function ( el ) {
3817
+ replaceWith( el, empty( el ) );
3818
+ });
3819
+ return frag;
3820
+ };
3821
+
3822
+ var removeBlockQuote = function (/* frag */) {
3823
+ return this.createDefaultBlock([
3824
+ this.createElement( 'INPUT', {
3825
+ id: startSelectionId,
3826
+ type: 'hidden'
3827
+ }),
3828
+ this.createElement( 'INPUT', {
3829
+ id: endSelectionId,
3830
+ type: 'hidden'
3831
+ })
3832
+ ]);
3833
+ };
3834
+
3835
+ var makeList = function ( self, frag, type ) {
3836
+ var walker = getBlockWalker( frag, self._root ),
3837
+ node, tag, prev, newLi,
3838
+ tagAttributes = self._config.tagAttributes,
3839
+ listAttrs = tagAttributes[ type.toLowerCase() ],
3840
+ listItemAttrs = tagAttributes.li;
3841
+
3842
+ while ( node = walker.nextNode() ) {
3843
+ if ( node.parentNode.nodeName === 'LI' ) {
3844
+ node = node.parentNode;
3845
+ walker.currentNode = node.lastChild;
3846
+ }
3847
+ if ( node.nodeName !== 'LI' ) {
3848
+ newLi = self.createElement( 'LI', listItemAttrs );
3849
+ if ( node.dir ) {
3850
+ newLi.dir = node.dir;
3851
+ }
3852
+
3853
+ // Have we replaced the previous block with a new <ul>/<ol>?
3854
+ if ( ( prev = node.previousSibling ) && prev.nodeName === type ) {
3855
+ prev.appendChild( newLi );
3856
+ detach( node );
3857
+ }
3858
+ // Otherwise, replace this block with the <ul>/<ol>
3859
+ else {
3860
+ replaceWith(
3861
+ node,
3862
+ self.createElement( type, listAttrs, [
3863
+ newLi
3864
+ ])
3865
+ );
3866
+ }
3867
+ newLi.appendChild( empty( node ) );
3868
+ walker.currentNode = newLi;
3869
+ } else {
3870
+ node = node.parentNode;
3871
+ tag = node.nodeName;
3872
+ if ( tag !== type && ( /^[OU]L$/.test( tag ) ) ) {
3873
+ replaceWith( node,
3874
+ self.createElement( type, listAttrs, [ empty( node ) ] )
3875
+ );
3876
+ }
3877
+ }
3878
+ }
3879
+ };
3880
+
3881
+ var makeUnorderedList = function ( frag ) {
3882
+ makeList( this, frag, 'UL' );
3883
+ return frag;
3884
+ };
3885
+
3886
+ var makeOrderedList = function ( frag ) {
3887
+ makeList( this, frag, 'OL' );
3888
+ return frag;
3889
+ };
3890
+
3891
+ var removeList = function ( frag ) {
3892
+ var lists = frag.querySelectorAll( 'UL, OL' ),
3893
+ items = frag.querySelectorAll( 'LI' ),
3894
+ root = this._root,
3895
+ i, l, list, listFrag, item;
3896
+ for ( i = 0, l = lists.length; i < l; i += 1 ) {
3897
+ list = lists[i];
3898
+ listFrag = empty( list );
3899
+ fixContainer( listFrag, root );
3900
+ replaceWith( list, listFrag );
3901
+ }
3902
+
3903
+ for ( i = 0, l = items.length; i < l; i += 1 ) {
3904
+ item = items[i];
3905
+ if ( isBlock( item ) ) {
3906
+ replaceWith( item,
3907
+ this.createDefaultBlock([ empty( item ) ])
3908
+ );
3909
+ } else {
3910
+ fixContainer( item, root );
3911
+ replaceWith( item, empty( item ) );
3912
+ }
3913
+ }
3914
+ return frag;
3915
+ };
3916
+
3917
+ var getListSelection = function ( range, root ) {
3918
+ // Get start+end li in single common ancestor
3919
+ var list = range.commonAncestorContainer;
3920
+ var startLi = range.startContainer;
3921
+ var endLi = range.endContainer;
3922
+ while ( list && list !== root && !/^[OU]L$/.test( list.nodeName ) ) {
3923
+ list = list.parentNode;
3924
+ }
3925
+ if ( !list || list === root ) {
3926
+ return null;
3927
+ }
3928
+ if ( startLi === list ) {
3929
+ startLi = startLi.childNodes[ range.startOffset ];
3930
+ }
3931
+ if ( endLi === list ) {
3932
+ endLi = endLi.childNodes[ range.endOffset ];
3933
+ }
3934
+ while ( startLi && startLi.parentNode !== list ) {
3935
+ startLi = startLi.parentNode;
3936
+ }
3937
+ while ( endLi && endLi.parentNode !== list ) {
3938
+ endLi = endLi.parentNode;
3939
+ }
3940
+ return [ list, startLi, endLi ];
3941
+ };
3942
+
3943
+ proto.increaseListLevel = function ( range ) {
3944
+ if ( !range && !( range = this.getSelection() ) ) {
3945
+ return this.focus();
3946
+ }
3947
+
3948
+ var root = this._root;
3949
+ var listSelection = getListSelection( range, root );
3950
+ if ( !listSelection ) {
3951
+ return this.focus();
3952
+ }
3953
+
3954
+ var list = listSelection[0];
3955
+ var startLi = listSelection[1];
3956
+ var endLi = listSelection[2];
3957
+ if ( !startLi || startLi === list.firstChild ) {
3958
+ return this.focus();
3959
+ }
3960
+
3961
+ // Save undo checkpoint and bookmark selection
3962
+ this._recordUndoState( range, this._isInUndoState );
3963
+
3964
+ // Increase list depth
3965
+ var type = list.nodeName;
3966
+ var newParent = startLi.previousSibling;
3967
+ var listAttrs, next;
3968
+ if ( newParent.nodeName !== type ) {
3969
+ listAttrs = this._config.tagAttributes[ type.toLowerCase() ];
3970
+ newParent = this.createElement( type, listAttrs );
3971
+ list.insertBefore( newParent, startLi );
3972
+ }
3973
+ do {
3974
+ next = startLi === endLi ? null : startLi.nextSibling;
3975
+ newParent.appendChild( startLi );
3976
+ } while ( ( startLi = next ) );
3977
+ next = newParent.nextSibling;
3978
+ if ( next ) {
3979
+ mergeContainers( next, root );
3980
+ }
3981
+
3982
+ // Restore selection
3983
+ this._getRangeAndRemoveBookmark( range );
3984
+ this.setSelection( range );
3985
+ this._updatePath( range, true );
3986
+
3987
+ // We're not still in an undo state
3988
+ if ( !canObserveMutations ) {
3989
+ this._docWasChanged();
3990
+ }
3991
+
3992
+ return this.focus();
3993
+ };
3994
+
3995
+ proto.decreaseListLevel = function ( range ) {
3996
+ if ( !range && !( range = this.getSelection() ) ) {
3997
+ return this.focus();
3998
+ }
3999
+
4000
+ var root = this._root;
4001
+ var listSelection = getListSelection( range, root );
4002
+ if ( !listSelection ) {
4003
+ return this.focus();
4004
+ }
4005
+
4006
+ var list = listSelection[0];
4007
+ var startLi = listSelection[1];
4008
+ var endLi = listSelection[2];
4009
+ if ( !startLi ) {
4010
+ startLi = list.firstChild;
4011
+ }
4012
+ if ( !endLi ) {
4013
+ endLi = list.lastChild;
4014
+ }
4015
+
4016
+ // Save undo checkpoint and bookmark selection
4017
+ this._recordUndoState( range, this._isInUndoState );
4018
+
4019
+ // Find the new parent list node
4020
+ var newParent = list.parentNode;
4021
+ var next;
4022
+
4023
+ // Split list if necesary
4024
+ var insertBefore = !endLi.nextSibling ?
4025
+ list.nextSibling :
4026
+ split( list, endLi.nextSibling, newParent, root );
4027
+
4028
+ if ( newParent !== root && newParent.nodeName === 'LI' ) {
4029
+ newParent = newParent.parentNode;
4030
+ while ( insertBefore ) {
4031
+ next = insertBefore.nextSibling;
4032
+ endLi.appendChild( insertBefore );
4033
+ insertBefore = next;
4034
+ }
4035
+ insertBefore = list.parentNode.nextSibling;
4036
+ }
4037
+
4038
+ var makeNotList = !/^[OU]L$/.test( newParent.nodeName );
4039
+ do {
4040
+ next = startLi === endLi ? null : startLi.nextSibling;
4041
+ list.removeChild( startLi );
4042
+ if ( makeNotList && startLi.nodeName === 'LI' ) {
4043
+ startLi = this.createDefaultBlock([ empty( startLi ) ]);
4044
+ }
4045
+ newParent.insertBefore( startLi, insertBefore );
4046
+ } while ( ( startLi = next ) );
4047
+
4048
+ if ( !list.firstChild ) {
4049
+ detach( list );
4050
+ }
4051
+
4052
+ if ( insertBefore ) {
4053
+ mergeContainers( insertBefore, root );
4054
+ }
4055
+
4056
+ // Restore selection
4057
+ this._getRangeAndRemoveBookmark( range );
4058
+ this.setSelection( range );
4059
+ this._updatePath( range, true );
4060
+
4061
+ // We're not still in an undo state
4062
+ if ( !canObserveMutations ) {
4063
+ this._docWasChanged();
4064
+ }
4065
+
4066
+ return this.focus();
4067
+ };
4068
+
4069
+ proto._ensureBottomLine = function () {
4070
+ var root = this._root;
4071
+ var last = root.lastElementChild;
4072
+ if ( !last ||
4073
+ last.nodeName !== this._config.blockTag || !isBlock( last ) ) {
4074
+ root.appendChild( this.createDefaultBlock() );
4075
+ }
4076
+ };
4077
+
4078
+ // --- Keyboard interaction ---
4079
+
4080
+ proto.setKeyHandler = function ( key, fn ) {
4081
+ this._keyHandlers[ key ] = fn;
4082
+ return this;
4083
+ };
4084
+
4085
+ // --- Get/Set data ---
4086
+
4087
+ proto._getHTML = function () {
4088
+ return this._root.innerHTML;
4089
+ };
4090
+
4091
+ proto._setHTML = function ( html ) {
4092
+ var root = this._root;
4093
+ var node = root;
4094
+ node.innerHTML = html;
4095
+ do {
4096
+ fixCursor( node, root );
4097
+ } while ( node = getNextBlock( node, root ) );
4098
+ this._ignoreChange = true;
4099
+ };
4100
+
4101
+ proto.getHTML = function ( withBookMark ) {
4102
+ var brs = [],
4103
+ root, node, fixer, html, l, range;
4104
+ if ( withBookMark && ( range = this.getSelection() ) ) {
4105
+ this._saveRangeToBookmark( range );
4106
+ }
4107
+ if ( useTextFixer ) {
4108
+ root = this._root;
4109
+ node = root;
4110
+ while ( node = getNextBlock( node, root ) ) {
4111
+ if ( !node.textContent && !node.querySelector( 'BR' ) ) {
4112
+ fixer = this.createElement( 'BR' );
4113
+ node.appendChild( fixer );
4114
+ brs.push( fixer );
4115
+ }
4116
+ }
4117
+ }
4118
+ html = this._getHTML().replace( /\u200B/g, '' );
4119
+ if ( useTextFixer ) {
4120
+ l = brs.length;
4121
+ while ( l-- ) {
4122
+ detach( brs[l] );
4123
+ }
4124
+ }
4125
+ if ( range ) {
4126
+ this._getRangeAndRemoveBookmark( range );
4127
+ }
4128
+ return html;
4129
+ };
4130
+
4131
+ proto.setHTML = function ( html ) {
4132
+ var config = this._config;
4133
+ var sanitizeToDOMFragment = config.isSetHTMLSanitized ?
4134
+ config.sanitizeToDOMFragment : null;
4135
+ var root = this._root;
4136
+ var div, frag, child;
4137
+
4138
+ // Parse HTML into DOM tree
4139
+ if ( typeof sanitizeToDOMFragment === 'function' ) {
4140
+ frag = sanitizeToDOMFragment( html, false, this );
4141
+ } else {
4142
+ div = this.createElement( 'DIV' );
4143
+ div.innerHTML = html;
4144
+ frag = this._doc.createDocumentFragment();
4145
+ frag.appendChild( empty( div ) );
4146
+ }
4147
+
4148
+ cleanTree( frag );
4149
+ cleanupBRs( frag, root, false );
4150
+
4151
+ fixContainer( frag, root );
4152
+
4153
+ // Fix cursor
4154
+ var node = frag;
4155
+ while ( node = getNextBlock( node, root ) ) {
4156
+ fixCursor( node, root );
4157
+ }
4158
+
4159
+ // Don't fire an input event
4160
+ this._ignoreChange = true;
4161
+
4162
+ // Remove existing root children
4163
+ while ( child = root.lastChild ) {
4164
+ root.removeChild( child );
4165
+ }
4166
+
4167
+ // And insert new content
4168
+ root.appendChild( frag );
4169
+ fixCursor( root, root );
4170
+
4171
+ // Reset the undo stack
4172
+ this._undoIndex = -1;
4173
+ this._undoStack.length = 0;
4174
+ this._undoStackLength = 0;
4175
+ this._isInUndoState = false;
4176
+
4177
+ // Record undo state
4178
+ var range = this._getRangeAndRemoveBookmark() ||
4179
+ this._createRange( root.firstChild, 0 );
4180
+ this.saveUndoState( range );
4181
+ // IE will also set focus when selecting text so don't use
4182
+ // setSelection. Instead, just store it in lastSelection, so if
4183
+ // anything calls getSelection before first focus, we have a range
4184
+ // to return.
4185
+ this._lastSelection = range;
4186
+ enableRestoreSelection.call( this );
4187
+ this._updatePath( range, true );
4188
+
4189
+ return this;
4190
+ };
4191
+
4192
+ proto.insertElement = function ( el, range ) {
4193
+ if ( !range ) {
4194
+ range = this.getSelection();
4195
+ }
4196
+ range.collapse( true );
4197
+ if ( isInline( el ) ) {
4198
+ insertNodeInRange( range, el );
4199
+ range.setStartAfter( el );
4200
+ } else {
4201
+ // Get containing block node.
4202
+ var root = this._root;
4203
+ var splitNode = getStartBlockOfRange( range, root ) || root;
4204
+ var parent, nodeAfterSplit;
4205
+ // While at end of container node, move up DOM tree.
4206
+ while ( splitNode !== root && !splitNode.nextSibling ) {
4207
+ splitNode = splitNode.parentNode;
4208
+ }
4209
+ // If in the middle of a container node, split up to root.
4210
+ if ( splitNode !== root ) {
4211
+ parent = splitNode.parentNode;
4212
+ nodeAfterSplit = split( parent, splitNode.nextSibling, root, root );
4213
+ }
4214
+ if ( nodeAfterSplit ) {
4215
+ root.insertBefore( el, nodeAfterSplit );
4216
+ } else {
4217
+ root.appendChild( el );
4218
+ // Insert blank line below block.
4219
+ nodeAfterSplit = this.createDefaultBlock();
4220
+ root.appendChild( nodeAfterSplit );
4221
+ }
4222
+ range.setStart( nodeAfterSplit, 0 );
4223
+ range.setEnd( nodeAfterSplit, 0 );
4224
+ moveRangeBoundariesDownTree( range );
4225
+ }
4226
+ this.focus();
4227
+ this.setSelection( range );
4228
+ this._updatePath( range );
4229
+
4230
+ if ( !canObserveMutations ) {
4231
+ this._docWasChanged();
4232
+ }
4233
+
4234
+ return this;
4235
+ };
4236
+
4237
+ proto.insertImage = function ( src, attributes ) {
4238
+ var img = this.createElement( 'IMG', mergeObjects({
4239
+ src: src
4240
+ }, attributes, true ));
4241
+ this.insertElement( img );
4242
+ return img;
4243
+ };
4244
+
4245
+ var linkRegExp = /\b((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))|([\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,}\b)/i;
4246
+
4247
+ var addLinks = function ( frag, root, self ) {
4248
+ var doc = frag.ownerDocument,
4249
+ walker = new TreeWalker( frag, SHOW_TEXT,
4250
+ function ( node ) {
4251
+ return !getNearest( node, root, 'A' );
4252
+ }, false ),
4253
+ defaultAttributes = self._config.tagAttributes.a,
4254
+ node, data, parent, match, index, endIndex, child;
4255
+ while ( node = walker.nextNode() ) {
4256
+ data = node.data;
4257
+ parent = node.parentNode;
4258
+ while ( match = linkRegExp.exec( data ) ) {
4259
+ index = match.index;
4260
+ endIndex = index + match[0].length;
4261
+ if ( index ) {
4262
+ child = doc.createTextNode( data.slice( 0, index ) );
4263
+ parent.insertBefore( child, node );
4264
+ }
4265
+ child = self.createElement( 'A', mergeObjects({
4266
+ href: match[1] ?
4267
+ /^(?:ht|f)tps?:/.test( match[1] ) ?
4268
+ match[1] :
4269
+ 'http://' + match[1] :
4270
+ 'mailto:' + match[2]
4271
+ }, defaultAttributes, false ));
4272
+ child.textContent = data.slice( index, endIndex );
4273
+ parent.insertBefore( child, node );
4274
+ node.data = data = data.slice( endIndex );
4275
+ }
4276
+ }
4277
+ };
4278
+
4279
+ // Insert HTML at the cursor location. If the selection is not collapsed
4280
+ // insertTreeFragmentIntoRange will delete the selection so that it is replaced
4281
+ // by the html being inserted.
4282
+ proto.insertHTML = function ( html, isPaste ) {
4283
+ var config = this._config;
4284
+ var sanitizeToDOMFragment = config.isInsertedHTMLSanitized ?
4285
+ config.sanitizeToDOMFragment : null;
4286
+ var range = this.getSelection();
4287
+ var doc = this._doc;
4288
+ var startFragmentIndex, endFragmentIndex;
4289
+ var div, frag, root, node, event;
4290
+
4291
+ // Edge doesn't just copy the fragment, but includes the surrounding guff
4292
+ // including the full <head> of the page. Need to strip this out. If
4293
+ // available use DOMPurify to parse and sanitise.
4294
+ if ( typeof sanitizeToDOMFragment === 'function' ) {
4295
+ frag = sanitizeToDOMFragment( html, isPaste, this );
4296
+ } else {
4297
+ if ( isPaste ) {
4298
+ startFragmentIndex = html.indexOf( '<!--StartFragment-->' );
4299
+ endFragmentIndex = html.lastIndexOf( '<!--EndFragment-->' );
4300
+ if ( startFragmentIndex > -1 && endFragmentIndex > -1 ) {
4301
+ html = html.slice( startFragmentIndex + 20, endFragmentIndex );
4302
+ }
4303
+ }
4304
+ // Wrap with <tr> if html contains dangling <td> tags
4305
+ if ( /<\/td>((?!<\/tr>)[\s\S])*$/i.test( html ) ) {
4306
+ html = '<TR>' + html + '</TR>';
4307
+ }
4308
+ // Wrap with <table> if html contains dangling <tr> tags
4309
+ if ( /<\/tr>((?!<\/table>)[\s\S])*$/i.test( html ) ) {
4310
+ html = '<TABLE>' + html + '</TABLE>';
4311
+ }
4312
+ // Parse HTML into DOM tree
4313
+ div = this.createElement( 'DIV' );
4314
+ div.innerHTML = html;
4315
+ frag = doc.createDocumentFragment();
4316
+ frag.appendChild( empty( div ) );
4317
+ }
4318
+
4319
+ // Record undo checkpoint
4320
+ this.saveUndoState( range );
4321
+
4322
+ try {
4323
+ root = this._root;
4324
+ node = frag;
4325
+ event = {
4326
+ fragment: frag,
4327
+ preventDefault: function () {
4328
+ this.defaultPrevented = true;
4329
+ },
4330
+ defaultPrevented: false
4331
+ };
4332
+
4333
+ addLinks( frag, frag, this );
4334
+ cleanTree( frag );
4335
+ cleanupBRs( frag, root, false );
4336
+ removeEmptyInlines( frag );
4337
+ frag.normalize();
4338
+
4339
+ while ( node = getNextBlock( node, frag ) ) {
4340
+ fixCursor( node, root );
4341
+ }
4342
+
4343
+ if ( isPaste ) {
4344
+ this.fireEvent( 'willPaste', event );
4345
+ }
4346
+
4347
+ if ( !event.defaultPrevented ) {
4348
+ insertTreeFragmentIntoRange( range, event.fragment, root );
4349
+ if ( !canObserveMutations ) {
4350
+ this._docWasChanged();
4351
+ }
4352
+ range.collapse( false );
4353
+ this._ensureBottomLine();
4354
+ }
4355
+
4356
+ this.setSelection( range );
4357
+ this._updatePath( range, true );
4358
+ // Safari sometimes loses focus after paste. Weird.
4359
+ if ( isPaste ) {
4360
+ this.focus();
4361
+ }
4362
+ } catch ( error ) {
4363
+ this.didError( error );
4364
+ }
4365
+ return this;
4366
+ };
4367
+
4368
+ var escapeHTMLFragement = function ( text ) {
4369
+ return text.split( '&' ).join( '&amp;' )
4370
+ .split( '<' ).join( '&lt;' )
4371
+ .split( '>' ).join( '&gt;' )
4372
+ .split( '"' ).join( '&quot;' );
4373
+ };
4374
+
4375
+ proto.insertPlainText = function ( plainText, isPaste ) {
4376
+ var lines = plainText.split( '\n' );
4377
+ var config = this._config;
4378
+ var tag = config.blockTag;
4379
+ var attributes = config.blockAttributes;
4380
+ var closeBlock = '</' + tag + '>';
4381
+ var openBlock = '<' + tag;
4382
+ var attr, i, l, line;
4383
+
4384
+ for ( attr in attributes ) {
4385
+ openBlock += ' ' + attr + '="' +
4386
+ escapeHTMLFragement( attributes[ attr ] ) +
4387
+ '"';
4388
+ }
4389
+ openBlock += '>';
4390
+
4391
+ for ( i = 0, l = lines.length; i < l; i += 1 ) {
4392
+ line = lines[i];
4393
+ line = escapeHTMLFragement( line ).replace( / (?= )/g, '&nbsp;' );
4394
+ // Wrap each line in <div></div>
4395
+ lines[i] = openBlock + ( line || '<BR>' ) + closeBlock;
4396
+ }
4397
+ return this.insertHTML( lines.join( '' ), isPaste );
4398
+ };
4399
+
4400
+ // --- Formatting ---
4401
+
4402
+ var command = function ( method, arg, arg2 ) {
4403
+ return function () {
4404
+ this[ method ]( arg, arg2 );
4405
+ return this.focus();
4406
+ };
4407
+ };
4408
+
4409
+ proto.addStyles = function ( styles ) {
4410
+ if ( styles ) {
4411
+ var head = this._doc.documentElement.firstChild,
4412
+ style = this.createElement( 'STYLE', {
4413
+ type: 'text/css'
4414
+ });
4415
+ style.appendChild( this._doc.createTextNode( styles ) );
4416
+ head.appendChild( style );
4417
+ }
4418
+ return this;
4419
+ };
4420
+
4421
+ proto.bold = command( 'changeFormat', { tag: 'B' } );
4422
+ proto.italic = command( 'changeFormat', { tag: 'I' } );
4423
+ proto.underline = command( 'changeFormat', { tag: 'U' } );
4424
+ proto.strikethrough = command( 'changeFormat', { tag: 'S' } );
4425
+ proto.subscript = command( 'changeFormat', { tag: 'SUB' }, { tag: 'SUP' } );
4426
+ proto.superscript = command( 'changeFormat', { tag: 'SUP' }, { tag: 'SUB' } );
4427
+
4428
+ proto.removeBold = command( 'changeFormat', null, { tag: 'B' } );
4429
+ proto.removeItalic = command( 'changeFormat', null, { tag: 'I' } );
4430
+ proto.removeUnderline = command( 'changeFormat', null, { tag: 'U' } );
4431
+ proto.removeStrikethrough = command( 'changeFormat', null, { tag: 'S' } );
4432
+ proto.removeSubscript = command( 'changeFormat', null, { tag: 'SUB' } );
4433
+ proto.removeSuperscript = command( 'changeFormat', null, { tag: 'SUP' } );
4434
+
4435
+ proto.makeLink = function ( url, attributes ) {
4436
+ var range = this.getSelection();
4437
+ if ( range.collapsed ) {
4438
+ var protocolEnd = url.indexOf( ':' ) + 1;
4439
+ if ( protocolEnd ) {
4440
+ while ( url[ protocolEnd ] === '/' ) { protocolEnd += 1; }
4441
+ }
4442
+ insertNodeInRange(
4443
+ range,
4444
+ this._doc.createTextNode( url.slice( protocolEnd ) )
4445
+ );
4446
+ }
4447
+ attributes = mergeObjects(
4448
+ mergeObjects({
4449
+ href: url
4450
+ }, attributes, true ),
4451
+ this._config.tagAttributes.a,
4452
+ false
4453
+ );
4454
+
4455
+ this.changeFormat({
4456
+ tag: 'A',
4457
+ attributes: attributes
4458
+ }, {
4459
+ tag: 'A'
4460
+ }, range );
4461
+ return this.focus();
4462
+ };
4463
+ proto.removeLink = function () {
4464
+ this.changeFormat( null, {
4465
+ tag: 'A'
4466
+ }, this.getSelection(), true );
4467
+ return this.focus();
4468
+ };
4469
+
4470
+ proto.setFontFace = function ( name ) {
4471
+ this.changeFormat( name ? {
4472
+ tag: 'SPAN',
4473
+ attributes: {
4474
+ 'class': FONT_FAMILY_CLASS,
4475
+ style: 'font-family: ' + name + ', sans-serif;'
4476
+ }
4477
+ } : null, {
4478
+ tag: 'SPAN',
4479
+ attributes: { 'class': FONT_FAMILY_CLASS }
4480
+ });
4481
+ return this.focus();
4482
+ };
4483
+ proto.setFontSize = function ( size ) {
4484
+ this.changeFormat( size ? {
4485
+ tag: 'SPAN',
4486
+ attributes: {
4487
+ 'class': FONT_SIZE_CLASS,
4488
+ style: 'font-size: ' +
4489
+ ( typeof size === 'number' ? size + 'px' : size )
4490
+ }
4491
+ } : null, {
4492
+ tag: 'SPAN',
4493
+ attributes: { 'class': FONT_SIZE_CLASS }
4494
+ });
4495
+ return this.focus();
4496
+ };
4497
+
4498
+ proto.setTextColour = function ( colour ) {
4499
+ this.changeFormat( colour ? {
4500
+ tag: 'SPAN',
4501
+ attributes: {
4502
+ 'class': COLOUR_CLASS,
4503
+ style: 'color:' + colour
4504
+ }
4505
+ } : null, {
4506
+ tag: 'SPAN',
4507
+ attributes: { 'class': COLOUR_CLASS }
4508
+ });
4509
+ return this.focus();
4510
+ };
4511
+
4512
+ proto.setHighlightColour = function ( colour ) {
4513
+ this.changeFormat( colour ? {
4514
+ tag: 'SPAN',
4515
+ attributes: {
4516
+ 'class': HIGHLIGHT_CLASS,
4517
+ style: 'background-color:' + colour
4518
+ }
4519
+ } : colour, {
4520
+ tag: 'SPAN',
4521
+ attributes: { 'class': HIGHLIGHT_CLASS }
4522
+ });
4523
+ return this.focus();
4524
+ };
4525
+
4526
+ proto.setTextAlignment = function ( alignment ) {
4527
+ this.forEachBlock( function ( block ) {
4528
+ var className = block.className
4529
+ .split( /\s+/ )
4530
+ .filter( function ( klass ) {
4531
+ return !!klass && !/^align/.test( klass );
4532
+ })
4533
+ .join( ' ' );
4534
+ if ( alignment ) {
4535
+ block.className = className + ' align-' + alignment;
4536
+ block.style.textAlign = alignment;
4537
+ } else {
4538
+ block.className = className;
4539
+ block.style.textAlign = '';
4540
+ }
4541
+ }, true );
4542
+ return this.focus();
4543
+ };
4544
+
4545
+ proto.setTextDirection = function ( direction ) {
4546
+ this.forEachBlock( function ( block ) {
4547
+ if ( direction ) {
4548
+ block.dir = direction;
4549
+ } else {
4550
+ block.removeAttribute( 'dir' );
4551
+ }
4552
+ }, true );
4553
+ return this.focus();
4554
+ };
4555
+
4556
+ function removeFormatting ( self, root, clean ) {
4557
+ var node, next;
4558
+ for ( node = root.firstChild; node; node = next ) {
4559
+ next = node.nextSibling;
4560
+ if ( isInline( node ) ) {
4561
+ if ( node.nodeType === TEXT_NODE || node.nodeName === 'BR' || node.nodeName === 'IMG' ) {
4562
+ clean.appendChild( node );
4563
+ continue;
4564
+ }
4565
+ } else if ( isBlock( node ) ) {
4566
+ clean.appendChild( self.createDefaultBlock([
4567
+ removeFormatting(
4568
+ self, node, self._doc.createDocumentFragment() )
4569
+ ]));
4570
+ continue;
4571
+ }
4572
+ removeFormatting( self, node, clean );
4573
+ }
4574
+ return clean;
4575
+ }
4576
+
4577
+ proto.removeAllFormatting = function ( range ) {
4578
+ if ( !range && !( range = this.getSelection() ) || range.collapsed ) {
4579
+ return this;
4580
+ }
4581
+
4582
+ var root = this._root;
4583
+ var stopNode = range.commonAncestorContainer;
4584
+ while ( stopNode && !isBlock( stopNode ) ) {
4585
+ stopNode = stopNode.parentNode;
4586
+ }
4587
+ if ( !stopNode ) {
4588
+ expandRangeToBlockBoundaries( range, root );
4589
+ stopNode = root;
4590
+ }
4591
+ if ( stopNode.nodeType === TEXT_NODE ) {
4592
+ return this;
4593
+ }
4594
+
4595
+ // Record undo point
4596
+ this.saveUndoState( range );
4597
+
4598
+ // Avoid splitting where we're already at edges.
4599
+ moveRangeBoundariesUpTree( range, stopNode, stopNode, root );
4600
+
4601
+ // Split the selection up to the block, or if whole selection in same
4602
+ // block, expand range boundaries to ends of block and split up to root.
4603
+ var doc = stopNode.ownerDocument;
4604
+ var startContainer = range.startContainer;
4605
+ var startOffset = range.startOffset;
4606
+ var endContainer = range.endContainer;
4607
+ var endOffset = range.endOffset;
4608
+
4609
+ // Split end point first to avoid problems when end and start
4610
+ // in same container.
4611
+ var formattedNodes = doc.createDocumentFragment();
4612
+ var cleanNodes = doc.createDocumentFragment();
4613
+ var nodeAfterSplit = split( endContainer, endOffset, stopNode, root );
4614
+ var nodeInSplit = split( startContainer, startOffset, stopNode, root );
4615
+ var nextNode, childNodes;
4616
+
4617
+ // Then replace contents in split with a cleaned version of the same:
4618
+ // blocks become default blocks, text and leaf nodes survive, everything
4619
+ // else is obliterated.
4620
+ while ( nodeInSplit !== nodeAfterSplit ) {
4621
+ nextNode = nodeInSplit.nextSibling;
4622
+ formattedNodes.appendChild( nodeInSplit );
4623
+ nodeInSplit = nextNode;
4624
+ }
4625
+ removeFormatting( this, formattedNodes, cleanNodes );
4626
+ cleanNodes.normalize();
4627
+ nodeInSplit = cleanNodes.firstChild;
4628
+ nextNode = cleanNodes.lastChild;
4629
+
4630
+ // Restore selection
4631
+ childNodes = stopNode.childNodes;
4632
+ if ( nodeInSplit ) {
4633
+ stopNode.insertBefore( cleanNodes, nodeAfterSplit );
4634
+ startOffset = indexOf.call( childNodes, nodeInSplit );
4635
+ endOffset = indexOf.call( childNodes, nextNode ) + 1;
4636
+ } else {
4637
+ startOffset = indexOf.call( childNodes, nodeAfterSplit );
4638
+ endOffset = startOffset;
4639
+ }
4640
+
4641
+ // Merge text nodes at edges, if possible
4642
+ range.setStart( stopNode, startOffset );
4643
+ range.setEnd( stopNode, endOffset );
4644
+ mergeInlines( stopNode, range );
4645
+
4646
+ // And move back down the tree
4647
+ moveRangeBoundariesDownTree( range );
4648
+
4649
+ this.setSelection( range );
4650
+ this._updatePath( range, true );
4651
+
4652
+ return this.focus();
4653
+ };
4654
+
4655
+ proto.increaseQuoteLevel = command( 'modifyBlocks', increaseBlockQuoteLevel );
4656
+ proto.decreaseQuoteLevel = command( 'modifyBlocks', decreaseBlockQuoteLevel );
4657
+
4658
+ proto.makeUnorderedList = command( 'modifyBlocks', makeUnorderedList );
4659
+ proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList );
4660
+ proto.removeList = command( 'modifyBlocks', removeList );
4661
+
4662
+ // Node.js exports
4663
+ Squire.isInline = isInline;
4664
+ Squire.isBlock = isBlock;
4665
+ Squire.isContainer = isContainer;
4666
+ Squire.getBlockWalker = getBlockWalker;
4667
+ Squire.getPreviousBlock = getPreviousBlock;
4668
+ Squire.getNextBlock = getNextBlock;
4669
+ Squire.areAlike = areAlike;
4670
+ Squire.hasTagAttributes = hasTagAttributes;
4671
+ Squire.getNearest = getNearest;
4672
+ Squire.isOrContains = isOrContains;
4673
+ Squire.detach = detach;
4674
+ Squire.replaceWith = replaceWith;
4675
+ Squire.empty = empty;
4676
+
4677
+ // Range.js exports
4678
+ Squire.getNodeBefore = getNodeBefore;
4679
+ Squire.getNodeAfter = getNodeAfter;
4680
+ Squire.insertNodeInRange = insertNodeInRange;
4681
+ Squire.extractContentsOfRange = extractContentsOfRange;
4682
+ Squire.deleteContentsOfRange = deleteContentsOfRange;
4683
+ Squire.insertTreeFragmentIntoRange = insertTreeFragmentIntoRange;
4684
+ Squire.isNodeContainedInRange = isNodeContainedInRange;
4685
+ Squire.moveRangeBoundariesDownTree = moveRangeBoundariesDownTree;
4686
+ Squire.moveRangeBoundariesUpTree = moveRangeBoundariesUpTree;
4687
+ Squire.getStartBlockOfRange = getStartBlockOfRange;
4688
+ Squire.getEndBlockOfRange = getEndBlockOfRange;
4689
+ Squire.contentWalker = contentWalker;
4690
+ Squire.rangeDoesStartAtBlockBoundary = rangeDoesStartAtBlockBoundary;
4691
+ Squire.rangeDoesEndAtBlockBoundary = rangeDoesEndAtBlockBoundary;
4692
+ Squire.expandRangeToBlockBoundaries = expandRangeToBlockBoundaries;
4693
+
4694
+ // Clipboard.js exports
4695
+ Squire.onPaste = onPaste;
4696
+
4697
+ // Editor.js exports
4698
+ Squire.addLinks = addLinks;
4699
+ Squire.splitBlock = splitBlock;
4700
+ Squire.startSelectionId = startSelectionId;
4701
+ Squire.endSelectionId = endSelectionId;
4702
+
4703
+ if ( typeof exports === 'object' ) {
4704
+ module.exports = Squire;
4705
+ } else if ( typeof define === 'function' && define.amd ) {
4706
+ define( function () {
4707
+ return Squire;
4708
+ });
4709
+ } else {
4710
+ win.Squire = Squire;
4711
+
4712
+ if ( top !== win &&
4713
+ doc.documentElement.getAttribute( 'data-squireinit' ) === 'true' ) {
4714
+ win.editor = new Squire( doc );
4715
+ if ( win.onEditorLoad ) {
4716
+ win.onEditorLoad( win.editor );
4717
+ win.onEditorLoad = null;
4718
+ }
4719
+ }
4720
+ }
4721
+
4722
+ }( document ) );