tui_editor-rails 1.0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 ) );