type_station 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }()));