type_station 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (441) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/type_station/editables/admin_bar.js.coffee +19 -48
  3. data/app/assets/javascripts/type_station/editables/entity_editor.js.coffee +152 -0
  4. data/app/assets/javascripts/type_station/editables/file_editor.js.coffee +46 -0
  5. data/app/assets/javascripts/type_station/editables/link_finder.js.coffee +26 -26
  6. data/app/assets/javascripts/type_station/editables/text_html_editor.js.coffee +63 -0
  7. data/app/assets/javascripts/type_station/init.js.coffee +8 -50
  8. data/app/assets/javascripts/type_station/lib/models.js.coffee +5 -5
  9. data/app/assets/javascripts/type_station/lib/ts.js.coffee +84 -1
  10. data/app/assets/stylesheets/type_station/admin_bar.css.scss +10 -16
  11. data/app/assets/stylesheets/type_station/base.css.scss +26 -51
  12. data/app/controllers/type_station/admin/entities_controller.rb +53 -0
  13. data/app/controllers/type_station/admin/pages_controller.rb +4 -50
  14. data/app/controllers/type_station/application_controller.rb +1 -11
  15. data/app/controllers/type_station/pages_controller.rb +5 -5
  16. data/app/models/type_station/content.rb +14 -1
  17. data/app/models/type_station/entity.rb +80 -0
  18. data/app/models/type_station/page.rb +8 -120
  19. data/app/presenters/type_station/admin_bar_presenter.rb +9 -0
  20. data/app/presenters/type_station/content_presenter.rb +12 -2
  21. data/app/presenters/type_station/page_presenter.rb +6 -39
  22. data/app/presenters/type_station/presenter.rb +71 -0
  23. data/app/views/type_station/toolbars/_admin_bar.html.haml +18 -123
  24. data/config/routes.rb +3 -1
  25. data/lib/type_station/blocks/base.rb +32 -20
  26. data/lib/type_station/blocks/entity.rb +56 -0
  27. data/lib/type_station/blocks/field.rb +34 -0
  28. data/lib/type_station/concerns/path_generator.rb +69 -0
  29. data/lib/type_station/concerns/templatable.rb +16 -0
  30. data/lib/type_station/concerns.rb +2 -0
  31. data/lib/type_station/configuration.rb +8 -4
  32. data/lib/type_station/dsl.rb +6 -7
  33. data/lib/type_station/engine.rb +6 -0
  34. data/lib/type_station/railtie.rb +3 -5
  35. data/lib/type_station/version.rb +2 -2
  36. data/lib/type_station/view_helpers.rb +23 -86
  37. data/spec/dummy/app/helpers/another_name_helper.rb +1 -1
  38. data/spec/dummy/app/models/team.rb +3 -0
  39. data/spec/dummy/app/presenters/index_presenter.rb +18 -0
  40. data/spec/dummy/app/presenters/team_presenter.rb +7 -0
  41. data/spec/dummy/app/views/pages/index_other.html.erb +79 -78
  42. data/spec/dummy/config/initializers/config.rb +3 -3
  43. data/spec/dummy/config/mongoid.yml +2 -2
  44. data/spec/dummy/log/development.log +3229 -63846
  45. data/spec/dummy/tmp/cache/assets/development/sass/{d54c17bdece82ab7e34807e87c417af01a5b0646 → 5195863f2e60bebe518d97f94bb3d4b177b0b425}/chosen.css.scssc +0 -0
  46. data/spec/dummy/tmp/cache/assets/development/sass/631a1227c978d11ae414a23c82459b12296c5574/admin_bar.css.scssc +0 -0
  47. data/spec/dummy/tmp/cache/assets/development/sass/631a1227c978d11ae414a23c82459b12296c5574/base.css.scssc +0 -0
  48. data/spec/dummy/tmp/cache/assets/development/sass/{6fe6492bf5ccb83234e7598f9cf2dee000d6fd1d → d9d756bcf855b539202ff507923feaef562a86d9}/_ionicons-font.scssc +0 -0
  49. data/spec/dummy/tmp/cache/assets/development/sass/{6fe6492bf5ccb83234e7598f9cf2dee000d6fd1d → d9d756bcf855b539202ff507923feaef562a86d9}/_ionicons-icons.scssc +0 -0
  50. data/spec/dummy/tmp/cache/assets/development/sass/{6fe6492bf5ccb83234e7598f9cf2dee000d6fd1d → d9d756bcf855b539202ff507923feaef562a86d9}/_ionicons-variables.scssc +0 -0
  51. data/spec/dummy/tmp/cache/assets/development/sass/{6fe6492bf5ccb83234e7598f9cf2dee000d6fd1d → d9d756bcf855b539202ff507923feaef562a86d9}/ionicons.scssc +0 -0
  52. data/spec/dummy/tmp/cache/assets/development/sprockets/03faf403247b5c2b01bfc3b194c9a108 +0 -0
  53. data/spec/dummy/tmp/cache/assets/development/sprockets/{42a12b3c21e3f05a34cb05b0c63c690c → 04ce8830b8d9eb9869b4e717c72e4c77} +0 -0
  54. data/spec/dummy/tmp/cache/assets/development/sprockets/{ffa56cf66ebfb8bbe51b752fa5e854b7 → 05111c950181c5c7db004ab0537ad63a} +0 -0
  55. data/spec/dummy/tmp/cache/assets/development/sprockets/{12f3bf6fe9d52ff385ee5f0e1a94e256 → 06f720204faca086942ed12e58cc7e99} +0 -0
  56. data/spec/dummy/tmp/cache/assets/development/sprockets/{d11321573db03471f91362fca3d3c5c5 → 06fe7bcbc391b00c5a196a93f90ecabb} +0 -0
  57. data/spec/dummy/tmp/cache/assets/development/sprockets/07a3408841510f4fb1eb0ede33cdb028 +0 -0
  58. data/spec/dummy/tmp/cache/assets/development/sprockets/1057bd97781bd01e53c6be1c18815331 +0 -0
  59. data/spec/dummy/tmp/cache/assets/development/sprockets/{311ffc36d5a7e1d3216e672b3e57e7ed → 11a2e83f32c39554a1d8d1812fcfefa8} +0 -0
  60. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  61. data/spec/dummy/tmp/cache/assets/development/sprockets/1e7d8fa3d4680fd1b85b0881f495525f +0 -0
  62. data/spec/dummy/tmp/cache/assets/development/sprockets/21bece9bdb4b2b78355dfc15633484fb +0 -0
  63. data/spec/dummy/tmp/cache/assets/development/sprockets/{a37ae28c4ac4e0bc0e2cb4423c40b24c → 268002ee16dde5d8865159003c5b9f56} +0 -0
  64. data/spec/dummy/tmp/cache/assets/development/sprockets/{8bec81b5959299e431f5adfc6c410719 → 2b6923a6b24ec5ce8800fedede741151} +0 -0
  65. data/spec/dummy/tmp/cache/assets/development/sprockets/2bf3770399e96a6b30d1d1078739c1f0 +0 -0
  66. data/spec/dummy/tmp/cache/assets/development/sprockets/2e14a727c7ff43dcd32c2d15c7d4e49c +0 -0
  67. data/spec/dummy/tmp/cache/assets/development/sprockets/{3030c5140945ca6d88763e4f9136b93d → 2ed9e055cb4bc9748131e083298e90f3} +0 -0
  68. data/spec/dummy/tmp/cache/assets/development/sprockets/{5c2d0bbd303da757c44328df9bccedf7 → 2f14bda8290314a0fd6c175fcad55e69} +0 -0
  69. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  70. data/spec/dummy/tmp/cache/assets/development/sprockets/{985af24e0c117b294f64311bf0187513 → 2fbff71a6bc8ae2a4172de51e568ea3b} +0 -0
  71. data/spec/dummy/tmp/cache/assets/development/sprockets/{f566af7037e0e3042b19bd5ee11b21c4 → 305b28bd53201553c837a27834d3fee9} +0 -0
  72. data/spec/dummy/tmp/cache/assets/development/sprockets/{c0c85148a1307bb6d6a87139811da3ad → 318672b968af34804563871c8b1e1d23} +0 -0
  73. data/spec/dummy/tmp/cache/assets/development/sprockets/{0b495c71c71a2d087e00d9f77c89b537 → 3203d681677ab8bf95858a71dca72ad1} +0 -0
  74. data/spec/dummy/tmp/cache/assets/development/sprockets/{4d6d33dd083743a6d1e0d708c4e4e7aa → 333827a9cf72b750448f816161e1ecda} +0 -0
  75. data/spec/dummy/tmp/cache/assets/development/sprockets/{0d8f54d1682f575aec8f27993d3af45e → 34c2fc984403383284ed5696d70c010e} +0 -0
  76. data/spec/dummy/tmp/cache/assets/development/sprockets/{0c7b9a1778b05de10a66fde42c60b47c → 35191399e8ceb198f89ffa9a8e5e6d18} +0 -0
  77. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  78. data/spec/dummy/tmp/cache/assets/development/sprockets/{90c324b461b60cdeb6b549dbf932f162 → 3af1f1791459e78c28c4e7e55115168b} +0 -0
  79. data/spec/dummy/tmp/cache/assets/development/sprockets/412cf59ab3b41a364279e5d53ad68152 +0 -0
  80. data/spec/dummy/tmp/cache/assets/development/sprockets/{f7345825a6e023e880e9a0744a4896bd → 4440e4eecd7f5abe3a350795b7c562a3} +0 -0
  81. data/spec/dummy/tmp/cache/assets/development/sprockets/48d06b052d87bd96e9e9b8a50a69b18a +0 -0
  82. data/spec/dummy/tmp/cache/assets/development/sprockets/4c48023968030daffd8c05e4e1ea78bf +0 -0
  83. data/spec/dummy/tmp/cache/assets/development/sprockets/{7ef9b3c736e553151f79c509136b1c44 → 4d43e505f0233833295b19c57e3f51bb} +0 -0
  84. data/spec/dummy/tmp/cache/assets/development/sprockets/{b6cef25e5142cb56f70ca0a8d31f312e → 5374e389194ac8d6d65ecfcfb0659919} +0 -0
  85. data/spec/dummy/tmp/cache/assets/development/sprockets/{cf5014041f97c55eee7c67feddf11b81 → 5403ae64c2a793b44bcc6728afa05e81} +0 -0
  86. data/spec/dummy/tmp/cache/assets/development/sprockets/55e73132107a6039a07892904fb85b3f +0 -0
  87. data/spec/dummy/tmp/cache/assets/development/sprockets/57096386bc19c2ad35327c072821d712 +0 -0
  88. data/spec/dummy/tmp/cache/assets/development/sprockets/57a2ad49228767f4bdd4a7d12ab9fdeb +0 -0
  89. data/spec/dummy/tmp/cache/assets/development/sprockets/{db1a08b3c680b077476f767a8addb44b → 58db8276645522fa111c4a297a6b3252} +0 -0
  90. data/spec/dummy/tmp/cache/assets/development/sprockets/5963d34af3b79a722db1c029191b2127 +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sprockets/{da50fa3c24d736a5eb19bc107acd4ae7 → 5fd77f69e8769780727bcddc3bbef6da} +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sprockets/6527d2ff5eb008ef2a32f7796a6490d6 +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sprockets/{8438f80ca5340a65edcf7a37bde5d43d → 6694f57666543a152f6f95b772233bd3} +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sprockets/685c03a878584655eb4475b4cdacdd75 +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sprockets/6aad7e14a81065dffac7093aacf234f2 +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sprockets/6c54d2efe05a3f58e5194a546a67d7b3 +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sprockets/6e3a8b3faf7a8b9b6491dc8a5c1e8d23 +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sprockets/{f1567fa9088d43b2fb0f9a0caa1ed107 → 6eea7ea5e4bfeeb41ccc235189ffd18b} +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sprockets/{d4248f8b11c6b21abffed4a5e718722d → 722eee3ce416c20905dda68eab3d517e} +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sprockets/{56afaea2e164564be4d5f5bbc7f81050 → 7960910c69f2bcf82925d090b4745c83} +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sprockets/{740af710fe4125080680a8561398cd43 → 7f512b27e66494dd23baf2e5fb34b96a} +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sprockets/{e842f1fb8542889be369b76982f3157c → 80dca6b775b0c8c933ba3bd53864a11f} +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sprockets/82cfb0f0bcd16b930ec1befc0d5cd8b8 +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sprockets/83c193129690d0c85c5370d822c4da82 +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sprockets/83d012091edd332e8b52f58cf1a8aba5 +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sprockets/848f8022bef8c0160c31cfa94841c896 +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sprockets/{0d5a85a524a12561b8cfff4e8d06b332 → 85944b215ca6c0d1a1d7806605dfd545} +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sprockets/{570677a99f6abc19d74f793d5cb24f5f → 88c61205fb088e828eaf562152fb80e1} +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sprockets/{849593e0c4cb1f2e2b3f7941be4bfb17 → 895bfeb62235ec6bfdc13f057e975c50} +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sprockets/8a5150409eaf099549443d23d5d53c88 +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sprockets/8db596b50a3235e0956dfbc06ac6c990 +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sprockets/8f4346bc63f61c8274390947394eeeb7 +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sprockets/{79b7c5b4127ac19346c56dd224bf8b19 → 8feb7e7adc10eec24e0ab54bef0c0857} +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sprockets/{a87019c62b5fb3b3dc75db921d98ad59 → 95130b18f8c813f4fa587e14f232c241} +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sprockets/{98bae9585542e37b839b68b25937fdf6 → 963c4df95ae6a8d8e54c7fe89d702fff} +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sprockets/973acef0f44b868ef5c17c4d65d80ab0 +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sprockets/{ce5a41536c75e4bc37a5b0be19e9f786 → 98a60691ad77eb6c878a60773f6ca69a} +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sprockets/9a5b201b4dddc98127641bcf852a4ea8 +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sprockets/9eafacb2fca5506e20523d97e92f5251 +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sprockets/a0c3540cd0a5390bd8985db285d1574f +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sprockets/a17ca57bada5cb7cb8c1d0b10fbd9904 +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sprockets/{ff96189f6d2677d5fa7c400167c74874 → a1b319a55f70f85dbee50bcb28aa81d4} +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sprockets/{99fd048521a12239746f91aa138613bf → a51f09cb29d7bd6071434928f56887af} +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sprockets/{f084a3a159f23d2f0f9943c4a2e5f8e9 → a6195a184397922f6afd2e7bf3d4a7e7} +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sprockets/a8ecf10c3e4c97b944e10c2cb0931fd5 +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sprockets/{0b230f1bf23bd7b1392e34cacde00da0 → aa5deb52794e98d34cc0b868202211ee} +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sprockets/aa9f5420651d522078eadd9de8350d35 +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sprockets/{99a40b6133b9eaa016bd6cf01fff987a → aab1005ca95fedeee05cb5f8d06eaa6c} +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sprockets/{21f725a9b3479e57849414f8272a905b → ab1e4897ca815d147fb473e23bf35bd7} +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sprockets/{a87cf4d10d892fb23f2fc818d7ca7207 → afa9de4377b9615980b737000fc68a2a} +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sprockets/b1fc0c5413b58bf1bf40a04dcd687d18 +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sprockets/b3383c0b0b1d8449e7a9c742a544edb7 +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sprockets/b3b877cfd5c63c892cfe8c6248e3d003 +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sprockets/{cad829df95038ddc2958dab7e1b591ad → b401f905161e2ce92571718065d5e35c} +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sprockets/b62bf39167cc044d19958ff731ef6578 +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sprockets/bb1b5b9fd84c67b2b994666f8162e79b +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sprockets/bc7d25832d4cd5332bdf3ec19783ae76 +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sprockets/bcdec54b8cd89a231c20c7c45d8eca71 +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sprockets/{492de35b811aed43fc60de0227a5d0ab → bf1e850366946d51a864f66042acb16d} +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sprockets/c06990d2e8f15c9de915a7b33d17dac5 +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sprockets/c136f3c36aea515920f7a51809e79094 +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sprockets/{c0600b832214f9e16294a1c37c0c3f6e → c39ca879cd6484827df2dd7605577066} +0 -0
  143. data/spec/dummy/tmp/cache/assets/development/sprockets/c4b181c541556124b2275b0ae4a94461 +0 -0
  144. data/spec/dummy/tmp/cache/assets/development/sprockets/c7699aedd27ae409c1ccf0c02a7782fc +0 -0
  145. data/spec/dummy/tmp/cache/assets/development/sprockets/{ef7d0257e22920401b54d6662e627252 → c85e062576453cbe6504556843184e48} +0 -0
  146. data/spec/dummy/tmp/cache/assets/development/sprockets/ccc931022e80caf6ac008099f2589e7d +0 -0
  147. data/spec/dummy/tmp/cache/assets/development/sprockets/{a8e9ef7143431aa34568a350dad82af2 → cf5e51adf346ca56aee15b72f394fce9} +0 -0
  148. data/spec/dummy/tmp/cache/assets/development/sprockets/{b6f21db694cacb7f5bcbb93cf45f65ed → cf6bd170bc3f231ca9da6434bd53359b} +0 -0
  149. data/spec/dummy/tmp/cache/assets/development/sprockets/cfcaaf334e027f46a4eddee54f3e3b27 +0 -0
  150. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  151. data/spec/dummy/tmp/cache/assets/development/sprockets/d54b81fedb82c1d520b92cfd20e18ef5 +0 -0
  152. data/spec/dummy/tmp/cache/assets/development/sprockets/d5de77884a5b0a88b13f76a0d8d2075e +0 -0
  153. data/spec/dummy/tmp/cache/assets/development/sprockets/d63daaafe9be33950037e285fb142eb5 +0 -0
  154. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  155. data/spec/dummy/tmp/cache/assets/development/sprockets/ddf237d5e172776d2061090bbf8fc6a5 +0 -0
  156. data/spec/dummy/tmp/cache/assets/development/sprockets/{e736f7cd2394078039a3b11e6ee42802 → deb9cd21864c66e80861376afc6e17cd} +0 -0
  157. data/spec/dummy/tmp/cache/assets/development/sprockets/{d02809f043bce2445374a43a8f64b4b8 → e03864d9763609a4fa6412863f2c9a8c} +0 -0
  158. data/spec/dummy/tmp/cache/assets/development/sprockets/e080ee3bf59ec12040e4cabaa97dc241 +0 -0
  159. data/spec/dummy/tmp/cache/assets/development/sprockets/e1bc9024b810c879e24aa1de0f013358 +0 -0
  160. data/spec/dummy/tmp/cache/assets/development/sprockets/{147a08ea451b98ff5a28a0088b7d5c89 → e348418df158e4447a3120415eaeb3c5} +0 -0
  161. data/spec/dummy/tmp/cache/assets/development/sprockets/e39cb3a82453b50e888022543695b4e2 +0 -0
  162. data/spec/dummy/tmp/cache/assets/development/sprockets/e77bfcad61fabf7ed2d4dd21e881b8c8 +0 -0
  163. data/spec/dummy/tmp/cache/assets/development/sprockets/{a855b42a37290315d53e93434721869d → ea306a2a3565b22cd16bf45555e751b5} +0 -0
  164. data/spec/dummy/tmp/cache/assets/development/sprockets/eb1b637b8414e22b792fe2f3549283e3 +0 -0
  165. data/spec/dummy/tmp/cache/assets/development/sprockets/{e8d6c5a2e13578e611f85cd7acd0088f → ebfd318084bf0cbcc2ab7ee6bf5c306c} +0 -0
  166. data/spec/dummy/tmp/cache/assets/development/sprockets/{42a7cd7e7e7d73b1882a085a3ed212b5 → ef58d6e4cfadfdd86e4f38c165c82e0a} +0 -0
  167. data/spec/dummy/tmp/cache/assets/development/sprockets/f4205d4b499b80a483c15c2408be1874 +0 -0
  168. data/spec/dummy/tmp/cache/assets/development/sprockets/{7316e0f7a256fd92f78a590981226fbe → f4a54e3803a4dbd2339782565404ffc1} +0 -0
  169. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  170. data/spec/dummy/tmp/cache/assets/development/sprockets/{d1e5823c113c9ee03e6c6d7773eb6fac → f7fe63074e55b9b04eef14bac17d6344} +0 -0
  171. data/spec/dummy/tmp/cache/assets/development/sprockets/f8a56681cc92b0dba2c2fdb753933a8b +0 -0
  172. data/spec/dummy/tmp/cache/assets/development/sprockets/{f14e64162f52a8d2d22ff9e1c1cc4fac → f9c9583263fc196b23e3630ecaca20ef} +0 -0
  173. data/spec/dummy/tmp/cache/assets/development/sprockets/fba3208cb66e48884d72c7875e5ff4ca +0 -0
  174. data/spec/dummy/tmp/cache/assets/development/sprockets/{6f7174fb7f1fbb8e01d382a128a979e8 → fee315e7956140b77895e8a26e569ca5} +0 -0
  175. data/spec/dummy/tmp/pids/server.pid +1 -0
  176. data/vendor/assets/javascripts/medium-editor.js +1919 -1125
  177. data/vendor/assets/stylesheets/medium-editor/medium-editor.css +39 -9
  178. data/vendor/assets/stylesheets/medium-editor/themes/bootstrap.css +3 -5
  179. data/vendor/assets/stylesheets/medium-editor/themes/bootstrap.min.css +1 -1
  180. data/vendor/assets/stylesheets/medium-editor/themes/default.css +6 -8
  181. data/vendor/assets/stylesheets/medium-editor/themes/default.min.css +1 -1
  182. data/vendor/assets/stylesheets/medium-editor/themes/flat.css +2 -2
  183. data/vendor/assets/stylesheets/medium-editor/themes/mani.css +4 -4
  184. data/vendor/assets/stylesheets/medium-editor/themes/mani.min.css +1 -1
  185. data/vendor/assets/stylesheets/medium-editor/themes/roman.css +4 -4
  186. data/vendor/assets/stylesheets/medium-editor/themes/roman.min.css +1 -1
  187. metadata +337 -669
  188. data/app/assets/javascripts/type_station/editables/edit_link.js.coffee +0 -90
  189. data/app/assets/javascripts/type_station/editables/edit_page.js.coffee +0 -116
  190. data/app/assets/javascripts/type_station/editables/file.js.coffee +0 -66
  191. data/app/assets/javascripts/type_station/editables/image.js.coffee +0 -68
  192. data/app/assets/javascripts/type_station/editables/new_page.js.coffee +0 -79
  193. data/app/assets/javascripts/type_station/editables/text_html.js.coffee +0 -66
  194. data/app/views/layouts/type_station/application.html.erb +0 -14
  195. data/lib/type_station/blocks/edit_link.rb +0 -27
  196. data/lib/type_station/blocks/edit_page.rb +0 -28
  197. data/lib/type_station/blocks/editable_file.rb +0 -19
  198. data/lib/type_station/blocks/editable_html.rb +0 -15
  199. data/lib/type_station/blocks/editable_image.rb +0 -19
  200. data/lib/type_station/blocks/editable_text.rb +0 -19
  201. data/lib/type_station/blocks/new_collection.rb +0 -15
  202. data/lib/type_station/blocks/new_page.rb +0 -19
  203. data/lib/type_station/blocks/static_link.rb +0 -31
  204. data/lib/type_station/blocks/stickable.rb +0 -23
  205. data/lib/type_station/helpers/edit.rb +0 -36
  206. data/lib/type_station/helpers/editable.rb +0 -37
  207. data/lib/type_station/helpers/new.rb +0 -25
  208. data/lib/type_station/helpers/utilities.rb +0 -40
  209. data/lib/type_station/helpers.rb +0 -2
  210. data/spec/dummy/log/test.log +0 -1380
  211. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_buttons.scssc +0 -0
  212. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_mixins.scssc +0 -0
  213. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_variables.scssc +0 -0
  214. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_alerts.scssc +0 -0
  215. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_background-variant.scssc +0 -0
  216. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_border-radius.scssc +0 -0
  217. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_buttons.scssc +0 -0
  218. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_center-block.scssc +0 -0
  219. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_clearfix.scssc +0 -0
  220. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_forms.scssc +0 -0
  221. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_gradients.scssc +0 -0
  222. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_grid-framework.scssc +0 -0
  223. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_grid.scssc +0 -0
  224. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_hide-text.scssc +0 -0
  225. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_image.scssc +0 -0
  226. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_labels.scssc +0 -0
  227. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_list-group.scssc +0 -0
  228. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_nav-divider.scssc +0 -0
  229. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_nav-vertical-align.scssc +0 -0
  230. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_opacity.scssc +0 -0
  231. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_pagination.scssc +0 -0
  232. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_panels.scssc +0 -0
  233. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_progress-bar.scssc +0 -0
  234. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_reset-filter.scssc +0 -0
  235. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_resize.scssc +0 -0
  236. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_responsive-visibility.scssc +0 -0
  237. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_size.scssc +0 -0
  238. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_tab-focus.scssc +0 -0
  239. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_table-row.scssc +0 -0
  240. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_text-emphasis.scssc +0 -0
  241. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_text-overflow.scssc +0 -0
  242. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_vendor-prefixes.scssc +0 -0
  243. data/spec/dummy/tmp/cache/assets/development/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/admin_bar.css.scssc +0 -0
  244. data/spec/dummy/tmp/cache/assets/development/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/base.css.scssc +0 -0
  245. data/spec/dummy/tmp/cache/assets/development/sprockets/07d82d3c1e3632b7f4bf807c2965e13b +0 -0
  246. data/spec/dummy/tmp/cache/assets/development/sprockets/0825425ebf24766be13ebf4e696dc267 +0 -0
  247. data/spec/dummy/tmp/cache/assets/development/sprockets/0aa47ff15e22d0e74389460968c32717 +0 -0
  248. data/spec/dummy/tmp/cache/assets/development/sprockets/0b163fa81b2ba6f9e0a9d3e6164af7cd +0 -0
  249. data/spec/dummy/tmp/cache/assets/development/sprockets/0cbda04368ddbf6d761e788334075e7c +0 -0
  250. data/spec/dummy/tmp/cache/assets/development/sprockets/0dd8d5d73e593c919592d3d02deb62e8 +0 -0
  251. data/spec/dummy/tmp/cache/assets/development/sprockets/0e846d0579634bd3924d701ebcd09040 +0 -0
  252. data/spec/dummy/tmp/cache/assets/development/sprockets/127cceefef1ada19384ec7fec980c5ee +0 -0
  253. data/spec/dummy/tmp/cache/assets/development/sprockets/182d8b06dcdb4f799088741f553903d3 +0 -0
  254. data/spec/dummy/tmp/cache/assets/development/sprockets/1b64a5fd92110dbd078f4ea964e2d0ad +0 -0
  255. data/spec/dummy/tmp/cache/assets/development/sprockets/1d67ea254300e2a208df8f296477fca2 +0 -0
  256. data/spec/dummy/tmp/cache/assets/development/sprockets/1ff95c0ddc6f6687c044d2a11b8323eb +0 -0
  257. data/spec/dummy/tmp/cache/assets/development/sprockets/2155dbeb10a65c2519b49179b3bc6f3a +0 -0
  258. data/spec/dummy/tmp/cache/assets/development/sprockets/2535956b4e5e79e8fbd3d30f68de7024 +0 -0
  259. data/spec/dummy/tmp/cache/assets/development/sprockets/28ad54c6e5882957dd269b1b2a3c3c75 +0 -0
  260. data/spec/dummy/tmp/cache/assets/development/sprockets/29a7d8d0307a932153f20c48b57ecda1 +0 -0
  261. data/spec/dummy/tmp/cache/assets/development/sprockets/2d0ee72828e0dcbaf2390d3d67595461 +0 -0
  262. data/spec/dummy/tmp/cache/assets/development/sprockets/2d13dd9465026b66cce912e30e142020 +0 -0
  263. data/spec/dummy/tmp/cache/assets/development/sprockets/300f6f1e08668cacf691691df4d13889 +0 -0
  264. data/spec/dummy/tmp/cache/assets/development/sprockets/30e3d4f790a4234a26c7f74efba15a2e +0 -0
  265. data/spec/dummy/tmp/cache/assets/development/sprockets/33ba18d4294cbb074bd3afee4728e2b9 +0 -0
  266. data/spec/dummy/tmp/cache/assets/development/sprockets/3571d341c3629bb4c9034d2f924f34ac +0 -0
  267. data/spec/dummy/tmp/cache/assets/development/sprockets/3a3d03c0a92f21419e1e85991774a28b +0 -0
  268. data/spec/dummy/tmp/cache/assets/development/sprockets/3dea6de9e06b1e372ebf4790f5eecc48 +0 -0
  269. data/spec/dummy/tmp/cache/assets/development/sprockets/3e7667a45f81a73030337cb8afe0acc3 +0 -0
  270. data/spec/dummy/tmp/cache/assets/development/sprockets/4144b15d96ec80b5c7fb367b57aaeef3 +0 -0
  271. data/spec/dummy/tmp/cache/assets/development/sprockets/43c1381ebee89f30d22f80f305f78ee6 +0 -0
  272. data/spec/dummy/tmp/cache/assets/development/sprockets/450d192e072fd68fb5c20711bdd21c9e +0 -0
  273. data/spec/dummy/tmp/cache/assets/development/sprockets/4729c7f6ab6a91a079ab04a8f04e932b +0 -0
  274. data/spec/dummy/tmp/cache/assets/development/sprockets/48f052ab53e11ca42b15cc7f40fe2d4c +0 -0
  275. data/spec/dummy/tmp/cache/assets/development/sprockets/4c324a18fe6bf7dbf7a04cccbfdb366e +0 -0
  276. data/spec/dummy/tmp/cache/assets/development/sprockets/4fc791a177c880f50bf425e8d2862c20 +0 -0
  277. data/spec/dummy/tmp/cache/assets/development/sprockets/51c578f550b2fd1ff7a873dbcf75b75b +0 -0
  278. data/spec/dummy/tmp/cache/assets/development/sprockets/535f36c50d5b36ff83bc03bc6a19b8fc +0 -0
  279. data/spec/dummy/tmp/cache/assets/development/sprockets/539ac885f1ce2ff4f1d693223a77b74d +0 -0
  280. data/spec/dummy/tmp/cache/assets/development/sprockets/53ef9dcaf6b6ddcefceec48187797dc5 +0 -0
  281. data/spec/dummy/tmp/cache/assets/development/sprockets/55c2f55c27a62b998b8d33555158da53 +0 -0
  282. data/spec/dummy/tmp/cache/assets/development/sprockets/575f0b5d245887cf587b0169d6993308 +0 -0
  283. data/spec/dummy/tmp/cache/assets/development/sprockets/579b1a727fe5b55dfe450b0835c56a65 +0 -0
  284. data/spec/dummy/tmp/cache/assets/development/sprockets/5a806147b739da644a296d2def76c243 +0 -0
  285. data/spec/dummy/tmp/cache/assets/development/sprockets/5d79ea654842f86bec8702b02616c633 +0 -0
  286. data/spec/dummy/tmp/cache/assets/development/sprockets/60b14e8e4c0d91f7af9d162cf4c67a9c +0 -0
  287. data/spec/dummy/tmp/cache/assets/development/sprockets/61d7a0e99e49f7a9917d385223a97015 +0 -0
  288. data/spec/dummy/tmp/cache/assets/development/sprockets/623a22125c47f6f4391fd1a56e40b8a0 +0 -0
  289. data/spec/dummy/tmp/cache/assets/development/sprockets/65cf3864264ab5fd70d6e74a934adf8d +0 -0
  290. data/spec/dummy/tmp/cache/assets/development/sprockets/6b734dd1ce043b913eb812a20ea2d42f +0 -0
  291. data/spec/dummy/tmp/cache/assets/development/sprockets/6e0fcc860289a427d98aa4ee86bd12b7 +0 -0
  292. data/spec/dummy/tmp/cache/assets/development/sprockets/709e51f7191f25018b0012df4ff6389f +0 -0
  293. data/spec/dummy/tmp/cache/assets/development/sprockets/70dfe9a7b27467587b0d4d00a7e0f6fb +0 -0
  294. data/spec/dummy/tmp/cache/assets/development/sprockets/71e10ee69b177ab54cb6033385a77ef9 +0 -0
  295. data/spec/dummy/tmp/cache/assets/development/sprockets/735d50df5dc976a13b56aa81f5970298 +0 -0
  296. data/spec/dummy/tmp/cache/assets/development/sprockets/73939a29e193b5a1fad031fa75f94119 +0 -0
  297. data/spec/dummy/tmp/cache/assets/development/sprockets/73cc8ce397fbfe4408bcac87302a6952 +0 -0
  298. data/spec/dummy/tmp/cache/assets/development/sprockets/7cfbbb15a3f9dcb4a5ee988ea309504d +0 -0
  299. data/spec/dummy/tmp/cache/assets/development/sprockets/7efe4acab136927c0b1df06d7c1212cd +0 -0
  300. data/spec/dummy/tmp/cache/assets/development/sprockets/7f14b0b39c11df4f58e03d390b589547 +0 -0
  301. data/spec/dummy/tmp/cache/assets/development/sprockets/7fc9c7161658070c78790a28b4ff590f +0 -0
  302. data/spec/dummy/tmp/cache/assets/development/sprockets/825bd4dba50f1ea2c0ae5e8a3708119e +0 -0
  303. data/spec/dummy/tmp/cache/assets/development/sprockets/84822f25094f67b515ad5f452381dcb0 +0 -0
  304. data/spec/dummy/tmp/cache/assets/development/sprockets/85c54085eea77d703dd054b1adcf309d +0 -0
  305. data/spec/dummy/tmp/cache/assets/development/sprockets/85d858ed64f06543161931735cb77748 +0 -0
  306. data/spec/dummy/tmp/cache/assets/development/sprockets/85fe1c52386ac4419fdf124a22228a98 +0 -0
  307. data/spec/dummy/tmp/cache/assets/development/sprockets/8803d0a63bbf3fce683c968d4a900480 +0 -0
  308. data/spec/dummy/tmp/cache/assets/development/sprockets/887e2151ef4cd6e915fd9bfc27c41d7d +0 -0
  309. data/spec/dummy/tmp/cache/assets/development/sprockets/8b33a159429f95c8fbb14e5d45b01967 +0 -0
  310. data/spec/dummy/tmp/cache/assets/development/sprockets/8caeabedf4518243187dbf20bf76a0d8 +0 -0
  311. data/spec/dummy/tmp/cache/assets/development/sprockets/8e60a95e73cedfb4b58fbf6fc32564b3 +0 -0
  312. data/spec/dummy/tmp/cache/assets/development/sprockets/92c1d07e65b1251e6a9f62064f749658 +0 -0
  313. data/spec/dummy/tmp/cache/assets/development/sprockets/92d59399e3f951cc11721f03a2b328fd +0 -0
  314. data/spec/dummy/tmp/cache/assets/development/sprockets/966db607ae0e7aad72cf171b8fec537a +0 -0
  315. data/spec/dummy/tmp/cache/assets/development/sprockets/9a9290586c54792a989ea679f38ebc98 +0 -0
  316. data/spec/dummy/tmp/cache/assets/development/sprockets/9c6580777be29320c993da37d292fcae +0 -0
  317. data/spec/dummy/tmp/cache/assets/development/sprockets/9c8cd77db76e28f0a074573a72509ce9 +0 -0
  318. data/spec/dummy/tmp/cache/assets/development/sprockets/9d5a258ab9e431a1a7eaf931ed4c3fb3 +0 -0
  319. data/spec/dummy/tmp/cache/assets/development/sprockets/9d961151482903524be9a51e312c8d49 +0 -0
  320. data/spec/dummy/tmp/cache/assets/development/sprockets/a165e7cd7871ea0af12d29ef10193272 +0 -0
  321. data/spec/dummy/tmp/cache/assets/development/sprockets/a2b3a282d5d8a5c20444d1a27f70a61e +0 -0
  322. data/spec/dummy/tmp/cache/assets/development/sprockets/a539d3e91d5fb8eeb973ac8706bbf5c9 +0 -0
  323. data/spec/dummy/tmp/cache/assets/development/sprockets/ab4192212bd78e95967e5a716548236f +0 -0
  324. data/spec/dummy/tmp/cache/assets/development/sprockets/acbb40a3fca4d574f84a89894274b5ab +0 -0
  325. data/spec/dummy/tmp/cache/assets/development/sprockets/af97ed366de5933460d41b9198ddc99e +0 -0
  326. data/spec/dummy/tmp/cache/assets/development/sprockets/afad1821c4be2be037ae8672c21db00a +0 -0
  327. data/spec/dummy/tmp/cache/assets/development/sprockets/b0acb9e437685aa3f0c0a2ca751a04ce +0 -0
  328. data/spec/dummy/tmp/cache/assets/development/sprockets/b0b0cf6bca2dad4b85f30801d615eac4 +0 -0
  329. data/spec/dummy/tmp/cache/assets/development/sprockets/b1a982f644c0350cb843b63d69a9506f +0 -0
  330. data/spec/dummy/tmp/cache/assets/development/sprockets/b772142455a4c78005e67264e239e867 +0 -0
  331. data/spec/dummy/tmp/cache/assets/development/sprockets/b85e6623f6fed5c83a7a375ff15bdd79 +0 -0
  332. data/spec/dummy/tmp/cache/assets/development/sprockets/b89a0282f37737eb86781a8ddbdf14a3 +0 -0
  333. data/spec/dummy/tmp/cache/assets/development/sprockets/bc2f1083a88e2e7c4e88f13f025824ed +0 -0
  334. data/spec/dummy/tmp/cache/assets/development/sprockets/bcf0359847dcb2f9179c6a46248bcea5 +0 -0
  335. data/spec/dummy/tmp/cache/assets/development/sprockets/bd3113e1418cbc640eb6e1b06d486163 +0 -0
  336. data/spec/dummy/tmp/cache/assets/development/sprockets/c4e5a8fece2f70f43c6e7cc4a790def7 +0 -0
  337. data/spec/dummy/tmp/cache/assets/development/sprockets/c4ec64020d1ff391bab8c2f0bbc7cd02 +0 -0
  338. data/spec/dummy/tmp/cache/assets/development/sprockets/c501910d385d42ea0cf75ae3c4f220e6 +0 -0
  339. data/spec/dummy/tmp/cache/assets/development/sprockets/c7628a8a59ea560abb9213543333222c +0 -0
  340. data/spec/dummy/tmp/cache/assets/development/sprockets/cb80f61fc678a70e99e16156ae6e07d1 +0 -0
  341. data/spec/dummy/tmp/cache/assets/development/sprockets/cc9a378dda4e65e86b1f3fd522d019a1 +0 -0
  342. data/spec/dummy/tmp/cache/assets/development/sprockets/cdaf4cc6f0288db4f6fa5b8073ac6272 +0 -0
  343. data/spec/dummy/tmp/cache/assets/development/sprockets/cdedc95319572f0c3e36a3a5a044572d +0 -0
  344. data/spec/dummy/tmp/cache/assets/development/sprockets/d05d272f053261f2d5e3dcca85f19172 +0 -0
  345. data/spec/dummy/tmp/cache/assets/development/sprockets/d05d2c6ed489a61e2a9756430af9f48f +0 -0
  346. data/spec/dummy/tmp/cache/assets/development/sprockets/d07ab5a5474c0fb71b3bd351947707e5 +0 -0
  347. data/spec/dummy/tmp/cache/assets/development/sprockets/d1f0a2d6fd58a203f3ca66950c822c33 +0 -0
  348. data/spec/dummy/tmp/cache/assets/development/sprockets/d3a2586089547d98e033029b94eb86a5 +0 -0
  349. data/spec/dummy/tmp/cache/assets/development/sprockets/d9924e9691068558c4ce3a20bec7fea1 +0 -0
  350. data/spec/dummy/tmp/cache/assets/development/sprockets/da9349ff5720f218552184c8fef40d7a +0 -0
  351. data/spec/dummy/tmp/cache/assets/development/sprockets/dd9f84cc20227ad66bbd1f26bbc11ccc +0 -0
  352. data/spec/dummy/tmp/cache/assets/development/sprockets/de9d72cab443cce32e1d45e62d2b4c04 +0 -0
  353. data/spec/dummy/tmp/cache/assets/development/sprockets/e03c578b73cf9936edc84ff2b48c07da +0 -0
  354. data/spec/dummy/tmp/cache/assets/development/sprockets/e04691fd3eaee6408ac1d1b7fade69e6 +0 -0
  355. data/spec/dummy/tmp/cache/assets/development/sprockets/e40f49d676971d013ad991bdf2dc344b +0 -0
  356. data/spec/dummy/tmp/cache/assets/development/sprockets/e72700144fea9ebdc4813bc00f023fc7 +0 -0
  357. data/spec/dummy/tmp/cache/assets/development/sprockets/e9b5bc7c65d160f7e31993bbe7a05d63 +0 -0
  358. data/spec/dummy/tmp/cache/assets/development/sprockets/ebdd71d1c3cf34d7d174e5c1abb123b7 +0 -0
  359. data/spec/dummy/tmp/cache/assets/development/sprockets/ec22367449630f093cf0449cea0abdfd +0 -0
  360. data/spec/dummy/tmp/cache/assets/development/sprockets/f479b0a1614f114b61a63664e172dec1 +0 -0
  361. data/spec/dummy/tmp/cache/assets/development/sprockets/f6eeeb889d424f50b0f725d4656e0721 +0 -0
  362. data/spec/dummy/tmp/cache/assets/development/sprockets/f7c0e18cf77ef251a27e9ff91578fb47 +0 -0
  363. data/spec/dummy/tmp/cache/assets/development/sprockets/fa8cab89937bf25aa48b23445f1b4f64 +0 -0
  364. data/spec/dummy/tmp/cache/assets/development/sprockets/fc5de94b46d8aff03ec5355f912a9a81 +0 -0
  365. data/spec/dummy/tmp/cache/assets/development/sprockets/fd1cae0417fea68172ab7b747a1d6fd8 +0 -0
  366. data/spec/dummy/tmp/cache/assets/development/sprockets/fd2dacd118d6197c3a9325e2103abe7d +0 -0
  367. data/spec/dummy/tmp/cache/assets/development/sprockets/fe43b3c0b7d3233a250ce9c46342919c +0 -0
  368. data/spec/dummy/tmp/cache/assets/test/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/admin_bar.css.scssc +0 -0
  369. data/spec/dummy/tmp/cache/assets/test/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/base.css.scssc +0 -0
  370. data/spec/dummy/tmp/cache/assets/test/sprockets/0825425ebf24766be13ebf4e696dc267 +0 -0
  371. data/spec/dummy/tmp/cache/assets/test/sprockets/0b495c71c71a2d087e00d9f77c89b537 +0 -0
  372. data/spec/dummy/tmp/cache/assets/test/sprockets/0cbda04368ddbf6d761e788334075e7c +0 -0
  373. data/spec/dummy/tmp/cache/assets/test/sprockets/0d8f54d1682f575aec8f27993d3af45e +0 -0
  374. data/spec/dummy/tmp/cache/assets/test/sprockets/12f3bf6fe9d52ff385ee5f0e1a94e256 +0 -0
  375. data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  376. data/spec/dummy/tmp/cache/assets/test/sprockets/182d8b06dcdb4f799088741f553903d3 +0 -0
  377. data/spec/dummy/tmp/cache/assets/test/sprockets/1b64a5fd92110dbd078f4ea964e2d0ad +0 -0
  378. data/spec/dummy/tmp/cache/assets/test/sprockets/2155dbeb10a65c2519b49179b3bc6f3a +0 -0
  379. data/spec/dummy/tmp/cache/assets/test/sprockets/21f725a9b3479e57849414f8272a905b +0 -0
  380. data/spec/dummy/tmp/cache/assets/test/sprockets/2535956b4e5e79e8fbd3d30f68de7024 +0 -0
  381. data/spec/dummy/tmp/cache/assets/test/sprockets/28ad54c6e5882957dd269b1b2a3c3c75 +0 -0
  382. data/spec/dummy/tmp/cache/assets/test/sprockets/29a7d8d0307a932153f20c48b57ecda1 +0 -0
  383. data/spec/dummy/tmp/cache/assets/test/sprockets/2d13dd9465026b66cce912e30e142020 +0 -0
  384. data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  385. data/spec/dummy/tmp/cache/assets/test/sprockets/300f6f1e08668cacf691691df4d13889 +0 -0
  386. data/spec/dummy/tmp/cache/assets/test/sprockets/311ffc36d5a7e1d3216e672b3e57e7ed +0 -0
  387. data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  388. data/spec/dummy/tmp/cache/assets/test/sprockets/3a3d03c0a92f21419e1e85991774a28b +0 -0
  389. data/spec/dummy/tmp/cache/assets/test/sprockets/3dea6de9e06b1e372ebf4790f5eecc48 +0 -0
  390. data/spec/dummy/tmp/cache/assets/test/sprockets/4144b15d96ec80b5c7fb367b57aaeef3 +0 -0
  391. data/spec/dummy/tmp/cache/assets/test/sprockets/4729c7f6ab6a91a079ab04a8f04e932b +0 -0
  392. data/spec/dummy/tmp/cache/assets/test/sprockets/4c324a18fe6bf7dbf7a04cccbfdb366e +0 -0
  393. data/spec/dummy/tmp/cache/assets/test/sprockets/535f36c50d5b36ff83bc03bc6a19b8fc +0 -0
  394. data/spec/dummy/tmp/cache/assets/test/sprockets/55c2f55c27a62b998b8d33555158da53 +0 -0
  395. data/spec/dummy/tmp/cache/assets/test/sprockets/56afaea2e164564be4d5f5bbc7f81050 +0 -0
  396. data/spec/dummy/tmp/cache/assets/test/sprockets/570677a99f6abc19d74f793d5cb24f5f +0 -0
  397. data/spec/dummy/tmp/cache/assets/test/sprockets/579b1a727fe5b55dfe450b0835c56a65 +0 -0
  398. data/spec/dummy/tmp/cache/assets/test/sprockets/6f7174fb7f1fbb8e01d382a128a979e8 +0 -0
  399. data/spec/dummy/tmp/cache/assets/test/sprockets/735d50df5dc976a13b56aa81f5970298 +0 -0
  400. data/spec/dummy/tmp/cache/assets/test/sprockets/7746e5b6582f701e40ad5ae69e7a6d25 +0 -0
  401. data/spec/dummy/tmp/cache/assets/test/sprockets/7f14b0b39c11df4f58e03d390b589547 +0 -0
  402. data/spec/dummy/tmp/cache/assets/test/sprockets/8091ad5bff4f0d3d70f874750430241b +0 -0
  403. data/spec/dummy/tmp/cache/assets/test/sprockets/825bd4dba50f1ea2c0ae5e8a3708119e +0 -0
  404. data/spec/dummy/tmp/cache/assets/test/sprockets/849593e0c4cb1f2e2b3f7941be4bfb17 +0 -0
  405. data/spec/dummy/tmp/cache/assets/test/sprockets/85d858ed64f06543161931735cb77748 +0 -0
  406. data/spec/dummy/tmp/cache/assets/test/sprockets/8803d0a63bbf3fce683c968d4a900480 +0 -0
  407. data/spec/dummy/tmp/cache/assets/test/sprockets/887e2151ef4cd6e915fd9bfc27c41d7d +0 -0
  408. data/spec/dummy/tmp/cache/assets/test/sprockets/8bec81b5959299e431f5adfc6c410719 +0 -0
  409. data/spec/dummy/tmp/cache/assets/test/sprockets/90c324b461b60cdeb6b549dbf932f162 +0 -0
  410. data/spec/dummy/tmp/cache/assets/test/sprockets/92d59399e3f951cc11721f03a2b328fd +0 -0
  411. data/spec/dummy/tmp/cache/assets/test/sprockets/966db607ae0e7aad72cf171b8fec537a +0 -0
  412. data/spec/dummy/tmp/cache/assets/test/sprockets/98bae9585542e37b839b68b25937fdf6 +0 -0
  413. data/spec/dummy/tmp/cache/assets/test/sprockets/99fd048521a12239746f91aa138613bf +0 -0
  414. data/spec/dummy/tmp/cache/assets/test/sprockets/a539d3e91d5fb8eeb973ac8706bbf5c9 +0 -0
  415. data/spec/dummy/tmp/cache/assets/test/sprockets/a87019c62b5fb3b3dc75db921d98ad59 +0 -0
  416. data/spec/dummy/tmp/cache/assets/test/sprockets/a87cf4d10d892fb23f2fc818d7ca7207 +0 -0
  417. data/spec/dummy/tmp/cache/assets/test/sprockets/ab4192212bd78e95967e5a716548236f +0 -0
  418. data/spec/dummy/tmp/cache/assets/test/sprockets/af97ed366de5933460d41b9198ddc99e +0 -0
  419. data/spec/dummy/tmp/cache/assets/test/sprockets/b6cef25e5142cb56f70ca0a8d31f312e +0 -0
  420. data/spec/dummy/tmp/cache/assets/test/sprockets/b6f21db694cacb7f5bcbb93cf45f65ed +0 -0
  421. data/spec/dummy/tmp/cache/assets/test/sprockets/bd3113e1418cbc640eb6e1b06d486163 +0 -0
  422. data/spec/dummy/tmp/cache/assets/test/sprockets/c0600b832214f9e16294a1c37c0c3f6e +0 -0
  423. data/spec/dummy/tmp/cache/assets/test/sprockets/cad829df95038ddc2958dab7e1b591ad +0 -0
  424. data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  425. data/spec/dummy/tmp/cache/assets/test/sprockets/d05d272f053261f2d5e3dcca85f19172 +0 -0
  426. data/spec/dummy/tmp/cache/assets/test/sprockets/d1e5823c113c9ee03e6c6d7773eb6fac +0 -0
  427. data/spec/dummy/tmp/cache/assets/test/sprockets/d1f0a2d6fd58a203f3ca66950c822c33 +0 -0
  428. data/spec/dummy/tmp/cache/assets/test/sprockets/d3a2586089547d98e033029b94eb86a5 +0 -0
  429. data/spec/dummy/tmp/cache/assets/test/sprockets/d4248f8b11c6b21abffed4a5e718722d +0 -0
  430. data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  431. data/spec/dummy/tmp/cache/assets/test/sprockets/da50fa3c24d736a5eb19bc107acd4ae7 +0 -0
  432. data/spec/dummy/tmp/cache/assets/test/sprockets/db1a08b3c680b077476f767a8addb44b +0 -0
  433. data/spec/dummy/tmp/cache/assets/test/sprockets/dd9f84cc20227ad66bbd1f26bbc11ccc +0 -0
  434. data/spec/dummy/tmp/cache/assets/test/sprockets/de9d72cab443cce32e1d45e62d2b4c04 +0 -0
  435. data/spec/dummy/tmp/cache/assets/test/sprockets/e03c578b73cf9936edc84ff2b48c07da +0 -0
  436. data/spec/dummy/tmp/cache/assets/test/sprockets/e842f1fb8542889be369b76982f3157c +0 -0
  437. data/spec/dummy/tmp/cache/assets/test/sprockets/ebdd71d1c3cf34d7d174e5c1abb123b7 +0 -0
  438. data/spec/dummy/tmp/cache/assets/test/sprockets/f14e64162f52a8d2d22ff9e1c1cc4fac +0 -0
  439. data/spec/dummy/tmp/cache/assets/test/sprockets/f566af7037e0e3042b19bd5ee11b21c4 +0 -0
  440. data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  441. data/spec/dummy/tmp/cache/assets/test/sprockets/fd1cae0417fea68172ab7b747a1d6fd8 +0 -0
@@ -1,253 +1,1399 @@
1
- function MediumEditor(elements, options) {
2
- 'use strict';
3
- return this.init(elements, options);
1
+ /*global self, document, DOMException */
2
+
3
+ /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
4
+
5
+ // Full polyfill for browsers with no classList support
6
+ if (!("classList" in document.createElement("_"))) {
7
+ (function (view) {
8
+
9
+ "use strict";
10
+
11
+ if (!('Element' in view)) return;
12
+
13
+ var
14
+ classListProp = "classList"
15
+ , protoProp = "prototype"
16
+ , elemCtrProto = view.Element[protoProp]
17
+ , objCtr = Object
18
+ , strTrim = String[protoProp].trim || function () {
19
+ return this.replace(/^\s+|\s+$/g, "");
20
+ }
21
+ , arrIndexOf = Array[protoProp].indexOf || function (item) {
22
+ var
23
+ i = 0
24
+ , len = this.length
25
+ ;
26
+ for (; i < len; i++) {
27
+ if (i in this && this[i] === item) {
28
+ return i;
29
+ }
30
+ }
31
+ return -1;
32
+ }
33
+ // Vendors: please allow content code to instantiate DOMExceptions
34
+ , DOMEx = function (type, message) {
35
+ this.name = type;
36
+ this.code = DOMException[type];
37
+ this.message = message;
38
+ }
39
+ , checkTokenAndGetIndex = function (classList, token) {
40
+ if (token === "") {
41
+ throw new DOMEx(
42
+ "SYNTAX_ERR"
43
+ , "An invalid or illegal string was specified"
44
+ );
45
+ }
46
+ if (/\s/.test(token)) {
47
+ throw new DOMEx(
48
+ "INVALID_CHARACTER_ERR"
49
+ , "String contains an invalid character"
50
+ );
51
+ }
52
+ return arrIndexOf.call(classList, token);
53
+ }
54
+ , ClassList = function (elem) {
55
+ var
56
+ trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
57
+ , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
58
+ , i = 0
59
+ , len = classes.length
60
+ ;
61
+ for (; i < len; i++) {
62
+ this.push(classes[i]);
63
+ }
64
+ this._updateClassName = function () {
65
+ elem.setAttribute("class", this.toString());
66
+ };
67
+ }
68
+ , classListProto = ClassList[protoProp] = []
69
+ , classListGetter = function () {
70
+ return new ClassList(this);
71
+ }
72
+ ;
73
+ // Most DOMException implementations don't allow calling DOMException's toString()
74
+ // on non-DOMExceptions. Error's toString() is sufficient here.
75
+ DOMEx[protoProp] = Error[protoProp];
76
+ classListProto.item = function (i) {
77
+ return this[i] || null;
78
+ };
79
+ classListProto.contains = function (token) {
80
+ token += "";
81
+ return checkTokenAndGetIndex(this, token) !== -1;
82
+ };
83
+ classListProto.add = function () {
84
+ var
85
+ tokens = arguments
86
+ , i = 0
87
+ , l = tokens.length
88
+ , token
89
+ , updated = false
90
+ ;
91
+ do {
92
+ token = tokens[i] + "";
93
+ if (checkTokenAndGetIndex(this, token) === -1) {
94
+ this.push(token);
95
+ updated = true;
96
+ }
97
+ }
98
+ while (++i < l);
99
+
100
+ if (updated) {
101
+ this._updateClassName();
102
+ }
103
+ };
104
+ classListProto.remove = function () {
105
+ var
106
+ tokens = arguments
107
+ , i = 0
108
+ , l = tokens.length
109
+ , token
110
+ , updated = false
111
+ , index
112
+ ;
113
+ do {
114
+ token = tokens[i] + "";
115
+ index = checkTokenAndGetIndex(this, token);
116
+ while (index !== -1) {
117
+ this.splice(index, 1);
118
+ updated = true;
119
+ index = checkTokenAndGetIndex(this, token);
120
+ }
121
+ }
122
+ while (++i < l);
123
+
124
+ if (updated) {
125
+ this._updateClassName();
126
+ }
127
+ };
128
+ classListProto.toggle = function (token, force) {
129
+ token += "";
130
+
131
+ var
132
+ result = this.contains(token)
133
+ , method = result ?
134
+ force !== true && "remove"
135
+ :
136
+ force !== false && "add"
137
+ ;
138
+
139
+ if (method) {
140
+ this[method](token);
141
+ }
142
+
143
+ if (force === true || force === false) {
144
+ return force;
145
+ } else {
146
+ return !result;
147
+ }
148
+ };
149
+ classListProto.toString = function () {
150
+ return this.join(" ");
151
+ };
152
+
153
+ if (objCtr.defineProperty) {
154
+ var classListPropDesc = {
155
+ get: classListGetter
156
+ , enumerable: true
157
+ , configurable: true
158
+ };
159
+ try {
160
+ objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
161
+ } catch (ex) { // IE 8 doesn't support enumerable:true
162
+ if (ex.number === -0x7FF5EC54) {
163
+ classListPropDesc.enumerable = false;
164
+ objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
165
+ }
166
+ }
167
+ } else if (objCtr[protoProp].__defineGetter__) {
168
+ elemCtrProto.__defineGetter__(classListProp, classListGetter);
169
+ }
170
+
171
+ }(self));
4
172
  }
5
173
 
6
- if (typeof module === 'object') {
7
- module.exports = MediumEditor;
8
- // AMD support
9
- } else if (typeof define === 'function' && define.amd) {
10
- define(function () {
11
- 'use strict';
12
- return MediumEditor;
13
- });
14
- }
174
+ (function (root, factory) {
175
+ 'use strict';
176
+ if (typeof module === 'object') {
177
+ module.exports = factory;
178
+ } else if (typeof define === 'function' && define.amd) {
179
+ define(function () {
180
+ return factory;
181
+ });
182
+ } else {
183
+ root.MediumEditor = factory;
184
+ }
185
+ }(this, function () {
186
+
187
+ 'use strict';
188
+
189
+ var Util;
190
+
191
+ (function (window, document) {
192
+ 'use strict';
193
+
194
+ function copyInto(dest, source, overwrite) {
195
+ var prop;
196
+ dest = dest || {};
197
+ for (prop in source) {
198
+ if (source.hasOwnProperty(prop) && (overwrite || dest.hasOwnProperty(prop) === false)) {
199
+ dest[prop] = source[prop];
200
+ }
201
+ }
202
+ return dest;
203
+ }
204
+
205
+ Util = {
206
+
207
+ // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
208
+ // by rg89
209
+ isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
210
+
211
+ // https://github.com/jashkenas/underscore
212
+ keyCode: {
213
+ BACKSPACE: 8,
214
+ TAB: 9,
215
+ ENTER: 13,
216
+ ESCAPE: 27,
217
+ SPACE: 32,
218
+ DELETE: 46
219
+ },
220
+
221
+ parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'],
222
+
223
+ defaults: function defaults(dest, source) {
224
+ return copyInto(dest, source);
225
+ },
226
+
227
+ extend: function extend(dest, source) {
228
+ return copyInto(dest, source, true);
229
+ },
230
+
231
+ derives: function derives(base, derived) {
232
+ var origPrototype = derived.prototype;
233
+ function Proto() { }
234
+ Proto.prototype = base.prototype;
235
+ derived.prototype = new Proto();
236
+ derived.prototype.constructor = base;
237
+ derived.prototype = copyInto(derived.prototype, origPrototype);
238
+ return derived;
239
+ },
240
+
241
+ // Find the next node in the DOM tree that represents any text that is being
242
+ // displayed directly next to the targetNode (passed as an argument)
243
+ // Text that appears directly next to the current node can be:
244
+ // - A sibling text node
245
+ // - A descendant of a sibling element
246
+ // - A sibling text node of an ancestor
247
+ // - A descendant of a sibling element of an ancestor
248
+ findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) {
249
+ var pastTarget = false,
250
+ nextNode,
251
+ nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false);
252
+
253
+ // Use a native NodeIterator to iterate over all the text nodes that are descendants
254
+ // of the rootNode. Once past the targetNode, choose the first non-empty text node
255
+ nextNode = nodeIterator.nextNode();
256
+ while (nextNode) {
257
+ if (nextNode === targetNode) {
258
+ pastTarget = true;
259
+ } else if (pastTarget) {
260
+ if (nextNode.nodeType === 3 && nextNode.nodeValue && nextNode.nodeValue.trim().length > 0) {
261
+ break;
262
+ }
263
+ }
264
+ nextNode = nodeIterator.nextNode();
265
+ }
266
+
267
+ return nextNode;
268
+ },
269
+
270
+ isDescendant: function isDescendant(parent, child) {
271
+ if (!parent || !child) {
272
+ return false;
273
+ }
274
+ var node = child.parentNode;
275
+ while (node !== null) {
276
+ if (node === parent) {
277
+ return true;
278
+ }
279
+ node = node.parentNode;
280
+ }
281
+ return false;
282
+ },
283
+
284
+ // https://github.com/jashkenas/underscore
285
+ isElement: function isElement(obj) {
286
+ return !!(obj && obj.nodeType === 1);
287
+ },
288
+
289
+ now: function now() {
290
+ return Date.now || new Date().getTime();
291
+ },
292
+
293
+ // https://github.com/jashkenas/underscore
294
+ throttle: function throttle(func, wait) {
295
+ var THROTTLE_INTERVAL = 50,
296
+ context,
297
+ args,
298
+ result,
299
+ timeout = null,
300
+ previous = 0,
301
+ later;
302
+
303
+ if (!wait && wait !== 0) {
304
+ wait = THROTTLE_INTERVAL;
305
+ }
306
+
307
+ later = function () {
308
+ previous = Util.now();
309
+ timeout = null;
310
+ result = func.apply(context, args);
311
+ if (!timeout) {
312
+ context = args = null;
313
+ }
314
+ };
315
+
316
+ return function () {
317
+ var currNow = Util.now(),
318
+ remaining = wait - (currNow - previous);
319
+ context = this;
320
+ args = arguments;
321
+ if (remaining <= 0 || remaining > wait) {
322
+ clearTimeout(timeout);
323
+ timeout = null;
324
+ previous = currNow;
325
+ result = func.apply(context, args);
326
+ if (!timeout) {
327
+ context = args = null;
328
+ }
329
+ } else if (!timeout) {
330
+ timeout = setTimeout(later, remaining);
331
+ }
332
+ return result;
333
+ };
334
+ },
335
+
336
+ traverseUp: function (current, testElementFunction) {
337
+
338
+ do {
339
+ if (current.nodeType === 1) {
340
+ if (testElementFunction(current)) {
341
+ return current;
342
+ }
343
+ // do not traverse upwards past the nearest containing editor
344
+ if (current.getAttribute('data-medium-element')) {
345
+ return false;
346
+ }
347
+ }
348
+
349
+ current = current.parentNode;
350
+ } while (current);
351
+
352
+ return false;
353
+
354
+ },
355
+
356
+ htmlEntities: function (str) {
357
+ // converts special characters (like <) into their escaped/encoded values (like &lt;).
358
+ // This allows you to show to display the string without the browser reading it as HTML.
359
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
360
+ },
361
+
362
+ // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
363
+ insertHTMLCommand: function (doc, html) {
364
+ var selection, range, el, fragment, node, lastNode;
365
+
366
+ if (doc.queryCommandSupported('insertHTML')) {
367
+ try {
368
+ return doc.execCommand('insertHTML', false, html);
369
+ } catch (ignore) {}
370
+ }
371
+
372
+ selection = window.getSelection();
373
+ if (selection.getRangeAt && selection.rangeCount) {
374
+ range = selection.getRangeAt(0);
375
+ range.deleteContents();
376
+
377
+ el = doc.createElement("div");
378
+ el.innerHTML = html;
379
+ fragment = doc.createDocumentFragment();
380
+ while (el.firstChild) {
381
+ node = el.firstChild;
382
+ lastNode = fragment.appendChild(node);
383
+ }
384
+ range.insertNode(fragment);
385
+
386
+ // Preserve the selection:
387
+ if (lastNode) {
388
+ range = range.cloneRange();
389
+ range.setStartAfter(lastNode);
390
+ range.collapse(true);
391
+ selection.removeAllRanges();
392
+ selection.addRange(range);
393
+ }
394
+ }
395
+ },
396
+
397
+ // TODO: not sure if this should be here
398
+ setTargetBlank: function (el) {
399
+ var i;
400
+ if (el.tagName.toLowerCase() === 'a') {
401
+ el.target = '_blank';
402
+ } else {
403
+ el = el.getElementsByTagName('a');
404
+
405
+ for (i = 0; i < el.length; i += 1) {
406
+ el[i].target = '_blank';
407
+ }
408
+ }
409
+ },
410
+
411
+ isListItemChild: function (node) {
412
+ var parentNode = node.parentNode,
413
+ tagName = parentNode.tagName.toLowerCase();
414
+ while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') {
415
+ if (tagName === 'li') {
416
+ return true;
417
+ }
418
+ parentNode = parentNode.parentNode;
419
+ if (parentNode && parentNode.tagName) {
420
+ tagName = parentNode.tagName.toLowerCase();
421
+ } else {
422
+ return false;
423
+ }
424
+ }
425
+ return false;
426
+ }
427
+ };
428
+ }(window, document));
429
+
430
+ var Selection;
431
+
432
+ (function (window, document) {
433
+ 'use strict';
434
+
435
+ Selection = {
436
+ // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
437
+ // by You
438
+ getSelectionStart: function (ownerDocument) {
439
+ var node = ownerDocument.getSelection().anchorNode,
440
+ startNode = (node && node.nodeType === 3 ? node.parentNode : node);
441
+ return startNode;
442
+ },
443
+
444
+ findMatchingSelectionParent: function (testElementFunction, contentWindow) {
445
+ var selection = contentWindow.getSelection(), range, current;
446
+
447
+ if (selection.rangeCount === 0) {
448
+ return false;
449
+ }
450
+
451
+ range = selection.getRangeAt(0);
452
+ current = range.commonAncestorContainer;
453
+
454
+ return Util.traverseUp(current, testElementFunction);
455
+ },
456
+
457
+ getSelectionElement: function (contentWindow) {
458
+ return this.findMatchingSelectionParent(function (el) {
459
+ return el.getAttribute('data-medium-element');
460
+ }, contentWindow);
461
+ },
462
+
463
+ selectionInContentEditableFalse: function (contentWindow) {
464
+ return this.findMatchingSelectionParent(function (el) {
465
+ return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
466
+ }, contentWindow);
467
+ },
468
+
469
+ // http://stackoverflow.com/questions/4176923/html-of-selected-text
470
+ // by Tim Down
471
+ getSelectionHtml: function getSelectionHtml() {
472
+ var i,
473
+ html = '',
474
+ sel,
475
+ len,
476
+ container;
477
+ if (this.options.contentWindow.getSelection !== undefined) {
478
+ sel = this.options.contentWindow.getSelection();
479
+ if (sel.rangeCount) {
480
+ container = this.options.ownerDocument.createElement('div');
481
+ for (i = 0, len = sel.rangeCount; i < len; i += 1) {
482
+ container.appendChild(sel.getRangeAt(i).cloneContents());
483
+ }
484
+ html = container.innerHTML;
485
+ }
486
+ } else if (this.options.ownerDocument.selection !== undefined) {
487
+ if (this.options.ownerDocument.selection.type === 'Text') {
488
+ html = this.options.ownerDocument.selection.createRange().htmlText;
489
+ }
490
+ }
491
+ return html;
492
+ },
493
+
494
+ /**
495
+ * Find the caret position within an element irrespective of any inline tags it may contain.
496
+ *
497
+ * @param {DOMElement} An element containing the cursor to find offsets relative to.
498
+ * @param {Range} A Range representing cursor position. Will window.getSelection if none is passed.
499
+ * @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element
500
+ */
501
+ getCaretOffsets: function getCaretOffsets(element, range) {
502
+ var preCaretRange, postCaretRange;
503
+
504
+ if (!range) {
505
+ range = window.getSelection().getRangeAt(0);
506
+ }
507
+
508
+ preCaretRange = range.cloneRange();
509
+ postCaretRange = range.cloneRange();
510
+
511
+ preCaretRange.selectNodeContents(element);
512
+ preCaretRange.setEnd(range.endContainer, range.endOffset);
513
+
514
+ postCaretRange.selectNodeContents(element);
515
+ postCaretRange.setStart(range.endContainer, range.endOffset);
516
+
517
+ return {
518
+ left: preCaretRange.toString().length,
519
+ right: postCaretRange.toString().length
520
+ };
521
+ },
522
+
523
+ // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
524
+ rangeSelectsSingleNode: function (range) {
525
+ var startNode = range.startContainer;
526
+ return startNode === range.endContainer &&
527
+ startNode.hasChildNodes() &&
528
+ range.endOffset === range.startOffset + 1;
529
+ },
530
+
531
+ getSelectedParentElement: function (range) {
532
+ var selectedParentElement = null;
533
+ if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) {
534
+ selectedParentElement = range.startContainer.childNodes[range.startOffset];
535
+ } else if (range.startContainer.nodeType === 3) {
536
+ selectedParentElement = range.startContainer.parentNode;
537
+ } else {
538
+ selectedParentElement = range.startContainer;
539
+ }
540
+ return selectedParentElement;
541
+ },
542
+
543
+ getSelectionData: function (el) {
544
+ var tagName;
545
+
546
+ if (el && el.tagName) {
547
+ tagName = el.tagName.toLowerCase();
548
+ }
549
+
550
+ while (el && Util.parentElements.indexOf(tagName) === -1) {
551
+ el = el.parentNode;
552
+ if (el && el.tagName) {
553
+ tagName = el.tagName.toLowerCase();
554
+ }
555
+ }
556
+
557
+ return {
558
+ el: el,
559
+ tagName: tagName
560
+ };
561
+ }
562
+ };
563
+ }(document, window));
564
+
565
+ var DefaultButton,
566
+ ButtonsData;
567
+
568
+ (function (window, document) {
569
+ 'use strict';
570
+
571
+ ButtonsData = {
572
+ 'bold': {
573
+ name: 'bold',
574
+ action: 'bold',
575
+ aria: 'bold',
576
+ tagNames: ['b', 'strong'],
577
+ style: {
578
+ prop: 'font-weight',
579
+ value: '700|bold'
580
+ },
581
+ useQueryState: true,
582
+ contentDefault: '<b>B</b>',
583
+ contentFA: '<i class="fa fa-bold"></i>',
584
+ key: 'b'
585
+ },
586
+ 'italic': {
587
+ name: 'italic',
588
+ action: 'italic',
589
+ aria: 'italic',
590
+ tagNames: ['i', 'em'],
591
+ style: {
592
+ prop: 'font-style',
593
+ value: 'italic'
594
+ },
595
+ useQueryState: true,
596
+ contentDefault: '<b><i>I</i></b>',
597
+ contentFA: '<i class="fa fa-italic"></i>',
598
+ key: 'i'
599
+ },
600
+ 'underline': {
601
+ name: 'underline',
602
+ action: 'underline',
603
+ aria: 'underline',
604
+ tagNames: ['u'],
605
+ style: {
606
+ prop: 'text-decoration',
607
+ value: 'underline'
608
+ },
609
+ useQueryState: true,
610
+ contentDefault: '<b><u>U</u></b>',
611
+ contentFA: '<i class="fa fa-underline"></i>',
612
+ key: 'u'
613
+ },
614
+ 'strikethrough': {
615
+ name: 'strikethrough',
616
+ action: 'strikethrough',
617
+ aria: 'strike through',
618
+ tagNames: ['strike'],
619
+ style: {
620
+ prop: 'text-decoration',
621
+ value: 'line-through'
622
+ },
623
+ useQueryState: true,
624
+ contentDefault: '<s>A</s>',
625
+ contentFA: '<i class="fa fa-strikethrough"></i>'
626
+ },
627
+ 'superscript': {
628
+ name: 'superscript',
629
+ action: 'superscript',
630
+ aria: 'superscript',
631
+ tagNames: ['sup'],
632
+ /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for superscript
633
+ https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
634
+ // useQueryState: true
635
+ contentDefault: '<b>x<sup>1</sup></b>',
636
+ contentFA: '<i class="fa fa-superscript"></i>'
637
+ },
638
+ 'subscript': {
639
+ name: 'subscript',
640
+ action: 'subscript',
641
+ aria: 'subscript',
642
+ tagNames: ['sub'],
643
+ /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for subscript
644
+ https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
645
+ // useQueryState: true
646
+ contentDefault: '<b>x<sub>1</sub></b>',
647
+ contentFA: '<i class="fa fa-subscript"></i>'
648
+ },
649
+ 'image': {
650
+ name: 'image',
651
+ action: 'image',
652
+ aria: 'image',
653
+ tagNames: ['img'],
654
+ contentDefault: '<b>image</b>',
655
+ contentFA: '<i class="fa fa-picture-o"></i>'
656
+ },
657
+ 'quote': {
658
+ name: 'quote',
659
+ action: 'append-blockquote',
660
+ aria: 'blockquote',
661
+ tagNames: ['blockquote'],
662
+ contentDefault: '<b>&ldquo;</b>',
663
+ contentFA: '<i class="fa fa-quote-right"></i>'
664
+ },
665
+ 'orderedlist': {
666
+ name: 'orderedlist',
667
+ action: 'insertorderedlist',
668
+ aria: 'ordered list',
669
+ tagNames: ['ol'],
670
+ useQueryState: true,
671
+ contentDefault: '<b>1.</b>',
672
+ contentFA: '<i class="fa fa-list-ol"></i>'
673
+ },
674
+ 'unorderedlist': {
675
+ name: 'unorderedlist',
676
+ action: 'insertunorderedlist',
677
+ aria: 'unordered list',
678
+ tagNames: ['ul'],
679
+ useQueryState: true,
680
+ contentDefault: '<b>&bull;</b>',
681
+ contentFA: '<i class="fa fa-list-ul"></i>'
682
+ },
683
+ 'pre': {
684
+ name: 'pre',
685
+ action: 'append-pre',
686
+ aria: 'preformatted text',
687
+ tagNames: ['pre'],
688
+ contentDefault: '<b>0101</b>',
689
+ contentFA: '<i class="fa fa-code fa-lg"></i>'
690
+ },
691
+ 'indent': {
692
+ name: 'indent',
693
+ action: 'indent',
694
+ aria: 'indent',
695
+ tagNames: [],
696
+ contentDefault: '<b>&rarr;</b>',
697
+ contentFA: '<i class="fa fa-indent"></i>'
698
+ },
699
+ 'outdent': {
700
+ name: 'outdent',
701
+ action: 'outdent',
702
+ aria: 'outdent',
703
+ tagNames: [],
704
+ contentDefault: '<b>&larr;</b>',
705
+ contentFA: '<i class="fa fa-outdent"></i>'
706
+ },
707
+ 'justifyCenter': {
708
+ name: 'justifyCenter',
709
+ action: 'justifyCenter',
710
+ aria: 'center justify',
711
+ tagNames: [],
712
+ style: {
713
+ prop: 'text-align',
714
+ value: 'center'
715
+ },
716
+ useQueryState: true,
717
+ contentDefault: '<b>C</b>',
718
+ contentFA: '<i class="fa fa-align-center"></i>'
719
+ },
720
+ 'justifyFull': {
721
+ name: 'justifyFull',
722
+ action: 'justifyFull',
723
+ aria: 'full justify',
724
+ tagNames: [],
725
+ style: {
726
+ prop: 'text-align',
727
+ value: 'justify'
728
+ },
729
+ useQueryState: true,
730
+ contentDefault: '<b>J</b>',
731
+ contentFA: '<i class="fa fa-align-justify"></i>'
732
+ },
733
+ 'justifyLeft': {
734
+ name: 'justifyLeft',
735
+ action: 'justifyLeft',
736
+ aria: 'left justify',
737
+ tagNames: [],
738
+ style: {
739
+ prop: 'text-align',
740
+ value: 'left'
741
+ },
742
+ useQueryState: true,
743
+ contentDefault: '<b>L</b>',
744
+ contentFA: '<i class="fa fa-align-left"></i>'
745
+ },
746
+ 'justifyRight': {
747
+ name: 'justifyRight',
748
+ action: 'justifyRight',
749
+ aria: 'right justify',
750
+ tagNames: [],
751
+ style: {
752
+ prop: 'text-align',
753
+ value: 'right'
754
+ },
755
+ useQueryState: true,
756
+ contentDefault: '<b>R</b>',
757
+ contentFA: '<i class="fa fa-align-right"></i>'
758
+ },
759
+ 'header1': {
760
+ name: 'header1',
761
+ action: function (options) {
762
+ return 'append-' + options.firstHeader;
763
+ },
764
+ aria: function (options) {
765
+ return options.firstHeader;
766
+ },
767
+ tagNames: function (options) {
768
+ return [options.firstHeader];
769
+ },
770
+ contentDefault: '<b>H1</b>'
771
+ },
772
+ 'header2': {
773
+ name: 'header2',
774
+ action: function (options) {
775
+ return 'append-' + options.secondHeader;
776
+ },
777
+ aria: function (options) {
778
+ return options.secondHeader;
779
+ },
780
+ tagNames: function (options) {
781
+ return [options.secondHeader];
782
+ },
783
+ contentDefault: '<b>H2</b>'
784
+ }
785
+ };
786
+
787
+ DefaultButton = function (options, instance) {
788
+ this.options = options;
789
+ this.name = options.name;
790
+ this.init(instance);
791
+ };
792
+
793
+ DefaultButton.prototype = {
794
+ init: function (instance) {
795
+ this.base = instance;
796
+
797
+ this.button = this.createButton();
798
+ this.base.on(this.button, 'click', this.handleClick.bind(this));
799
+ },
800
+ getButton: function () {
801
+ return this.button;
802
+ },
803
+ getAction: function () {
804
+ return (typeof this.options.action === 'function') ? this.options.action(this.base.options) : this.options.action;
805
+ },
806
+ getAria: function () {
807
+ return (typeof this.options.aria === 'function') ? this.options.aria(this.base.options) : this.options.aria;
808
+ },
809
+ getTagNames: function () {
810
+ return (typeof this.options.tagNames === 'function') ? this.options.tagNames(this.base.options) : this.options.tagNames;
811
+ },
812
+ createButton: function () {
813
+ var button = this.base.options.ownerDocument.createElement('button'),
814
+ content = this.options.contentDefault;
815
+ button.classList.add('medium-editor-action');
816
+ button.classList.add('medium-editor-action-' + this.name);
817
+ button.setAttribute('data-action', this.getAction());
818
+ button.setAttribute('aria-label', this.getAria());
819
+ if (this.base.options.buttonLabels) {
820
+ if (this.base.options.buttonLabels === 'fontawesome' && this.options.contentFA) {
821
+ content = this.options.contentFA;
822
+ } else if (typeof this.base.options.buttonLabels === 'object' && this.base.options.buttonLabels[this.name]) {
823
+ content = this.base.options.buttonLabels[this.options.name];
824
+ }
825
+ }
826
+ button.innerHTML = content;
827
+ return button;
828
+ },
829
+ handleClick: function (evt) {
830
+ evt.preventDefault();
831
+ evt.stopPropagation();
832
+
833
+ var action = this.getAction();
834
+
835
+ if (action) {
836
+ this.base.execAction(action);
837
+ }
838
+ },
839
+ isActive: function () {
840
+ return this.button.classList.contains(this.base.options.activeButtonClass);
841
+ },
842
+ setInactive: function () {
843
+ this.button.classList.remove(this.base.options.activeButtonClass);
844
+ delete this.knownState;
845
+ },
846
+ setActive: function () {
847
+ this.button.classList.add(this.base.options.activeButtonClass);
848
+ delete this.knownState;
849
+ },
850
+ queryCommandState: function () {
851
+ var queryState = null;
852
+ if (this.options.useQueryState) {
853
+ queryState = this.base.queryCommandState(this.getAction());
854
+ }
855
+ return queryState;
856
+ },
857
+ isAlreadyApplied: function (node) {
858
+ var isMatch = false,
859
+ tagNames = this.getTagNames(),
860
+ styleVals,
861
+ computedStyle;
862
+
863
+ if (this.knownState === false || this.knownState === true) {
864
+ return this.knownState;
865
+ }
866
+
867
+ if (tagNames && tagNames.length > 0 && node.tagName) {
868
+ isMatch = tagNames.indexOf(node.tagName.toLowerCase()) !== -1;
869
+ }
870
+
871
+ if (!isMatch && this.options.style) {
872
+ styleVals = this.options.style.value.split('|');
873
+ computedStyle = this.base.options.contentWindow.getComputedStyle(node, null).getPropertyValue(this.options.style.prop);
874
+ styleVals.forEach(function (val) {
875
+ if (!this.knownState) {
876
+ this.knownState = isMatch = (computedStyle.indexOf(val) !== -1);
877
+ }
878
+ }.bind(this));
879
+ }
880
+
881
+ return isMatch;
882
+ }
883
+ };
884
+ }(window, document));
885
+
886
+ var pasteHandler;
887
+
888
+ (function (window, document) {
889
+ 'use strict';
890
+ /*jslint regexp: true*/
891
+ /*
892
+ jslint does not allow character negation, because the negation
893
+ will not match any unicode characters. In the regexes in this
894
+ block, negation is used specifically to match the end of an html
895
+ tag, and in fact unicode characters *should* be allowed.
896
+ */
897
+ function createReplacements() {
898
+ return [
899
+
900
+ // replace two bogus tags that begin pastes from google docs
901
+ [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
902
+ [new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
903
+
904
+ // un-html spaces and newlines inserted by OS X
905
+ [new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
906
+ [new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
907
+
908
+ // replace google docs italics+bold with a span to be replaced once the html is inserted
909
+ [new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
910
+
911
+ // replace google docs italics with a span to be replaced once the html is inserted
912
+ [new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
913
+
914
+ //[replace google docs bolds with a span to be replaced once the html is inserted
915
+ [new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
916
+
917
+ // replace manually entered b/i/a tags with real ones
918
+ [new RegExp(/&lt;(\/?)(i|b|a)&gt;/gi), '<$1$2>'],
919
+
920
+ // replace manually a tags with real ones, converting smart-quotes from google docs
921
+ [new RegExp(/&lt;a\s+href=(&quot;|&rdquo;|&ldquo;|“|”)([^&]+)(&quot;|&rdquo;|&ldquo;|“|”)&gt;/gi), '<a href="$2">']
922
+
923
+ ];
924
+ }
925
+ /*jslint regexp: false*/
926
+
927
+ pasteHandler = {
928
+ handlePaste: function (element, evt, options) {
929
+ var paragraphs,
930
+ html = '',
931
+ p,
932
+ dataFormatHTML = 'text/html',
933
+ dataFormatPlain = 'text/plain';
934
+
935
+ element.classList.remove('medium-editor-placeholder');
936
+ if (!options.forcePlainText && !options.cleanPastedHTML) {
937
+ return element;
938
+ }
939
+
940
+ if (options.contentWindow.clipboardData && evt.clipboardData === undefined) {
941
+ evt.clipboardData = options.contentWindow.clipboardData;
942
+ // If window.clipboardData exists, but e.clipboardData doesn't exist,
943
+ // we're probably in IE. IE only has two possibilities for clipboard
944
+ // data format: 'Text' and 'URL'.
945
+ //
946
+ // Of the two, we want 'Text':
947
+ dataFormatHTML = 'Text';
948
+ dataFormatPlain = 'Text';
949
+ }
950
+
951
+ if (evt.clipboardData && evt.clipboardData.getData && !evt.defaultPrevented) {
952
+ evt.preventDefault();
953
+
954
+ if (options.cleanPastedHTML && evt.clipboardData.getData(dataFormatHTML)) {
955
+ return this.cleanPaste(evt.clipboardData.getData(dataFormatHTML), options);
956
+ }
957
+ if (!(options.disableReturn || element.getAttribute('data-disable-return'))) {
958
+ paragraphs = evt.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
959
+ for (p = 0; p < paragraphs.length; p += 1) {
960
+ if (paragraphs[p] !== '') {
961
+ html += '<p>' + Util.htmlEntities(paragraphs[p]) + '</p>';
962
+ }
963
+ }
964
+ Util.insertHTMLCommand(options.ownerDocument, html);
965
+ } else {
966
+ html = Util.htmlEntities(evt.clipboardData.getData(dataFormatPlain));
967
+ Util.insertHTMLCommand(options.ownerDocument, html);
968
+ }
969
+ }
970
+ },
971
+
972
+ cleanPaste: function (text, options) {
973
+ var i, elList, workEl,
974
+ el = Selection.getSelectionElement(options.contentWindow),
975
+ multiline = /<p|<br|<div/.test(text),
976
+ replacements = createReplacements();
977
+
978
+ for (i = 0; i < replacements.length; i += 1) {
979
+ text = text.replace(replacements[i][0], replacements[i][1]);
980
+ }
981
+
982
+ if (multiline) {
983
+ // double br's aren't converted to p tags, but we want paragraphs.
984
+ elList = text.split('<br><br>');
985
+
986
+ this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>', options.ownerDocument);
987
+
988
+ try {
989
+ options.ownerDocument.execCommand('insertText', false, "\n");
990
+ } catch (ignore) { }
991
+
992
+ // block element cleanup
993
+ elList = el.querySelectorAll('a,p,div,br');
994
+ for (i = 0; i < elList.length; i += 1) {
995
+ workEl = elList[i];
996
+
997
+ switch (workEl.tagName.toLowerCase()) {
998
+ case 'a':
999
+ if (options.targetBlank) {
1000
+ Util.setTargetBlank(workEl);
1001
+ }
1002
+ break;
1003
+ case 'p':
1004
+ case 'div':
1005
+ this.filterCommonBlocks(workEl);
1006
+ break;
1007
+ case 'br':
1008
+ this.filterLineBreak(workEl);
1009
+ break;
1010
+ }
1011
+ }
1012
+ } else {
1013
+ this.pasteHTML(text, options.ownerDocument);
1014
+ }
1015
+ },
1016
+
1017
+ pasteHTML: function (html, ownerDocument) {
1018
+ var elList, workEl, i, fragmentBody, pasteBlock = ownerDocument.createDocumentFragment();
1019
+
1020
+ pasteBlock.appendChild(ownerDocument.createElement('body'));
1021
+
1022
+ fragmentBody = pasteBlock.querySelector('body');
1023
+ fragmentBody.innerHTML = html;
1024
+
1025
+ this.cleanupSpans(fragmentBody, ownerDocument);
1026
+
1027
+ elList = fragmentBody.querySelectorAll('*');
1028
+ for (i = 0; i < elList.length; i += 1) {
1029
+ workEl = elList[i];
1030
+
1031
+ // delete ugly attributes
1032
+ workEl.removeAttribute('class');
1033
+ workEl.removeAttribute('style');
1034
+ workEl.removeAttribute('dir');
1035
+
1036
+ if (workEl.tagName.toLowerCase() === 'meta') {
1037
+ workEl.parentNode.removeChild(workEl);
1038
+ }
1039
+ }
1040
+ Util.insertHTMLCommand(ownerDocument, fragmentBody.innerHTML.replace(/&nbsp;/g, ' '));
1041
+ },
1042
+ isCommonBlock: function (el) {
1043
+ return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
1044
+ },
1045
+ filterCommonBlocks: function (el) {
1046
+ if (/^\s*$/.test(el.textContent)) {
1047
+ el.parentNode.removeChild(el);
1048
+ }
1049
+ },
1050
+ filterLineBreak: function (el) {
1051
+ if (this.isCommonBlock(el.previousElementSibling)) {
1052
+ // remove stray br's following common block elements
1053
+ this.removeWithParent(el);
1054
+ } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
1055
+ // remove br's just inside open or close tags of a div/p
1056
+ this.removeWithParent(el);
1057
+ } else if (el.parentNode.childElementCount === 1 && el.parentNode.textContent === '') {
1058
+ // and br's that are the only child of elements other than div/p
1059
+ this.removeWithParent(el);
1060
+ }
1061
+ },
1062
+
1063
+ // remove an element, including its parent, if it is the only element within its parent
1064
+ removeWithParent: function (el) {
1065
+ if (el && el.parentNode) {
1066
+ if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
1067
+ el.parentNode.parentNode.removeChild(el.parentNode);
1068
+ } else {
1069
+ el.parentNode.removeChild(el);
1070
+ }
1071
+ }
1072
+ },
15
1073
 
16
- (function (window, document) {
17
- 'use strict';
1074
+ cleanupSpans: function (container_el, ownerDocument) {
1075
+ var i,
1076
+ el,
1077
+ new_el,
1078
+ spans = container_el.querySelectorAll('.replace-with'),
1079
+ isCEF = function (el) {
1080
+ return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
1081
+ };
18
1082
 
19
- function extend(b, a) {
20
- var prop;
21
- if (b === undefined) {
22
- return a;
23
- }
24
- for (prop in a) {
25
- if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) {
26
- b[prop] = a[prop];
1083
+ for (i = 0; i < spans.length; i += 1) {
1084
+ el = spans[i];
1085
+ new_el = ownerDocument.createElement(el.classList.contains('bold') ? 'b' : 'i');
1086
+
1087
+ if (el.classList.contains('bold') && el.classList.contains('italic')) {
1088
+ // add an i tag as well if this has both italics and bold
1089
+ new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
1090
+ } else {
1091
+ new_el.innerHTML = el.innerHTML;
1092
+ }
1093
+ el.parentNode.replaceChild(new_el, el);
1094
+ }
1095
+
1096
+ spans = container_el.querySelectorAll('span');
1097
+ for (i = 0; i < spans.length; i += 1) {
1098
+ el = spans[i];
1099
+
1100
+ // bail if span is in contenteditable = false
1101
+ if (Util.traverseUp(el, isCEF)) {
1102
+ return false;
1103
+ }
1104
+
1105
+ // remove empty spans, replace others with their contents
1106
+ if (/^\s*$/.test()) {
1107
+ el.parentNode.removeChild(el);
1108
+ } else {
1109
+ el.parentNode.replaceChild(ownerDocument.createTextNode(el.textContent), el);
1110
+ }
27
1111
  }
28
1112
  }
29
- return b;
1113
+ };
1114
+ }(window, document));
1115
+
1116
+ var AnchorExtension;
1117
+
1118
+ (function (window, document) {
1119
+ 'use strict';
1120
+
1121
+ function AnchorDerived() {
1122
+ this.parent = true;
1123
+ this.options = {
1124
+ name: 'anchor',
1125
+ action: 'createLink',
1126
+ aria: 'link',
1127
+ tagNames: ['a'],
1128
+ contentDefault: '<b>#</b>',
1129
+ contentFA: '<i class="fa fa-link"></i>'
1130
+ };
1131
+ this.name = 'anchor';
1132
+ this.hasForm = true;
30
1133
  }
31
1134
 
32
- // https://github.com/jashkenas/underscore
33
- var now = Date.now || function () {
34
- return new Date().getTime();
35
- };
1135
+ AnchorDerived.prototype = {
36
1136
 
37
- // https://github.com/jashkenas/underscore
38
- function throttle(func, wait) {
39
- var THROTTLE_INTERVAL = 50,
40
- context,
41
- args,
42
- result,
43
- timeout = null,
44
- previous = 0,
45
- later;
46
-
47
- if (!wait && wait !== 0) {
48
- wait = THROTTLE_INTERVAL;
49
- }
1137
+ // Button and Extension handling
1138
+
1139
+ // Called when the button the toolbar is clicked
1140
+ // Overrides DefaultButton.handleClick
1141
+ handleClick: function (evt) {
1142
+ evt.preventDefault();
1143
+ evt.stopPropagation();
50
1144
 
51
- later = function () {
52
- previous = now();
53
- timeout = null;
54
- result = func.apply(context, args);
55
- if (!timeout) {
56
- context = args = null;
1145
+ if (!this.base.selection) {
1146
+ this.base.checkSelection();
57
1147
  }
58
- };
59
1148
 
60
- return function () {
61
- var currNow = now(),
62
- remaining = wait - (currNow - previous);
63
- context = this;
64
- args = arguments;
65
- if (remaining <= 0 || remaining > wait) {
66
- clearTimeout(timeout);
67
- timeout = null;
68
- previous = currNow;
69
- result = func.apply(context, args);
70
- if (!timeout) {
71
- context = args = null;
72
- }
73
- } else if (!timeout) {
74
- timeout = setTimeout(later, remaining);
1149
+ var selectedParentElement = Selection.getSelectedParentElement(this.base.selectionRange);
1150
+ if (selectedParentElement.tagName &&
1151
+ selectedParentElement.tagName.toLowerCase() === 'a') {
1152
+ return this.base.execAction('unlink');
75
1153
  }
76
- return result;
77
- };
78
- }
79
1154
 
80
- function isDescendant(parent, child) {
81
- var node = child.parentNode;
82
- while (node !== null) {
83
- if (node === parent) {
84
- return true;
1155
+ if (!this.isDisplayed()) {
1156
+ this.showForm();
85
1157
  }
86
- node = node.parentNode;
87
- }
88
- return false;
89
- }
90
1158
 
91
- // Find the next node in the DOM tree that represents any text that is being
92
- // displayed directly next to the targetNode (passed as an argument)
93
- // Text that appears directly next to the current node can be:
94
- // - A sibling text node
95
- // - A descendant of a sibling element
96
- // - A sibling text node of an ancestor
97
- // - A descendant of a sibling element of an ancestor
98
- function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) {
99
- var pastTarget = false,
100
- nextNode,
101
- nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false);
102
-
103
- // Use a native NodeIterator to iterate over all the text nodes that are descendants
104
- // of the rootNode. Once past the targetNode, choose the first non-empty text node
105
- nextNode = nodeIterator.nextNode();
106
- while (nextNode) {
107
- if (nextNode === targetNode) {
108
- pastTarget = true;
109
- } else if (pastTarget) {
110
- if (nextNode.nodeType === 3 && nextNode.nodeValue && nextNode.nodeValue.trim().length > 0) {
111
- break;
112
- }
1159
+ return false;
1160
+ },
1161
+
1162
+ // Called by medium-editor to append form to the toolbar
1163
+ getForm: function () {
1164
+ if (!this.anchorForm) {
1165
+ this.anchorForm = this.createForm();
113
1166
  }
114
- nextNode = nodeIterator.nextNode();
115
- }
1167
+ return this.anchorForm;
1168
+ },
116
1169
 
117
- return nextNode;
118
- }
1170
+ // Used by medium-editor when the default toolbar is to be displayed
1171
+ isDisplayed: function () {
1172
+ return this.getForm().style.display === 'block';
1173
+ },
119
1174
 
120
- // http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element
121
- // by Tim Down
122
- function saveSelection() {
123
- var i,
124
- len,
125
- ranges,
126
- sel = this.options.contentWindow.getSelection();
127
- if (sel.getRangeAt && sel.rangeCount) {
128
- ranges = [];
129
- for (i = 0, len = sel.rangeCount; i < len; i += 1) {
130
- ranges.push(sel.getRangeAt(i));
1175
+ hideForm: function () {
1176
+ this.getForm().style.display = 'none';
1177
+ this.getInput().value = '';
1178
+ },
1179
+
1180
+ showForm: function (link_value) {
1181
+ var input = this.getInput();
1182
+
1183
+ this.base.saveSelection();
1184
+ this.base.hideToolbarDefaultActions();
1185
+ this.getForm().style.display = 'block';
1186
+ this.base.setToolbarPosition();
1187
+ this.base.keepToolbarAlive = true;
1188
+
1189
+ input.value = link_value || '';
1190
+ input.focus();
1191
+ },
1192
+
1193
+ // Called by core when tearing down medium-editor (deactivate)
1194
+ deactivate: function () {
1195
+ if (!this.anchorForm) {
1196
+ return false;
131
1197
  }
132
- return ranges;
133
- }
134
- return null;
135
- }
136
1198
 
137
- function restoreSelection(savedSel) {
138
- var i,
139
- len,
140
- sel = this.options.contentWindow.getSelection();
141
- if (savedSel) {
142
- sel.removeAllRanges();
143
- for (i = 0, len = savedSel.length; i < len; i += 1) {
144
- sel.addRange(savedSel[i]);
1199
+ if (this.anchorForm.parentNode) {
1200
+ this.anchorForm.parentNode.removeChild(this.anchorForm);
145
1201
  }
146
- }
147
- }
148
1202
 
149
- // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
150
- // by You
151
- function getSelectionStart() {
152
- var node = this.options.ownerDocument.getSelection().anchorNode,
153
- startNode = (node && node.nodeType === 3 ? node.parentNode : node);
154
- return startNode;
155
- }
1203
+ delete this.anchorForm;
1204
+ },
156
1205
 
157
- // http://stackoverflow.com/questions/4176923/html-of-selected-text
158
- // by Tim Down
159
- function getSelectionHtml() {
160
- var i,
161
- html = '',
162
- sel,
163
- len,
164
- container;
165
- if (this.options.contentWindow.getSelection !== undefined) {
166
- sel = this.options.contentWindow.getSelection();
167
- if (sel.rangeCount) {
168
- container = this.options.ownerDocument.createElement('div');
169
- for (i = 0, len = sel.rangeCount; i < len; i += 1) {
170
- container.appendChild(sel.getRangeAt(i).cloneContents());
171
- }
172
- html = container.innerHTML;
1206
+ // core methods
1207
+
1208
+ doLinkCreation: function () {
1209
+ var targetCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-target'),
1210
+ buttonCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-button'),
1211
+ opts = {
1212
+ url: this.getInput().value
1213
+ };
1214
+
1215
+ this.base.restoreSelection();
1216
+
1217
+ if (this.base.options.checkLinkFormat) {
1218
+ opts.url = this.checkLinkFormat(opts.url);
173
1219
  }
174
- } else if (this.options.ownerDocument.selection !== undefined) {
175
- if (this.options.ownerDocument.selection.type === 'Text') {
176
- html = this.options.ownerDocument.selection.createRange().htmlText;
1220
+
1221
+ if (targetCheckbox && targetCheckbox.checked) {
1222
+ opts.target = "_blank";
1223
+ } else {
1224
+ opts.target = "_self";
177
1225
  }
178
- }
179
- return html;
180
- }
181
1226
 
182
- /**
183
- * Find the caret position within an element irrespective of any inline tags it may contain.
184
- *
185
- * @param {DOMElement} An element containing the cursor to find offsets relative to.
186
- * @param {Range} A Range representing cursor position. Will window.getSelection if none is passed.
187
- * @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element
188
- */
189
- function getCaretOffsets(element, range) {
190
- var preCaretRange, postCaretRange;
191
-
192
- if (!range) {
193
- range = window.getSelection().getRangeAt(0);
194
- }
1227
+ if (buttonCheckbox && buttonCheckbox.checked) {
1228
+ opts.buttonClass = this.base.options.anchorButtonClass;
1229
+ }
1230
+
1231
+ this.base.createLink(opts);
1232
+ this.base.keepToolbarAlive = false;
1233
+ this.base.checkSelection();
1234
+ },
195
1235
 
196
- preCaretRange = range.cloneRange();
197
- postCaretRange = range.cloneRange();
1236
+ checkLinkFormat: function (value) {
1237
+ var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/;
1238
+ return (re.test(value) ? '' : 'http://') + value;
1239
+ },
198
1240
 
199
- preCaretRange.selectNodeContents(element);
200
- preCaretRange.setEnd(range.endContainer, range.endOffset);
1241
+ doFormCancel: function () {
1242
+ this.base.restoreSelection();
1243
+ this.base.keepToolbarAlive = false;
1244
+ this.base.checkSelection();
1245
+ },
201
1246
 
202
- postCaretRange.selectNodeContents(element);
203
- postCaretRange.setStart(range.endContainer, range.endOffset);
1247
+ // form creation and event handling
204
1248
 
205
- return {
206
- left: preCaretRange.toString().length,
207
- right: postCaretRange.toString().length
208
- };
209
- }
1249
+ createForm: function () {
1250
+ var doc = this.base.options.ownerDocument,
1251
+ form = doc.createElement('div'),
1252
+ input = doc.createElement('input'),
1253
+ close = doc.createElement('a'),
1254
+ save = doc.createElement('a'),
1255
+ target,
1256
+ target_label,
1257
+ button,
1258
+ button_label;
210
1259
 
1260
+ // Anchor Form (div)
1261
+ form.className = 'medium-editor-toolbar-form';
1262
+ form.id = 'medium-editor-toolbar-form-anchor-' + this.base.id;
211
1263
 
212
- // https://github.com/jashkenas/underscore
213
- function isElement(obj) {
214
- return !!(obj && obj.nodeType === 1);
215
- }
1264
+ // Handle clicks on the form itself
1265
+ this.base.on(form, 'click', this.handleFormClick.bind(this));
216
1266
 
217
- // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
218
- function insertHTMLCommand(doc, html) {
219
- var selection, range, el, fragment, node, lastNode;
1267
+ // Add url textbox
1268
+ input.setAttribute('type', 'text');
1269
+ input.className = 'medium-editor-toolbar-input';
1270
+ input.setAttribute('placeholder', this.base.options.anchorInputPlaceholder);
1271
+ form.appendChild(input);
220
1272
 
221
- if (doc.queryCommandSupported('insertHTML')) {
222
- try {
223
- return doc.execCommand('insertHTML', false, html);
224
- } catch (ignore) {}
225
- }
1273
+ // Handle typing in the textbox
1274
+ this.base.on(input, 'keyup', this.handleTextboxKeyup.bind(this));
226
1275
 
227
- selection = window.getSelection();
228
- if (selection.getRangeAt && selection.rangeCount) {
229
- range = selection.getRangeAt(0);
230
- range.deleteContents();
1276
+ // Handle clicks into the textbox
1277
+ this.base.on(input, 'click', this.handleFormClick.bind(this));
1278
+
1279
+ // Add save buton
1280
+ save.setAttribute('href', '#');
1281
+ save.className = 'medium-editor-toobar-save';
1282
+ save.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
1283
+ '<i class="fa fa-check"></i>' :
1284
+ '&#10003;';
1285
+ form.appendChild(save);
1286
+
1287
+ // Handle save button clicks (capture)
1288
+ this.base.on(save, 'click', this.handleSaveClick.bind(this), true);
1289
+
1290
+ // Add close button
1291
+ close.setAttribute('href', '#');
1292
+ close.className = 'medium-editor-toobar-close';
1293
+ close.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
1294
+ '<i class="fa fa-times"></i>' :
1295
+ '&times;';
1296
+ form.appendChild(close);
1297
+
1298
+ // Handle close button clicks
1299
+ this.base.on(close, 'click', this.handleCloseClick.bind(this));
1300
+
1301
+ // (Optional) Add 'open in new window' checkbox
1302
+ if (this.base.options.anchorTarget) {
1303
+ target = doc.createElement('input');
1304
+ target.setAttribute('type', 'checkbox');
1305
+ target.className = 'medium-editor-toolbar-anchor-target';
1306
+
1307
+ target_label = doc.createElement('label');
1308
+ target_label.innerHTML = this.base.options.anchorInputCheckboxLabel;
1309
+ target_label.insertBefore(target, target_label.firstChild);
231
1310
 
232
- el = doc.createElement("div");
233
- el.innerHTML = html;
234
- fragment = doc.createDocumentFragment();
235
- while (el.firstChild) {
236
- node = el.firstChild;
237
- lastNode = fragment.appendChild(node);
1311
+ form.appendChild(target_label);
238
1312
  }
239
- range.insertNode(fragment);
240
1313
 
241
- // Preserve the selection:
242
- if (lastNode) {
243
- range = range.cloneRange();
244
- range.setStartAfter(lastNode);
245
- range.collapse(true);
246
- selection.removeAllRanges();
247
- selection.addRange(range);
1314
+ // (Optional) Add 'add button class to anchor' checkbox
1315
+ if (this.base.options.anchorButton) {
1316
+ button = doc.createElement('input');
1317
+ button.setAttribute('type', 'checkbox');
1318
+ button.className = 'medium-editor-toolbar-anchor-button';
1319
+
1320
+ button_label = doc.createElement('label');
1321
+ button_label.innerHTML = "Button";
1322
+ button_label.insertBefore(button, button_label.firstChild);
1323
+
1324
+ form.appendChild(button_label);
1325
+ }
1326
+
1327
+ // Handle click (capture) & focus (capture) outside of the form
1328
+ this.base.on(doc.body, 'click', this.handleOutsideInteraction.bind(this), true);
1329
+ this.base.on(doc.body, 'focus', this.handleOutsideInteraction.bind(this), true);
1330
+
1331
+ return form;
1332
+ },
1333
+
1334
+ getInput: function () {
1335
+ return this.getForm().querySelector('input.medium-editor-toolbar-input');
1336
+ },
1337
+
1338
+ handleOutsideInteraction: function (event) {
1339
+ if (event.target !== this.getForm() &&
1340
+ !Util.isDescendant(this.getForm(), event.target) &&
1341
+ !Util.isDescendant(this.base.toolbarActions, event.target)) {
1342
+ this.base.keepToolbarAlive = false;
1343
+ this.base.checkSelection();
1344
+ }
1345
+ },
1346
+
1347
+ handleTextboxKeyup: function (event) {
1348
+ // For ENTER -> create the anchor
1349
+ if (event.keyCode === Util.keyCode.ENTER) {
1350
+ event.preventDefault();
1351
+ this.doLinkCreation();
1352
+ return;
1353
+ }
1354
+
1355
+ // For ESCAPE -> close the form
1356
+ if (event.keyCode === Util.keyCode.ESCAPE) {
1357
+ event.preventDefault();
1358
+ this.doFormCancel();
248
1359
  }
1360
+ },
1361
+
1362
+ handleFormClick: function (event) {
1363
+ // make sure not to hide form when clicking inside the form
1364
+ event.stopPropagation();
1365
+ this.base.keepToolbarAlive = true;
1366
+ },
1367
+
1368
+ handleSaveClick: function (event) {
1369
+ // Clicking Save -> create the anchor
1370
+ event.preventDefault();
1371
+ this.doLinkCreation();
1372
+ },
1373
+
1374
+ handleCloseClick: function (event) {
1375
+ // Click Close -> close the form
1376
+ event.preventDefault();
1377
+ this.doFormCancel();
249
1378
  }
250
- }
1379
+ };
1380
+
1381
+ AnchorExtension = Util.derives(DefaultButton, AnchorDerived);
1382
+ }(window, document));
1383
+
1384
+ function MediumEditor(elements, options) {
1385
+ 'use strict';
1386
+ return this.init(elements, options);
1387
+ }
1388
+
1389
+ (function () {
1390
+ 'use strict';
1391
+
1392
+ MediumEditor.statics = {
1393
+ ButtonsData: ButtonsData,
1394
+ DefaultButton: DefaultButton,
1395
+ AnchorExtension: AnchorExtension
1396
+ };
251
1397
 
252
1398
  MediumEditor.prototype = {
253
1399
  defaults: {
@@ -266,9 +1412,10 @@ if (typeof module === 'object') {
266
1412
  disableDoubleReturn: false,
267
1413
  disableToolbar: false,
268
1414
  disableEditing: false,
269
- disableAnchorForm: false,
270
1415
  disablePlaceholders: false,
1416
+ toolbarAlign: 'center',
271
1417
  elementsContainer: false,
1418
+ imageDragging: true,
272
1419
  standardizeSelectionStart: false,
273
1420
  contentWindow: window,
274
1421
  ownerDocument: document,
@@ -286,19 +1433,15 @@ if (typeof module === 'object') {
286
1433
  lastButtonClass: 'medium-editor-button-last'
287
1434
  },
288
1435
 
289
- // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
290
- // by rg89
291
- isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
292
-
293
1436
  init: function (elements, options) {
294
1437
  var uniqueId = 1;
295
1438
 
296
- this.options = extend(options, this.defaults);
1439
+ this.options = Util.defaults(options, this.defaults);
297
1440
  this.setElementSelection(elements);
298
1441
  if (this.elements.length === 0) {
299
1442
  return;
300
1443
  }
301
- this.parentElements = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'];
1444
+
302
1445
  if (!this.options.elementsContainer) {
303
1446
  this.options.elementsContainer = this.options.ownerDocument.body;
304
1447
  }
@@ -316,13 +1459,14 @@ if (typeof module === 'object') {
316
1459
  this.events = [];
317
1460
  this.isActive = true;
318
1461
  this.initThrottledMethods()
1462
+ .initCommands()
319
1463
  .initElements()
320
1464
  .bindSelect()
1465
+ .bindDragDrop()
321
1466
  .bindPaste()
322
1467
  .setPlaceholders()
323
1468
  .bindElementActions()
324
1469
  .bindWindowActions();
325
- //.passInstance();
326
1470
  },
327
1471
 
328
1472
  on: function (target, event, listener, useCapture) {
@@ -373,7 +1517,7 @@ if (typeof module === 'object') {
373
1517
  // handleResize is throttled because:
374
1518
  // - It will be called when the browser is resizing, which can fire many times very quickly
375
1519
  // - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
376
- this.handleResize = throttle(function () {
1520
+ this.handleResize = Util.throttle(function () {
377
1521
  if (self.isActive) {
378
1522
  self.positionToolbarIfShown();
379
1523
  }
@@ -383,7 +1527,7 @@ if (typeof module === 'object') {
383
1527
  // - This method could be called many times due to the type of event handlers that are calling it
384
1528
  // - We want a slight delay so that other events in the stack can run, some of which may
385
1529
  // prevent the toolbar from being hidden (via this.keepToolbarAlive).
386
- this.handleBlur = throttle(function () {
1530
+ this.handleBlur = Util.throttle(function () {
387
1531
  if (self.isActive && !self.keepToolbarAlive) {
388
1532
  self.hideToolbarActions();
389
1533
  }
@@ -412,11 +1556,8 @@ if (typeof module === 'object') {
412
1556
  }
413
1557
  // Init toolbar
414
1558
  if (addToolbar) {
415
- this.passInstance()
416
- .callExtensions('init')
417
- .initToolbar()
418
- .bindButtons()
419
- .bindAnchorForm()
1559
+ this.initToolbar()
1560
+ .setFirstAndLastButtons()
420
1561
  .bindAnchorPreview();
421
1562
  }
422
1563
  return this;
@@ -431,7 +1572,7 @@ if (typeof module === 'object') {
431
1572
  selector = this.options.ownerDocument.querySelectorAll(selector);
432
1573
  }
433
1574
  // If element, put into array
434
- if (isElement(selector)) {
1575
+ if (Util.isElement(selector)) {
435
1576
  selector = [selector];
436
1577
  }
437
1578
  // Convert NodeList (or other array like object) into an array
@@ -442,9 +1583,18 @@ if (typeof module === 'object') {
442
1583
  var self = this,
443
1584
  blurFunction = function (e) {
444
1585
  var isDescendantOfEditorElements = false,
1586
+ selection = self.options.contentWindow.getSelection(),
1587
+ selRange = selection.isCollapsed ?
1588
+ null :
1589
+ Selection.getSelectedParentElement(selection.getRangeAt(0)),
445
1590
  i;
1591
+
1592
+ // This control was introduced also to avoid the toolbar
1593
+ // to disapper when selecting from right to left and
1594
+ // the selection ends at the beginning of the text.
446
1595
  for (i = 0; i < self.elements.length; i += 1) {
447
- if (isDescendant(self.elements[i], e.target)) {
1596
+ if (Util.isDescendant(self.elements[i], e.target)
1597
+ || Util.isDescendant(self.elements[i], selRange)) {
448
1598
  isDescendantOfEditorElements = true;
449
1599
  break;
450
1600
  }
@@ -453,8 +1603,8 @@ if (typeof module === 'object') {
453
1603
  if (e.target !== self.toolbar
454
1604
  && self.elements.indexOf(e.target) === -1
455
1605
  && !isDescendantOfEditorElements
456
- && !isDescendant(self.toolbar, e.target)
457
- && !isDescendant(self.anchorPreview, e.target)) {
1606
+ && !Util.isDescendant(self.toolbar, e.target)
1607
+ && !Util.isDescendant(self.anchorPreview, e.target)) {
458
1608
 
459
1609
  // Activate the placeholder
460
1610
  if (!self.options.disablePlaceholders) {
@@ -507,7 +1657,6 @@ if (typeof module === 'object') {
507
1657
  // Bind the return and tab keypress events
508
1658
  this.bindReturn(i)
509
1659
  .bindKeydown(i)
510
- .bindBlur()
511
1660
  .bindClick(i);
512
1661
  }
513
1662
 
@@ -544,6 +1693,60 @@ if (typeof module === 'object') {
544
1693
  return content;
545
1694
  },
546
1695
 
1696
+ initExtension: function (extension, name) {
1697
+ if (extension.parent) {
1698
+ extension.base = this;
1699
+ }
1700
+ if (typeof extension.init === 'function') {
1701
+ extension.init(this);
1702
+ }
1703
+ if (!extension.name) {
1704
+ extension.name = name;
1705
+ }
1706
+ return extension;
1707
+ },
1708
+
1709
+ initCommands: function () {
1710
+ var buttons = this.options.buttons,
1711
+ extensions = this.options.extensions,
1712
+ ext,
1713
+ name;
1714
+ this.commands = [];
1715
+
1716
+ buttons.forEach(function (buttonName) {
1717
+ if (extensions[buttonName]) {
1718
+ ext = this.initExtension(extensions[buttonName], buttonName);
1719
+ this.commands.push(ext);
1720
+ } else if (buttonName === 'anchor') {
1721
+ ext = this.initExtension(new AnchorExtension(), buttonName);
1722
+ this.commands.push(ext);
1723
+ } else if (ButtonsData.hasOwnProperty(buttonName)) {
1724
+ ext = new DefaultButton(ButtonsData[buttonName], this);
1725
+ this.commands.push(ext);
1726
+ }
1727
+ }.bind(this));
1728
+
1729
+ for (name in extensions) {
1730
+ if (extensions.hasOwnProperty(name) && buttons.indexOf(name) === -1) {
1731
+ ext = this.initExtension(extensions[name], name);
1732
+ }
1733
+ }
1734
+
1735
+ return this;
1736
+ },
1737
+
1738
+ getExtensionByName: function (name) {
1739
+ var extension;
1740
+ if (this.commands && this.commands.length) {
1741
+ this.commands.forEach(function (ext) {
1742
+ if (ext.name === name) {
1743
+ extension = ext;
1744
+ }
1745
+ });
1746
+ }
1747
+ return extension;
1748
+ },
1749
+
547
1750
  /**
548
1751
  * Helper function to call a method with a number of parameters on all registered extensions.
549
1752
  * The function assures that the function exists before calling.
@@ -571,36 +1774,13 @@ if (typeof module === 'object') {
571
1774
  return this;
572
1775
  },
573
1776
 
574
- /**
575
- * Pass current Medium Editor instance to all extensions
576
- * if extension constructor has 'parent' attribute set to 'true'
577
- *
578
- */
579
- passInstance: function () {
580
- var self = this,
581
- ext,
582
- name;
583
-
584
- for (name in self.options.extensions) {
585
- if (self.options.extensions.hasOwnProperty(name)) {
586
- ext = self.options.extensions[name];
587
-
588
- if (ext.parent) {
589
- ext.base = self;
590
- }
591
- }
592
- }
593
-
594
- return self;
595
- },
596
-
597
1777
  bindParagraphCreation: function (index) {
598
1778
  var self = this;
599
1779
  this.on(this.elements[index], 'keypress', function (e) {
600
1780
  var node,
601
1781
  tagName;
602
- if (e.which === 32) {
603
- node = getSelectionStart.call(self);
1782
+ if (e.which === Util.keyCode.SPACE) {
1783
+ node = Selection.getSelectionStart(self.options.ownerDocument);
604
1784
  tagName = node.tagName.toLowerCase();
605
1785
  if (tagName === 'a') {
606
1786
  self.options.ownerDocument.execCommand('unlink', false, null);
@@ -609,22 +1789,22 @@ if (typeof module === 'object') {
609
1789
  });
610
1790
 
611
1791
  this.on(this.elements[index], 'keyup', function (e) {
612
- var node = getSelectionStart.call(self),
1792
+ var node = Selection.getSelectionStart(self.options.ownerDocument),
613
1793
  tagName,
614
1794
  editorElement;
615
1795
 
616
1796
  if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) {
617
1797
  self.options.ownerDocument.execCommand('formatBlock', false, 'p');
618
1798
  }
619
- if (e.which === 13) {
620
- node = getSelectionStart.call(self);
1799
+ if (e.which === Util.keyCode.ENTER) {
1800
+ node = Selection.getSelectionStart(self.options.ownerDocument);
621
1801
  tagName = node.tagName.toLowerCase();
622
- editorElement = self.getSelectionElement();
1802
+ editorElement = Selection.getSelectionElement(self.options.contentWindow);
623
1803
 
624
1804
  if (!(self.options.disableReturn || editorElement.getAttribute('data-disable-return')) &&
625
- tagName !== 'li' && !self.isListItemChild(node)) {
626
- if (!e.shiftKey) {
627
-
1805
+ tagName !== 'li' && !Util.isListItemChild(node)) {
1806
+
1807
+ if (!e.shiftKey && !e.ctrlKey) {
628
1808
  // paragraph creation should not be forced within a header tag
629
1809
  if (!/h\d/.test(tagName)) {
630
1810
  self.options.ownerDocument.execCommand('formatBlock', false, 'p');
@@ -639,32 +1819,15 @@ if (typeof module === 'object') {
639
1819
  return this;
640
1820
  },
641
1821
 
642
- isListItemChild: function (node) {
643
- var parentNode = node.parentNode,
644
- tagName = parentNode.tagName.toLowerCase();
645
- while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') {
646
- if (tagName === 'li') {
647
- return true;
648
- }
649
- parentNode = parentNode.parentNode;
650
- if (parentNode && parentNode.tagName) {
651
- tagName = parentNode.tagName.toLowerCase();
652
- } else {
653
- return false;
654
- }
655
- }
656
- return false;
657
- },
658
-
659
1822
  bindReturn: function (index) {
660
1823
  var self = this;
661
1824
  this.on(this.elements[index], 'keypress', function (e) {
662
- if (e.which === 13) {
1825
+ if (e.which === Util.keyCode.ENTER) {
663
1826
  if (self.options.disableReturn || this.getAttribute('data-disable-return')) {
664
1827
  e.preventDefault();
665
1828
  } else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) {
666
- var node = getSelectionStart.call(self);
667
- if (node && node.textContent === '\n') {
1829
+ var node = Selection.getSelectionStart(self.options.contentWindow);
1830
+ if (node && node.textContent.trim() === '') {
668
1831
  e.preventDefault();
669
1832
  }
670
1833
  }
@@ -676,17 +1839,20 @@ if (typeof module === 'object') {
676
1839
  bindKeydown: function (index) {
677
1840
  var self = this;
678
1841
  this.on(this.elements[index], 'keydown', function (e) {
1842
+ var node, tag, key;
679
1843
 
680
- if (e.which === 9) {
1844
+ if (e.which === Util.keyCode.TAB) {
681
1845
  // Override tab only for pre nodes
682
- var tag = getSelectionStart.call(self).tagName.toLowerCase();
1846
+ node = Selection.getSelectionStart(self.options.ownerDocument);
1847
+ tag = node && node.tagName.toLowerCase();
1848
+
683
1849
  if (tag === 'pre') {
684
1850
  e.preventDefault();
685
1851
  self.options.ownerDocument.execCommand('insertHtml', null, ' ');
686
1852
  }
687
1853
 
688
1854
  // Tab to indent list structures!
689
- if (tag === 'li') {
1855
+ if (tag === 'li' || Util.isListItemChild(node)) {
690
1856
  e.preventDefault();
691
1857
 
692
1858
  // If Shift is down, outdent, otherwise indent
@@ -696,36 +1862,42 @@ if (typeof module === 'object') {
696
1862
  self.options.ownerDocument.execCommand('indent', e);
697
1863
  }
698
1864
  }
699
- } else if (e.which === 8 || e.which === 46 || e.which === 13) {
1865
+ } else if (e.which === Util.keyCode.BACKSPACE || e.which === Util.keyCode.DELETE || e.which === Util.keyCode.ENTER) {
700
1866
 
701
1867
  // Bind keys which can create or destroy a block element: backspace, delete, return
702
1868
  self.onBlockModifier(e);
703
1869
 
1870
+ } else if (e.ctrlKey || e.metaKey) {
1871
+ key = String.fromCharCode(e.which || e.keyCode).toLowerCase();
1872
+ self.commands.forEach(function (extension) {
1873
+ if (extension.options.key && extension.options.key === key) {
1874
+ extension.handleClick(e);
1875
+ }
1876
+ });
704
1877
  }
705
1878
  });
706
1879
  return this;
707
1880
  },
708
1881
 
709
1882
  onBlockModifier: function (e) {
710
- var range, sel, p, node = getSelectionStart.call(this),
1883
+ var range, sel, p, node = Selection.getSelectionStart(this.options.ownerDocument),
711
1884
  tagName = node.tagName.toLowerCase(),
712
1885
  isEmpty = /^(\s+|<br\/?>)?$/i,
713
1886
  isHeader = /h\d/i;
714
1887
 
715
- // backspace or return
716
- if ((e.which === 8 || e.which === 13)
1888
+ if ((e.which === Util.keyCode.BACKSPACE || e.which === Util.keyCode.ENTER)
717
1889
  && node.previousElementSibling
718
1890
  // in a header
719
1891
  && isHeader.test(tagName)
720
1892
  // at the very end of the block
721
- && getCaretOffsets(node).left === 0) {
722
- if (e.which === 8 && isEmpty.test(node.previousElementSibling.innerHTML)) {
1893
+ && Selection.getCaretOffsets(node).left === 0) {
1894
+ if (e.which === Util.keyCode.BACKSPACE && isEmpty.test(node.previousElementSibling.innerHTML)) {
723
1895
  // backspacing the begining of a header into an empty previous element will
724
1896
  // change the tagName of the current node to prevent one
725
1897
  // instead delete previous node and cancel the event.
726
1898
  node.previousElementSibling.parentNode.removeChild(node.previousElementSibling);
727
1899
  e.preventDefault();
728
- } else if (e.which === 13) {
1900
+ } else if (e.which === Util.keyCode.ENTER) {
729
1901
  // hitting return in the begining of a header will create empty header elements before the current one
730
1902
  // instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph
731
1903
  p = this.options.ownerDocument.createElement('p');
@@ -733,9 +1905,7 @@ if (typeof module === 'object') {
733
1905
  node.previousElementSibling.parentNode.insertBefore(p, node);
734
1906
  e.preventDefault();
735
1907
  }
736
-
737
- // delete
738
- } else if (e.which === 46
1908
+ } else if (e.which === Util.keyCode.DELETE
739
1909
  && node.nextElementSibling
740
1910
  && node.previousElementSibling
741
1911
  // not in a header
@@ -757,100 +1927,12 @@ if (typeof module === 'object') {
757
1927
  range.collapse(true);
758
1928
 
759
1929
  sel.removeAllRanges();
760
- sel.addRange(range);
761
-
762
- node.previousElementSibling.parentNode.removeChild(node);
763
-
764
- e.preventDefault();
765
- }
766
-
767
- },
1930
+ sel.addRange(range);
768
1931
 
769
- buttonTemplate: function (btnType) {
770
- var buttonLabels = this.getButtonLabels(this.options.buttonLabels),
771
- buttonTemplates = {
772
- 'bold': '<button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b" aria-label="bold">' + buttonLabels.bold + '</button>',
773
- 'italic': '<button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i" aria-label="italic">' + buttonLabels.italic + '</button>',
774
- 'underline': '<button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u" aria-label="underline">' + buttonLabels.underline + '</button>',
775
- 'strikethrough': '<button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike" aria-label="strike through">' + buttonLabels.strikethrough + '</button>',
776
- 'superscript': '<button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup" aria-label="superscript">' + buttonLabels.superscript + '</button>',
777
- 'subscript': '<button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub" aria-label="subscript">' + buttonLabels.subscript + '</button>',
778
- 'anchor': '<button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a" aria-label="link">' + buttonLabels.anchor + '</button>',
779
- 'image': '<button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img" aria-label="image">' + buttonLabels.image + '</button>',
780
- 'header1': '<button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '" aria-label="h1">' + buttonLabels.header1 + '</button>',
781
- 'header2': '<button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + ' "aria-label="h2">' + buttonLabels.header2 + '</button>',
782
- 'quote': '<button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote" aria-label="blockquote">' + buttonLabels.quote + '</button>',
783
- 'orderedlist': '<button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol" aria-label="ordered list">' + buttonLabels.orderedlist + '</button>',
784
- 'unorderedlist': '<button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul" aria-label="unordered list">' + buttonLabels.unorderedlist + '</button>',
785
- 'pre': '<button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre" aria-label="preformatted text">' + buttonLabels.pre + '</button>',
786
- 'indent': '<button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul" aria-label="indent">' + buttonLabels.indent + '</button>',
787
- 'outdent': '<button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul" aria-label="outdent">' + buttonLabels.outdent + '</button>',
788
- 'justifyCenter': '<button class="medium-editor-action medium-editor-action-justifyCenter" data-action="justifyCenter" data-element="" aria-label="center justify">' + buttonLabels.justifyCenter + '</button>',
789
- 'justifyFull': '<button class="medium-editor-action medium-editor-action-justifyFull" data-action="justifyFull" data-element="" aria-label="full justify">' + buttonLabels.justifyFull + '</button>',
790
- 'justifyLeft': '<button class="medium-editor-action medium-editor-action-justifyLeft" data-action="justifyLeft" data-element="" aria-label="left justify">' + buttonLabels.justifyLeft + '</button>',
791
- 'justifyRight': '<button class="medium-editor-action medium-editor-action-justifyRight" data-action="justifyRight" data-element="" aria-label="right justify">' + buttonLabels.justifyRight + '</button>'
792
- };
793
- return buttonTemplates[btnType] || false;
794
- },
1932
+ node.previousElementSibling.parentNode.removeChild(node);
795
1933
 
796
- // TODO: break method
797
- getButtonLabels: function (buttonLabelType) {
798
- var customButtonLabels,
799
- attrname,
800
- buttonLabels = {
801
- 'bold': '<b>B</b>',
802
- 'italic': '<b><i>I</i></b>',
803
- 'underline': '<b><u>U</u></b>',
804
- 'strikethrough': '<s>A</s>',
805
- 'superscript': '<b>x<sup>1</sup></b>',
806
- 'subscript': '<b>x<sub>1</sub></b>',
807
- 'anchor': '<b>#</b>',
808
- 'image': '<b>image</b>',
809
- 'header1': '<b>H1</b>',
810
- 'header2': '<b>H2</b>',
811
- 'quote': '<b>&ldquo;</b>',
812
- 'orderedlist': '<b>1.</b>',
813
- 'unorderedlist': '<b>&bull;</b>',
814
- 'pre': '<b>0101</b>',
815
- 'indent': '<b>&rarr;</b>',
816
- 'outdent': '<b>&larr;</b>',
817
- 'justifyCenter': '<b>C</b>',
818
- 'justifyFull': '<b>J</b>',
819
- 'justifyLeft': '<b>L</b>',
820
- 'justifyRight': '<b>R</b>'
821
- };
822
- if (buttonLabelType === 'fontawesome') {
823
- customButtonLabels = {
824
- 'bold': '<i class="fa fa-bold"></i>',
825
- 'italic': '<i class="fa fa-italic"></i>',
826
- 'underline': '<i class="fa fa-underline"></i>',
827
- 'strikethrough': '<i class="fa fa-strikethrough"></i>',
828
- 'superscript': '<i class="fa fa-superscript"></i>',
829
- 'subscript': '<i class="fa fa-subscript"></i>',
830
- 'anchor': '<i class="fa fa-link"></i>',
831
- 'image': '<i class="fa fa-picture-o"></i>',
832
- 'quote': '<i class="fa fa-quote-right"></i>',
833
- 'orderedlist': '<i class="fa fa-list-ol"></i>',
834
- 'unorderedlist': '<i class="fa fa-list-ul"></i>',
835
- 'pre': '<i class="fa fa-code fa-lg"></i>',
836
- 'indent': '<i class="fa fa-indent"></i>',
837
- 'outdent': '<i class="fa fa-outdent"></i>',
838
- 'justifyCenter': '<i class="fa fa-align-center"></i>',
839
- 'justifyFull': '<i class="fa fa-align-justify"></i>',
840
- 'justifyLeft': '<i class="fa fa-align-left"></i>',
841
- 'justifyRight': '<i class="fa fa-align-right"></i>'
842
- };
843
- } else if (typeof buttonLabelType === 'object') {
844
- customButtonLabels = buttonLabelType;
845
- }
846
- if (typeof customButtonLabels === 'object') {
847
- for (attrname in customButtonLabels) {
848
- if (customButtonLabels.hasOwnProperty(attrname)) {
849
- buttonLabels[attrname] = customButtonLabels[attrname];
850
- }
851
- }
1934
+ e.preventDefault();
852
1935
  }
853
- return buttonLabels;
854
1936
  },
855
1937
 
856
1938
  initToolbar: function () {
@@ -858,17 +1940,10 @@ if (typeof module === 'object') {
858
1940
  return this;
859
1941
  }
860
1942
  this.toolbar = this.createToolbar();
861
- this.addExtensionForms();
862
1943
  this.keepToolbarAlive = false;
863
1944
  this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions');
864
1945
  this.anchorPreview = this.createAnchorPreview();
865
1946
 
866
- if (!this.options.disableAnchorForm) {
867
- this.anchorForm = this.toolbar.querySelector('.medium-editor-toolbar-form');
868
- this.anchorInput = this.anchorForm.querySelector('input.medium-editor-toolbar-input');
869
- this.anchorTarget = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-target');
870
- this.anchorButton = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-button');
871
- }
872
1947
  return this;
873
1948
  },
874
1949
 
@@ -884,145 +1959,127 @@ if (typeof module === 'object') {
884
1959
  }
885
1960
 
886
1961
  toolbar.appendChild(this.toolbarButtons());
887
- if (!this.options.disableAnchorForm) {
888
- toolbar.appendChild(this.toolbarFormAnchor());
889
- }
1962
+
1963
+ // Add any forms that extensions may have
1964
+ this.commands.forEach(function (extension) {
1965
+ if (extension.hasForm) {
1966
+ toolbar.appendChild(extension.getForm());
1967
+ }
1968
+ });
1969
+
890
1970
  this.options.elementsContainer.appendChild(toolbar);
891
1971
  return toolbar;
892
1972
  },
893
1973
 
894
1974
  //TODO: actionTemplate
895
1975
  toolbarButtons: function () {
896
- var btns = this.options.buttons,
897
- ul = this.options.ownerDocument.createElement('ul'),
1976
+ var ul = this.options.ownerDocument.createElement('ul'),
898
1977
  li,
899
- i,
900
- btn,
901
- ext;
1978
+ btn;
902
1979
 
903
1980
  ul.id = 'medium-editor-toolbar-actions' + this.id;
904
1981
  ul.className = 'medium-editor-toolbar-actions clearfix';
905
1982
 
906
- for (i = 0; i < btns.length; i += 1) {
907
- if (this.options.extensions.hasOwnProperty(btns[i])) {
908
- ext = this.options.extensions[btns[i]];
909
- btn = ext.getButton !== undefined ? ext.getButton(this) : null;
910
- if (ext.hasForm) {
911
- btn.setAttribute('data-form', 'medium-editor-toolbar-form-' + btns[i] + '-' + this.id);
912
- }
913
- } else {
914
- btn = this.buttonTemplate(btns[i]);
915
- }
916
-
917
- if (btn) {
1983
+ this.commands.forEach(function (extension) {
1984
+ if (typeof extension.getButton === 'function') {
1985
+ btn = extension.getButton(this);
918
1986
  li = this.options.ownerDocument.createElement('li');
919
- if (isElement(btn)) {
1987
+ if (Util.isElement(btn)) {
920
1988
  li.appendChild(btn);
921
1989
  } else {
922
1990
  li.innerHTML = btn;
923
1991
  }
924
1992
  ul.appendChild(li);
925
1993
  }
926
- }
1994
+ }.bind(this));
927
1995
 
928
1996
  return ul;
929
1997
  },
930
1998
 
931
- addExtensionForms: function () {
932
- var extensions = this.options.extensions,
933
- ext,
934
- name,
935
- form,
936
- id;
937
-
938
- for (name in extensions) {
939
- if (extensions.hasOwnProperty(name)) {
940
- ext = extensions[name];
941
- if (ext.hasForm) {
942
- form = ext.getForm !== undefined ? ext.getForm() : null;
943
- }
944
- if (form) {
945
- id = 'medium-editor-toolbar-form-' + name + '-' + this.id;
946
- form.className = 'medium-editor-toolbar-form';
947
- form.id = id;
948
- ext.getForm().id = id;
949
- this.toolbar.appendChild(form);
1999
+ bindSelect: function () {
2000
+ var i,
2001
+ blurHelper = function (event) {
2002
+ // Do not close the toolbar when bluring the editable area and clicking into the anchor form
2003
+ if (event &&
2004
+ event.type &&
2005
+ event.type.toLowerCase() === 'blur' &&
2006
+ event.relatedTarget &&
2007
+ Util.isDescendant(this.toolbar, event.relatedTarget)) {
2008
+ return false;
950
2009
  }
951
- }
952
- }
953
- },
954
-
955
- toolbarFormAnchor: function () {
956
- var anchor = this.options.ownerDocument.createElement('div'),
957
- input = this.options.ownerDocument.createElement('input'),
958
- target_label = this.options.ownerDocument.createElement('label'),
959
- target = this.options.ownerDocument.createElement('input'),
960
- button_label = this.options.ownerDocument.createElement('label'),
961
- button = this.options.ownerDocument.createElement('input'),
962
- close = this.options.ownerDocument.createElement('a'),
963
- save = this.options.ownerDocument.createElement('a');
964
-
965
- close.setAttribute('href', '#');
966
- close.className = 'medium-editor-toobar-close';
967
- close.innerHTML = '&times;';
968
-
969
- save.setAttribute('href', '#');
970
- save.className = 'medium-editor-toobar-save';
971
- save.innerHTML = '&#10003;';
2010
+ this.checkSelection();
2011
+ }.bind(this),
2012
+ timeoutHelper = function () {
2013
+ setTimeout(function () {
2014
+ this.checkSelection();
2015
+ }.bind(this), 0);
2016
+ }.bind(this);
972
2017
 
973
- input.setAttribute('type', 'text');
974
- input.className = 'medium-editor-toolbar-input';
975
- input.setAttribute('placeholder', this.options.anchorInputPlaceholder);
976
-
977
-
978
- target.setAttribute('type', 'checkbox');
979
- target.className = 'medium-editor-toolbar-anchor-target';
980
- target_label.innerHTML = this.options.anchorInputCheckboxLabel;
981
- target_label.insertBefore(target, target_label.firstChild);
2018
+ this.on(this.options.ownerDocument.documentElement, 'mouseup', this.checkSelection.bind(this));
982
2019
 
983
- button.setAttribute('type', 'checkbox');
984
- button.className = 'medium-editor-toolbar-anchor-button';
985
- button_label.innerHTML = "Button";
986
- button_label.insertBefore(button, button_label.firstChild);
987
-
988
-
989
- anchor.className = 'medium-editor-toolbar-form';
990
- anchor.id = 'medium-editor-toolbar-form-anchor-' + this.id;
991
- anchor.appendChild(input);
2020
+ for (i = 0; i < this.elements.length; i += 1) {
2021
+ this.on(this.elements[i], 'keyup', this.checkSelection.bind(this));
2022
+ this.on(this.elements[i], 'blur', blurHelper);
2023
+ this.on(this.elements[i], 'click', timeoutHelper);
2024
+ }
992
2025
 
993
- anchor.appendChild(save);
994
- anchor.appendChild(close);
2026
+ return this;
2027
+ },
995
2028
 
996
- if (this.options.anchorTarget) {
997
- anchor.appendChild(target_label);
998
- }
2029
+ bindDragDrop: function () {
2030
+ var self = this, i, className, onDrag, onDrop, element;
999
2031
 
1000
- if (this.options.anchorButton) {
1001
- anchor.appendChild(button_label);
2032
+ if (!self.options.imageDragging) {
2033
+ return this;
1002
2034
  }
1003
2035
 
1004
- return anchor;
1005
- },
2036
+ className = 'medium-editor-dragover';
1006
2037
 
1007
- bindSelect: function () {
1008
- var self = this,
1009
- i;
2038
+ onDrag = function (e) {
2039
+ e.preventDefault();
2040
+ e.dataTransfer.dropEffect = "copy";
1010
2041
 
1011
- this.checkSelectionWrapper = function (e) {
1012
- // Do not close the toolbar when bluring the editable area and clicking into the anchor form
1013
- if (!self.options.disableAnchorForm && e && self.clickingIntoArchorForm(e)) {
1014
- return false;
2042
+ if (e.type === "dragover") {
2043
+ this.classList.add(className);
2044
+ } else {
2045
+ this.classList.remove(className);
1015
2046
  }
1016
-
1017
- self.checkSelection();
1018
2047
  };
1019
2048
 
1020
- this.on(this.options.ownerDocument.documentElement, 'mouseup', this.checkSelectionWrapper);
2049
+ onDrop = function (e) {
2050
+ var files;
2051
+ e.preventDefault();
2052
+ e.stopPropagation();
2053
+ files = Array.prototype.slice.call(e.dataTransfer.files, 0);
2054
+ files.some(function (file) {
2055
+ if (file.type.match("image")) {
2056
+ var fileReader, id;
2057
+ fileReader = new FileReader();
2058
+ fileReader.readAsDataURL(file);
2059
+
2060
+ id = 'medium-img-' + (+new Date());
2061
+ Util.insertHTMLCommand(self.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />');
2062
+
2063
+ fileReader.onload = function () {
2064
+ var img = document.getElementById(id);
2065
+ if (img) {
2066
+ img.removeAttribute('id');
2067
+ img.removeAttribute('class');
2068
+ img.src = fileReader.result;
2069
+ }
2070
+ };
2071
+ }
2072
+ });
2073
+ this.classList.remove(className);
2074
+ };
1021
2075
 
1022
2076
  for (i = 0; i < this.elements.length; i += 1) {
1023
- this.on(this.elements[i], 'keyup', this.checkSelectionWrapper);
1024
- this.on(this.elements[i], 'blur', this.checkSelectionWrapper);
1025
- this.on(this.elements[i], 'click', this.checkSelectionWrapper);
2077
+ element = this.elements[i];
2078
+
2079
+
2080
+ this.on(element, 'dragover', onDrag);
2081
+ this.on(element, 'dragleave', onDrag);
2082
+ this.on(element, 'drop', onDrop);
1026
2083
  }
1027
2084
  return this;
1028
2085
  },
@@ -1045,18 +2102,16 @@ if (typeof module === 'object') {
1045
2102
 
1046
2103
  newSelection = this.options.contentWindow.getSelection();
1047
2104
  if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
1048
- (this.options.allowMultiParagraphSelection === false && this.hasMultiParagraphs()) ||
1049
- this.selectionInContentEditableFalse()) {
1050
-
2105
+ (this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected()) ||
2106
+ Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
1051
2107
  if (!this.options.staticToolbar) {
1052
2108
  this.hideToolbarActions();
1053
- } else if (this.anchorForm && this.anchorForm.style.display === 'block') {
1054
- this.setToolbarButtonStates();
1055
- this.showToolbarActions();
2109
+ } else {
2110
+ this.showAndUpdateToolbar();
1056
2111
  }
1057
2112
 
1058
2113
  } else {
1059
- selectionElement = this.getSelectionElement();
2114
+ selectionElement = Selection.getSelectionElement(this.options.contentWindow);
1060
2115
  if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
1061
2116
  if (!this.options.staticToolbar) {
1062
2117
  this.hideToolbarActions();
@@ -1069,21 +2124,14 @@ if (typeof module === 'object') {
1069
2124
  return this;
1070
2125
  },
1071
2126
 
1072
- clickingIntoArchorForm: function (e) {
1073
- var self = this;
1074
-
1075
- if (e.type && e.type.toLowerCase() === 'blur' && e.relatedTarget && e.relatedTarget === self.anchorInput) {
1076
- return true;
1077
- }
1078
-
1079
- return false;
1080
- },
1081
-
1082
- hasMultiParagraphs: function () {
1083
- var selectionHtml = getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
1084
- hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g);
2127
+ // Checks for existance of multiple block elements in the current selection
2128
+ multipleBlockElementsSelected: function () {
2129
+ /*jslint regexp: true*/
2130
+ var selectionHtml = Selection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
2131
+ hasMultiParagraphs = selectionHtml.match(/<(p|h[1-6]|blockquote)[^>]*>/g);
2132
+ /*jslint regexp: false*/
1085
2133
 
1086
- return (hasMultiParagraphs ? hasMultiParagraphs.length : 0);
2134
+ return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
1087
2135
  },
1088
2136
 
1089
2137
  checkSelectionElement: function (newSelection, selectionElement) {
@@ -1113,7 +2161,7 @@ if (typeof module === 'object') {
1113
2161
  if (this.options.standardizeSelectionStart &&
1114
2162
  this.selectionRange.startContainer.nodeValue &&
1115
2163
  (this.selectionRange.startOffset === this.selectionRange.startContainer.nodeValue.length)) {
1116
- adjacentNode = findAdjacentTextNodeWithContent(this.getSelectionElement(), this.selectionRange.startContainer, this.options.ownerDocument);
2164
+ adjacentNode = Util.findAdjacentTextNodeWithContent(Selection.getSelectionElement(this.options.contentWindow), this.selectionRange.startContainer, this.options.ownerDocument);
1117
2165
  if (adjacentNode) {
1118
2166
  offset = 0;
1119
2167
  while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
@@ -1130,9 +2178,7 @@ if (typeof module === 'object') {
1130
2178
 
1131
2179
  for (i = 0; i < this.elements.length; i += 1) {
1132
2180
  if (this.elements[i] === selectionElement) {
1133
- this.setToolbarButtonStates()
1134
- .setToolbarPosition()
1135
- .showToolbarActions();
2181
+ this.showAndUpdateToolbar();
1136
2182
  return;
1137
2183
  }
1138
2184
  }
@@ -1142,101 +2188,95 @@ if (typeof module === 'object') {
1142
2188
  }
1143
2189
  },
1144
2190
 
1145
- findMatchingSelectionParent: function (testElementFunction) {
1146
- var selection = this.options.contentWindow.getSelection(), range, current;
1147
-
1148
- if (selection.rangeCount === 0) {
1149
- return false;
1150
- }
1151
-
1152
- range = selection.getRangeAt(0);
1153
- current = range.commonAncestorContainer;
1154
-
1155
- do {
1156
- if (current.nodeType === 1) {
1157
- if (testElementFunction(current)) {
1158
- return current;
1159
- }
1160
- // do not traverse upwards past the nearest containing editor
1161
- if (current.getAttribute('data-medium-element')) {
1162
- return false;
1163
- }
1164
- }
1165
-
1166
- current = current.parentNode;
1167
- } while (current);
1168
-
1169
- return false;
1170
- },
1171
-
1172
- getSelectionElement: function () {
1173
- return this.findMatchingSelectionParent(function (el) {
1174
- return el.getAttribute('data-medium-element');
1175
- });
1176
- },
1177
-
1178
- selectionInContentEditableFalse: function () {
1179
- return this.findMatchingSelectionParent(function (el) {
1180
- return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
1181
- });
2191
+ showAndUpdateToolbar: function () {
2192
+ this.setToolbarButtonStates()
2193
+ .setToolbarPosition()
2194
+ .showToolbarDefaultActions();
1182
2195
  },
1183
2196
 
1184
2197
  setToolbarPosition: function () {
1185
2198
  // document.documentElement for IE 9
1186
2199
  var scrollTop = (this.options.ownerDocument.documentElement && this.options.ownerDocument.documentElement.scrollTop) || this.options.ownerDocument.body.scrollTop,
1187
- container = this.elements[0],
1188
- containerRect = container.getBoundingClientRect(),
1189
- containerTop = containerRect.top + scrollTop,
1190
- buttonHeight = 50,
1191
2200
  selection = this.options.contentWindow.getSelection(),
2201
+ windowWidth = this.options.contentWindow.innerWidth,
2202
+ container = Selection.getSelectionElement(this.options.contentWindow),
2203
+ buttonHeight = 50,
2204
+ toolbarWidth,
2205
+ toolbarHeight,
2206
+ halfOffsetWidth,
2207
+ defaultLeft,
2208
+ containerRect,
2209
+ containerTop,
2210
+ containerCenter,
1192
2211
  range,
1193
2212
  boundary,
1194
2213
  middleBoundary,
1195
- defaultLeft = (this.options.diffLeft) - (this.toolbar.offsetWidth / 2),
1196
- halfOffsetWidth = this.toolbar.offsetWidth / 2,
1197
- containerCenter = (containerRect.left + (containerRect.width / 2));
2214
+ targetLeft;
1198
2215
 
1199
- if (selection.focusNode === null) {
2216
+ // If there isn't a valid selection, bail
2217
+ if (!container || !this.options.contentWindow.getSelection().focusNode) {
1200
2218
  return this;
1201
2219
  }
1202
2220
 
1203
- this.showToolbar();
2221
+ // If the container isn't part of this medium-editor instance, bail
2222
+ if (this.elements.indexOf(container) === -1) {
2223
+ return this;
2224
+ }
2225
+
2226
+ // Calculate container dimensions
2227
+ containerRect = container.getBoundingClientRect();
2228
+ containerTop = containerRect.top + scrollTop;
2229
+ containerCenter = (containerRect.left + (containerRect.width / 2));
2230
+
2231
+ // position the toolbar at left 0, so we can get the real width of the toolbar
2232
+ this.toolbar.style.left = '0';
2233
+ toolbarWidth = this.toolbar.offsetWidth;
2234
+ toolbarHeight = this.toolbar.offsetHeight;
2235
+ halfOffsetWidth = toolbarWidth / 2;
2236
+ defaultLeft = this.options.diffLeft - halfOffsetWidth;
1204
2237
 
1205
2238
  if (this.options.staticToolbar) {
2239
+ this.showToolbar();
1206
2240
 
1207
2241
  if (this.options.stickyToolbar) {
1208
-
1209
2242
  // If it's beyond the height of the editor, position it at the bottom of the editor
1210
- if (scrollTop > (containerTop + this.elements[0].offsetHeight - this.toolbar.offsetHeight)) {
1211
- this.toolbar.style.top = (containerTop + this.elements[0].offsetHeight) + 'px';
2243
+ if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight)) {
2244
+ this.toolbar.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
2245
+ this.toolbar.classList.remove('sticky-toolbar');
1212
2246
 
1213
2247
  // Stick the toolbar to the top of the window
1214
- } else if (scrollTop > (containerTop - this.toolbar.offsetHeight)) {
2248
+ } else if (scrollTop > (containerTop - toolbarHeight)) {
1215
2249
  this.toolbar.classList.add('sticky-toolbar');
1216
2250
  this.toolbar.style.top = "0px";
2251
+
1217
2252
  // Normal static toolbar position
1218
2253
  } else {
1219
2254
  this.toolbar.classList.remove('sticky-toolbar');
1220
- this.toolbar.style.top = containerTop - this.toolbar.offsetHeight + "px";
2255
+ this.toolbar.style.top = containerTop - toolbarHeight + "px";
1221
2256
  }
1222
-
1223
2257
  } else {
1224
- this.toolbar.style.top = containerTop - this.toolbar.offsetHeight + "px";
2258
+ this.toolbar.style.top = containerTop - toolbarHeight + "px";
1225
2259
  }
1226
2260
 
1227
- if (this.options.toolbarAlign) {
1228
- if (this.options.toolbarAlign === 'left') {
1229
- this.toolbar.style.left = containerRect.left + "px";
1230
- } else if (this.options.toolbarAlign === 'center') {
1231
- this.toolbar.style.left = (containerCenter - halfOffsetWidth) + "px";
1232
- } else {
1233
- this.toolbar.style.left = (containerRect.right - this.toolbar.offsetWidth) + "px";
1234
- }
1235
- } else {
1236
- this.toolbar.style.left = (containerCenter - halfOffsetWidth) + "px";
2261
+ if (this.options.toolbarAlign === 'left') {
2262
+ targetLeft = containerRect.left;
2263
+ } else if (this.options.toolbarAlign === 'center') {
2264
+ targetLeft = containerCenter - halfOffsetWidth;
2265
+ } else if (this.options.toolbarAlign === 'right') {
2266
+ targetLeft = containerRect.right - toolbarWidth;
2267
+ }
2268
+
2269
+ if (targetLeft < 0) {
2270
+ targetLeft = 0;
2271
+ } else if ((targetLeft + toolbarWidth) > windowWidth) {
2272
+ targetLeft = windowWidth - toolbarWidth;
1237
2273
  }
1238
2274
 
2275
+ this.toolbar.style.left = targetLeft + 'px';
2276
+
1239
2277
  } else if (!selection.isCollapsed) {
2278
+ this.showToolbar();
2279
+
1240
2280
  range = selection.getRangeAt(0);
1241
2281
  boundary = range.getBoundingClientRect();
1242
2282
  middleBoundary = (boundary.left + boundary.right) / 2;
@@ -1244,16 +2284,16 @@ if (typeof module === 'object') {
1244
2284
  if (boundary.top < buttonHeight) {
1245
2285
  this.toolbar.classList.add('medium-toolbar-arrow-over');
1246
2286
  this.toolbar.classList.remove('medium-toolbar-arrow-under');
1247
- this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - this.toolbar.offsetHeight + 'px';
2287
+ this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
1248
2288
  } else {
1249
2289
  this.toolbar.classList.add('medium-toolbar-arrow-under');
1250
2290
  this.toolbar.classList.remove('medium-toolbar-arrow-over');
1251
- this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - this.toolbar.offsetHeight + 'px';
2291
+ this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
1252
2292
  }
1253
2293
  if (middleBoundary < halfOffsetWidth) {
1254
2294
  this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px';
1255
- } else if ((this.options.contentWindow.innerWidth - middleBoundary) < halfOffsetWidth) {
1256
- this.toolbar.style.left = this.options.contentWindow.innerWidth + defaultLeft - halfOffsetWidth + 'px';
2295
+ } else if ((windowWidth - middleBoundary) < halfOffsetWidth) {
2296
+ this.toolbar.style.left = windowWidth + defaultLeft - halfOffsetWidth + 'px';
1257
2297
  } else {
1258
2298
  this.toolbar.style.left = defaultLeft + middleBoundary + 'px';
1259
2299
  }
@@ -1265,21 +2305,57 @@ if (typeof module === 'object') {
1265
2305
  },
1266
2306
 
1267
2307
  setToolbarButtonStates: function () {
1268
- var buttons = this.toolbarActions.querySelectorAll('button'),
1269
- i;
1270
- for (i = 0; i < buttons.length; i += 1) {
1271
- buttons[i].classList.remove(this.options.activeButtonClass);
1272
- }
2308
+ this.commands.forEach(function (extension) {
2309
+ if (typeof extension.isActive === 'function') {
2310
+ extension.setInactive();
2311
+ }
2312
+ }.bind(this));
1273
2313
  this.checkActiveButtons();
1274
2314
  return this;
1275
2315
  },
1276
2316
 
1277
2317
  checkActiveButtons: function () {
1278
2318
  var elements = Array.prototype.slice.call(this.elements),
1279
- parentNode = this.getSelectedParentElement();
1280
- while (parentNode.tagName !== undefined && this.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
1281
- this.activateButton(parentNode.tagName.toLowerCase());
1282
- this.callExtensions('checkState', parentNode);
2319
+ manualStateChecks = [],
2320
+ queryState = null,
2321
+ parentNode,
2322
+ checkExtension = function (extension) {
2323
+ if (typeof extension.checkState === 'function') {
2324
+ extension.checkState(parentNode);
2325
+ } else if (typeof extension.isActive === 'function' &&
2326
+ typeof extension.isAlreadyApplied === 'function') {
2327
+ if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
2328
+ extension.setActive();
2329
+ }
2330
+ }
2331
+ };
2332
+
2333
+ if (!this.selectionRange) {
2334
+ return;
2335
+ }
2336
+ parentNode = Selection.getSelectedParentElement(this.selectionRange);
2337
+
2338
+ // Loop through all commands
2339
+ this.commands.forEach(function (command) {
2340
+ // For those commands where we can use document.queryCommandState(), do so
2341
+ if (typeof command.queryCommandState === 'function') {
2342
+ queryState = command.queryCommandState();
2343
+ // If queryCommandState returns a valid value, we can trust the browser
2344
+ // and don't need to do our manual checks
2345
+ if (queryState !== null) {
2346
+ if (queryState) {
2347
+ command.setActive();
2348
+ }
2349
+ return;
2350
+ }
2351
+ }
2352
+ // We can't use queryCommandState for this command, so add to manualStateChecks
2353
+ manualStateChecks.push(command);
2354
+ });
2355
+
2356
+ // Climb up the DOM and do manual checks for whether a certain command is currently enabled for this node
2357
+ while (parentNode.tagName !== undefined && Util.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
2358
+ manualStateChecks.forEach(checkExtension.bind(this));
1283
2359
 
1284
2360
  // we can abort the search upwards if we leave the contentEditable element
1285
2361
  if (elements.indexOf(parentNode) !== -1) {
@@ -1289,45 +2365,8 @@ if (typeof module === 'object') {
1289
2365
  }
1290
2366
  },
1291
2367
 
1292
- activateButton: function (tag) {
1293
- var el = this.toolbar.querySelector('[data-element="' + tag + '"]');
1294
- if (el !== null && el.className.indexOf(this.options.activeButtonClass) === -1) {
1295
- el.className += ' ' + this.options.activeButtonClass;
1296
- }
1297
- },
1298
-
1299
- bindButtons: function () {
1300
- var buttons = this.toolbar.querySelectorAll('button'),
1301
- i,
1302
- self = this,
1303
- triggerAction = function (e) {
1304
- e.preventDefault();
1305
- e.stopPropagation();
1306
- if (self.selection === undefined) {
1307
- self.checkSelection();
1308
- }
1309
- if (this.className.indexOf(self.options.activeButtonClass) > -1) {
1310
- this.classList.remove(self.options.activeButtonClass);
1311
- } else {
1312
- this.className += ' ' + self.options.activeButtonClass;
1313
- }
1314
- if (this.hasAttribute('data-action')) {
1315
- self.execAction(this.getAttribute('data-action'), e);
1316
- }
1317
- // Allows extension buttons to show a form
1318
- // TO DO: Improve this
1319
- if (this.hasAttribute('data-form')) {
1320
- self.showForm(this.getAttribute('data-form'), e);
1321
- }
1322
- };
1323
- for (i = 0; i < buttons.length; i += 1) {
1324
- this.on(buttons[i], 'click', triggerAction);
1325
- }
1326
- this.setFirstAndLastItems(buttons);
1327
- return this;
1328
- },
1329
-
1330
- setFirstAndLastItems: function (buttons) {
2368
+ setFirstAndLastButtons: function () {
2369
+ var buttons = this.toolbar.querySelectorAll('button');
1331
2370
  if (buttons.length > 0) {
1332
2371
  buttons[0].className += ' ' + this.options.firstButtonClass;
1333
2372
  buttons[buttons.length - 1].className += ' ' + this.options.lastButtonClass;
@@ -1335,82 +2374,84 @@ if (typeof module === 'object') {
1335
2374
  return this;
1336
2375
  },
1337
2376
 
1338
- execAction: function (action, e) {
1339
- if (action.indexOf('append-') > -1) {
1340
- this.execFormatBlock(action.replace('append-', ''));
1341
- this.setToolbarPosition();
1342
- this.setToolbarButtonStates();
1343
- } else if (action === 'anchor') {
1344
- if (!this.options.disableAnchorForm) {
1345
- this.triggerAnchorAction(e);
1346
- }
1347
- } else if (action === 'image') {
1348
- this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
1349
- } else {
1350
- this.options.ownerDocument.execCommand(action, false, null);
1351
- this.setToolbarPosition();
2377
+ // Wrapper around document.queryCommandState for checking whether an action has already
2378
+ // been applied to the current selection
2379
+ queryCommandState: function (action) {
2380
+ var fullAction = /^full-(.+)$/gi,
2381
+ match,
2382
+ queryState = null;
2383
+
2384
+ // Actions starting with 'full-' need to be modified since this is a medium-editor concept
2385
+ match = fullAction.exec(action);
2386
+ if (match) {
2387
+ action = match[1];
1352
2388
  }
1353
- },
1354
2389
 
1355
- // Method to show an extension's form
1356
- // TO DO: Improve this
1357
- showForm: function (formId, e) {
1358
- this.toolbarActions.style.display = 'none';
1359
- this.saveSelection();
1360
- var form = document.getElementById(formId);
1361
- form.style.display = 'block';
1362
- this.setToolbarPosition();
1363
- this.keepToolbarAlive = true;
1364
- },
2390
+ try {
2391
+ queryState = this.options.ownerDocument.queryCommandState(action);
2392
+ } catch (exc) {
2393
+ queryState = null;
2394
+ }
1365
2395
 
1366
- // Method to show an extension's form
1367
- // TO DO: Improve this
1368
- hideForm: function (form, e) {
1369
- var el = document.getElementById(form.id);
1370
- el.style.display = 'none';
1371
- this.showToolbarActions();
1372
- this.setToolbarPosition();
1373
- restoreSelection.call(this, this.savedSelection);
2396
+ return queryState;
1374
2397
  },
1375
2398
 
1376
- // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
1377
- rangeSelectsSingleNode: function (range) {
1378
- var startNode = range.startContainer;
1379
- return startNode === range.endContainer &&
1380
- startNode.hasChildNodes() &&
1381
- range.endOffset === range.startOffset + 1;
1382
- },
2399
+ execAction: function (action, opts) {
2400
+ /*jslint regexp: true*/
2401
+ var fullAction = /^full-(.+)$/gi,
2402
+ match,
2403
+ result;
2404
+ /*jslint regexp: false*/
1383
2405
 
1384
- getSelectedParentElement: function () {
1385
- var selectedParentElement = null,
1386
- range = this.selectionRange;
1387
- if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) {
1388
- selectedParentElement = range.startContainer.childNodes[range.startOffset];
1389
- } else if (range.startContainer.nodeType === 3) {
1390
- selectedParentElement = range.startContainer.parentNode;
2406
+ // Actions starting with 'full-' should be applied to to the entire contents of the editable element
2407
+ // (ie full-bold, full-append-pre, etc.)
2408
+ match = fullAction.exec(action);
2409
+ if (match) {
2410
+ // Store the current selection to be restored after applying the action
2411
+ this.saveSelection();
2412
+ // Select all of the contents before calling the action
2413
+ this.selectAllContents();
2414
+ result = this.execActionInternal(match[1], opts);
2415
+ // Restore the previous selection
2416
+ this.restoreSelection();
1391
2417
  } else {
1392
- selectedParentElement = range.startContainer;
2418
+ result = this.execActionInternal(action, opts);
1393
2419
  }
1394
- return selectedParentElement;
2420
+
2421
+ this.checkSelection();
2422
+ return result;
1395
2423
  },
1396
2424
 
1397
- triggerAnchorAction: function () {
1398
- var selectedParentElement = this.getSelectedParentElement();
1399
- if (selectedParentElement.tagName &&
1400
- selectedParentElement.tagName.toLowerCase() === 'a') {
1401
- this.options.ownerDocument.execCommand('unlink', false, null);
1402
- } else if (this.anchorForm) {
1403
- if (this.anchorForm.style.display === 'block') {
1404
- this.showToolbarActions();
1405
- } else {
1406
- this.showAnchorForm();
1407
- }
2425
+ execActionInternal: function (action, opts) {
2426
+ /*jslint regexp: true*/
2427
+ var appendAction = /^append-(.+)$/gi,
2428
+ match;
2429
+ /*jslint regexp: false*/
2430
+
2431
+ // Actions starting with 'append-' should attempt to format a block of text ('formatBlock') using a specific
2432
+ // type of block element (ie append-blockquote, append-h1, append-pre, etc.)
2433
+ match = appendAction.exec(action);
2434
+ if (match) {
2435
+ return this.execFormatBlock(match[1]);
1408
2436
  }
1409
- return this;
2437
+
2438
+ if (action === 'createLink') {
2439
+ return this.createLink(opts);
2440
+ }
2441
+
2442
+ if (action === 'image') {
2443
+ return this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
2444
+ }
2445
+
2446
+ return this.options.ownerDocument.execCommand(action, false, null);
2447
+ },
2448
+
2449
+ getSelectedParentElement: function () {
2450
+ return Selection.getSelectedParentElement();
1410
2451
  },
1411
2452
 
1412
2453
  execFormatBlock: function (el) {
1413
- var selectionData = this.getSelectionData(this.selection.anchorNode);
2454
+ var selectionData = Selection.getSelectionData(this.selection.anchorNode);
1414
2455
  // FF handles blockquote differently on formatBlock
1415
2456
  // allowing nesting, we need to use outdent
1416
2457
  // https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
@@ -1425,7 +2466,7 @@ if (typeof module === 'object') {
1425
2466
  // blockquote needs to be called as indent
1426
2467
  // http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie
1427
2468
  // http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777
1428
- if (this.isIE) {
2469
+ if (Util.isIE) {
1429
2470
  if (el === 'blockquote') {
1430
2471
  return this.options.ownerDocument.execCommand('indent', false, el);
1431
2472
  }
@@ -1434,32 +2475,45 @@ if (typeof module === 'object') {
1434
2475
  return this.options.ownerDocument.execCommand('formatBlock', false, el);
1435
2476
  },
1436
2477
 
1437
- getSelectionData: function (el) {
1438
- var tagName;
2478
+ isToolbarDefaultActionsShown: function () {
2479
+ return !!this.toolbarActions && this.toolbarActions.style.display === 'block';
2480
+ },
1439
2481
 
1440
- if (el && el.tagName) {
1441
- tagName = el.tagName.toLowerCase();
2482
+ hideToolbarDefaultActions: function () {
2483
+ if (this.toolbarActions && this.isToolbarDefaultActionsShown()) {
2484
+ this.commands.forEach(function (extension) {
2485
+ if (extension.onHide && typeof extension.onHide === 'function') {
2486
+ extension.onHide();
2487
+ }
2488
+ });
2489
+ this.toolbarActions.style.display = 'none';
1442
2490
  }
2491
+ },
1443
2492
 
1444
- while (el && this.parentElements.indexOf(tagName) === -1) {
1445
- el = el.parentNode;
1446
- if (el && el.tagName) {
1447
- tagName = el.tagName.toLowerCase();
1448
- }
2493
+ showToolbarDefaultActions: function () {
2494
+ this.hideExtensionForms();
2495
+
2496
+ if (this.toolbarActions && !this.isToolbarDefaultActionsShown()) {
2497
+ this.toolbarActions.style.display = 'block';
1449
2498
  }
1450
2499
 
1451
- return {
1452
- el: el,
1453
- tagName: tagName
1454
- };
2500
+ this.keepToolbarAlive = false;
2501
+ // Using setTimeout + options.delay because:
2502
+ // We will actually be displaying the toolbar, which should be controlled by options.delay
2503
+ this.delay(function () {
2504
+ this.showToolbar();
2505
+ }.bind(this));
2506
+
2507
+ return this;
1455
2508
  },
1456
2509
 
1457
- getFirstChild: function (el) {
1458
- var firstChild = el.firstChild;
1459
- while (firstChild !== null && firstChild.nodeType !== 1) {
1460
- firstChild = firstChild.nextSibling;
1461
- }
1462
- return firstChild;
2510
+ hideExtensionForms: function () {
2511
+ // Hide all extension forms
2512
+ this.commands.forEach(function (extension) {
2513
+ if (extension.hasForm && extension.isDisplayed()) {
2514
+ extension.hideForm();
2515
+ }
2516
+ });
1463
2517
  },
1464
2518
 
1465
2519
  isToolbarShown: function () {
@@ -1469,8 +2523,8 @@ if (typeof module === 'object') {
1469
2523
  showToolbar: function () {
1470
2524
  if (this.toolbar && !this.isToolbarShown()) {
1471
2525
  this.toolbar.classList.add('medium-editor-toolbar-active');
1472
- if (this.onShowToolbar) {
1473
- this.onShowToolbar();
2526
+ if (typeof this.options.onShowToolbar === 'function') {
2527
+ this.options.onShowToolbar();
1474
2528
  }
1475
2529
  }
1476
2530
  },
@@ -1478,134 +2532,127 @@ if (typeof module === 'object') {
1478
2532
  hideToolbar: function () {
1479
2533
  if (this.isToolbarShown()) {
1480
2534
  this.toolbar.classList.remove('medium-editor-toolbar-active');
1481
- if (this.onHideToolbar) {
1482
- this.onHideToolbar();
2535
+ if (typeof this.options.onHideToolbar === 'function') {
2536
+ this.options.onHideToolbar();
1483
2537
  }
1484
2538
  }
1485
2539
  },
1486
2540
 
1487
2541
  hideToolbarActions: function () {
2542
+ this.commands.forEach(function (extension) {
2543
+ if (extension.onHide && typeof extension.onHide === 'function') {
2544
+ extension.onHide();
2545
+ }
2546
+ });
1488
2547
  this.keepToolbarAlive = false;
1489
2548
  this.hideToolbar();
1490
2549
  },
1491
2550
 
1492
- showToolbarActions: function () {
1493
- var self = this;
1494
- if (this.anchorForm) {
1495
- this.anchorForm.style.display = 'none';
1496
- }
1497
- this.toolbarActions.style.display = 'block';
1498
- this.keepToolbarAlive = false;
1499
- // Using setTimeout + options.delay because:
1500
- // We will actually be displaying the toolbar, which should be controlled by options.delay
1501
- this.delay(function () {
1502
- self.showToolbar();
1503
- });
1504
- },
1505
-
1506
- saveSelection: function () {
1507
- this.savedSelection = saveSelection.call(this);
1508
- },
1509
-
1510
- restoreSelection: function () {
1511
- restoreSelection.call(this, this.savedSelection);
1512
- },
1513
-
1514
- showAnchorForm: function (link_value) {
1515
- if (!this.anchorForm) {
1516
- return;
1517
- }
1518
-
1519
- this.toolbarActions.style.display = 'none';
1520
- this.saveSelection();
1521
- this.anchorForm.style.display = 'block';
1522
- this.setToolbarPosition();
1523
- this.keepToolbarAlive = true;
1524
- this.anchorInput.focus();
1525
- this.anchorInput.value = link_value || '';
1526
- },
2551
+ selectAllContents: function () {
2552
+ var range = this.options.ownerDocument.createRange(),
2553
+ sel = this.options.contentWindow.getSelection(),
2554
+ currNode = Selection.getSelectionElement(this.options.contentWindow);
1527
2555
 
1528
- bindAnchorForm: function () {
1529
- if (!this.anchorForm) {
1530
- return this;
1531
- }
2556
+ if (currNode) {
2557
+ // Move to the lowest descendant node that still selects all of the contents
2558
+ while (currNode.children.length === 1) {
2559
+ currNode = currNode.children[0];
2560
+ }
1532
2561
 
1533
- var linkCancel = this.anchorForm.querySelector('a.medium-editor-toobar-close'),
1534
- linkSave = this.anchorForm.querySelector('a.medium-editor-toobar-save'),
1535
- self = this;
2562
+ range.selectNodeContents(currNode);
2563
+ sel.removeAllRanges();
2564
+ sel.addRange(range);
2565
+ }
2566
+ },
1536
2567
 
1537
- this.on(this.anchorForm, 'click', function (e) {
1538
- e.stopPropagation();
1539
- self.keepToolbarAlive = true;
1540
- });
2568
+ // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
2569
+ // Tim Down
2570
+ // TODO: move to selection.js and clean up old methods there
2571
+ saveSelection: function () {
2572
+ this.selectionState = null;
1541
2573
 
1542
- this.on(this.anchorInput, 'keyup', function (e) {
1543
- var button = null,
1544
- target;
2574
+ var selection = this.options.contentWindow.getSelection(),
2575
+ range,
2576
+ preSelectionRange,
2577
+ start,
2578
+ editableElementIndex = -1;
1545
2579
 
1546
- if (false) { //(e.keyCode === 13) {
1547
- e.preventDefault();
1548
- if (self.options.anchorTarget && self.anchorTarget.checked) {
1549
- target = "_blank";
1550
- } else {
1551
- target = "_self";
1552
- }
2580
+ if (selection.rangeCount > 0) {
2581
+ range = selection.getRangeAt(0);
2582
+ preSelectionRange = range.cloneRange();
1553
2583
 
1554
- if (self.options.anchorButton && self.anchorButton.checked) {
1555
- button = self.options.anchorButtonClass;
2584
+ // Find element current selection is inside
2585
+ this.elements.forEach(function (el, index) {
2586
+ if (el === range.startContainer || Util.isDescendant(el, range.startContainer)) {
2587
+ editableElementIndex = index;
2588
+ return false;
1556
2589
  }
2590
+ });
1557
2591
 
1558
- self.createLink(this, target, button);
1559
- } else if (e.keyCode === 27) {
1560
- e.preventDefault();
1561
- self.showToolbarActions();
1562
- restoreSelection.call(self, self.savedSelection);
1563
- }
1564
- });
1565
-
1566
- this.on(linkSave, 'click', function (e) {
1567
- var button = null,
1568
- target;
1569
- e.preventDefault();
1570
- if (self.options.anchorTarget && self.anchorTarget.checked) {
1571
- target = "_blank";
1572
- } else {
1573
- target = "_self";
1574
- }
2592
+ if (editableElementIndex > -1) {
2593
+ preSelectionRange.selectNodeContents(this.elements[editableElementIndex]);
2594
+ preSelectionRange.setEnd(range.startContainer, range.startOffset);
2595
+ start = preSelectionRange.toString().length;
1575
2596
 
1576
- if (self.options.anchorButton && self.anchorButton.checked) {
1577
- button = self.options.anchorButtonClass;
2597
+ this.selectionState = {
2598
+ start: start,
2599
+ end: start + range.toString().length,
2600
+ editableElementIndex: editableElementIndex
2601
+ };
1578
2602
  }
2603
+ }
2604
+ },
1579
2605
 
1580
- self.createLink(self.anchorInput, target, button);
1581
- }, true);
1582
-
1583
- this.on(this.anchorInput, 'click', function (e) {
1584
- // make sure not to hide form when cliking into the input
1585
- e.stopPropagation();
1586
- self.keepToolbarAlive = true;
1587
- });
2606
+ // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
2607
+ // Tim Down
2608
+ // TODO: move to selection.js and clean up old methods there
2609
+ restoreSelection: function () {
2610
+ if (!this.selectionState) {
2611
+ return;
2612
+ }
1588
2613
 
1589
- // Hide the anchor form when focusing outside of it.
1590
- this.on(this.options.ownerDocument.body, 'click', function (e) {
1591
- if (e.target !== self.anchorForm && !isDescendant(self.anchorForm, e.target) && !isDescendant(self.toolbarActions, e.target)) {
1592
- self.keepToolbarAlive = false;
1593
- self.checkSelection();
2614
+ var editableElement = this.elements[this.selectionState.editableElementIndex],
2615
+ charIndex = 0,
2616
+ range = this.options.ownerDocument.createRange(),
2617
+ nodeStack = [editableElement],
2618
+ node,
2619
+ foundStart = false,
2620
+ stop = false,
2621
+ i,
2622
+ sel,
2623
+ nextCharIndex;
2624
+
2625
+ range.setStart(editableElement, 0);
2626
+ range.collapse(true);
2627
+
2628
+ node = nodeStack.pop();
2629
+ while (!stop && node) {
2630
+ if (node.nodeType === 3) {
2631
+ nextCharIndex = charIndex + node.length;
2632
+ if (!foundStart && this.selectionState.start >= charIndex && this.selectionState.start <= nextCharIndex) {
2633
+ range.setStart(node, this.selectionState.start - charIndex);
2634
+ foundStart = true;
2635
+ }
2636
+ if (foundStart && this.selectionState.end >= charIndex && this.selectionState.end <= nextCharIndex) {
2637
+ range.setEnd(node, this.selectionState.end - charIndex);
2638
+ stop = true;
2639
+ }
2640
+ charIndex = nextCharIndex;
2641
+ } else {
2642
+ i = node.childNodes.length - 1;
2643
+ while (i >= 0) {
2644
+ nodeStack.push(node.childNodes[i]);
2645
+ i -= 1;
2646
+ }
1594
2647
  }
1595
- }, true);
1596
- this.on(this.options.ownerDocument.body, 'focus', function (e) {
1597
- if (e.target !== self.anchorForm && !isDescendant(self.anchorForm, e.target) && !isDescendant(self.toolbarActions, e.target)) {
1598
- self.keepToolbarAlive = false;
1599
- self.checkSelection();
2648
+ if (!stop) {
2649
+ node = nodeStack.pop();
1600
2650
  }
1601
- }, true);
2651
+ }
1602
2652
 
1603
- this.on(linkCancel, 'click', function (e) {
1604
- e.preventDefault();
1605
- self.showToolbarActions();
1606
- restoreSelection.call(self, self.savedSelection);
1607
- });
1608
- return this;
2653
+ sel = this.options.contentWindow.getSelection();
2654
+ sel.removeAllRanges();
2655
+ sel.addRange(range);
1609
2656
  },
1610
2657
 
1611
2658
  hideAnchorPreview: function () {
@@ -1626,7 +2673,7 @@ if (typeof module === 'object') {
1626
2673
  halfOffsetWidth,
1627
2674
  defaultLeft;
1628
2675
 
1629
- self.anchorPreview.querySelector('i').textContent = anchorEl.href;
2676
+ self.anchorPreview.querySelector('i').textContent = anchorEl.attributes.href.value;
1630
2677
  halfOffsetWidth = self.anchorPreview.offsetWidth / 2;
1631
2678
  defaultLeft = self.options.diffLeft - halfOffsetWidth;
1632
2679
 
@@ -1711,25 +2758,26 @@ if (typeof module === 'object') {
1711
2758
  '</div>';
1712
2759
  },
1713
2760
 
1714
- anchorPreviewClickHandler: function (e) {
1715
- if (!this.options.disableAnchorForm && this.activeAnchor) {
2761
+ anchorPreviewClickHandler: function (event) {
2762
+ var range,
2763
+ sel,
2764
+ anchorExtension = this.getExtensionByName('anchor');
1716
2765
 
1717
- var self = this,
1718
- range = this.options.ownerDocument.createRange(),
1719
- sel = this.options.contentWindow.getSelection();
2766
+ if (anchorExtension && this.activeAnchor) {
2767
+ range = this.options.ownerDocument.createRange();
2768
+ range.selectNodeContents(this.activeAnchor);
1720
2769
 
1721
- range.selectNodeContents(self.activeAnchor);
2770
+ sel = this.options.contentWindow.getSelection();
1722
2771
  sel.removeAllRanges();
1723
2772
  sel.addRange(range);
1724
2773
  // Using setTimeout + options.delay because:
1725
- // We may actually be displaying the anchor preview, which should be controlled by options.delay
2774
+ // We may actually be displaying the anchor form, which should be controlled by options.delay
1726
2775
  this.delay(function () {
1727
- if (self.activeAnchor) {
1728
- self.showAnchorForm(self.activeAnchor.href);
2776
+ if (this.activeAnchor) {
2777
+ anchorExtension.showForm(this.activeAnchor.attributes.href.value);
1729
2778
  }
1730
- self.keepToolbarAlive = false;
1731
- });
1732
-
2779
+ this.keepToolbarAlive = false;
2780
+ }.bind(this));
1733
2781
  }
1734
2782
 
1735
2783
  this.hideAnchorPreview();
@@ -1782,27 +2830,33 @@ if (typeof module === 'object') {
1782
2830
  return this;
1783
2831
  },
1784
2832
 
1785
- checkLinkFormat: function (value) {
1786
- var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/;
1787
- return (re.test(value) ? '' : 'http://') + value;
1788
- },
2833
+ createLink: function (opts) {
2834
+ var customEvent,
2835
+ i;
1789
2836
 
1790
- setTargetBlank: function (el) {
1791
- var i;
1792
- el = el || getSelectionStart.call(this);
1793
- if (el.tagName.toLowerCase() === 'a') {
1794
- el.target = '_blank';
1795
- } else {
1796
- el = el.getElementsByTagName('a');
2837
+ if (opts.url && opts.url.trim().length > 0) {
2838
+ this.options.ownerDocument.execCommand('createLink', false, opts.url);
1797
2839
 
1798
- for (i = 0; i < el.length; i += 1) {
1799
- el[i].target = '_blank';
2840
+ if (this.options.targetBlank || opts.target === '_blank') {
2841
+ Util.setTargetBlank(Selection.getSelectionStart(this.options.ownerDocument));
2842
+ }
2843
+
2844
+ if (opts.buttonClass) {
2845
+ this.setButtonClass(opts.buttonClass);
2846
+ }
2847
+ }
2848
+
2849
+ if (this.options.targetBlank || opts.target === "_blank" || opts.buttonClass) {
2850
+ customEvent = this.options.ownerDocument.createEvent("HTMLEvents");
2851
+ customEvent.initEvent("input", true, true, this.options.contentWindow);
2852
+ for (i = 0; i < this.elements.length; i += 1) {
2853
+ this.elements[i].dispatchEvent(customEvent);
1800
2854
  }
1801
2855
  }
1802
2856
  },
1803
2857
 
1804
2858
  setButtonClass: function (buttonClass) {
1805
- var el = getSelectionStart.call(this),
2859
+ var el = Selection.getSelectionStart(this.options.ownerDocument),
1806
2860
  classes = buttonClass.split(' '),
1807
2861
  i,
1808
2862
  j;
@@ -1820,47 +2874,6 @@ if (typeof module === 'object') {
1820
2874
  }
1821
2875
  },
1822
2876
 
1823
- createLink: function (input, target, buttonClass) {
1824
- var i, event;
1825
-
1826
- this.createLinkInternal(input.value, target, buttonClass);
1827
-
1828
- if (this.options.targetBlank || target === "_blank" || buttonClass) {
1829
- event = this.options.ownerDocument.createEvent("HTMLEvents");
1830
- event.initEvent("input", true, true, this.options.contentWindow);
1831
- for (i = 0; i < this.elements.length; i += 1) {
1832
- this.elements[i].dispatchEvent(event);
1833
- }
1834
- }
1835
-
1836
- this.checkSelection();
1837
- this.showToolbarActions();
1838
- input.value = '';
1839
- },
1840
-
1841
- createLinkInternal: function (url, target, buttonClass) {
1842
- if (!url || url.trim().length === 0) {
1843
- this.hideToolbarActions();
1844
- return;
1845
- }
1846
-
1847
- restoreSelection.call(this, this.savedSelection);
1848
-
1849
- if (this.options.checkLinkFormat) {
1850
- url = this.checkLinkFormat(url);
1851
- }
1852
-
1853
- this.options.ownerDocument.execCommand('createLink', false, url);
1854
-
1855
- if (this.options.targetBlank || target === "_blank") {
1856
- this.setTargetBlank();
1857
- }
1858
-
1859
- if (buttonClass) {
1860
- this.setButtonClass(buttonClass);
1861
- }
1862
- },
1863
-
1864
2877
  positionToolbarIfShown: function () {
1865
2878
  if (this.isToolbarShown()) {
1866
2879
  this.setToolbarPosition();
@@ -1881,6 +2894,9 @@ if (typeof module === 'object') {
1881
2894
  this.on(this.options.contentWindow, 'resize', function () {
1882
2895
  self.handleResize();
1883
2896
  });
2897
+
2898
+ this.bindBlur();
2899
+
1884
2900
  return this;
1885
2901
  },
1886
2902
 
@@ -1912,59 +2928,19 @@ if (typeof module === 'object') {
1912
2928
  this.elements[i].removeAttribute('data-medium-element');
1913
2929
  }
1914
2930
 
1915
- this.removeAllEvents();
1916
- },
2931
+ this.commands.forEach(function (extension) {
2932
+ if (typeof extension.deactivate === 'function') {
2933
+ extension.deactivate();
2934
+ }
2935
+ }.bind(this));
1917
2936
 
1918
- htmlEntities: function (str) {
1919
- // converts special characters (like <) into their escaped/encoded values (like &lt;).
1920
- // This allows you to show to display the string without the browser reading it as HTML.
1921
- return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2937
+ this.removeAllEvents();
1922
2938
  },
1923
2939
 
1924
2940
  bindPaste: function () {
1925
2941
  var i, self = this;
1926
2942
  this.pasteWrapper = function (e) {
1927
- var paragraphs,
1928
- html = '',
1929
- p,
1930
- dataFormatHTML = 'text/html',
1931
- dataFormatPlain = 'text/plain';
1932
-
1933
- this.classList.remove('medium-editor-placeholder');
1934
- if (!self.options.forcePlainText && !self.options.cleanPastedHTML) {
1935
- return this;
1936
- }
1937
-
1938
- if (self.options.contentWindow.clipboardData && e.clipboardData === undefined) {
1939
- e.clipboardData = self.options.contentWindow.clipboardData;
1940
- // If window.clipboardData exists, but e.clipboardData doesn't exist,
1941
- // we're probably in IE. IE only has two possibilities for clipboard
1942
- // data format: 'Text' and 'URL'.
1943
- //
1944
- // Of the two, we want 'Text':
1945
- dataFormatHTML = 'Text';
1946
- dataFormatPlain = 'Text';
1947
- }
1948
-
1949
- if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) {
1950
- e.preventDefault();
1951
-
1952
- if (self.options.cleanPastedHTML && e.clipboardData.getData(dataFormatHTML)) {
1953
- return self.cleanPaste(e.clipboardData.getData(dataFormatHTML));
1954
- }
1955
- if (!(self.options.disableReturn || this.getAttribute('data-disable-return'))) {
1956
- paragraphs = e.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
1957
- for (p = 0; p < paragraphs.length; p += 1) {
1958
- if (paragraphs[p] !== '') {
1959
- html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>';
1960
- }
1961
- }
1962
- insertHTMLCommand(self.options.ownerDocument, html);
1963
- } else {
1964
- html = self.htmlEntities(e.clipboardData.getData(dataFormatPlain));
1965
- insertHTMLCommand(self.options.ownerDocument, html);
1966
- }
1967
- }
2943
+ pasteHandler.handlePaste(this, e, self.options);
1968
2944
  };
1969
2945
  for (i = 0; i < this.elements.length; i += 1) {
1970
2946
  this.on(this.elements[i], 'paste', this.pasteWrapper);
@@ -1985,197 +2961,15 @@ if (typeof module === 'object') {
1985
2961
  },
1986
2962
 
1987
2963
  cleanPaste: function (text) {
1988
-
1989
- /*jslint regexp: true*/
1990
- /*
1991
- jslint does not allow character negation, because the negation
1992
- will not match any unicode characters. In the regexes in this
1993
- block, negation is used specifically to match the end of an html
1994
- tag, and in fact unicode characters *should* be allowed.
1995
- */
1996
- var i, elList, workEl,
1997
- el = this.getSelectionElement(),
1998
- multiline = /<p|<br|<div/.test(text),
1999
- replacements = [
2000
-
2001
- // replace two bogus tags that begin pastes from google docs
2002
- [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
2003
- [new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
2004
-
2005
- // un-html spaces and newlines inserted by OS X
2006
- [new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
2007
- [new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
2008
-
2009
- // replace google docs italics+bold with a span to be replaced once the html is inserted
2010
- [new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
2011
-
2012
- // replace google docs italics with a span to be replaced once the html is inserted
2013
- [new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
2014
-
2015
- //[replace google docs bolds with a span to be replaced once the html is inserted
2016
- [new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
2017
-
2018
- // replace manually entered b/i/a tags with real ones
2019
- [new RegExp(/&lt;(\/?)(i|b|a)&gt;/gi), '<$1$2>'],
2020
-
2021
- // replace manually a tags with real ones, converting smart-quotes from google docs
2022
- [new RegExp(/&lt;a\s+href=(&quot;|&rdquo;|&ldquo;|“|”)([^&]+)(&quot;|&rdquo;|&ldquo;|“|”)&gt;/gi), '<a href="$2">']
2023
-
2024
- ];
2025
- /*jslint regexp: false*/
2026
-
2027
- for (i = 0; i < replacements.length; i += 1) {
2028
- text = text.replace(replacements[i][0], replacements[i][1]);
2029
- }
2030
-
2031
- if (multiline) {
2032
-
2033
- // double br's aren't converted to p tags, but we want paragraphs.
2034
- elList = text.split('<br><br>');
2035
-
2036
- this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
2037
- this.options.ownerDocument.execCommand('insertText', false, "\n");
2038
-
2039
- // block element cleanup
2040
- elList = el.querySelectorAll('a,p,div,br');
2041
- for (i = 0; i < elList.length; i += 1) {
2042
-
2043
- workEl = elList[i];
2044
-
2045
- switch (workEl.tagName.toLowerCase()) {
2046
- case 'a':
2047
- if (this.options.targetBlank) {
2048
- this.setTargetBlank(workEl);
2049
- }
2050
- break;
2051
- case 'p':
2052
- case 'div':
2053
- this.filterCommonBlocks(workEl);
2054
- break;
2055
- case 'br':
2056
- this.filterLineBreak(workEl);
2057
- break;
2058
- }
2059
-
2060
- }
2061
-
2062
-
2063
- } else {
2064
-
2065
- this.pasteHTML(text);
2066
-
2067
- }
2068
-
2964
+ pasteHandler.cleanPaste(text, this.options);
2069
2965
  },
2070
2966
 
2071
2967
  pasteHTML: function (html) {
2072
- var elList, workEl, i, fragmentBody, pasteBlock = this.options.ownerDocument.createDocumentFragment();
2073
-
2074
- pasteBlock.appendChild(this.options.ownerDocument.createElement('body'));
2075
-
2076
- fragmentBody = pasteBlock.querySelector('body');
2077
- fragmentBody.innerHTML = html;
2078
-
2079
- this.cleanupSpans(fragmentBody);
2080
-
2081
- elList = fragmentBody.querySelectorAll('*');
2082
- for (i = 0; i < elList.length; i += 1) {
2083
-
2084
- workEl = elList[i];
2085
-
2086
- // delete ugly attributes
2087
- workEl.removeAttribute('class');
2088
- workEl.removeAttribute('style');
2089
- workEl.removeAttribute('dir');
2090
-
2091
- if (workEl.tagName.toLowerCase() === 'meta') {
2092
- workEl.parentNode.removeChild(workEl);
2093
- }
2094
-
2095
- }
2096
- insertHTMLCommand(this.options.ownerDocument, fragmentBody.innerHTML.replace(/&nbsp;/g, ' '));
2097
- },
2098
- isCommonBlock: function (el) {
2099
- return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
2100
- },
2101
- filterCommonBlocks: function (el) {
2102
- if (/^\s*$/.test(el.textContent)) {
2103
- el.parentNode.removeChild(el);
2104
- }
2105
- },
2106
- filterLineBreak: function (el) {
2107
- if (this.isCommonBlock(el.previousElementSibling)) {
2108
-
2109
- // remove stray br's following common block elements
2110
- el.parentNode.removeChild(el);
2111
-
2112
- } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
2113
-
2114
- // remove br's just inside open or close tags of a div/p
2115
- el.parentNode.removeChild(el);
2116
-
2117
- } else if (el.parentNode.childElementCount === 1) {
2118
-
2119
- // and br's that are the only child of a div/p
2120
- this.removeWithParent(el);
2121
-
2122
- }
2123
-
2124
- },
2125
-
2126
- // remove an element, including its parent, if it is the only element within its parent
2127
- removeWithParent: function (el) {
2128
- if (el && el.parentNode) {
2129
- if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
2130
- el.parentNode.parentNode.removeChild(el.parentNode);
2131
- } else {
2132
- el.parentNode.removeChild(el.parentNode);
2133
- }
2134
- }
2135
- },
2136
-
2137
- cleanupSpans: function (container_el) {
2138
-
2139
- var i,
2140
- el,
2141
- new_el,
2142
- spans = container_el.querySelectorAll('.replace-with');
2143
-
2144
- for (i = 0; i < spans.length; i += 1) {
2145
-
2146
- el = spans[i];
2147
- new_el = this.options.ownerDocument.createElement(el.classList.contains('bold') ? 'b' : 'i');
2148
-
2149
- if (el.classList.contains('bold') && el.classList.contains('italic')) {
2150
-
2151
- // add an i tag as well if this has both italics and bold
2152
- new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
2153
-
2154
- } else {
2155
-
2156
- new_el.innerHTML = el.innerHTML;
2157
-
2158
- }
2159
- el.parentNode.replaceChild(new_el, el);
2160
-
2161
- }
2162
-
2163
- spans = container_el.querySelectorAll('span');
2164
- for (i = 0; i < spans.length; i += 1) {
2165
-
2166
- el = spans[i];
2167
-
2168
- // remove empty spans, replace others with their contents
2169
- if (/^\s*$/.test()) {
2170
- el.parentNode.removeChild(el);
2171
- } else {
2172
- el.parentNode.replaceChild(this.options.ownerDocument.createTextNode(el.textContent), el);
2173
- }
2174
-
2175
- }
2176
-
2968
+ pasteHandler.pasteHTML(html, this.options.ownerDocument);
2177
2969
  }
2178
-
2179
2970
  };
2180
2971
 
2181
- }(window, document));
2972
+ }());
2973
+
2974
+ return MediumEditor;
2975
+ }()));