type_station 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +4 -4
  2. data/lib/type_station/version.rb +1 -1
  3. data/spec/dummy/log/development.log +6757 -0
  4. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_buttons.scssc +0 -0
  5. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_mixins.scssc +0 -0
  6. data/spec/dummy/tmp/cache/assets/development/sass/d51071614a47c21d9e7a31bac67dfe51fed9abe3/_variables.scssc +0 -0
  7. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_alerts.scssc +0 -0
  8. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_background-variant.scssc +0 -0
  9. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_border-radius.scssc +0 -0
  10. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_buttons.scssc +0 -0
  11. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_center-block.scssc +0 -0
  12. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_clearfix.scssc +0 -0
  13. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_forms.scssc +0 -0
  14. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_gradients.scssc +0 -0
  15. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_grid-framework.scssc +0 -0
  16. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_grid.scssc +0 -0
  17. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_hide-text.scssc +0 -0
  18. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_image.scssc +0 -0
  19. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_labels.scssc +0 -0
  20. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_list-group.scssc +0 -0
  21. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_nav-divider.scssc +0 -0
  22. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_nav-vertical-align.scssc +0 -0
  23. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_opacity.scssc +0 -0
  24. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_pagination.scssc +0 -0
  25. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_panels.scssc +0 -0
  26. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_progress-bar.scssc +0 -0
  27. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_reset-filter.scssc +0 -0
  28. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_resize.scssc +0 -0
  29. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_responsive-visibility.scssc +0 -0
  30. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_size.scssc +0 -0
  31. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_tab-focus.scssc +0 -0
  32. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_table-row.scssc +0 -0
  33. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_text-emphasis.scssc +0 -0
  34. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_text-overflow.scssc +0 -0
  35. data/spec/dummy/tmp/cache/assets/development/sass/e63a0fef5d60e74f917815e496f07ccb348b5adc/_vendor-prefixes.scssc +0 -0
  36. data/spec/dummy/tmp/cache/assets/development/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/admin_bar.css.scssc +0 -0
  37. data/spec/dummy/tmp/cache/assets/development/sass/fde2fd9fd4415e2893f7c202128d5909c847645f/base.css.scssc +0 -0
  38. data/spec/dummy/tmp/cache/assets/development/sprockets/0825425ebf24766be13ebf4e696dc267 +0 -0
  39. data/spec/dummy/tmp/cache/assets/development/sprockets/0aa47ff15e22d0e74389460968c32717 +0 -0
  40. data/spec/dummy/tmp/cache/assets/development/sprockets/0b495c71c71a2d087e00d9f77c89b537 +0 -0
  41. data/spec/dummy/tmp/cache/assets/development/sprockets/0cbda04368ddbf6d761e788334075e7c +0 -0
  42. data/spec/dummy/tmp/cache/assets/development/sprockets/0d8f54d1682f575aec8f27993d3af45e +0 -0
  43. data/spec/dummy/tmp/cache/assets/development/sprockets/0dd8d5d73e593c919592d3d02deb62e8 +0 -0
  44. data/spec/dummy/tmp/cache/assets/development/sprockets/0e846d0579634bd3924d701ebcd09040 +0 -0
  45. data/spec/dummy/tmp/cache/assets/development/sprockets/127cceefef1ada19384ec7fec980c5ee +0 -0
  46. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  47. data/spec/dummy/tmp/cache/assets/development/sprockets/147a08ea451b98ff5a28a0088b7d5c89 +0 -0
  48. data/spec/dummy/tmp/cache/assets/development/sprockets/182d8b06dcdb4f799088741f553903d3 +0 -0
  49. data/spec/dummy/tmp/cache/assets/development/sprockets/1b64a5fd92110dbd078f4ea964e2d0ad +0 -0
  50. data/spec/dummy/tmp/cache/assets/development/sprockets/2155dbeb10a65c2519b49179b3bc6f3a +0 -0
  51. data/spec/dummy/tmp/cache/assets/development/sprockets/21f725a9b3479e57849414f8272a905b +0 -0
  52. data/spec/dummy/tmp/cache/assets/development/sprockets/2535956b4e5e79e8fbd3d30f68de7024 +0 -0
  53. data/spec/dummy/tmp/cache/assets/development/sprockets/28ad54c6e5882957dd269b1b2a3c3c75 +0 -0
  54. data/spec/dummy/tmp/cache/assets/development/sprockets/2d0ee72828e0dcbaf2390d3d67595461 +0 -0
  55. data/spec/dummy/tmp/cache/assets/development/sprockets/2d13dd9465026b66cce912e30e142020 +0 -0
  56. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  57. data/spec/dummy/tmp/cache/assets/development/sprockets/300f6f1e08668cacf691691df4d13889 +0 -0
  58. data/spec/dummy/tmp/cache/assets/development/sprockets/30e3d4f790a4234a26c7f74efba15a2e +0 -0
  59. data/spec/dummy/tmp/cache/assets/development/sprockets/311ffc36d5a7e1d3216e672b3e57e7ed +0 -0
  60. data/spec/dummy/tmp/cache/assets/development/sprockets/33ba18d4294cbb074bd3afee4728e2b9 +0 -0
  61. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  62. data/spec/dummy/tmp/cache/assets/development/sprockets/3a3d03c0a92f21419e1e85991774a28b +0 -0
  63. data/spec/dummy/tmp/cache/assets/development/sprockets/3dea6de9e06b1e372ebf4790f5eecc48 +0 -0
  64. data/spec/dummy/tmp/cache/assets/development/sprockets/3e7667a45f81a73030337cb8afe0acc3 +0 -0
  65. data/spec/dummy/tmp/cache/assets/development/sprockets/4144b15d96ec80b5c7fb367b57aaeef3 +0 -0
  66. data/spec/dummy/tmp/cache/assets/development/sprockets/4729c7f6ab6a91a079ab04a8f04e932b +0 -0
  67. data/spec/dummy/tmp/cache/assets/development/sprockets/48f052ab53e11ca42b15cc7f40fe2d4c +0 -0
  68. data/spec/dummy/tmp/cache/assets/development/sprockets/4c324a18fe6bf7dbf7a04cccbfdb366e +0 -0
  69. data/spec/dummy/tmp/cache/assets/development/sprockets/4fc791a177c880f50bf425e8d2862c20 +0 -0
  70. data/spec/dummy/tmp/cache/assets/development/sprockets/535f36c50d5b36ff83bc03bc6a19b8fc +0 -0
  71. data/spec/dummy/tmp/cache/assets/development/sprockets/55c2f55c27a62b998b8d33555158da53 +0 -0
  72. data/spec/dummy/tmp/cache/assets/development/sprockets/56afaea2e164564be4d5f5bbc7f81050 +0 -0
  73. data/spec/dummy/tmp/cache/assets/development/sprockets/570677a99f6abc19d74f793d5cb24f5f +0 -0
  74. data/spec/dummy/tmp/cache/assets/development/sprockets/575f0b5d245887cf587b0169d6993308 +0 -0
  75. data/spec/dummy/tmp/cache/assets/development/sprockets/579b1a727fe5b55dfe450b0835c56a65 +0 -0
  76. data/spec/dummy/tmp/cache/assets/development/sprockets/5a806147b739da644a296d2def76c243 +0 -0
  77. data/spec/dummy/tmp/cache/assets/development/sprockets/5c2d0bbd303da757c44328df9bccedf7 +0 -0
  78. data/spec/dummy/tmp/cache/assets/development/sprockets/60b14e8e4c0d91f7af9d162cf4c67a9c +0 -0
  79. data/spec/dummy/tmp/cache/assets/development/sprockets/61d7a0e99e49f7a9917d385223a97015 +0 -0
  80. data/spec/dummy/tmp/cache/assets/development/sprockets/623a22125c47f6f4391fd1a56e40b8a0 +0 -0
  81. data/spec/dummy/tmp/cache/assets/development/sprockets/65cf3864264ab5fd70d6e74a934adf8d +0 -0
  82. data/spec/dummy/tmp/cache/assets/development/sprockets/6e0fcc860289a427d98aa4ee86bd12b7 +0 -0
  83. data/spec/dummy/tmp/cache/assets/development/sprockets/6f7174fb7f1fbb8e01d382a128a979e8 +0 -0
  84. data/spec/dummy/tmp/cache/assets/development/sprockets/70dfe9a7b27467587b0d4d00a7e0f6fb +0 -0
  85. data/spec/dummy/tmp/cache/assets/development/sprockets/71e10ee69b177ab54cb6033385a77ef9 +0 -0
  86. data/spec/dummy/tmp/cache/assets/development/sprockets/73cc8ce397fbfe4408bcac87302a6952 +0 -0
  87. data/spec/dummy/tmp/cache/assets/development/sprockets/740af710fe4125080680a8561398cd43 +0 -0
  88. data/spec/dummy/tmp/cache/assets/development/sprockets/7cfbbb15a3f9dcb4a5ee988ea309504d +0 -0
  89. data/spec/dummy/tmp/cache/assets/development/sprockets/7f14b0b39c11df4f58e03d390b589547 +0 -0
  90. data/spec/dummy/tmp/cache/assets/development/sprockets/7fc9c7161658070c78790a28b4ff590f +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sprockets/825bd4dba50f1ea2c0ae5e8a3708119e +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sprockets/85c54085eea77d703dd054b1adcf309d +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sprockets/85d858ed64f06543161931735cb77748 +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sprockets/85fe1c52386ac4419fdf124a22228a98 +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sprockets/8803d0a63bbf3fce683c968d4a900480 +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sprockets/887e2151ef4cd6e915fd9bfc27c41d7d +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sprockets/8b33a159429f95c8fbb14e5d45b01967 +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sprockets/8bec81b5959299e431f5adfc6c410719 +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sprockets/8caeabedf4518243187dbf20bf76a0d8 +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sprockets/8e60a95e73cedfb4b58fbf6fc32564b3 +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sprockets/90c324b461b60cdeb6b549dbf932f162 +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sprockets/92c1d07e65b1251e6a9f62064f749658 +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sprockets/92d59399e3f951cc11721f03a2b328fd +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sprockets/966db607ae0e7aad72cf171b8fec537a +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sprockets/99fd048521a12239746f91aa138613bf +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sprockets/9c8cd77db76e28f0a074573a72509ce9 +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sprockets/a165e7cd7871ea0af12d29ef10193272 +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sprockets/a2b3a282d5d8a5c20444d1a27f70a61e +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sprockets/a539d3e91d5fb8eeb973ac8706bbf5c9 +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sprockets/a87019c62b5fb3b3dc75db921d98ad59 +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sprockets/a87cf4d10d892fb23f2fc818d7ca7207 +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sprockets/ab4192212bd78e95967e5a716548236f +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sprockets/af97ed366de5933460d41b9198ddc99e +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sprockets/afad1821c4be2be037ae8672c21db00a +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sprockets/b1a982f644c0350cb843b63d69a9506f +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sprockets/b6f21db694cacb7f5bcbb93cf45f65ed +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sprockets/b89a0282f37737eb86781a8ddbdf14a3 +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sprockets/bc2f1083a88e2e7c4e88f13f025824ed +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sprockets/bd3113e1418cbc640eb6e1b06d486163 +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sprockets/c0600b832214f9e16294a1c37c0c3f6e +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sprockets/c0c85148a1307bb6d6a87139811da3ad +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sprockets/c4ec64020d1ff391bab8c2f0bbc7cd02 +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sprockets/cb80f61fc678a70e99e16156ae6e07d1 +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sprockets/cc9a378dda4e65e86b1f3fd522d019a1 +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sprockets/ce5a41536c75e4bc37a5b0be19e9f786 +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sprockets/cf5014041f97c55eee7c67feddf11b81 +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sprockets/d02809f043bce2445374a43a8f64b4b8 +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sprockets/d05d272f053261f2d5e3dcca85f19172 +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sprockets/d05d2c6ed489a61e2a9756430af9f48f +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sprockets/d07ab5a5474c0fb71b3bd351947707e5 +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sprockets/d1e5823c113c9ee03e6c6d7773eb6fac +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sprockets/d1f0a2d6fd58a203f3ca66950c822c33 +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sprockets/d4248f8b11c6b21abffed4a5e718722d +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sprockets/db1a08b3c680b077476f767a8addb44b +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sprockets/dd9f84cc20227ad66bbd1f26bbc11ccc +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sprockets/de9d72cab443cce32e1d45e62d2b4c04 +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sprockets/e03c578b73cf9936edc84ff2b48c07da +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sprockets/e04691fd3eaee6408ac1d1b7fade69e6 +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sprockets/e40f49d676971d013ad991bdf2dc344b +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sprockets/e736f7cd2394078039a3b11e6ee42802 +0 -0
  143. data/spec/dummy/tmp/cache/assets/development/sprockets/ebdd71d1c3cf34d7d174e5c1abb123b7 +0 -0
  144. data/spec/dummy/tmp/cache/assets/development/sprockets/ec22367449630f093cf0449cea0abdfd +0 -0
  145. data/spec/dummy/tmp/cache/assets/development/sprockets/f084a3a159f23d2f0f9943c4a2e5f8e9 +0 -0
  146. data/spec/dummy/tmp/cache/assets/development/sprockets/f14e64162f52a8d2d22ff9e1c1cc4fac +0 -0
  147. data/spec/dummy/tmp/cache/assets/development/sprockets/f566af7037e0e3042b19bd5ee11b21c4 +0 -0
  148. data/spec/dummy/tmp/cache/assets/development/sprockets/f7c0e18cf77ef251a27e9ff91578fb47 +0 -0
  149. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  150. data/spec/dummy/tmp/cache/assets/development/sprockets/fa8cab89937bf25aa48b23445f1b4f64 +0 -0
  151. data/spec/dummy/tmp/cache/assets/development/sprockets/fc5de94b46d8aff03ec5355f912a9a81 +0 -0
  152. data/spec/dummy/tmp/cache/assets/development/sprockets/fd1cae0417fea68172ab7b747a1d6fd8 +0 -0
  153. data/spec/dummy/tmp/cache/assets/development/sprockets/fd2dacd118d6197c3a9325e2103abe7d +0 -0
  154. data/spec/dummy/tmp/cache/assets/development/sprockets/fe43b3c0b7d3233a250ce9c46342919c +0 -0
  155. data/vendor/assets/javascripts/drop.js +1799 -0
  156. data/vendor/assets/javascripts/jquery.cloudinary.js +800 -0
  157. data/vendor/assets/javascripts/medium-editor.js +1920 -0
  158. data/vendor/assets/javascripts/vex.combined.min.js +2 -0
  159. data/vendor/assets/stylesheets/drop-theme-arrows-bounce-dark.css +222 -0
  160. data/vendor/assets/stylesheets/drop-theme-arrows-bounce.css +230 -0
  161. data/vendor/assets/stylesheets/drop-theme-arrows.css +120 -0
  162. data/vendor/assets/stylesheets/medium-editor/medium-editor.css +170 -0
  163. data/vendor/assets/stylesheets/medium-editor/themes/bootstrap.css +67 -0
  164. data/vendor/assets/stylesheets/medium-editor/themes/bootstrap.min.css +1 -0
  165. data/vendor/assets/stylesheets/medium-editor/themes/default.css +60 -0
  166. data/vendor/assets/stylesheets/medium-editor/themes/default.min.css +1 -0
  167. data/vendor/assets/stylesheets/medium-editor/themes/flat.css +56 -0
  168. data/vendor/assets/stylesheets/medium-editor/themes/flat.min.css +1 -0
  169. data/vendor/assets/stylesheets/medium-editor/themes/mani.css +53 -0
  170. data/vendor/assets/stylesheets/medium-editor/themes/mani.min.css +1 -0
  171. data/vendor/assets/stylesheets/medium-editor/themes/roman.css +54 -0
  172. data/vendor/assets/stylesheets/medium-editor/themes/roman.min.css +1 -0
  173. data/vendor/assets/stylesheets/vex-theme-os.css +533 -0
  174. data/vendor/assets/stylesheets/vex.css +335 -0
  175. metadata +326 -2
@@ -0,0 +1,1920 @@
1
+ function MediumEditor(elements, options) {
2
+ 'use strict';
3
+ return this.init(elements, options);
4
+ }
5
+
6
+ if (typeof module === 'object') {
7
+ module.exports = MediumEditor;
8
+ }
9
+ // AMD support
10
+ else if (typeof define === 'function' && define.amd) {
11
+ define(function () {
12
+ 'use strict';
13
+ return MediumEditor;
14
+ });
15
+ }
16
+
17
+ (function (window, document) {
18
+ 'use strict';
19
+
20
+ function extend(b, a) {
21
+ var prop;
22
+ if (b === undefined) {
23
+ return a;
24
+ }
25
+ for (prop in a) {
26
+ if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) {
27
+ b[prop] = a[prop];
28
+ }
29
+ }
30
+ return b;
31
+ }
32
+
33
+ // https://github.com/jashkenas/underscore
34
+ var now = Date.now || function() {
35
+ return new Date().getTime();
36
+ };
37
+
38
+ // https://github.com/jashkenas/underscore
39
+ function debounce(func, wait) {
40
+ var DEBOUNCE_INTERVAL = 50,
41
+ timeout,
42
+ args,
43
+ context,
44
+ timestamp,
45
+ result,
46
+ later;
47
+
48
+ if (!wait && wait !== 0) {
49
+ wait = DEBOUNCE_INTERVAL;
50
+ }
51
+ later = function() {
52
+ var last = now() - timestamp;
53
+ if (last < wait && last > 0) {
54
+ timeout = setTimeout(later, wait - last);
55
+ } else {
56
+ timeout = null;
57
+ result = func.apply(context, args);
58
+ if (!timeout) {
59
+ context = args = null;
60
+ }
61
+ }
62
+ };
63
+
64
+ return function() {
65
+ context = this;
66
+ args = arguments;
67
+ timestamp = now();
68
+ if (!timeout) {
69
+ timeout = setTimeout(later, wait);
70
+ }
71
+
72
+ return result;
73
+ };
74
+ }
75
+
76
+ function isDescendant(parent, child) {
77
+ var node = child.parentNode;
78
+ while (node !== null) {
79
+ if (node === parent) {
80
+ return true;
81
+ }
82
+ node = node.parentNode;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ // http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element
88
+ // by Tim Down
89
+ function saveSelection() {
90
+ var i,
91
+ len,
92
+ ranges,
93
+ sel = this.options.contentWindow.getSelection();
94
+ if (sel.getRangeAt && sel.rangeCount) {
95
+ ranges = [];
96
+ for (i = 0, len = sel.rangeCount; i < len; i += 1) {
97
+ ranges.push(sel.getRangeAt(i));
98
+ }
99
+ return ranges;
100
+ }
101
+ return null;
102
+ }
103
+
104
+ function restoreSelection(savedSel) {
105
+ var i,
106
+ len,
107
+ sel = this.options.contentWindow.getSelection();
108
+ if (savedSel) {
109
+ sel.removeAllRanges();
110
+ for (i = 0, len = savedSel.length; i < len; i += 1) {
111
+ sel.addRange(savedSel[i]);
112
+ }
113
+ }
114
+ }
115
+
116
+ // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
117
+ // by You
118
+ function getSelectionStart() {
119
+ var node = this.options.ownerDocument.getSelection().anchorNode,
120
+ startNode = (node && node.nodeType === 3 ? node.parentNode : node);
121
+ return startNode;
122
+ }
123
+
124
+ // http://stackoverflow.com/questions/4176923/html-of-selected-text
125
+ // by Tim Down
126
+ function getSelectionHtml() {
127
+ var i,
128
+ html = '',
129
+ sel,
130
+ len,
131
+ container;
132
+ if (this.options.contentWindow.getSelection !== undefined) {
133
+ sel = this.options.contentWindow.getSelection();
134
+ if (sel.rangeCount) {
135
+ container = this.options.ownerDocument.createElement('div');
136
+ for (i = 0, len = sel.rangeCount; i < len; i += 1) {
137
+ container.appendChild(sel.getRangeAt(i).cloneContents());
138
+ }
139
+ html = container.innerHTML;
140
+ }
141
+ } else if (this.options.ownerDocument.selection !== undefined) {
142
+ if (this.options.ownerDocument.selection.type === 'Text') {
143
+ html = this.options.ownerDocument.selection.createRange().htmlText;
144
+ }
145
+ }
146
+ return html;
147
+ }
148
+
149
+ // https://github.com/jashkenas/underscore
150
+ function isElement(obj) {
151
+ return !!(obj && obj.nodeType === 1);
152
+ }
153
+
154
+ // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
155
+ function insertHTMLCommand(doc, html) {
156
+ var selection, range, el, fragment, node, lastNode;
157
+
158
+ if (doc.queryCommandSupported('insertHTML')) {
159
+ return doc.execCommand('insertHTML', false, html);
160
+ }
161
+
162
+ selection = window.getSelection();
163
+ if (selection.getRangeAt && selection.rangeCount) {
164
+ range = selection.getRangeAt(0);
165
+ range.deleteContents();
166
+
167
+ el = doc.createElement("div");
168
+ el.innerHTML = html;
169
+ fragment = doc.createDocumentFragment();
170
+ while (el.firstChild) {
171
+ node = el.firstChild;
172
+ lastNode = fragment.appendChild(node);
173
+ }
174
+ range.insertNode(fragment);
175
+
176
+ // Preserve the selection:
177
+ if (lastNode) {
178
+ range = range.cloneRange();
179
+ range.setStartAfter(lastNode);
180
+ range.collapse(true);
181
+ selection.removeAllRanges();
182
+ selection.addRange(range);
183
+ }
184
+ }
185
+ }
186
+
187
+ MediumEditor.prototype = {
188
+ defaults: {
189
+ allowMultiParagraphSelection: true,
190
+ anchorInputPlaceholder: 'Paste or type a link',
191
+ anchorPreviewHideDelay: 500,
192
+ buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'],
193
+ buttonLabels: false,
194
+ checkLinkFormat: false,
195
+ cleanPastedHTML: false,
196
+ delay: 0,
197
+ diffLeft: 0,
198
+ diffTop: -10,
199
+ disableReturn: false,
200
+ disableDoubleReturn: false,
201
+ disableToolbar: false,
202
+ disableEditing: false,
203
+ disableAnchorForm: false,
204
+ disablePlaceholders: false,
205
+ elementsContainer: false,
206
+ contentWindow: window,
207
+ ownerDocument: document,
208
+ firstHeader: 'h3',
209
+ forcePlainText: true,
210
+ placeholder: 'Type your text',
211
+ secondHeader: 'h4',
212
+ targetBlank: false,
213
+ anchorTarget: false,
214
+ anchorButton: false,
215
+ anchorButtonClass: 'btn',
216
+ extensions: {},
217
+ activeButtonClass: 'medium-editor-button-active',
218
+ firstButtonClass: 'medium-editor-button-first',
219
+ lastButtonClass: 'medium-editor-button-last'
220
+ },
221
+
222
+ // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
223
+ // by rg89
224
+ isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
225
+
226
+ init: function (elements, options) {
227
+ var uniqueId = 1;
228
+
229
+ this.options = extend(options, this.defaults);
230
+ this.setElementSelection(elements);
231
+ if (this.elements.length === 0) {
232
+ return;
233
+ }
234
+ this.parentElements = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'];
235
+ if (!this.options.elementsContainer) {
236
+ this.options.elementsContainer = document.body;
237
+ }
238
+
239
+ while (this.options.elementsContainer.querySelector('#medium-editor-toolbar-' + uniqueId)) {
240
+ uniqueId = uniqueId + 1;
241
+ }
242
+
243
+ this.id = uniqueId;
244
+
245
+ return this.setup();
246
+ },
247
+
248
+ setup: function () {
249
+ this.events = [];
250
+ this.isActive = true;
251
+ this.initElements()
252
+ .bindSelect()
253
+ .bindPaste()
254
+ .setPlaceholders()
255
+ .bindElementActions()
256
+ .bindWindowActions()
257
+ .passInstance();
258
+ },
259
+
260
+ on: function(target, event, listener, useCapture) {
261
+ target.addEventListener(event, listener, useCapture);
262
+ this.events.push([target, event, listener, useCapture]);
263
+ },
264
+
265
+ off: function(target, event, listener, useCapture) {
266
+ var index = this.events.indexOf([target, event, listener, useCapture]),
267
+ e;
268
+ if(index !== -1) {
269
+ e = this.events.splice(index, 1);
270
+ e[0].removeEventListener(e[1], e[2], e[3]);
271
+ }
272
+ },
273
+
274
+ removeAllEvents: function() {
275
+ var e = this.events.pop();
276
+ while(e) {
277
+ e[0].removeEventListener(e[1], e[2], e[3]);
278
+ e = this.events.pop();
279
+ }
280
+ },
281
+
282
+ initElements: function () {
283
+ this.updateElementList();
284
+ var i,
285
+ addToolbar = false;
286
+ for (i = 0; i < this.elements.length; i += 1) {
287
+ if (!this.options.disableEditing && !this.elements[i].getAttribute('data-disable-editing')) {
288
+ this.elements[i].setAttribute('contentEditable', true);
289
+ }
290
+ if (!this.elements[i].getAttribute('data-placeholder')) {
291
+ this.elements[i].setAttribute('data-placeholder', this.options.placeholder);
292
+ }
293
+ this.elements[i].setAttribute('data-medium-element', true);
294
+ this.bindParagraphCreation(i);
295
+ if (!this.options.disableToolbar && !this.elements[i].getAttribute('data-disable-toolbar')) {
296
+ addToolbar = true;
297
+ }
298
+ }
299
+ // Init toolbar
300
+ if (addToolbar) {
301
+ this.initToolbar()
302
+ .bindButtons()
303
+ .bindAnchorForm()
304
+ .bindAnchorPreview();
305
+ }
306
+ return this;
307
+ },
308
+
309
+ setElementSelection: function (selector) {
310
+ this.elementSelection = selector;
311
+ this.updateElementList();
312
+ },
313
+
314
+ updateElementList: function () {
315
+ this.elements = typeof this.elementSelection === 'string' ? this.options.ownerDocument.querySelectorAll(this.elementSelection) : this.elementSelection;
316
+ if (this.elements.nodeType === 1) {
317
+ this.elements = [this.elements];
318
+ }
319
+ },
320
+
321
+ // handleBlur is debounced because:
322
+ // - This method could be called many times due to the type of event handlers that are calling it
323
+ // - We want a slight delay so that other events in the stack can run, some of which may
324
+ // prevent the toolbar from being hidden (via this.keepToolbarAlive).
325
+ handleBlur: debounce(function() {
326
+ if ( !this.keepToolbarAlive ) {
327
+ // this.hideToolbarActions();
328
+ }
329
+ }),
330
+
331
+ bindBlur: function(i) {
332
+ var self = this,
333
+ blurFunction = function(e){
334
+ // If it's not part of the editor, or the toolbar
335
+ if ( e.target !== self.toolbar
336
+ && e.target !== self.elements[0]
337
+ && !isDescendant(self.elements[0], e.target)
338
+ && !isDescendant(self.toolbar, e.target)
339
+ && !isDescendant(self.anchorPreview, e.target)) {
340
+
341
+ // Activate the placeholder
342
+ if (!self.options.disablePlaceholders) {
343
+ self.placeholderWrapper(self.elements[0], e);
344
+ }
345
+
346
+ // Hide the toolbar after a small delay so we can prevent this on toolbar click
347
+ self.handleBlur();
348
+ }
349
+ };
350
+
351
+ // Hide the toolbar when focusing outside of the editor.
352
+ this.on(document.body, 'click', blurFunction, true);
353
+ this.on(document.body, 'focus', blurFunction, true);
354
+
355
+ return this;
356
+ },
357
+
358
+ bindKeypress: function(i) {
359
+ if (this.options.disablePlaceholders) {
360
+ return this;
361
+ }
362
+
363
+ var self = this;
364
+
365
+ // Set up the keypress events
366
+ this.on(this.elements[i], 'keypress', function(event){
367
+ self.placeholderWrapper(this,event);
368
+ });
369
+
370
+ return this;
371
+ },
372
+
373
+ bindClick: function(i) {
374
+ var self = this;
375
+
376
+ this.on(this.elements[i], 'click', function(){
377
+ if (!self.options.disablePlaceholders) {
378
+ // Remove placeholder
379
+ this.classList.remove('medium-editor-placeholder');
380
+ }
381
+
382
+ if ( self.options.staticToolbar ) {
383
+ self.setToolbarPosition();
384
+ }
385
+ });
386
+
387
+ return this;
388
+ },
389
+
390
+ /**
391
+ * This handles blur and keypress events on elements
392
+ * Including Placeholders, and tooldbar hiding on blur
393
+ */
394
+ bindElementActions: function() {
395
+ var i;
396
+
397
+ for (i = 0; i < this.elements.length; i += 1) {
398
+
399
+ if (!this.options.disablePlaceholders) {
400
+ // Active all of the placeholders
401
+ this.activatePlaceholder(this.elements[i]);
402
+ }
403
+
404
+ // Bind the return and tab keypress events
405
+ this.bindReturn(i)
406
+ .bindTab(i)
407
+ .bindBlur(i)
408
+ .bindClick(i)
409
+ .bindKeypress(i);
410
+
411
+ }
412
+
413
+ return this;
414
+ },
415
+
416
+ // Two functions to handle placeholders
417
+ activatePlaceholder: function (el) {
418
+
419
+ if (!(el.querySelector('img')) &&
420
+ !(el.querySelector('blockquote')) &&
421
+ el.textContent.replace(/^\s+|\s+$/g, '') === '') {
422
+
423
+ el.classList.add('medium-editor-placeholder');
424
+ }
425
+ },
426
+ placeholderWrapper: function (el, e) {
427
+ el.classList.remove('medium-editor-placeholder');
428
+ if (e.type !== 'keypress') {
429
+ this.activatePlaceholder(el);
430
+ }
431
+ },
432
+
433
+ serialize: function () {
434
+ var i,
435
+ elementid,
436
+ content = {};
437
+ for (i = 0; i < this.elements.length; i += 1) {
438
+ elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i;
439
+ content[elementid] = {
440
+ value: this.elements[i].innerHTML.trim()
441
+ };
442
+ }
443
+ return content;
444
+ },
445
+
446
+ /**
447
+ * Helper function to call a method with a number of parameters on all registered extensions.
448
+ * The function assures that the function exists before calling.
449
+ *
450
+ * @param {string} funcName name of the function to call
451
+ * @param [args] arguments passed into funcName
452
+ */
453
+ callExtensions: function (funcName) {
454
+ if (arguments.length < 1) {
455
+ return;
456
+ }
457
+
458
+ var args = Array.prototype.slice.call(arguments, 1),
459
+ ext,
460
+ name;
461
+
462
+ for (name in this.options.extensions) {
463
+ if (this.options.extensions.hasOwnProperty(name)) {
464
+ ext = this.options.extensions[name];
465
+ if (ext[funcName] !== undefined) {
466
+ ext[funcName].apply(ext, args);
467
+ }
468
+ }
469
+ }
470
+ },
471
+
472
+ /**
473
+ * Pass current Medium Editor instance to all extensions
474
+ * if extension constructor has 'parent' attribute set to 'true'
475
+ *
476
+ */
477
+ passInstance: function () {
478
+ var self = this,
479
+ ext,
480
+ name;
481
+
482
+ for (name in self.options.extensions) {
483
+ if (self.options.extensions.hasOwnProperty(name)) {
484
+ ext = self.options.extensions[name];
485
+
486
+ if (ext.parent) {
487
+ ext.base = self;
488
+ }
489
+ }
490
+ }
491
+
492
+ return self;
493
+ },
494
+
495
+ bindParagraphCreation: function (index) {
496
+ var self = this;
497
+ this.on(this.elements[index], 'keypress', function (e) {
498
+ var node,
499
+ tagName;
500
+ if (e.which === 32) {
501
+ node = getSelectionStart.call(self);
502
+ tagName = node.tagName.toLowerCase();
503
+ if (tagName === 'a') {
504
+ document.execCommand('unlink', false, null);
505
+ }
506
+ }
507
+ });
508
+
509
+ this.on(this.elements[index], 'keyup', function (e) {
510
+ var node = getSelectionStart.call(self),
511
+ tagName,
512
+ editorElement;
513
+
514
+ if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) {
515
+ document.execCommand('formatBlock', false, 'p');
516
+ }
517
+ if (e.which === 13) {
518
+ node = getSelectionStart.call(self);
519
+ tagName = node.tagName.toLowerCase();
520
+ editorElement = self.getSelectionElement();
521
+
522
+ if (!(self.options.disableReturn || editorElement.getAttribute('data-disable-return')) &&
523
+ tagName !== 'li' && !self.isListItemChild(node)) {
524
+ if (!e.shiftKey) {
525
+ document.execCommand('formatBlock', false, 'p');
526
+ }
527
+ if (tagName === 'a') {
528
+ document.execCommand('unlink', false, null);
529
+ }
530
+ }
531
+ }
532
+ });
533
+ return this;
534
+ },
535
+
536
+ isListItemChild: function (node) {
537
+ var parentNode = node.parentNode,
538
+ tagName = parentNode.tagName.toLowerCase();
539
+ while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') {
540
+ if (tagName === 'li') {
541
+ return true;
542
+ }
543
+ parentNode = parentNode.parentNode;
544
+ if (parentNode && parentNode.tagName) {
545
+ tagName = parentNode.tagName.toLowerCase();
546
+ } else {
547
+ return false;
548
+ }
549
+ }
550
+ return false;
551
+ },
552
+
553
+ bindReturn: function (index) {
554
+ var self = this;
555
+ this.on(this.elements[index], 'keypress', function (e) {
556
+ if (e.which === 13) {
557
+ if (self.options.disableReturn || this.getAttribute('data-disable-return')) {
558
+ e.preventDefault();
559
+ } else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) {
560
+ var node = getSelectionStart.call(self);
561
+ if (node && node.textContent === '\n') {
562
+ e.preventDefault();
563
+ }
564
+ }
565
+ }
566
+ });
567
+ return this;
568
+ },
569
+
570
+ bindTab: function (index) {
571
+ var self = this;
572
+ this.on(this.elements[index], 'keydown', function (e) {
573
+ if (e.which === 9) {
574
+ // Override tab only for pre nodes
575
+ var tag = getSelectionStart.call(self).tagName.toLowerCase();
576
+ if (tag === 'pre') {
577
+ e.preventDefault();
578
+ document.execCommand('insertHtml', null, ' ');
579
+ }
580
+
581
+ // Tab to indent list structures!
582
+ if (tag === 'li') {
583
+ e.preventDefault();
584
+
585
+ // If Shift is down, outdent, otherwise indent
586
+ if (e.shiftKey) {
587
+ document.execCommand('outdent', e);
588
+ } else {
589
+ document.execCommand('indent', e);
590
+ }
591
+ }
592
+ }
593
+ });
594
+ return this;
595
+ },
596
+
597
+ buttonTemplate: function (btnType) {
598
+ var buttonLabels = this.getButtonLabels(this.options.buttonLabels),
599
+ buttonTemplates = {
600
+ 'bold': '<button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b">' + buttonLabels.bold + '</button>',
601
+ 'italic': '<button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i">' + buttonLabels.italic + '</button>',
602
+ 'underline': '<button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u">' + buttonLabels.underline + '</button>',
603
+ 'strikethrough': '<button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike">' + buttonLabels.strikethrough +'</button>',
604
+ 'superscript': '<button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup">' + buttonLabels.superscript + '</button>',
605
+ 'subscript': '<button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub">' + buttonLabels.subscript + '</button>',
606
+ 'anchor': '<button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a">' + buttonLabels.anchor + '</button>',
607
+ 'image': '<button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img">' + buttonLabels.image + '</button>',
608
+ 'header1': '<button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '">' + buttonLabels.header1 + '</button>',
609
+ 'header2': '<button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + '">' + buttonLabels.header2 + '</button>',
610
+ 'quote': '<button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote">' + buttonLabels.quote + '</button>',
611
+ 'orderedlist': '<button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol">' + buttonLabels.orderedlist + '</button>',
612
+ 'unorderedlist': '<button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul">' + buttonLabels.unorderedlist + '</button>',
613
+ 'pre': '<button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre">' + buttonLabels.pre + '</button>',
614
+ 'indent': '<button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul">' + buttonLabels.indent + '</button>',
615
+ 'outdent': '<button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul">' + buttonLabels.outdent + '</button>',
616
+ 'justifyCenter': '<button class="medium-editor-action medium-editor-action-justifyCenter" data-action="justifyCenter" data-element="">' + buttonLabels.justifyCenter + '</button>',
617
+ 'justifyFull': '<button class="medium-editor-action medium-editor-action-justifyFull" data-action="justifyFull" data-element="">' + buttonLabels.justifyFull + '</button>',
618
+ 'justifyLeft': '<button class="medium-editor-action medium-editor-action-justifyLeft" data-action="justifyLeft" data-element="">' + buttonLabels.justifyLeft + '</button>',
619
+ 'justifyRight': '<button class="medium-editor-action medium-editor-action-justifyRight" data-action="justifyRight" data-element="">' + buttonLabels.justifyRight + '</button>'
620
+ };
621
+ return buttonTemplates[btnType] || false;
622
+ },
623
+
624
+ // TODO: break method
625
+ getButtonLabels: function (buttonLabelType) {
626
+ var customButtonLabels,
627
+ attrname,
628
+ buttonLabels = {
629
+ 'bold': '<b>B</b>',
630
+ 'italic': '<b><i>I</i></b>',
631
+ 'underline': '<b><u>U</u></b>',
632
+ 'strikethrough': '<s>A</s>',
633
+ 'superscript': '<b>x<sup>1</sup></b>',
634
+ 'subscript': '<b>x<sub>1</sub></b>',
635
+ 'anchor': '<b>#</b>',
636
+ 'image': '<b>image</b>',
637
+ 'header1': '<b>H1</b>',
638
+ 'header2': '<b>H2</b>',
639
+ 'quote': '<b>&ldquo;</b>',
640
+ 'orderedlist': '<b>1.</b>',
641
+ 'unorderedlist': '<b>&bull;</b>',
642
+ 'pre': '<b>0101</b>',
643
+ 'indent': '<b>&rarr;</b>',
644
+ 'outdent': '<b>&larr;</b>',
645
+ 'justifyCenter': '<b>C</b>',
646
+ 'justifyFull': '<b>J</b>',
647
+ 'justifyLeft': '<b>L</b>',
648
+ 'justifyRight': '<b>R</b>'
649
+ };
650
+ if (buttonLabelType === 'fontawesome') {
651
+ customButtonLabels = {
652
+ 'bold': '<i class="fa fa-bold"></i>',
653
+ 'italic': '<i class="fa fa-italic"></i>',
654
+ 'underline': '<i class="fa fa-underline"></i>',
655
+ 'strikethrough': '<i class="fa fa-strikethrough"></i>',
656
+ 'superscript': '<i class="fa fa-superscript"></i>',
657
+ 'subscript': '<i class="fa fa-subscript"></i>',
658
+ 'anchor': '<i class="fa fa-link"></i>',
659
+ 'image': '<i class="fa fa-picture-o"></i>',
660
+ 'quote': '<i class="fa fa-quote-right"></i>',
661
+ 'orderedlist': '<i class="fa fa-list-ol"></i>',
662
+ 'unorderedlist': '<i class="fa fa-list-ul"></i>',
663
+ 'pre': '<i class="fa fa-code fa-lg"></i>',
664
+ 'indent': '<i class="fa fa-indent"></i>',
665
+ 'outdent': '<i class="fa fa-outdent"></i>',
666
+ 'justifyCenter': '<i class="fa fa-align-center"></i>',
667
+ 'justifyFull': '<i class="fa fa-align-justify"></i>',
668
+ 'justifyLeft': '<i class="fa fa-align-left"></i>',
669
+ 'justifyRight': '<i class="fa fa-align-right"></i>'
670
+ };
671
+ } else if (typeof buttonLabelType === 'object') {
672
+ customButtonLabels = buttonLabelType;
673
+ }
674
+ if (typeof customButtonLabels === 'object') {
675
+ for (attrname in customButtonLabels) {
676
+ if (customButtonLabels.hasOwnProperty(attrname)) {
677
+ buttonLabels[attrname] = customButtonLabels[attrname];
678
+ }
679
+ }
680
+ }
681
+ return buttonLabels;
682
+ },
683
+
684
+ initToolbar: function () {
685
+ if (this.toolbar) {
686
+ return this;
687
+ }
688
+ this.toolbar = this.createToolbar();
689
+ this.keepToolbarAlive = false;
690
+ this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions');
691
+ this.anchorPreview = this.createAnchorPreview();
692
+
693
+ if (!this.options.disableAnchorForm) {
694
+ this.anchorForm = this.toolbar.querySelector('.medium-editor-toolbar-form-anchor');
695
+ this.anchorInput = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-input');
696
+ this.anchorTarget = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-target');
697
+ this.anchorButton = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-button');
698
+ }
699
+ return this;
700
+ },
701
+
702
+ createToolbar: function () {
703
+ var toolbar = document.createElement('div');
704
+ toolbar.id = 'medium-editor-toolbar-' + this.id;
705
+ toolbar.className = 'medium-editor-toolbar';
706
+
707
+ if ( this.options.staticToolbar ) {
708
+ toolbar.className += " static-toolbar";
709
+ } else {
710
+ toolbar.className += " stalker-toolbar";
711
+ }
712
+
713
+ toolbar.appendChild(this.toolbarButtons());
714
+ if (!this.options.disableAnchorForm) {
715
+ toolbar.appendChild(this.toolbarFormAnchor());
716
+ }
717
+ this.options.elementsContainer.appendChild(toolbar);
718
+ return toolbar;
719
+ },
720
+
721
+ //TODO: actionTemplate
722
+ toolbarButtons: function () {
723
+ var btns = this.options.buttons,
724
+ ul = document.createElement('ul'),
725
+ li,
726
+ i,
727
+ btn,
728
+ ext;
729
+
730
+ ul.id = 'medium-editor-toolbar-actions' + this.id;
731
+ ul.className = 'medium-editor-toolbar-actions clearfix';
732
+
733
+ for (i = 0; i < btns.length; i += 1) {
734
+ if (this.options.extensions.hasOwnProperty(btns[i])) {
735
+ ext = this.options.extensions[btns[i]];
736
+ btn = ext.getButton !== undefined ? ext.getButton() : null;
737
+ } else {
738
+ btn = this.buttonTemplate(btns[i]);
739
+ }
740
+
741
+ if (btn) {
742
+ li = document.createElement('li');
743
+ if (isElement(btn)) {
744
+ li.appendChild(btn);
745
+ } else {
746
+ li.innerHTML = btn;
747
+ }
748
+ ul.appendChild(li);
749
+ }
750
+ }
751
+
752
+ return ul;
753
+ },
754
+
755
+ toolbarFormAnchor: function () {
756
+ var anchor = document.createElement('div'),
757
+ input = document.createElement('input'),
758
+ target_label = document.createElement('label'),
759
+ target = document.createElement('input'),
760
+ button_label = document.createElement('label'),
761
+ button = document.createElement('input'),
762
+ close = document.createElement('a'),
763
+ save = document.createElement('a');
764
+
765
+ close.setAttribute('href', '#');
766
+ close.className = 'medium-editor-toobar-anchor-close';
767
+ close.innerHTML = '&times;';
768
+
769
+ save.setAttribute('href', '#');
770
+ save.className = 'medium-editor-toobar-anchor-save';
771
+ save.innerHTML = '&#10003;';
772
+
773
+ input.setAttribute('type', 'text');
774
+ input.className = 'medium-editor-toolbar-anchor-input';
775
+ input.setAttribute('placeholder', this.options.anchorInputPlaceholder);
776
+
777
+
778
+ target.setAttribute('type', 'checkbox');
779
+ target.className = 'medium-editor-toolbar-anchor-target';
780
+ target_label.innerHTML = "Open in New Window?";
781
+ target_label.insertBefore(target, target_label.firstChild);
782
+
783
+ button.setAttribute('type', 'checkbox');
784
+ button.className = 'medium-editor-toolbar-anchor-button';
785
+ button_label.innerHTML = "Button";
786
+ button_label.insertBefore(button, button_label.firstChild);
787
+
788
+
789
+ anchor.className = 'medium-editor-toolbar-form-anchor';
790
+ anchor.id = 'medium-editor-toolbar-form-anchor-' + this.id;
791
+ anchor.appendChild(input);
792
+
793
+ anchor.appendChild(save);
794
+ anchor.appendChild(close);
795
+
796
+ if (this.options.anchorTarget) {
797
+ anchor.appendChild(target_label);
798
+ }
799
+
800
+ if (this.options.anchorButton) {
801
+ anchor.appendChild(button_label);
802
+ }
803
+
804
+ return anchor;
805
+ },
806
+
807
+ bindSelect: function () {
808
+ var self = this,
809
+ i;
810
+
811
+ this.checkSelectionWrapper = function (e) {
812
+ // Do not close the toolbar when bluring the editable area and clicking into the anchor form
813
+ if (!self.options.disableAnchorForm && e && self.clickingIntoArchorForm(e)) {
814
+ return false;
815
+ }
816
+
817
+ self.checkSelection();
818
+ };
819
+
820
+ this.on(document.documentElement, 'mouseup', this.checkSelectionWrapper);
821
+
822
+ for (i = 0; i < this.elements.length; i += 1) {
823
+ this.on(this.elements[i], 'keyup', this.checkSelectionWrapper);
824
+ this.on(this.elements[i], 'blur', this.checkSelectionWrapper);
825
+ this.on(this.elements[i], 'click', this.checkSelectionWrapper);
826
+ }
827
+ return this;
828
+ },
829
+
830
+ checkSelection: function () {
831
+ var newSelection,
832
+ selectionElement;
833
+
834
+ if (this.keepToolbarAlive !== true && !this.options.disableToolbar) {
835
+
836
+ newSelection = this.options.contentWindow.getSelection();
837
+ if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
838
+ (this.options.allowMultiParagraphSelection === false && this.hasMultiParagraphs()) ||
839
+ this.selectionInContentEditableFalse()) {
840
+
841
+ if ( !this.options.staticToolbar ) {
842
+ this.hideToolbarActions();
843
+ } else if (this.anchorForm && this.anchorForm.style.display === 'block') {
844
+ this.setToolbarButtonStates();
845
+ this.showToolbarActions();
846
+ }
847
+
848
+ } else {
849
+ selectionElement = this.getSelectionElement();
850
+ if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
851
+ if ( !this.options.staticToolbar ) {
852
+ this.hideToolbarActions();
853
+ }
854
+ } else {
855
+ this.checkSelectionElement(newSelection, selectionElement);
856
+ }
857
+ }
858
+ }
859
+ return this;
860
+ },
861
+
862
+ clickingIntoArchorForm: function (e) {
863
+ var self = this;
864
+
865
+ if (e.type && e.type.toLowerCase() === 'blur' && e.relatedTarget && e.relatedTarget === self.anchorInput) {
866
+ return true;
867
+ }
868
+
869
+ return false;
870
+ },
871
+
872
+ hasMultiParagraphs: function () {
873
+ var selectionHtml = getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
874
+ hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g);
875
+
876
+ return (hasMultiParagraphs ? hasMultiParagraphs.length : 0);
877
+ },
878
+
879
+ checkSelectionElement: function (newSelection, selectionElement) {
880
+ var i;
881
+ this.selection = newSelection;
882
+ this.selectionRange = this.selection.getRangeAt(0);
883
+ for (i = 0; i < this.elements.length; i += 1) {
884
+ if (this.elements[i] === selectionElement) {
885
+ this.setToolbarButtonStates()
886
+ .setToolbarPosition()
887
+ .showToolbarActions();
888
+ return;
889
+ }
890
+ }
891
+
892
+ if ( !this.options.staticToolbar ) {
893
+ this.hideToolbarActions();
894
+ }
895
+ },
896
+
897
+ findMatchingSelectionParent: function(testElementFunction) {
898
+ var selection = this.options.contentWindow.getSelection(), range, current;
899
+
900
+ if (selection.rangeCount === 0) {
901
+ return false;
902
+ }
903
+
904
+ range = selection.getRangeAt(0);
905
+ current = range.commonAncestorContainer;
906
+
907
+ do {
908
+ if (current.nodeType === 1){
909
+ if ( testElementFunction(current) )
910
+ {
911
+ return current;
912
+ }
913
+ // do not traverse upwards past the nearest containing editor
914
+ if (current.getAttribute('data-medium-element')) {
915
+ return false;
916
+ }
917
+ }
918
+
919
+ current = current.parentNode;
920
+ } while (current);
921
+
922
+ return false;
923
+ },
924
+
925
+ getSelectionElement: function () {
926
+ return this.findMatchingSelectionParent(function(el) {
927
+ return el.getAttribute('data-medium-element');
928
+ });
929
+ },
930
+
931
+ selectionInContentEditableFalse: function () {
932
+ return this.findMatchingSelectionParent(function(el) {
933
+ return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
934
+ });
935
+ },
936
+
937
+ setToolbarPosition: function () {
938
+ // document.documentElement for IE 9
939
+ var scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop,
940
+ container = this.elements[0],
941
+ containerRect = container.getBoundingClientRect(),
942
+ containerTop = containerRect.top + scrollTop,
943
+ buttonHeight = 50,
944
+ selection = window.getSelection(),
945
+ range,
946
+ boundary,
947
+ middleBoundary,
948
+ defaultLeft = (this.options.diffLeft) - (this.toolbar.offsetWidth / 2),
949
+ halfOffsetWidth = this.toolbar.offsetWidth / 2;
950
+
951
+ if ( selection.focusNode === null ) {
952
+ return this;
953
+ }
954
+
955
+ this.showToolbar();
956
+
957
+ if ( this.options.staticToolbar ) {
958
+
959
+ if ( this.options.stickyToolbar ) {
960
+
961
+ // If it's beyond the height of the editor, position it at the bottom of the editor
962
+ if ( scrollTop > (containerTop + this.elements[0].offsetHeight - this.toolbar.offsetHeight)) {
963
+ this.toolbar.style.top = (containerTop + this.elements[0].offsetHeight) + 'px';
964
+ }
965
+ // Stick the toolbar to the top of the window
966
+ else if ( scrollTop > (containerTop - this.toolbar.offsetHeight) ) {
967
+ this.toolbar.classList.add('sticky-toolbar');
968
+ this.toolbar.style.top = "0px";
969
+ }
970
+ // Normal static toolbar position
971
+ else {
972
+ this.toolbar.classList.remove('sticky-toolbar');
973
+ this.toolbar.style.top = containerTop - this.toolbar.offsetHeight + "px";
974
+ }
975
+
976
+ } else {
977
+ this.toolbar.style.top = containerTop - this.toolbar.offsetHeight + "px";
978
+ }
979
+
980
+ this.toolbar.style.left = containerRect.left + "px";
981
+
982
+ } else if (!selection.isCollapsed) {
983
+ range = selection.getRangeAt(0);
984
+ boundary = range.getBoundingClientRect();
985
+ middleBoundary = (boundary.left + boundary.right) / 2;
986
+
987
+ if (boundary.top < buttonHeight) {
988
+ this.toolbar.classList.add('medium-toolbar-arrow-over');
989
+ this.toolbar.classList.remove('medium-toolbar-arrow-under');
990
+ this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - this.toolbar.offsetHeight + 'px';
991
+ } else {
992
+ this.toolbar.classList.add('medium-toolbar-arrow-under');
993
+ this.toolbar.classList.remove('medium-toolbar-arrow-over');
994
+ this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - this.toolbar.offsetHeight + 'px';
995
+ }
996
+ if (middleBoundary < halfOffsetWidth) {
997
+ this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px';
998
+ } else if ((this.options.contentWindow.innerWidth - middleBoundary) < halfOffsetWidth) {
999
+ this.toolbar.style.left = this.options.contentWindow.innerWidth + defaultLeft - halfOffsetWidth + 'px';
1000
+ } else {
1001
+ this.toolbar.style.left = defaultLeft + middleBoundary + 'px';
1002
+ }
1003
+ }
1004
+
1005
+ this.hideAnchorPreview();
1006
+
1007
+ return this;
1008
+ },
1009
+
1010
+ setToolbarButtonStates: function () {
1011
+ var buttons = this.toolbarActions.querySelectorAll('button'),
1012
+ i;
1013
+ for (i = 0; i < buttons.length; i += 1) {
1014
+ buttons[i].classList.remove(this.options.activeButtonClass);
1015
+ }
1016
+ this.checkActiveButtons();
1017
+ return this;
1018
+ },
1019
+
1020
+ checkActiveButtons: function () {
1021
+ var elements = Array.prototype.slice.call(this.elements),
1022
+ parentNode = this.getSelectedParentElement();
1023
+ while (parentNode.tagName !== undefined && this.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
1024
+ this.activateButton(parentNode.tagName.toLowerCase());
1025
+ this.callExtensions('checkState', parentNode);
1026
+
1027
+ // we can abort the search upwards if we leave the contentEditable element
1028
+ if (elements.indexOf(parentNode) !== -1) {
1029
+ break;
1030
+ }
1031
+ parentNode = parentNode.parentNode;
1032
+ }
1033
+ },
1034
+
1035
+ activateButton: function (tag) {
1036
+ var el = this.toolbar.querySelector('[data-element="' + tag + '"]');
1037
+ if (el !== null && el.className.indexOf(this.options.activeButtonClass) === -1) {
1038
+ el.className += ' ' + this.options.activeButtonClass;
1039
+ }
1040
+ },
1041
+
1042
+ bindButtons: function () {
1043
+ var buttons = this.toolbar.querySelectorAll('button'),
1044
+ i,
1045
+ self = this,
1046
+ triggerAction = function (e) {
1047
+ e.preventDefault();
1048
+ e.stopPropagation();
1049
+ if (self.selection === undefined) {
1050
+ self.checkSelection();
1051
+ }
1052
+ if (this.className.indexOf(self.options.activeButtonClass) > -1) {
1053
+ this.classList.remove(self.options.activeButtonClass);
1054
+ } else {
1055
+ this.className += ' ' + self.options.activeButtonClass;
1056
+ }
1057
+ if (this.hasAttribute('data-action')) {
1058
+ self.execAction(this.getAttribute('data-action'), e);
1059
+ }
1060
+ };
1061
+ for (i = 0; i < buttons.length; i += 1) {
1062
+ this.on(buttons[i], 'click', triggerAction);
1063
+ }
1064
+ this.setFirstAndLastItems(buttons);
1065
+ return this;
1066
+ },
1067
+
1068
+ setFirstAndLastItems: function (buttons) {
1069
+ if (buttons.length > 0) {
1070
+ buttons[0].className += ' ' + this.options.firstButtonClass;
1071
+ buttons[buttons.length - 1].className += ' ' + this.options.lastButtonClass;
1072
+ }
1073
+ return this;
1074
+ },
1075
+
1076
+ execAction: function (action, e) {
1077
+ if (action.indexOf('append-') > -1) {
1078
+ this.execFormatBlock(action.replace('append-', ''));
1079
+ this.setToolbarPosition();
1080
+ this.setToolbarButtonStates();
1081
+ } else if (action === 'anchor') {
1082
+ if (!this.options.disableAnchorForm) {
1083
+ this.triggerAnchorAction(e);
1084
+ }
1085
+ } else if (action === 'image') {
1086
+ this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
1087
+ } else {
1088
+ this.options.ownerDocument.execCommand(action, false, null);
1089
+ this.setToolbarPosition();
1090
+ }
1091
+ },
1092
+
1093
+ // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
1094
+ rangeSelectsSingleNode: function (range) {
1095
+ var startNode = range.startContainer;
1096
+ return startNode === range.endContainer &&
1097
+ startNode.hasChildNodes() &&
1098
+ range.endOffset === range.startOffset + 1;
1099
+ },
1100
+
1101
+ getSelectedParentElement: function () {
1102
+ var selectedParentElement = null,
1103
+ range = this.selectionRange;
1104
+ if (this.rangeSelectsSingleNode(range)) {
1105
+ selectedParentElement = range.startContainer.childNodes[range.startOffset];
1106
+ } else if (range.startContainer.nodeType === 3) {
1107
+ selectedParentElement = range.startContainer.parentNode;
1108
+ } else {
1109
+ selectedParentElement = range.startContainer;
1110
+ }
1111
+ return selectedParentElement;
1112
+ },
1113
+
1114
+ triggerAnchorAction: function () {
1115
+ var selectedParentElement = this.getSelectedParentElement();
1116
+ if (selectedParentElement.tagName &&
1117
+ selectedParentElement.tagName.toLowerCase() === 'a') {
1118
+ this.options.ownerDocument.execCommand('unlink', false, null);
1119
+ } else if (this.anchorForm) {
1120
+ if (this.anchorForm.style.display === 'block') {
1121
+ this.showToolbarActions();
1122
+ } else {
1123
+ this.showAnchorForm();
1124
+ }
1125
+ }
1126
+ return this;
1127
+ },
1128
+
1129
+ execFormatBlock: function (el) {
1130
+ var selectionData = this.getSelectionData(this.selection.anchorNode);
1131
+ // FF handles blockquote differently on formatBlock
1132
+ // allowing nesting, we need to use outdent
1133
+ // https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
1134
+ if (el === 'blockquote' && selectionData.el &&
1135
+ selectionData.el.parentNode.tagName.toLowerCase() === 'blockquote') {
1136
+ return this.options.ownerDocument.execCommand('outdent', false, null);
1137
+ }
1138
+ if (selectionData.tagName === el) {
1139
+ el = 'p';
1140
+ }
1141
+ // When IE we need to add <> to heading elements and
1142
+ // blockquote needs to be called as indent
1143
+ // http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie
1144
+ // http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777
1145
+ if (this.isIE) {
1146
+ if (el === 'blockquote') {
1147
+ return this.options.ownerDocument.execCommand('indent', false, el);
1148
+ }
1149
+ el = '<' + el + '>';
1150
+ }
1151
+ return this.options.ownerDocument.execCommand('formatBlock', false, el);
1152
+ },
1153
+
1154
+ getSelectionData: function (el) {
1155
+ var tagName;
1156
+
1157
+ if (el && el.tagName) {
1158
+ tagName = el.tagName.toLowerCase();
1159
+ }
1160
+
1161
+ while (el && this.parentElements.indexOf(tagName) === -1) {
1162
+ el = el.parentNode;
1163
+ if (el && el.tagName) {
1164
+ tagName = el.tagName.toLowerCase();
1165
+ }
1166
+ }
1167
+
1168
+ return {
1169
+ el: el,
1170
+ tagName: tagName
1171
+ };
1172
+ },
1173
+
1174
+ getFirstChild: function (el) {
1175
+ var firstChild = el.firstChild;
1176
+ while (firstChild !== null && firstChild.nodeType !== 1) {
1177
+ firstChild = firstChild.nextSibling;
1178
+ }
1179
+ return firstChild;
1180
+ },
1181
+
1182
+ isToolbarShown: function() {
1183
+ return this.toolbar && this.toolbar.classList.contains('medium-editor-toolbar-active');
1184
+ },
1185
+
1186
+ showToolbar: function() {
1187
+ if (this.toolbar && !this.isToolbarShown()) {
1188
+ this.toolbar.classList.add('medium-editor-toolbar-active');
1189
+ }
1190
+ },
1191
+
1192
+ hideToolbar: function() {
1193
+ if (this.isToolbarShown()) {
1194
+ this.toolbar.classList.remove('medium-editor-toolbar-active');
1195
+ if (this.onHideToolbar) {
1196
+ this.onHideToolbar();
1197
+ }
1198
+ }
1199
+ },
1200
+
1201
+ hideToolbarActions: function () {
1202
+ this.keepToolbarAlive = false;
1203
+ this.hideToolbar();
1204
+ },
1205
+
1206
+ showToolbarActions: function () {
1207
+ var self = this;
1208
+ if (this.anchorForm) {
1209
+ this.anchorForm.style.display = 'none';
1210
+ }
1211
+ this.toolbarActions.style.display = 'block';
1212
+ this.keepToolbarAlive = false;
1213
+ // Using setTimeout + options.delay because:
1214
+ // We will actually be displaying the toolbar, which should be controlled by options.delay
1215
+ setTimeout(function () {
1216
+ self.showToolbar();
1217
+ }, this.options.delay);
1218
+ },
1219
+
1220
+ saveSelection: function() {
1221
+ this.savedSelection = saveSelection.call(this);
1222
+ },
1223
+
1224
+ restoreSelection: function() {
1225
+ restoreSelection.call(this, this.savedSelection);
1226
+ },
1227
+
1228
+ showAnchorForm: function (link_value) {
1229
+ if (!this.anchorForm) {
1230
+ return;
1231
+ }
1232
+
1233
+ this.toolbarActions.style.display = 'none';
1234
+ this.saveSelection();
1235
+ this.anchorForm.style.display = 'block';
1236
+ this.setToolbarPosition();
1237
+ this.keepToolbarAlive = true;
1238
+ this.anchorInput.focus();
1239
+ this.anchorInput.value = link_value || '';
1240
+ },
1241
+
1242
+ bindAnchorForm: function () {
1243
+ if (!this.anchorForm) {
1244
+ return this;
1245
+ }
1246
+
1247
+ var linkCancel = this.anchorForm.querySelector('a.medium-editor-toobar-anchor-close'),
1248
+ linkSave = this.anchorForm.querySelector('a.medium-editor-toobar-anchor-save'),
1249
+ self = this;
1250
+
1251
+ this.on(this.anchorForm, 'click', function (e) {
1252
+ e.stopPropagation();
1253
+ self.keepToolbarAlive = true;
1254
+ });
1255
+
1256
+ this.on(this.anchorInput, 'keyup', function (e) {
1257
+ var button = null,
1258
+ target;
1259
+
1260
+ if (e.keyCode === 13) {
1261
+ e.preventDefault();
1262
+ if (self.options.anchorTarget && self.anchorTarget.checked) {
1263
+ target = "_blank";
1264
+ }
1265
+ else {
1266
+ target = "_self";
1267
+ }
1268
+
1269
+ if (self.options.anchorButton && self.anchorButton.checked) {
1270
+ button = self.options.anchorButtonClass;
1271
+ }
1272
+
1273
+ self.createLink(this, target, button);
1274
+ }
1275
+ else if (e.keyCode === 27) {
1276
+ e.preventDefault();
1277
+ self.showToolbarActions();
1278
+ restoreSelection.call(self, self.savedSelection);
1279
+ }
1280
+ });
1281
+
1282
+ this.on(linkSave, 'click', function(e) {
1283
+ var button = null,
1284
+ target;
1285
+ e.preventDefault();
1286
+ if ( self.options.anchorTarget && self.anchorTarget.checked) {
1287
+ target = "_blank";
1288
+ }
1289
+ else {
1290
+ target = "_self";
1291
+ }
1292
+
1293
+ if (self.options.anchorButton && self.anchorButton.checked) {
1294
+ button = self.options.anchorButtonClass;
1295
+ }
1296
+
1297
+ self.createLink(self.anchorInput, target, button);
1298
+ }, true);
1299
+
1300
+ this.on(this.anchorInput, 'click', function (e) {
1301
+ // make sure not to hide form when cliking into the input
1302
+ e.stopPropagation();
1303
+ self.keepToolbarAlive = true;
1304
+ });
1305
+
1306
+ // Hide the anchor form when focusing outside of it.
1307
+ this.on(this.options.ownerDocument.body, 'click', function (e) {
1308
+ if (e.target !== self.anchorForm && !isDescendant(self.anchorForm, e.target) && !isDescendant(self.toolbarActions, e.target)) {
1309
+ self.keepToolbarAlive = false;
1310
+ self.checkSelection();
1311
+ }
1312
+ }, true);
1313
+ this.on(this.options.ownerDocument.body, 'focus', function (e) {
1314
+ if (e.target !== self.anchorForm && !isDescendant(self.anchorForm, e.target) && !isDescendant(self.toolbarActions, e.target)) {
1315
+ self.keepToolbarAlive = false;
1316
+ self.checkSelection();
1317
+ }
1318
+ }, true);
1319
+
1320
+ this.on(linkCancel, 'click', function (e) {
1321
+ e.preventDefault();
1322
+ self.showToolbarActions();
1323
+ restoreSelection.call(self, self.savedSelection);
1324
+ });
1325
+ return this;
1326
+ },
1327
+
1328
+
1329
+ hideAnchorPreview: function () {
1330
+ this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');
1331
+ },
1332
+
1333
+ // TODO: break method
1334
+ showAnchorPreview: function (anchorEl) {
1335
+ if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')
1336
+ || anchorEl.getAttribute('data-disable-preview')) {
1337
+ return true;
1338
+ }
1339
+
1340
+ var self = this,
1341
+ buttonHeight = 40,
1342
+ boundary = anchorEl.getBoundingClientRect(),
1343
+ middleBoundary = (boundary.left + boundary.right) / 2,
1344
+ halfOffsetWidth,
1345
+ defaultLeft;
1346
+
1347
+ self.anchorPreview.querySelector('i').textContent = anchorEl.href;
1348
+ halfOffsetWidth = self.anchorPreview.offsetWidth / 2;
1349
+ defaultLeft = self.options.diffLeft - halfOffsetWidth;
1350
+
1351
+ self.observeAnchorPreview(anchorEl);
1352
+
1353
+ self.anchorPreview.classList.add('medium-toolbar-arrow-over');
1354
+ self.anchorPreview.classList.remove('medium-toolbar-arrow-under');
1355
+ self.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - self.options.diffTop + this.options.contentWindow.pageYOffset - self.anchorPreview.offsetHeight) + 'px';
1356
+ if (middleBoundary < halfOffsetWidth) {
1357
+ self.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
1358
+ } else if ((this.options.contentWindow.innerWidth - middleBoundary) < halfOffsetWidth) {
1359
+ self.anchorPreview.style.left = this.options.contentWindow.innerWidth + defaultLeft - halfOffsetWidth + 'px';
1360
+ } else {
1361
+ self.anchorPreview.style.left = defaultLeft + middleBoundary + 'px';
1362
+ }
1363
+
1364
+ if (this.anchorPreview && !this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
1365
+ this.anchorPreview.classList.add('medium-editor-anchor-preview-active');
1366
+ }
1367
+
1368
+ return this;
1369
+ },
1370
+
1371
+ // TODO: break method
1372
+ observeAnchorPreview: function (anchorEl) {
1373
+ var self = this,
1374
+ lastOver = (new Date()).getTime(),
1375
+ over = true,
1376
+ stamp = function () {
1377
+ lastOver = (new Date()).getTime();
1378
+ over = true;
1379
+ },
1380
+ unstamp = function (e) {
1381
+ if (!e.relatedTarget || !/anchor-preview/.test(e.relatedTarget.className)) {
1382
+ over = false;
1383
+ }
1384
+ },
1385
+ interval_timer = setInterval(function () {
1386
+ if (over) {
1387
+ return true;
1388
+ }
1389
+ var durr = (new Date()).getTime() - lastOver;
1390
+ if (durr > self.options.anchorPreviewHideDelay) {
1391
+ // hide the preview 1/2 second after mouse leaves the link
1392
+ self.hideAnchorPreview();
1393
+
1394
+ // cleanup
1395
+ clearInterval(interval_timer);
1396
+ self.off(self.anchorPreview, 'mouseover', stamp);
1397
+ self.off(self.anchorPreview, 'mouseout', unstamp);
1398
+ self.off(anchorEl, 'mouseover', stamp);
1399
+ self.off(anchorEl, 'mouseout', unstamp);
1400
+
1401
+ }
1402
+ }, 200);
1403
+
1404
+ this.on(self.anchorPreview, 'mouseover', stamp);
1405
+ this.on(self.anchorPreview, 'mouseout', unstamp);
1406
+ this.on(anchorEl, 'mouseover', stamp);
1407
+ this.on(anchorEl, 'mouseout', unstamp);
1408
+ },
1409
+
1410
+ createAnchorPreview: function () {
1411
+ var self = this,
1412
+ anchorPreview = this.options.ownerDocument.createElement('div');
1413
+
1414
+ anchorPreview.id = 'medium-editor-anchor-preview-' + this.id;
1415
+ anchorPreview.className = 'medium-editor-anchor-preview';
1416
+ anchorPreview.innerHTML = this.anchorPreviewTemplate();
1417
+ this.options.elementsContainer.appendChild(anchorPreview);
1418
+
1419
+ this.on(anchorPreview, 'click', function () {
1420
+ self.anchorPreviewClickHandler();
1421
+ });
1422
+
1423
+ return anchorPreview;
1424
+ },
1425
+
1426
+ anchorPreviewTemplate: function () {
1427
+ return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' +
1428
+ ' <i class="medium-editor-toolbar-anchor-preview-inner"></i>' +
1429
+ '</div>';
1430
+ },
1431
+
1432
+ anchorPreviewClickHandler: function (e) {
1433
+ if (!this.options.disableAnchorForm && this.activeAnchor) {
1434
+
1435
+ var self = this,
1436
+ range = this.options.ownerDocument.createRange(),
1437
+ sel = this.options.contentWindow.getSelection();
1438
+
1439
+ range.selectNodeContents(self.activeAnchor);
1440
+ sel.removeAllRanges();
1441
+ sel.addRange(range);
1442
+ // Using setTimeout + options.delay because:
1443
+ // We may actually be displaying the anchor preview, which should be controlled by options.delay
1444
+ setTimeout(function () {
1445
+ if (self.activeAnchor) {
1446
+ self.showAnchorForm(self.activeAnchor.href);
1447
+ }
1448
+ self.keepToolbarAlive = false;
1449
+ }, this.options.delay);
1450
+
1451
+ }
1452
+
1453
+ this.hideAnchorPreview();
1454
+ },
1455
+
1456
+ editorAnchorObserver: function (e) {
1457
+ var self = this,
1458
+ overAnchor = true,
1459
+ leaveAnchor = function () {
1460
+ // mark the anchor as no longer hovered, and stop listening
1461
+ overAnchor = false;
1462
+ self.off(self.activeAnchor, 'mouseout', leaveAnchor);
1463
+ };
1464
+
1465
+ if (e.target && e.target.tagName.toLowerCase() === 'a') {
1466
+
1467
+ // Detect empty href attributes
1468
+ // The browser will make href="" or href="#top"
1469
+ // into absolute urls when accessed as e.targed.href, so check the html
1470
+ if (!/href=["']\S+["']/.test(e.target.outerHTML) || /href=["']#\S+["']/.test(e.target.outerHTML)) {
1471
+ return true;
1472
+ }
1473
+
1474
+ // only show when hovering on anchors
1475
+ if (this.isToolbarShown()) {
1476
+ // only show when toolbar is not present
1477
+ return true;
1478
+ }
1479
+ this.activeAnchor = e.target;
1480
+ this.on(this.activeAnchor, 'mouseout', leaveAnchor);
1481
+ // Using setTimeout + options.delay because:
1482
+ // - We're going to show the anchor preview according to the configured delay
1483
+ // if the mouse has not left the anchor tag in that time
1484
+ setTimeout(function () {
1485
+ if (overAnchor) {
1486
+ self.showAnchorPreview(e.target);
1487
+ }
1488
+ }, this.options.delay);
1489
+ }
1490
+ },
1491
+
1492
+ bindAnchorPreview: function (index) {
1493
+ var i, self = this;
1494
+ this.editorAnchorObserverWrapper = function (e) {
1495
+ self.editorAnchorObserver(e);
1496
+ };
1497
+ for (i = 0; i < this.elements.length; i += 1) {
1498
+ this.on(this.elements[i], 'mouseover', this.editorAnchorObserverWrapper);
1499
+ }
1500
+ return this;
1501
+ },
1502
+
1503
+ checkLinkFormat: function (value) {
1504
+ var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/;
1505
+ return (re.test(value) ? '' : 'http://') + value;
1506
+ },
1507
+
1508
+ setTargetBlank: function (el) {
1509
+ var i;
1510
+ el = el || getSelectionStart.call(this);
1511
+ if (el.tagName.toLowerCase() === 'a') {
1512
+ el.target = '_blank';
1513
+ } else {
1514
+ el = el.getElementsByTagName('a');
1515
+
1516
+ for (i = 0; i < el.length; i += 1) {
1517
+ el[i].target = '_blank';
1518
+ }
1519
+ }
1520
+ },
1521
+
1522
+ setButtonClass: function (buttonClass) {
1523
+ var el = getSelectionStart.call(this),
1524
+ classes = buttonClass.split(' '),
1525
+ i, j;
1526
+ if (el.tagName.toLowerCase() === 'a') {
1527
+ for (j = 0; j < classes.length; j += 1) {
1528
+ el.classList.add(classes[j]);
1529
+ }
1530
+ } else {
1531
+ el = el.getElementsByTagName('a');
1532
+ for (i = 0; i < el.length; i += 1) {
1533
+ for (j = 0; j < classes.length; j += 1) {
1534
+ el[i].classList.add(classes[j]);
1535
+ }
1536
+ }
1537
+ }
1538
+ },
1539
+
1540
+ createLink: function (input, target, buttonClass) {
1541
+ var i, event;
1542
+
1543
+ if (input.value.trim().length === 0) {
1544
+ this.hideToolbarActions();
1545
+ return;
1546
+ }
1547
+
1548
+ restoreSelection.call(this, this.savedSelection);
1549
+
1550
+ if (this.options.checkLinkFormat) {
1551
+ input.value = this.checkLinkFormat(input.value);
1552
+ }
1553
+
1554
+ this.options.ownerDocument.execCommand('createLink', false, input.value);
1555
+
1556
+ if (this.options.targetBlank || target === "_blank") {
1557
+ this.setTargetBlank();
1558
+ }
1559
+
1560
+ if (buttonClass) {
1561
+ this.setButtonClass(buttonClass);
1562
+ }
1563
+
1564
+ if (this.options.targetBlank || target === "_blank" || buttonClass) {
1565
+ event = this.options.ownerDocument.createEvent("HTMLEvents");
1566
+ event.initEvent("input", true, true, this.options.contentWindow);
1567
+ for (i = 0; i < this.elements.length; i += 1) {
1568
+ this.elements[i].dispatchEvent(event);
1569
+ }
1570
+ }
1571
+
1572
+ this.checkSelection();
1573
+ this.showToolbarActions();
1574
+ input.value = '';
1575
+ },
1576
+
1577
+ positionToolbarIfShown: function() {
1578
+ if (this.isToolbarShown()) {
1579
+ this.setToolbarPosition();
1580
+ }
1581
+ },
1582
+
1583
+ // handleResize is debounced because:
1584
+ // - It will be called when the browser is resizing, which can fire many times very quickly
1585
+ // - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
1586
+ handleResize: debounce(function() {
1587
+ this.positionToolbarIfShown();
1588
+ }),
1589
+
1590
+ bindWindowActions: function () {
1591
+ var self = this;
1592
+
1593
+ // Add a scroll event for sticky toolbar
1594
+ if ( this.options.staticToolbar && this.options.stickyToolbar ) {
1595
+ // On scroll, re-position the toolbar
1596
+ this.on(window, 'scroll', function() {
1597
+ self.positionToolbarIfShown();
1598
+ }, true);
1599
+ }
1600
+
1601
+ this.on(this.options.contentWindow, 'resize', function() {
1602
+ self.handleResize();
1603
+ });
1604
+ return this;
1605
+ },
1606
+
1607
+ activate: function () {
1608
+ if (this.isActive) {
1609
+ return;
1610
+ }
1611
+
1612
+ this.setup();
1613
+ },
1614
+
1615
+ // TODO: break method
1616
+ deactivate: function () {
1617
+ var i;
1618
+ if (!this.isActive) {
1619
+ return;
1620
+ }
1621
+ this.isActive = false;
1622
+
1623
+ if (this.toolbar !== undefined) {
1624
+ this.options.elementsContainer.removeChild(this.anchorPreview);
1625
+ this.options.elementsContainer.removeChild(this.toolbar);
1626
+ delete this.toolbar;
1627
+ delete this.anchorPreview;
1628
+ }
1629
+
1630
+ for (i = 0; i < this.elements.length; i += 1) {
1631
+ this.elements[i].removeAttribute('contentEditable');
1632
+ this.elements[i].removeAttribute('data-medium-element');
1633
+ }
1634
+
1635
+ this.removeAllEvents();
1636
+ },
1637
+
1638
+ htmlEntities: function (str) {
1639
+ // converts special characters (like <) into their escaped/encoded values (like &lt;).
1640
+ // This allows you to show to display the string without the browser reading it as HTML.
1641
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1642
+ },
1643
+
1644
+ bindPaste: function () {
1645
+ var i, self = this;
1646
+ this.pasteWrapper = function (e) {
1647
+ var paragraphs,
1648
+ html = '',
1649
+ p,
1650
+ dataFormatHTML = 'text/html',
1651
+ dataFormatPlain = 'text/plain';
1652
+
1653
+ this.classList.remove('medium-editor-placeholder');
1654
+ if (!self.options.forcePlainText && !self.options.cleanPastedHTML) {
1655
+ return this;
1656
+ }
1657
+
1658
+ if (window.clipboardData && e.clipboardData === undefined) {
1659
+ e.clipboardData = window.clipboardData;
1660
+ // If window.clipboardData exists, but e.clipboardData doesn't exist,
1661
+ // we're probably in IE. IE only has two possibilities for clipboard
1662
+ // data format: 'Text' and 'URL'.
1663
+ //
1664
+ // Of the two, we want 'Text':
1665
+ dataFormatHTML = 'Text';
1666
+ dataFormatPlain = 'Text';
1667
+ }
1668
+
1669
+ if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) {
1670
+ e.preventDefault();
1671
+
1672
+ if (self.options.cleanPastedHTML && e.clipboardData.getData(dataFormatHTML)) {
1673
+ return self.cleanPaste(e.clipboardData.getData(dataFormatHTML));
1674
+ }
1675
+ if (!(self.options.disableReturn || this.getAttribute('data-disable-return'))) {
1676
+ paragraphs = e.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
1677
+ for (p = 0; p < paragraphs.length; p += 1) {
1678
+ if (paragraphs[p] !== '') {
1679
+ if (navigator.userAgent.match(/firefox/i) && p === 0) {
1680
+ html += self.htmlEntities(paragraphs[p]);
1681
+ } else {
1682
+ html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>';
1683
+ }
1684
+ }
1685
+ }
1686
+ insertHTMLCommand(self.options.ownerDocument, html);
1687
+ } else {
1688
+ html = self.htmlEntities(e.clipboardData.getData(dataFormatPlain));
1689
+ insertHTMLCommand(self.options.ownerDocument, html);
1690
+ }
1691
+ }
1692
+ };
1693
+ for (i = 0; i < this.elements.length; i += 1) {
1694
+ this.on(this.elements[i], 'paste', this.pasteWrapper);
1695
+ }
1696
+ return this;
1697
+ },
1698
+
1699
+ setPlaceholders: function () {
1700
+ if (this.options.disablePlaceholders) {
1701
+ return this;
1702
+ }
1703
+
1704
+ var i,
1705
+ activatePlaceholder = function (el) {
1706
+ if (!(el.querySelector('img')) &&
1707
+ !(el.querySelector('blockquote')) &&
1708
+ el.textContent.replace(/^\s+|\s+$/g, '') === '') {
1709
+ el.classList.add('medium-editor-placeholder');
1710
+ }
1711
+ },
1712
+ placeholderWrapper = function (e) {
1713
+ this.classList.remove('medium-editor-placeholder');
1714
+ if (e.type !== 'keypress') {
1715
+ activatePlaceholder(this);
1716
+ }
1717
+ };
1718
+ for (i = 0; i < this.elements.length; i += 1) {
1719
+ activatePlaceholder(this.elements[i]);
1720
+ this.on(this.elements[i], 'blur', placeholderWrapper);
1721
+ this.on(this.elements[i], 'keypress', placeholderWrapper);
1722
+ }
1723
+ return this;
1724
+ },
1725
+
1726
+ cleanPaste: function (text) {
1727
+
1728
+ /*jslint regexp: true*/
1729
+ /*
1730
+ jslint does not allow character negation, because the negation
1731
+ will not match any unicode characters. In the regexes in this
1732
+ block, negation is used specifically to match the end of an html
1733
+ tag, and in fact unicode characters *should* be allowed.
1734
+ */
1735
+ var i, elList, workEl,
1736
+ el = this.getSelectionElement(),
1737
+ multiline = /<p|<br|<div/.test(text),
1738
+ replacements = [
1739
+
1740
+ // replace two bogus tags that begin pastes from google docs
1741
+ [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
1742
+ [new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
1743
+
1744
+ // un-html spaces and newlines inserted by OS X
1745
+ [new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
1746
+ [new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
1747
+
1748
+ // replace google docs italics+bold with a span to be replaced once the html is inserted
1749
+ [new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
1750
+
1751
+ // replace google docs italics with a span to be replaced once the html is inserted
1752
+ [new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
1753
+
1754
+ //[replace google docs bolds with a span to be replaced once the html is inserted
1755
+ [new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
1756
+
1757
+ // replace manually entered b/i/a tags with real ones
1758
+ [new RegExp(/&lt;(\/?)(i|b|a)&gt;/gi), '<$1$2>'],
1759
+
1760
+ // replace manually a tags with real ones, converting smart-quotes from google docs
1761
+ [new RegExp(/&lt;a\s+href=(&quot;|&rdquo;|&ldquo;|“|”)([^&]+)(&quot;|&rdquo;|&ldquo;|“|”)&gt;/gi), '<a href="$2">']
1762
+
1763
+ ];
1764
+ /*jslint regexp: false*/
1765
+
1766
+ for (i = 0; i < replacements.length; i += 1) {
1767
+ text = text.replace(replacements[i][0], replacements[i][1]);
1768
+ }
1769
+
1770
+ if (multiline) {
1771
+
1772
+ // double br's aren't converted to p tags, but we want paragraphs.
1773
+ elList = text.split('<br><br>');
1774
+
1775
+ this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
1776
+ this.options.ownerDocument.execCommand('insertText', false, "\n");
1777
+
1778
+ // block element cleanup
1779
+ elList = el.querySelectorAll('a,p,div,br');
1780
+ for (i = 0; i < elList.length; i += 1) {
1781
+
1782
+ workEl = elList[i];
1783
+
1784
+ switch (workEl.tagName.toLowerCase()) {
1785
+ case 'a':
1786
+ if (this.options.targetBlank){
1787
+ this.setTargetBlank(workEl);
1788
+ }
1789
+ break;
1790
+ case 'p':
1791
+ case 'div':
1792
+ this.filterCommonBlocks(workEl);
1793
+ break;
1794
+ case 'br':
1795
+ this.filterLineBreak(workEl);
1796
+ break;
1797
+ }
1798
+
1799
+ }
1800
+
1801
+
1802
+ } else {
1803
+
1804
+ this.pasteHTML(text);
1805
+
1806
+ }
1807
+
1808
+ },
1809
+
1810
+ pasteHTML: function (html) {
1811
+ var elList, workEl, i, fragmentBody, pasteBlock = this.options.ownerDocument.createDocumentFragment();
1812
+
1813
+ pasteBlock.appendChild(this.options.ownerDocument.createElement('body'));
1814
+
1815
+ fragmentBody = pasteBlock.querySelector('body');
1816
+ fragmentBody.innerHTML = html;
1817
+
1818
+ this.cleanupSpans(fragmentBody);
1819
+
1820
+ elList = fragmentBody.querySelectorAll('*');
1821
+ for (i = 0; i < elList.length; i += 1) {
1822
+
1823
+ workEl = elList[i];
1824
+
1825
+ // delete ugly attributes
1826
+ workEl.removeAttribute('class');
1827
+ workEl.removeAttribute('style');
1828
+ workEl.removeAttribute('dir');
1829
+
1830
+ if (workEl.tagName.toLowerCase() === 'meta') {
1831
+ workEl.parentNode.removeChild(workEl);
1832
+ }
1833
+
1834
+ }
1835
+ this.options.ownerDocument.execCommand('insertHTML', false, fragmentBody.innerHTML.replace(/&nbsp;/g, ' '));
1836
+ },
1837
+ isCommonBlock: function (el) {
1838
+ return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
1839
+ },
1840
+ filterCommonBlocks: function (el) {
1841
+ if (/^\s*$/.test(el.textContent)) {
1842
+ el.parentNode.removeChild(el);
1843
+ }
1844
+ },
1845
+ filterLineBreak: function (el) {
1846
+ if (this.isCommonBlock(el.previousElementSibling)) {
1847
+
1848
+ // remove stray br's following common block elements
1849
+ el.parentNode.removeChild(el);
1850
+
1851
+ } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
1852
+
1853
+ // remove br's just inside open or close tags of a div/p
1854
+ el.parentNode.removeChild(el);
1855
+
1856
+ } else if (el.parentNode.childElementCount === 1) {
1857
+
1858
+ // and br's that are the only child of a div/p
1859
+ this.removeWithParent(el);
1860
+
1861
+ }
1862
+
1863
+ },
1864
+
1865
+ // remove an element, including its parent, if it is the only element within its parent
1866
+ removeWithParent: function (el) {
1867
+ if (el && el.parentNode) {
1868
+ if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
1869
+ el.parentNode.parentNode.removeChild(el.parentNode);
1870
+ } else {
1871
+ el.parentNode.removeChild(el.parentNode);
1872
+ }
1873
+ }
1874
+ },
1875
+
1876
+ cleanupSpans: function (container_el) {
1877
+
1878
+ var i,
1879
+ el,
1880
+ new_el,
1881
+ spans = container_el.querySelectorAll('.replace-with');
1882
+
1883
+ for (i = 0; i < spans.length; i += 1) {
1884
+
1885
+ el = spans[i];
1886
+ new_el = this.options.ownerDocument.createElement(el.classList.contains('bold') ? 'b' : 'i');
1887
+
1888
+ if (el.classList.contains('bold') && el.classList.contains('italic')) {
1889
+
1890
+ // add an i tag as well if this has both italics and bold
1891
+ new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
1892
+
1893
+ } else {
1894
+
1895
+ new_el.innerHTML = el.innerHTML;
1896
+
1897
+ }
1898
+ el.parentNode.replaceChild(new_el, el);
1899
+
1900
+ }
1901
+
1902
+ spans = container_el.querySelectorAll('span');
1903
+ for (i = 0; i < spans.length; i += 1) {
1904
+
1905
+ el = spans[i];
1906
+
1907
+ // remove empty spans, replace others with their contents
1908
+ if (/^\s*$/.test()) {
1909
+ el.parentNode.removeChild(el);
1910
+ } else {
1911
+ el.parentNode.replaceChild(this.options.ownerDocument.createTextNode(el.textContent), el);
1912
+ }
1913
+
1914
+ }
1915
+
1916
+ }
1917
+
1918
+ };
1919
+
1920
+ }(window, document));