blocky 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (397) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +59 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/blocky/application.js +21 -0
  6. data/app/assets/javascripts/blocky/flash_messages.js +20 -0
  7. data/app/assets/stylesheets/blocky/_imports.scss +3 -0
  8. data/app/assets/stylesheets/blocky/_variables.scss +37 -0
  9. data/app/assets/stylesheets/blocky/application.css.scss +20 -0
  10. data/app/assets/stylesheets/blocky/application/content_blocks/edit.scss +13 -0
  11. data/app/assets/stylesheets/blocky/application/content_blocks/index.scss +81 -0
  12. data/app/assets/stylesheets/blocky/application/layout.scss +30 -0
  13. data/app/assets/stylesheets/blocky/shared/buttons.scss +119 -0
  14. data/app/assets/stylesheets/blocky/shared/flash_messages.scss +28 -0
  15. data/app/assets/stylesheets/blocky/shared/typography/body.scss +91 -0
  16. data/app/assets/stylesheets/blocky/shared/typography/forms.scss +81 -0
  17. data/app/assets/stylesheets/blocky/shared/typography/headings.scss +38 -0
  18. data/app/assets/stylesheets/blocky/shared/typography/lists.scss +22 -0
  19. data/app/controllers/blocky/application_controller.rb +7 -0
  20. data/app/controllers/blocky/content_blocks_controller.rb +44 -0
  21. data/app/controllers/concerns/blocky/auth.rb +53 -0
  22. data/app/helpers/blocky/application_helper.rb +26 -0
  23. data/app/helpers/blocky_helper.rb +18 -0
  24. data/app/models/blocky/ability.rb +11 -0
  25. data/app/models/blocky/content_block.rb +27 -0
  26. data/app/views/blocky/content_blocks/_content_block.html.erb +1 -0
  27. data/app/views/blocky/content_blocks/edit.html.erb +44 -0
  28. data/app/views/blocky/content_blocks/index.html.erb +84 -0
  29. data/app/views/layouts/blocky/_flash_messages.html.erb +13 -0
  30. data/app/views/layouts/blocky/application.html.erb +20 -0
  31. data/config/jshint.json +28 -0
  32. data/config/routes.rb +3 -0
  33. data/db/migrate/20140326210255_create_blocky_content_blocks.rb +12 -0
  34. data/lib/blocky.rb +8 -0
  35. data/lib/blocky/engine.rb +19 -0
  36. data/lib/blocky/version.rb +3 -0
  37. data/lib/generators/blocky/install_generator.rb +94 -0
  38. data/lib/tasks/blocky_tasks.rake +4 -0
  39. data/spec/dummy/README.rdoc +28 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  44. data/spec/dummy/app/controllers/static_pages_controller.rb +7 -0
  45. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  46. data/spec/dummy/app/models/blocky/ability.rb +9 -0
  47. data/spec/dummy/app/views/layouts/application.html.erb +25 -0
  48. data/spec/dummy/app/views/static_pages/contact.html.erb +5 -0
  49. data/spec/dummy/app/views/static_pages/home.html.erb +7 -0
  50. data/spec/dummy/bin/bundle +3 -0
  51. data/spec/dummy/bin/rails +4 -0
  52. data/spec/dummy/bin/rake +4 -0
  53. data/spec/dummy/config.ru +4 -0
  54. data/spec/dummy/config/application.rb +23 -0
  55. data/spec/dummy/config/boot.rb +5 -0
  56. data/spec/dummy/config/database.yml +25 -0
  57. data/spec/dummy/config/environment.rb +5 -0
  58. data/spec/dummy/config/environments/development.rb +29 -0
  59. data/spec/dummy/config/environments/production.rb +80 -0
  60. data/spec/dummy/config/environments/test.rb +36 -0
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/spec/dummy/config/initializers/blocky.rb +4 -0
  63. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/spec/dummy/config/initializers/inflections.rb +16 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  67. data/spec/dummy/config/initializers/session_store.rb +3 -0
  68. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/spec/dummy/config/locales/en.yml +23 -0
  70. data/spec/dummy/config/routes.rb +5 -0
  71. data/spec/dummy/db/development.sqlite3 +0 -0
  72. data/spec/dummy/db/migrate/20140326211453_create_blocky_content_blocks.blocky.rb +13 -0
  73. data/spec/dummy/db/schema.rb +25 -0
  74. data/spec/dummy/db/test.sqlite3 +0 -0
  75. data/spec/dummy/log/development.log +14038 -0
  76. data/spec/dummy/log/test.log +151 -0
  77. data/spec/dummy/public/404.html +58 -0
  78. data/spec/dummy/public/422.html +58 -0
  79. data/spec/dummy/public/500.html +57 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/tmp/cache/E76/DE0/blocky%2Fcontent_blocks%2F1-20140326211612204255000 +1 -0
  82. data/spec/dummy/tmp/cache/E79/630/blocky%2Fcontent_blocks%2F5-20140326220740803013000 +0 -0
  83. data/spec/dummy/tmp/cache/E7D/2C0/blocky%2Fcontent_blocks%2F2-20140326211803501817000 +1 -0
  84. data/spec/dummy/tmp/cache/E7F/480/blocky%2Fcontent_blocks%2F2-20140326211735204743000 +0 -0
  85. data/spec/dummy/tmp/cache/E83/720/blocky%2Fcontent_blocks%2F3-20140326212218805733000 +0 -0
  86. data/spec/dummy/tmp/cache/E85/4F0/blocky%2Fcontent_blocks%2F1-20140326211503578725000 +0 -0
  87. data/spec/dummy/tmp/cache/E8A/C50/blocky%2Fcontent_blocks%2F6-20140520152442627674000 +0 -0
  88. data/spec/dummy/tmp/cache/E8F/EE0/blocky%2Fcontent_blocks%2F4-20140326214706659274000 +0 -0
  89. data/spec/dummy/tmp/cache/E95/020/blocky%2Fcontent_blocks%2F4-20140520170832796695000 +2 -0
  90. data/spec/dummy/tmp/cache/assets/development/sass/1330522281c54fc4f0f88c06856f0cc8052c25e8/buttons.scssc +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sass/1330522281c54fc4f0f88c06856f0cc8052c25e8/flash_messages.scssc +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_compact.scssc +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_flex-grid.scssc +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_grid-width.scssc +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_linear-gradient.scssc +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_modular-scale.scssc +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_px-to-em.scssc +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_radial-gradient.scssc +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_tint-shade.scssc +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sass/16328cd177fa77f37d597cddc166b9d61ed7bdc6/_transition-property-name.scssc +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_bordered-pulled.scssc +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_core.scssc +0 -0
  103. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_fixed-width.scssc +0 -0
  104. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_icons.scssc +0 -0
  105. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_larger.scssc +0 -0
  106. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_list.scssc +0 -0
  107. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_mixins.scssc +0 -0
  108. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_path.scssc +0 -0
  109. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_rotated-flipped.scssc +0 -0
  110. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_spinning.scssc +0 -0
  111. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_stacked.scssc +0 -0
  112. data/spec/dummy/tmp/cache/assets/development/sass/2ed2f1a12f96a5966a3f2f4a226be3068f8e609b/_variables.scssc +0 -0
  113. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_fill-parent.scssc +0 -0
  114. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_grid.scssc +0 -0
  115. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_media.scssc +0 -0
  116. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_omega.scssc +0 -0
  117. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_outer-container.scssc +0 -0
  118. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_pad.scssc +0 -0
  119. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_private.scssc +0 -0
  120. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_reset.scssc +0 -0
  121. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_row.scssc +0 -0
  122. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_shift.scssc +0 -0
  123. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_span-columns.scssc +0 -0
  124. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_to-deprecate.scssc +0 -0
  125. data/spec/dummy/tmp/cache/assets/development/sass/3fb34f29991e8a63348b29fade50f1de29147c8f/_visual-grid.scssc +0 -0
  126. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/_imports.scssc +0 -0
  127. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/_variables.scssc +0 -0
  128. data/spec/dummy/tmp/cache/assets/development/sass/45a8d9925ba36450f7e3862c914588ae8720003e/application.css.scssc +0 -0
  129. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_deprecated-webkit-gradient.scssc +0 -0
  130. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_gradient-positions-parser.scssc +0 -0
  131. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_linear-positions-parser.scssc +0 -0
  132. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_radial-arg-parser.scssc +0 -0
  133. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_radial-positions-parser.scssc +0 -0
  134. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_render-gradients.scssc +0 -0
  135. data/spec/dummy/tmp/cache/assets/development/sass/4808b00b5a9150b9a854e6877435916328f1da81/_shape-size-stripper.scssc +0 -0
  136. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_animation.scssc +0 -0
  137. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_appearance.scssc +0 -0
  138. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_backface-visibility.scssc +0 -0
  139. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_background-image.scssc +0 -0
  140. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_background.scssc +0 -0
  141. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_border-image.scssc +0 -0
  142. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_border-radius.scssc +0 -0
  143. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_box-sizing.scssc +0 -0
  144. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_columns.scssc +0 -0
  145. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_flex-box.scssc +0 -0
  146. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_font-face.scssc +0 -0
  147. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_hidpi-media-query.scssc +0 -0
  148. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_image-rendering.scssc +0 -0
  149. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_inline-block.scssc +0 -0
  150. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_keyframes.scssc +0 -0
  151. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_linear-gradient.scssc +0 -0
  152. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_perspective.scssc +0 -0
  153. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_placeholder.scssc +0 -0
  154. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_radial-gradient.scssc +0 -0
  155. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_transform.scssc +0 -0
  156. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_transition.scssc +0 -0
  157. data/spec/dummy/tmp/cache/assets/development/sass/5e68b12f5742e819b59fae21d1dedfbc3da30721/_user-select.scssc +0 -0
  158. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/body.scssc +0 -0
  159. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/forms.scssc +0 -0
  160. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/headings.scssc +0 -0
  161. data/spec/dummy/tmp/cache/assets/development/sass/7c52080b507114ff73ac654c0177e1dca603143b/lists.scssc +0 -0
  162. data/spec/dummy/tmp/cache/assets/development/sass/86088d5ecf0a5701fd22ac3b30255eb2bb94f550/_bourbon-deprecated-upcoming.scssc +0 -0
  163. data/spec/dummy/tmp/cache/assets/development/sass/9de1036a47599e1de75fdb2094e4960ffb18ffcf/_grid.scssc +0 -0
  164. data/spec/dummy/tmp/cache/assets/development/sass/9de1036a47599e1de75fdb2094e4960ffb18ffcf/_visual-grid.scssc +0 -0
  165. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/_bourbon.scssc +0 -0
  166. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/_neat.scssc +0 -0
  167. data/spec/dummy/tmp/cache/assets/development/sass/9e0f22094073a2727149869ee451bf5155b6b1de/font-awesome.scssc +0 -0
  168. data/spec/dummy/tmp/cache/assets/development/sass/ab9fa2e8e2c4d0592ca80a0885630a538ee36082/_neat-helpers.scssc +0 -0
  169. data/spec/dummy/tmp/cache/assets/development/sass/ccd8e9094d6c3af8a9493bb7fee8eae1bf9b4c52/_new-breakpoint.scssc +0 -0
  170. data/spec/dummy/tmp/cache/assets/development/sass/ccd8e9094d6c3af8a9493bb7fee8eae1bf9b4c52/_private.scssc +0 -0
  171. data/spec/dummy/tmp/cache/assets/development/sass/e32eee285e5a7bcd847a61263245c70a38794724/edit.scssc +0 -0
  172. data/spec/dummy/tmp/cache/assets/development/sass/e32eee285e5a7bcd847a61263245c70a38794724/index.scssc +0 -0
  173. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_button.scssc +0 -0
  174. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_clearfix.scssc +0 -0
  175. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_font-family.scssc +0 -0
  176. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_hide-text.scssc +0 -0
  177. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_html5-input-types.scssc +0 -0
  178. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_position.scssc +0 -0
  179. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_prefixer.scssc +0 -0
  180. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_retina-image.scssc +0 -0
  181. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_size.scssc +0 -0
  182. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_timing-functions.scssc +0 -0
  183. data/spec/dummy/tmp/cache/assets/development/sass/e792c58dcbc932d7b0bbb62be67c59da9796d04b/_triangle.scssc +0 -0
  184. data/spec/dummy/tmp/cache/assets/development/sass/f6aab6f5c59ff0b3c4ff7e11237d10cd45b5353c/layout.scssc +0 -0
  185. data/spec/dummy/tmp/cache/assets/development/sprockets/00b315c99e05fd17eb6ee63c05874610 +0 -0
  186. data/spec/dummy/tmp/cache/assets/development/sprockets/0427945c7b4a3a10bc1b628680c1ac11 +0 -0
  187. data/spec/dummy/tmp/cache/assets/development/sprockets/07a7381ab720b1fcdf48d09e6f4aca3f +0 -0
  188. data/spec/dummy/tmp/cache/assets/development/sprockets/0cba4b819c78286147e22cce1cff06de +0 -0
  189. data/spec/dummy/tmp/cache/assets/development/sprockets/0d045cc286244754010ae01a766487a9 +0 -0
  190. data/spec/dummy/tmp/cache/assets/development/sprockets/0f027312d7555aefd043bc94e678e35d +0 -0
  191. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  192. data/spec/dummy/tmp/cache/assets/development/sprockets/1590fdab706270c5d350582acc774711 +0 -0
  193. data/spec/dummy/tmp/cache/assets/development/sprockets/17d324480cb2a2a47bedc3ef4d022b6d +0 -0
  194. data/spec/dummy/tmp/cache/assets/development/sprockets/21eee9c5050e081a5a1cf91cea7d268f +0 -0
  195. data/spec/dummy/tmp/cache/assets/development/sprockets/2510376ff2d7b20d23bc38a46fcfa055 +0 -0
  196. data/spec/dummy/tmp/cache/assets/development/sprockets/26dfb7b51a72e95c414c783f2ba69222 +0 -0
  197. data/spec/dummy/tmp/cache/assets/development/sprockets/2a12abb42fb17b6520d9d9ad589a20af +0 -0
  198. data/spec/dummy/tmp/cache/assets/development/sprockets/2a87faf509efd55dfdbd16b0fa8239b4 +0 -0
  199. data/spec/dummy/tmp/cache/assets/development/sprockets/2c9e3b499f8c7da0a3d3de2510f78846 +0 -0
  200. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  201. data/spec/dummy/tmp/cache/assets/development/sprockets/2fd51b2bfa391389108d8de69733d84b +0 -0
  202. data/spec/dummy/tmp/cache/assets/development/sprockets/3075115024bd10f7adb951b3baec184c +0 -0
  203. data/spec/dummy/tmp/cache/assets/development/sprockets/309a524a7494382fd3346a82b810c20b +0 -0
  204. data/spec/dummy/tmp/cache/assets/development/sprockets/346918a665641c5e32c0bb368f032b07 +0 -0
  205. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  206. data/spec/dummy/tmp/cache/assets/development/sprockets/359a931a346dd73a40ea88a3e145e4b3 +0 -0
  207. data/spec/dummy/tmp/cache/assets/development/sprockets/3a8c2654e77e18a35f25642e5e3bcb88 +0 -0
  208. data/spec/dummy/tmp/cache/assets/development/sprockets/3a8ecba67750b8ad7df3ba9362857756 +0 -0
  209. data/spec/dummy/tmp/cache/assets/development/sprockets/3aa302111c6bea14859db6fea34cdb94 +0 -0
  210. data/spec/dummy/tmp/cache/assets/development/sprockets/3b05198beb41d8fc2b4a207128140f89 +0 -0
  211. data/spec/dummy/tmp/cache/assets/development/sprockets/3cc020c5e2e8ab2e4acd1b5a9fdc16ab +0 -0
  212. data/spec/dummy/tmp/cache/assets/development/sprockets/3d89aed04c6cdc16f097a2aabfa2b867 +0 -0
  213. data/spec/dummy/tmp/cache/assets/development/sprockets/420c2d9425469c305387fa81d5ef1b30 +0 -0
  214. data/spec/dummy/tmp/cache/assets/development/sprockets/43272d99260411613e550de399f08be8 +0 -0
  215. data/spec/dummy/tmp/cache/assets/development/sprockets/4443d626de6926b2b0e97011a9d3283b +0 -0
  216. data/spec/dummy/tmp/cache/assets/development/sprockets/44ee5490907c6fdf6530eeea203a3908 +0 -0
  217. data/spec/dummy/tmp/cache/assets/development/sprockets/4c016d0efbbfe9fa01bc4d8d800f8548 +0 -0
  218. data/spec/dummy/tmp/cache/assets/development/sprockets/4f41b46926dc79b6d378c60e5e12b1ee +0 -0
  219. data/spec/dummy/tmp/cache/assets/development/sprockets/4ffbbdb75e91e3ef76f0356bd8c9fdb9 +0 -0
  220. data/spec/dummy/tmp/cache/assets/development/sprockets/530a0c5b0d469facb66e00ca952d9069 +0 -0
  221. data/spec/dummy/tmp/cache/assets/development/sprockets/562b764135bc28106a524123de319fac +0 -0
  222. data/spec/dummy/tmp/cache/assets/development/sprockets/59cc9befd7bad256dc0139dc7c32cf34 +0 -0
  223. data/spec/dummy/tmp/cache/assets/development/sprockets/5d455edb538f158d67b710d4c8fb9674 +0 -0
  224. data/spec/dummy/tmp/cache/assets/development/sprockets/5ea0f006a9c4f5744aa307242e43d11a +0 -0
  225. data/spec/dummy/tmp/cache/assets/development/sprockets/5f1b58c3d1b05b422b95fad6ff1236ad +0 -0
  226. data/spec/dummy/tmp/cache/assets/development/sprockets/5f9c0944ded3ac10412c77764c4f83a2 +0 -0
  227. data/spec/dummy/tmp/cache/assets/development/sprockets/613e23bb1e9f2c8dbd8166803b85c57c +0 -0
  228. data/spec/dummy/tmp/cache/assets/development/sprockets/61ac9eefcbf748e59c79b6e84835babd +0 -0
  229. data/spec/dummy/tmp/cache/assets/development/sprockets/64b57c4794688cd1dd9723c89f564ad7 +0 -0
  230. data/spec/dummy/tmp/cache/assets/development/sprockets/65e56fb427fa0e3e148515a9ea998426 +0 -0
  231. data/spec/dummy/tmp/cache/assets/development/sprockets/7344269d882a3e910b3806b007dd9d71 +0 -0
  232. data/spec/dummy/tmp/cache/assets/development/sprockets/74104d7c75a8966708478065693bcca8 +0 -0
  233. data/spec/dummy/tmp/cache/assets/development/sprockets/7415c9f0125deaebddd1c12b2c26367d +0 -0
  234. data/spec/dummy/tmp/cache/assets/development/sprockets/7703867f0dd6cf86b9eb01bce45d1f02 +0 -0
  235. data/spec/dummy/tmp/cache/assets/development/sprockets/7871e5235770cb3607023e3646149255 +0 -0
  236. data/spec/dummy/tmp/cache/assets/development/sprockets/7ce4b6005f5a1535c297ebe897723551 +0 -0
  237. data/spec/dummy/tmp/cache/assets/development/sprockets/8035b3603b1f603513ce0491ef5ee98c +0 -0
  238. data/spec/dummy/tmp/cache/assets/development/sprockets/87b0d0df42a2bb5d08a5417fd0274a6e +0 -0
  239. data/spec/dummy/tmp/cache/assets/development/sprockets/88b8972e085bc284af152cf7b158334c +0 -0
  240. data/spec/dummy/tmp/cache/assets/development/sprockets/8b94d81de8f6f238037cab9a4a1a0c30 +0 -0
  241. data/spec/dummy/tmp/cache/assets/development/sprockets/8bd0f5b2d316327657d6709cf130a550 +0 -0
  242. data/spec/dummy/tmp/cache/assets/development/sprockets/8efc9dce2839c7874c8c1bb8a662acc9 +0 -0
  243. data/spec/dummy/tmp/cache/assets/development/sprockets/8f4e3d9674b96cae9b98843cd6ff18c8 +0 -0
  244. data/spec/dummy/tmp/cache/assets/development/sprockets/92a576a2bd81929a26b143cc02e430aa +0 -0
  245. data/spec/dummy/tmp/cache/assets/development/sprockets/9e58e917e624e0cd3b2154f01fd24962 +0 -0
  246. data/spec/dummy/tmp/cache/assets/development/sprockets/a0c1f9e3d33f8de75d0a594321456c9b +0 -0
  247. data/spec/dummy/tmp/cache/assets/development/sprockets/a147199745f7229dc608d3cefce5df45 +0 -0
  248. data/spec/dummy/tmp/cache/assets/development/sprockets/a72bca95d5c7b54e61fa4e0ebc869d4e +0 -0
  249. data/spec/dummy/tmp/cache/assets/development/sprockets/abe150382a2bf6466264fe8866f34157 +0 -0
  250. data/spec/dummy/tmp/cache/assets/development/sprockets/afa453539adb4bc1f7d844dcb361460d +0 -0
  251. data/spec/dummy/tmp/cache/assets/development/sprockets/b0352a0c3bd6f28326a7fc34fa51fa16 +0 -0
  252. data/spec/dummy/tmp/cache/assets/development/sprockets/b0f77fd26cd7fd672f25ec90ed445fe9 +0 -0
  253. data/spec/dummy/tmp/cache/assets/development/sprockets/b0f88bcdb6579681288ffc314d36281b +0 -0
  254. data/spec/dummy/tmp/cache/assets/development/sprockets/b304f5f5248fd1b0910adaeaf4430da9 +0 -0
  255. data/spec/dummy/tmp/cache/assets/development/sprockets/b5dd7a2c9cdd592440c578b49edff2cb +0 -0
  256. data/spec/dummy/tmp/cache/assets/development/sprockets/b7f85043b18466180fb325cbc7e709c8 +0 -0
  257. data/spec/dummy/tmp/cache/assets/development/sprockets/b8096cbfaf0aa44715d5411926461117 +0 -0
  258. data/spec/dummy/tmp/cache/assets/development/sprockets/b95fb213c4dd9e7f1fdfe6120e88e2e8 +0 -0
  259. data/spec/dummy/tmp/cache/assets/development/sprockets/be95835376c06f09fdd2a3afc3dd195e +0 -0
  260. data/spec/dummy/tmp/cache/assets/development/sprockets/bfacd7859276780db88488d7d7e41b59 +0 -0
  261. data/spec/dummy/tmp/cache/assets/development/sprockets/c954e3a10a9a27d22bbde51534852ce1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/development/sprockets/cee17b1df9168d8c9075ec7c74d54cb5 +0 -0
  263. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  264. data/spec/dummy/tmp/cache/assets/development/sprockets/d101f82ff4e2f7db4eb46cc760aa1d3f +0 -0
  265. data/spec/dummy/tmp/cache/assets/development/sprockets/d1689f5fa7ff2ae20077f9de7fd130e1 +0 -0
  266. data/spec/dummy/tmp/cache/assets/development/sprockets/d31431b0b9739b7fa0843ccfd02d002b +0 -0
  267. data/spec/dummy/tmp/cache/assets/development/sprockets/d410c298186b4110d38ff3d89b29c925 +0 -0
  268. data/spec/dummy/tmp/cache/assets/development/sprockets/d4b45f77d1eed50fcf90f8c5511b99b6 +0 -0
  269. data/spec/dummy/tmp/cache/assets/development/sprockets/d62a46b4d21da7c9091dcbd8fa01da34 +0 -0
  270. data/spec/dummy/tmp/cache/assets/development/sprockets/d6477bff4412a5abb06c8acf405c8a87 +0 -0
  271. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  272. data/spec/dummy/tmp/cache/assets/development/sprockets/d985df74e53b12bfa3af9ea8608eacb2 +0 -0
  273. data/spec/dummy/tmp/cache/assets/development/sprockets/d9db27d114516b1b698ad3520e97d1f9 +0 -0
  274. data/spec/dummy/tmp/cache/assets/development/sprockets/da4c7a7a685a765695e1d781590e6a5a +0 -0
  275. data/spec/dummy/tmp/cache/assets/development/sprockets/dbe056c885afef869a46852b1faa9e60 +0 -0
  276. data/spec/dummy/tmp/cache/assets/development/sprockets/df0a14f77c1e0b39c4284d6d6fce9c97 +0 -0
  277. data/spec/dummy/tmp/cache/assets/development/sprockets/e01e232c8b2f8d1711bcd44d6e017dab +0 -0
  278. data/spec/dummy/tmp/cache/assets/development/sprockets/e333f0976af05d7bc7eca78d1383c2af +0 -0
  279. data/spec/dummy/tmp/cache/assets/development/sprockets/e95f94b756f854b191f179bd558ce3c6 +0 -0
  280. data/spec/dummy/tmp/cache/assets/development/sprockets/eb20aca7df145f553b39afab67d5c0a0 +0 -0
  281. data/spec/dummy/tmp/cache/assets/development/sprockets/edaff551a27cde6faf69cc45ba5de859 +0 -0
  282. data/spec/dummy/tmp/cache/assets/development/sprockets/ee44b9ce0a9a1e5e56418ab84da62277 +0 -0
  283. data/spec/dummy/tmp/cache/assets/development/sprockets/f345273bfd8088c36038c49a5fae3218 +0 -0
  284. data/spec/dummy/tmp/cache/assets/development/sprockets/f3a1691881f9395089d43324197ff863 +0 -0
  285. data/spec/dummy/tmp/cache/assets/development/sprockets/f4e80d0621ebd9be1b2b595ce543037f +0 -0
  286. data/spec/dummy/tmp/cache/assets/development/sprockets/f521da421cda5fe4162aeec62e594eca +0 -0
  287. data/spec/dummy/tmp/cache/assets/development/sprockets/f6fcfc1f4419ff69a85c86234ba475cf +0 -0
  288. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  289. data/spec/dummy/tmp/cache/assets/development/sprockets/f8c2ccce91ab7c89ad8922be93c6f363 +0 -0
  290. data/spec/dummy/tmp/cache/assets/development/sprockets/fa0df232aba62e841f6077c735cec1aa +0 -0
  291. data/spec/dummy/tmp/cache/assets/development/sprockets/fb892eb8050ec923cc8084138b1e6e28 +0 -0
  292. data/spec/factories/content_block_factory.rb +13 -0
  293. data/spec/models/blocky/content_block_spec.rb +37 -0
  294. data/spec/spec_helper.rb +34 -0
  295. data/vendor/assets/fonts/blocky/FontAwesome.otf +0 -0
  296. data/vendor/assets/fonts/blocky/fontawesome-webfont.eot +0 -0
  297. data/vendor/assets/fonts/blocky/fontawesome-webfont.svg +414 -0
  298. data/vendor/assets/fonts/blocky/fontawesome-webfont.ttf +0 -0
  299. data/vendor/assets/fonts/blocky/fontawesome-webfont.woff +0 -0
  300. data/vendor/assets/javascripts/blocky/bootstrap.js +1951 -0
  301. data/vendor/assets/javascripts/blocky/codemirror/closebrackets.js +84 -0
  302. data/vendor/assets/javascripts/blocky/codemirror/codemirror.js +4442 -0
  303. data/vendor/assets/javascripts/blocky/codemirror/formatting.js +1 -0
  304. data/vendor/assets/javascripts/blocky/codemirror/xml.js +1 -0
  305. data/vendor/assets/javascripts/blocky/jquery.js +10338 -0
  306. data/vendor/assets/javascripts/blocky/jquery_ujs.js +398 -0
  307. data/vendor/assets/javascripts/blocky/summernote.js +3922 -0
  308. data/vendor/assets/stylesheets/blocky/_bourbon.scss +59 -0
  309. data/vendor/assets/stylesheets/blocky/_neat.scss +21 -0
  310. data/vendor/assets/stylesheets/blocky/bootstrap.css +5785 -0
  311. data/vendor/assets/stylesheets/blocky/bootstrap.css.map +1 -0
  312. data/vendor/assets/stylesheets/blocky/bourbon/_bourbon-deprecated-upcoming.scss +13 -0
  313. data/vendor/assets/stylesheets/blocky/bourbon/addons/_button.scss +273 -0
  314. data/vendor/assets/stylesheets/blocky/bourbon/addons/_clearfix.scss +29 -0
  315. data/vendor/assets/stylesheets/blocky/bourbon/addons/_font-family.scss +5 -0
  316. data/vendor/assets/stylesheets/blocky/bourbon/addons/_hide-text.scss +5 -0
  317. data/vendor/assets/stylesheets/blocky/bourbon/addons/_html5-input-types.scss +56 -0
  318. data/vendor/assets/stylesheets/blocky/bourbon/addons/_position.scss +42 -0
  319. data/vendor/assets/stylesheets/blocky/bourbon/addons/_prefixer.scss +49 -0
  320. data/vendor/assets/stylesheets/blocky/bourbon/addons/_retina-image.scss +32 -0
  321. data/vendor/assets/stylesheets/blocky/bourbon/addons/_size.scss +44 -0
  322. data/vendor/assets/stylesheets/blocky/bourbon/addons/_timing-functions.scss +32 -0
  323. data/vendor/assets/stylesheets/blocky/bourbon/addons/_triangle.scss +45 -0
  324. data/vendor/assets/stylesheets/blocky/bourbon/css3/_animation.scss +52 -0
  325. data/vendor/assets/stylesheets/blocky/bourbon/css3/_appearance.scss +3 -0
  326. data/vendor/assets/stylesheets/blocky/bourbon/css3/_backface-visibility.scss +6 -0
  327. data/vendor/assets/stylesheets/blocky/bourbon/css3/_background-image.scss +48 -0
  328. data/vendor/assets/stylesheets/blocky/bourbon/css3/_background.scss +103 -0
  329. data/vendor/assets/stylesheets/blocky/bourbon/css3/_border-image.scss +55 -0
  330. data/vendor/assets/stylesheets/blocky/bourbon/css3/_border-radius.scss +22 -0
  331. data/vendor/assets/stylesheets/blocky/bourbon/css3/_box-sizing.scss +4 -0
  332. data/vendor/assets/stylesheets/blocky/bourbon/css3/_columns.scss +47 -0
  333. data/vendor/assets/stylesheets/blocky/bourbon/css3/_flex-box.scss +52 -0
  334. data/vendor/assets/stylesheets/blocky/bourbon/css3/_font-face.scss +23 -0
  335. data/vendor/assets/stylesheets/blocky/bourbon/css3/_hidpi-media-query.scss +10 -0
  336. data/vendor/assets/stylesheets/blocky/bourbon/css3/_image-rendering.scss +13 -0
  337. data/vendor/assets/stylesheets/blocky/bourbon/css3/_inline-block.scss +8 -0
  338. data/vendor/assets/stylesheets/blocky/bourbon/css3/_keyframes.scss +43 -0
  339. data/vendor/assets/stylesheets/blocky/bourbon/css3/_linear-gradient.scss +41 -0
  340. data/vendor/assets/stylesheets/blocky/bourbon/css3/_perspective.scss +8 -0
  341. data/vendor/assets/stylesheets/blocky/bourbon/css3/_placeholder.scss +29 -0
  342. data/vendor/assets/stylesheets/blocky/bourbon/css3/_radial-gradient.scss +44 -0
  343. data/vendor/assets/stylesheets/blocky/bourbon/css3/_transform.scss +15 -0
  344. data/vendor/assets/stylesheets/blocky/bourbon/css3/_transition.scss +34 -0
  345. data/vendor/assets/stylesheets/blocky/bourbon/css3/_user-select.scss +3 -0
  346. data/vendor/assets/stylesheets/blocky/bourbon/functions/_compact.scss +11 -0
  347. data/vendor/assets/stylesheets/blocky/bourbon/functions/_flex-grid.scss +39 -0
  348. data/vendor/assets/stylesheets/blocky/bourbon/functions/_grid-width.scss +13 -0
  349. data/vendor/assets/stylesheets/blocky/bourbon/functions/_linear-gradient.scss +13 -0
  350. data/vendor/assets/stylesheets/blocky/bourbon/functions/_modular-scale.scss +40 -0
  351. data/vendor/assets/stylesheets/blocky/bourbon/functions/_px-to-em.scss +8 -0
  352. data/vendor/assets/stylesheets/blocky/bourbon/functions/_radial-gradient.scss +23 -0
  353. data/vendor/assets/stylesheets/blocky/bourbon/functions/_tint-shade.scss +9 -0
  354. data/vendor/assets/stylesheets/blocky/bourbon/functions/_transition-property-name.scss +22 -0
  355. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_deprecated-webkit-gradient.scss +39 -0
  356. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_gradient-positions-parser.scss +13 -0
  357. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_linear-positions-parser.scss +61 -0
  358. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_radial-arg-parser.scss +69 -0
  359. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_radial-positions-parser.scss +18 -0
  360. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_render-gradients.scss +26 -0
  361. data/vendor/assets/stylesheets/blocky/bourbon/helpers/_shape-size-stripper.scss +10 -0
  362. data/vendor/assets/stylesheets/blocky/codemirror/codemirror.css +1 -0
  363. data/vendor/assets/stylesheets/blocky/codemirror/monokai.css +1 -0
  364. data/vendor/assets/stylesheets/blocky/font-awesome.scss +17 -0
  365. data/vendor/assets/stylesheets/blocky/font-awesome/_bordered-pulled.scss +16 -0
  366. data/vendor/assets/stylesheets/blocky/font-awesome/_core.scss +12 -0
  367. data/vendor/assets/stylesheets/blocky/font-awesome/_fixed-width.scss +6 -0
  368. data/vendor/assets/stylesheets/blocky/font-awesome/_icons.scss +412 -0
  369. data/vendor/assets/stylesheets/blocky/font-awesome/_larger.scss +13 -0
  370. data/vendor/assets/stylesheets/blocky/font-awesome/_list.scss +19 -0
  371. data/vendor/assets/stylesheets/blocky/font-awesome/_mixins.scss +20 -0
  372. data/vendor/assets/stylesheets/blocky/font-awesome/_path.scss +13 -0
  373. data/vendor/assets/stylesheets/blocky/font-awesome/_rotated-flipped.scss +9 -0
  374. data/vendor/assets/stylesheets/blocky/font-awesome/_spinning.scss +30 -0
  375. data/vendor/assets/stylesheets/blocky/font-awesome/_stacked.scss +20 -0
  376. data/vendor/assets/stylesheets/blocky/font-awesome/_variables.scss +381 -0
  377. data/vendor/assets/stylesheets/blocky/neat/_neat-helpers.scss +7 -0
  378. data/vendor/assets/stylesheets/blocky/neat/functions/_new-breakpoint.scss +16 -0
  379. data/vendor/assets/stylesheets/blocky/neat/functions/_private.scss +125 -0
  380. data/vendor/assets/stylesheets/blocky/neat/grid/_fill-parent.scss +7 -0
  381. data/vendor/assets/stylesheets/blocky/neat/grid/_grid.scss +5 -0
  382. data/vendor/assets/stylesheets/blocky/neat/grid/_media.scss +51 -0
  383. data/vendor/assets/stylesheets/blocky/neat/grid/_omega.scss +79 -0
  384. data/vendor/assets/stylesheets/blocky/neat/grid/_outer-container.scss +8 -0
  385. data/vendor/assets/stylesheets/blocky/neat/grid/_pad.scss +8 -0
  386. data/vendor/assets/stylesheets/blocky/neat/grid/_private.scss +50 -0
  387. data/vendor/assets/stylesheets/blocky/neat/grid/_reset.scss +12 -0
  388. data/vendor/assets/stylesheets/blocky/neat/grid/_row.scss +17 -0
  389. data/vendor/assets/stylesheets/blocky/neat/grid/_shift.scss +16 -0
  390. data/vendor/assets/stylesheets/blocky/neat/grid/_span-columns.scss +45 -0
  391. data/vendor/assets/stylesheets/blocky/neat/grid/_to-deprecate.scss +57 -0
  392. data/vendor/assets/stylesheets/blocky/neat/grid/_visual-grid.scss +41 -0
  393. data/vendor/assets/stylesheets/blocky/neat/settings/_grid.scss +7 -0
  394. data/vendor/assets/stylesheets/blocky/neat/settings/_visual-grid.scss +5 -0
  395. data/vendor/assets/stylesheets/blocky/normalize.css +423 -0
  396. data/vendor/assets/stylesheets/blocky/summernote.css +1 -0
  397. metadata +991 -0
@@ -0,0 +1,398 @@
1
+ (function($, undefined) {
2
+
3
+ /**
4
+ * Unobtrusive scripting adapter for jQuery
5
+ * https://github.com/rails/jquery-ujs
6
+ *
7
+ * Requires jQuery 1.7.0 or later.
8
+ *
9
+ * Released under the MIT license
10
+ *
11
+ */
12
+
13
+ // Cut down on the number of issues from people inadvertently including jquery_ujs twice
14
+ // by detecting and raising an error when it happens.
15
+ if ( $.rails !== undefined ) {
16
+ $.error('jquery-ujs has already been loaded!');
17
+ }
18
+
19
+ // Shorthand to make it a little easier to call public rails functions from within rails.js
20
+ var rails;
21
+ var $document = $(document);
22
+
23
+ $.rails = rails = {
24
+ // Link elements bound by jquery-ujs
25
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
26
+
27
+ // Button elements bound by jquery-ujs
28
+ buttonClickSelector: 'button[data-remote]',
29
+
30
+ // Select elements bound by jquery-ujs
31
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
32
+
33
+ // Form elements bound by jquery-ujs
34
+ formSubmitSelector: 'form',
35
+
36
+ // Form input elements bound by jquery-ujs
37
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
38
+
39
+ // Form input elements disabled during form submission
40
+ disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
41
+
42
+ // Form input elements re-enabled after form submission
43
+ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
44
+
45
+ // Form required input elements
46
+ requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
47
+
48
+ // Form file input elements
49
+ fileInputSelector: 'input[type=file]',
50
+
51
+ // Link onClick disable selector with possible reenable after remote submission
52
+ linkDisableSelector: 'a[data-disable-with]',
53
+
54
+ // Make sure that every Ajax request sends the CSRF token
55
+ CSRFProtection: function(xhr) {
56
+ var token = $('meta[name="csrf-token"]').attr('content');
57
+ if (token) xhr.setRequestHeader('X-CSRF-Token', token);
58
+ },
59
+
60
+ // making sure that all forms have actual up-to-date token(cached forms contain old one)
61
+ refreshCSRFTokens: function(){
62
+ var csrfToken = $('meta[name=csrf-token]').attr('content');
63
+ var csrfParam = $('meta[name=csrf-param]').attr('content');
64
+ $('form input[name="' + csrfParam + '"]').val(csrfToken);
65
+ },
66
+
67
+ // Triggers an event on an element and returns false if the event result is false
68
+ fire: function(obj, name, data) {
69
+ var event = $.Event(name);
70
+ obj.trigger(event, data);
71
+ return event.result !== false;
72
+ },
73
+
74
+ // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
75
+ confirm: function(message) {
76
+ return confirm(message);
77
+ },
78
+
79
+ // Default ajax function, may be overridden with custom function in $.rails.ajax
80
+ ajax: function(options) {
81
+ return $.ajax(options);
82
+ },
83
+
84
+ // Default way to get an element's href. May be overridden at $.rails.href.
85
+ href: function(element) {
86
+ return element.attr('href');
87
+ },
88
+
89
+ // Submits "remote" forms and links with ajax
90
+ handleRemote: function(element) {
91
+ var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options;
92
+
93
+ if (rails.fire(element, 'ajax:before')) {
94
+ elCrossDomain = element.data('cross-domain');
95
+ crossDomain = elCrossDomain === undefined ? null : elCrossDomain;
96
+ withCredentials = element.data('with-credentials') || null;
97
+ dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
98
+
99
+ if (element.is('form')) {
100
+ method = element.attr('method');
101
+ url = element.attr('action');
102
+ data = element.serializeArray();
103
+ // memoized value from clicked submit button
104
+ var button = element.data('ujs:submit-button');
105
+ if (button) {
106
+ data.push(button);
107
+ element.data('ujs:submit-button', null);
108
+ }
109
+ } else if (element.is(rails.inputChangeSelector)) {
110
+ method = element.data('method');
111
+ url = element.data('url');
112
+ data = element.serialize();
113
+ if (element.data('params')) data = data + "&" + element.data('params');
114
+ } else if (element.is(rails.buttonClickSelector)) {
115
+ method = element.data('method') || 'get';
116
+ url = element.data('url');
117
+ data = element.serialize();
118
+ if (element.data('params')) data = data + "&" + element.data('params');
119
+ } else {
120
+ method = element.data('method');
121
+ url = rails.href(element);
122
+ data = element.data('params') || null;
123
+ }
124
+
125
+ options = {
126
+ type: method || 'GET', data: data, dataType: dataType,
127
+ // stopping the "ajax:beforeSend" event will cancel the ajax request
128
+ beforeSend: function(xhr, settings) {
129
+ if (settings.dataType === undefined) {
130
+ xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
131
+ }
132
+ return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
133
+ },
134
+ success: function(data, status, xhr) {
135
+ element.trigger('ajax:success', [data, status, xhr]);
136
+ },
137
+ complete: function(xhr, status) {
138
+ element.trigger('ajax:complete', [xhr, status]);
139
+ },
140
+ error: function(xhr, status, error) {
141
+ element.trigger('ajax:error', [xhr, status, error]);
142
+ },
143
+ crossDomain: crossDomain
144
+ };
145
+
146
+ // There is no withCredentials for IE6-8 when
147
+ // "Enable native XMLHTTP support" is disabled
148
+ if (withCredentials) {
149
+ options.xhrFields = {
150
+ withCredentials: withCredentials
151
+ };
152
+ }
153
+
154
+ // Only pass url to `ajax` options if not blank
155
+ if (url) { options.url = url; }
156
+
157
+ var jqxhr = rails.ajax(options);
158
+ element.trigger('ajax:send', jqxhr);
159
+ return jqxhr;
160
+ } else {
161
+ return false;
162
+ }
163
+ },
164
+
165
+ // Handles "data-method" on links such as:
166
+ // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
167
+ handleMethod: function(link) {
168
+ var href = rails.href(link),
169
+ method = link.data('method'),
170
+ target = link.attr('target'),
171
+ csrfToken = $('meta[name=csrf-token]').attr('content'),
172
+ csrfParam = $('meta[name=csrf-param]').attr('content'),
173
+ form = $('<form method="post" action="' + href + '"></form>'),
174
+ metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
175
+
176
+ if (csrfParam !== undefined && csrfToken !== undefined) {
177
+ metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
178
+ }
179
+
180
+ if (target) { form.attr('target', target); }
181
+
182
+ form.hide().append(metadataInput).appendTo('body');
183
+ form.submit();
184
+ },
185
+
186
+ /* Disables form elements:
187
+ - Caches element value in 'ujs:enable-with' data store
188
+ - Replaces element text with value of 'data-disable-with' attribute
189
+ - Sets disabled property to true
190
+ */
191
+ disableFormElements: function(form) {
192
+ form.find(rails.disableSelector).each(function() {
193
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
194
+ element.data('ujs:enable-with', element[method]());
195
+ element[method](element.data('disable-with'));
196
+ element.prop('disabled', true);
197
+ });
198
+ },
199
+
200
+ /* Re-enables disabled form elements:
201
+ - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
202
+ - Sets disabled property to false
203
+ */
204
+ enableFormElements: function(form) {
205
+ form.find(rails.enableSelector).each(function() {
206
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
207
+ if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
208
+ element.prop('disabled', false);
209
+ });
210
+ },
211
+
212
+ /* For 'data-confirm' attribute:
213
+ - Fires `confirm` event
214
+ - Shows the confirmation dialog
215
+ - Fires the `confirm:complete` event
216
+
217
+ Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
218
+ Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
219
+ Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
220
+ return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
221
+ */
222
+ allowAction: function(element) {
223
+ var message = element.data('confirm'),
224
+ answer = false, callback;
225
+ if (!message) { return true; }
226
+
227
+ if (rails.fire(element, 'confirm')) {
228
+ answer = rails.confirm(message);
229
+ callback = rails.fire(element, 'confirm:complete', [answer]);
230
+ }
231
+ return answer && callback;
232
+ },
233
+
234
+ // Helper function which checks for blank inputs in a form that match the specified CSS selector
235
+ blankInputs: function(form, specifiedSelector, nonBlank) {
236
+ var inputs = $(), input, valueToCheck,
237
+ selector = specifiedSelector || 'input,textarea',
238
+ allInputs = form.find(selector);
239
+
240
+ allInputs.each(function() {
241
+ input = $(this);
242
+ valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val();
243
+ // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey
244
+ if (!valueToCheck === !nonBlank) {
245
+
246
+ // Don't count unchecked required radio if other radio with same name is checked
247
+ if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) {
248
+ return true; // Skip to next input
249
+ }
250
+
251
+ inputs = inputs.add(input);
252
+ }
253
+ });
254
+ return inputs.length ? inputs : false;
255
+ },
256
+
257
+ // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
258
+ nonBlankInputs: function(form, specifiedSelector) {
259
+ return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
260
+ },
261
+
262
+ // Helper function, needed to provide consistent behavior in IE
263
+ stopEverything: function(e) {
264
+ $(e.target).trigger('ujs:everythingStopped');
265
+ e.stopImmediatePropagation();
266
+ return false;
267
+ },
268
+
269
+ // replace element's html with the 'data-disable-with' after storing original html
270
+ // and prevent clicking on it
271
+ disableElement: function(element) {
272
+ element.data('ujs:enable-with', element.html()); // store enabled state
273
+ element.html(element.data('disable-with')); // set to disabled state
274
+ element.bind('click.railsDisable', function(e) { // prevent further clicking
275
+ return rails.stopEverything(e);
276
+ });
277
+ },
278
+
279
+ // restore element to its original state which was disabled by 'disableElement' above
280
+ enableElement: function(element) {
281
+ if (element.data('ujs:enable-with') !== undefined) {
282
+ element.html(element.data('ujs:enable-with')); // set to old enabled state
283
+ element.removeData('ujs:enable-with'); // clean up cache
284
+ }
285
+ element.unbind('click.railsDisable'); // enable element
286
+ }
287
+
288
+ };
289
+
290
+ if (rails.fire($document, 'rails:attachBindings')) {
291
+
292
+ $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
293
+
294
+ $document.delegate(rails.linkDisableSelector, 'ajax:complete', function() {
295
+ rails.enableElement($(this));
296
+ });
297
+
298
+ $document.delegate(rails.linkClickSelector, 'click.rails', function(e) {
299
+ var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
300
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
301
+
302
+ if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
303
+
304
+ if (link.data('remote') !== undefined) {
305
+ if (metaClick && (!method || method === 'GET') && !data) { return true; }
306
+
307
+ var handleRemote = rails.handleRemote(link);
308
+ // response from rails.handleRemote() will either be false or a deferred object promise.
309
+ if (handleRemote === false) {
310
+ rails.enableElement(link);
311
+ } else {
312
+ handleRemote.error( function() { rails.enableElement(link); } );
313
+ }
314
+ return false;
315
+
316
+ } else if (link.data('method')) {
317
+ rails.handleMethod(link);
318
+ return false;
319
+ }
320
+ });
321
+
322
+ $document.delegate(rails.buttonClickSelector, 'click.rails', function(e) {
323
+ var button = $(this);
324
+ if (!rails.allowAction(button)) return rails.stopEverything(e);
325
+
326
+ rails.handleRemote(button);
327
+ return false;
328
+ });
329
+
330
+ $document.delegate(rails.inputChangeSelector, 'change.rails', function(e) {
331
+ var link = $(this);
332
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
333
+
334
+ rails.handleRemote(link);
335
+ return false;
336
+ });
337
+
338
+ $document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
339
+ var form = $(this),
340
+ remote = form.data('remote') !== undefined,
341
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
342
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
343
+
344
+ if (!rails.allowAction(form)) return rails.stopEverything(e);
345
+
346
+ // skip other logic when required values are missing or file upload is present
347
+ if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
348
+ return rails.stopEverything(e);
349
+ }
350
+
351
+ if (remote) {
352
+ if (nonBlankFileInputs) {
353
+ // slight timeout so that the submit button gets properly serialized
354
+ // (make it easy for event handler to serialize form without disabled values)
355
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
356
+ var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
357
+
358
+ // re-enable form elements if event bindings return false (canceling normal form submission)
359
+ if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
360
+
361
+ return aborted;
362
+ }
363
+
364
+ rails.handleRemote(form);
365
+ return false;
366
+
367
+ } else {
368
+ // slight timeout so that the submit button gets properly serialized
369
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
370
+ }
371
+ });
372
+
373
+ $document.delegate(rails.formInputClickSelector, 'click.rails', function(event) {
374
+ var button = $(this);
375
+
376
+ if (!rails.allowAction(button)) return rails.stopEverything(event);
377
+
378
+ // register the pressed submit button
379
+ var name = button.attr('name'),
380
+ data = name ? {name:name, value:button.val()} : null;
381
+
382
+ button.closest('form').data('ujs:submit-button', data);
383
+ });
384
+
385
+ $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) {
386
+ if (this == event.target) rails.disableFormElements($(this));
387
+ });
388
+
389
+ $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) {
390
+ if (this == event.target) rails.enableFormElements($(this));
391
+ });
392
+
393
+ $(function(){
394
+ rails.refreshCSRFTokens();
395
+ });
396
+ }
397
+
398
+ })( jQuery );
@@ -0,0 +1,3922 @@
1
+ /**
2
+ * Super simple wysiwyg editor on Bootstrap v0.5.2
3
+ * http://hackerwins.github.io/summernote/
4
+ *
5
+ * summernote.js
6
+ * Copyright 2013 Alan Hong. and outher contributors
7
+ * summernote may be freely distributed under the MIT license./
8
+ *
9
+ * Date: 2014-05-16T11:28Z
10
+ */
11
+ (function (factory) {
12
+ /* global define */
13
+ if (typeof define === 'function' && define.amd) {
14
+ // AMD. Register as an anonymous module.
15
+ define(['jquery', 'codemirror'], factory);
16
+ } else {
17
+ // Browser globals: jQuery, CodeMirror
18
+ factory(window.jQuery, window.CodeMirror);
19
+ }
20
+ }(function ($, CodeMirror) {
21
+
22
+
23
+
24
+ if ('function' !== typeof Array.prototype.reduce) {
25
+ /**
26
+ * Array.prototype.reduce fallback
27
+ *
28
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
29
+ */
30
+ Array.prototype.reduce = function (callback, optInitialValue) {
31
+ var idx, value, length = this.length >>> 0, isValueSet = false;
32
+ if (1 < arguments.length) {
33
+ value = optInitialValue;
34
+ isValueSet = true;
35
+ }
36
+ for (idx = 0; length > idx; ++idx) {
37
+ if (this.hasOwnProperty(idx)) {
38
+ if (isValueSet) {
39
+ value = callback(value, this[idx], idx, this);
40
+ } else {
41
+ value = this[idx];
42
+ isValueSet = true;
43
+ }
44
+ }
45
+ }
46
+ if (!isValueSet) {
47
+ throw new TypeError('Reduce of empty array with no initial value');
48
+ }
49
+ return value;
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Object which check platform and agent
55
+ */
56
+ var agent = {
57
+ bMac: navigator.appVersion.indexOf('Mac') > -1,
58
+ bMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
59
+ bFF: navigator.userAgent.indexOf('Firefox') > -1,
60
+ jqueryVersion: parseFloat($.fn.jquery),
61
+ bCodeMirror: !!CodeMirror
62
+ };
63
+
64
+ /**
65
+ * func utils (for high-order func's arg)
66
+ */
67
+ var func = (function () {
68
+ var eq = function (elA) {
69
+ return function (elB) {
70
+ return elA === elB;
71
+ };
72
+ };
73
+
74
+ var eq2 = function (elA, elB) {
75
+ return elA === elB;
76
+ };
77
+
78
+ var ok = function () {
79
+ return true;
80
+ };
81
+
82
+ var fail = function () {
83
+ return false;
84
+ };
85
+
86
+ var not = function (f) {
87
+ return function () {
88
+ return !f.apply(f, arguments);
89
+ };
90
+ };
91
+
92
+ var self = function (a) {
93
+ return a;
94
+ };
95
+
96
+ var idCounter = 0;
97
+
98
+ /**
99
+ * generate a globally-unique id
100
+ *
101
+ * @param {String} [prefix]
102
+ */
103
+ var uniqueId = function (prefix) {
104
+ var id = ++idCounter + '';
105
+ return prefix ? prefix + id : id;
106
+ };
107
+
108
+ /**
109
+ * returns bnd (bounds) from rect
110
+ *
111
+ * - IE Compatability Issue: http://goo.gl/sRLOAo
112
+ * - Scroll Issue: http://goo.gl/sNjUc
113
+ *
114
+ * @param {Rect} rect
115
+ * @return {Object} bounds
116
+ * @return {Number} bounds.top
117
+ * @return {Number} bounds.left
118
+ * @return {Number} bounds.width
119
+ * @return {Number} bounds.height
120
+ */
121
+ var rect2bnd = function (rect) {
122
+ var $document = $(document);
123
+ return {
124
+ top: rect.top + $document.scrollTop(),
125
+ left: rect.left + $document.scrollLeft(),
126
+ width: rect.right - rect.left,
127
+ height: rect.bottom - rect.top
128
+ };
129
+ };
130
+
131
+ /**
132
+ * returns a copy of the object where the keys have become the values and the values the keys.
133
+ * @param {Object} obj
134
+ * @return {Object}
135
+ */
136
+ var invertObject = function (obj) {
137
+ var inverted = {};
138
+ for (var key in obj) {
139
+ if (obj.hasOwnProperty(key)) {
140
+ inverted[obj[key]] = key;
141
+ }
142
+ }
143
+ return inverted;
144
+ };
145
+
146
+ return {
147
+ eq: eq,
148
+ eq2: eq2,
149
+ ok: ok,
150
+ fail: fail,
151
+ not: not,
152
+ self: self,
153
+ uniqueId: uniqueId,
154
+ rect2bnd: rect2bnd,
155
+ invertObject: invertObject
156
+ };
157
+ })();
158
+
159
+ /**
160
+ * list utils
161
+ */
162
+ var list = (function () {
163
+ /**
164
+ * returns the first element of an array.
165
+ * @param {Array} array
166
+ */
167
+ var head = function (array) {
168
+ return array[0];
169
+ };
170
+
171
+ /**
172
+ * returns the last element of an array.
173
+ * @param {Array} array
174
+ */
175
+ var last = function (array) {
176
+ return array[array.length - 1];
177
+ };
178
+
179
+ /**
180
+ * returns everything but the last entry of the array.
181
+ * @param {Array} array
182
+ */
183
+ var initial = function (array) {
184
+ return array.slice(0, array.length - 1);
185
+ };
186
+
187
+ /**
188
+ * returns the rest of the elements in an array.
189
+ * @param {Array} array
190
+ */
191
+ var tail = function (array) {
192
+ return array.slice(1);
193
+ };
194
+
195
+ /**
196
+ * returns next item.
197
+ * @param {Array} array
198
+ */
199
+ var next = function (array, item) {
200
+ var idx = array.indexOf(item);
201
+ if (idx === -1) { return null; }
202
+
203
+ return array[idx + 1];
204
+ };
205
+
206
+ /**
207
+ * returns prev item.
208
+ * @param {Array} array
209
+ */
210
+ var prev = function (array, item) {
211
+ var idx = array.indexOf(item);
212
+ if (idx === -1) { return null; }
213
+
214
+ return array[idx - 1];
215
+ };
216
+
217
+ /**
218
+ * get sum from a list
219
+ * @param {Array} array - array
220
+ * @param {Function} fn - iterator
221
+ */
222
+ var sum = function (array, fn) {
223
+ fn = fn || func.self;
224
+ return array.reduce(function (memo, v) {
225
+ return memo + fn(v);
226
+ }, 0);
227
+ };
228
+
229
+ /**
230
+ * returns a copy of the collection with array type.
231
+ * @param {Collection} collection - collection eg) node.childNodes, ...
232
+ */
233
+ var from = function (collection) {
234
+ var result = [], idx = -1, length = collection.length;
235
+ while (++idx < length) {
236
+ result[idx] = collection[idx];
237
+ }
238
+ return result;
239
+ };
240
+
241
+ /**
242
+ * cluster elements by predicate function.
243
+ * @param {Array} array - array
244
+ * @param {Function} fn - predicate function for cluster rule
245
+ * @param {Array[]}
246
+ */
247
+ var clusterBy = function (array, fn) {
248
+ if (array.length === 0) { return []; }
249
+ var aTail = tail(array);
250
+ return aTail.reduce(function (memo, v) {
251
+ var aLast = last(memo);
252
+ if (fn(last(aLast), v)) {
253
+ aLast[aLast.length] = v;
254
+ } else {
255
+ memo[memo.length] = [v];
256
+ }
257
+ return memo;
258
+ }, [[head(array)]]);
259
+ };
260
+
261
+ /**
262
+ * returns a copy of the array with all falsy values removed
263
+ * @param {Array} array - array
264
+ * @param {Function} fn - predicate function for cluster rule
265
+ */
266
+ var compact = function (array) {
267
+ var aResult = [];
268
+ for (var idx = 0, sz = array.length; idx < sz; idx ++) {
269
+ if (array[idx]) { aResult.push(array[idx]); }
270
+ }
271
+ return aResult;
272
+ };
273
+
274
+ return { head: head, last: last, initial: initial, tail: tail,
275
+ prev: prev, next: next, sum: sum, from: from,
276
+ compact: compact, clusterBy: clusterBy };
277
+ })();
278
+
279
+ /**
280
+ * Dom functions
281
+ */
282
+ var dom = (function () {
283
+ /**
284
+ * returns whether node is `note-editable` or not.
285
+ *
286
+ * @param {Element} node
287
+ * @return {Boolean}
288
+ */
289
+ var isEditable = function (node) {
290
+ return node && $(node).hasClass('note-editable');
291
+ };
292
+
293
+ var isControlSizing = function (node) {
294
+ return node && $(node).hasClass('note-control-sizing');
295
+ };
296
+
297
+ /**
298
+ * build layoutInfo from $editor(.note-editor)
299
+ *
300
+ * @param {jQuery} $editor
301
+ * @return {Object}
302
+ */
303
+ var buildLayoutInfo = function ($editor) {
304
+ var makeFinder;
305
+
306
+ // air mode
307
+ if ($editor.hasClass('note-air-editor')) {
308
+ var id = list.last($editor.attr('id').split('-'));
309
+ makeFinder = function (sIdPrefix) {
310
+ return function () { return $(sIdPrefix + id); };
311
+ };
312
+
313
+ return {
314
+ editor: function () { return $editor; },
315
+ editable: function () { return $editor; },
316
+ popover: makeFinder('#note-popover-'),
317
+ handle: makeFinder('#note-handle-'),
318
+ dialog: makeFinder('#note-dialog-')
319
+ };
320
+
321
+ // frame mode
322
+ } else {
323
+ makeFinder = function (sClassName) {
324
+ return function () { return $editor.find(sClassName); };
325
+ };
326
+ return {
327
+ editor: function () { return $editor; },
328
+ dropzone: makeFinder('.note-dropzone'),
329
+ toolbar: makeFinder('.note-toolbar'),
330
+ editable: makeFinder('.note-editable'),
331
+ codable: makeFinder('.note-codable'),
332
+ statusbar: makeFinder('.note-statusbar'),
333
+ popover: makeFinder('.note-popover'),
334
+ handle: makeFinder('.note-handle'),
335
+ dialog: makeFinder('.note-dialog')
336
+ };
337
+ }
338
+ };
339
+
340
+ /**
341
+ * returns predicate which judge whether nodeName is same
342
+ * @param {String} sNodeName
343
+ */
344
+ var makePredByNodeName = function (sNodeName) {
345
+ // nodeName is always uppercase.
346
+ return function (node) {
347
+ return node && node.nodeName === sNodeName;
348
+ };
349
+ };
350
+
351
+ var isPara = function (node) {
352
+ // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
353
+ return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName);
354
+ };
355
+
356
+ var isList = function (node) {
357
+ return node && /^UL|^OL/.test(node.nodeName);
358
+ };
359
+
360
+ var isCell = function (node) {
361
+ return node && /^TD|^TH/.test(node.nodeName);
362
+ };
363
+
364
+ /**
365
+ * find nearest ancestor predicate hit
366
+ *
367
+ * @param {Element} node
368
+ * @param {Function} pred - predicate function
369
+ */
370
+ var ancestor = function (node, pred) {
371
+ while (node) {
372
+ if (pred(node)) { return node; }
373
+ if (isEditable(node)) { break; }
374
+
375
+ node = node.parentNode;
376
+ }
377
+ return null;
378
+ };
379
+
380
+ /**
381
+ * returns new array of ancestor nodes (until predicate hit).
382
+ *
383
+ * @param {Element} node
384
+ * @param {Function} [optional] pred - predicate function
385
+ */
386
+ var listAncestor = function (node, pred) {
387
+ pred = pred || func.fail;
388
+
389
+ var aAncestor = [];
390
+ ancestor(node, function (el) {
391
+ aAncestor.push(el);
392
+ return pred(el);
393
+ });
394
+ return aAncestor;
395
+ };
396
+
397
+ /**
398
+ * returns common ancestor node between two nodes.
399
+ *
400
+ * @param {Element} nodeA
401
+ * @param {Element} nodeB
402
+ */
403
+ var commonAncestor = function (nodeA, nodeB) {
404
+ var aAncestor = listAncestor(nodeA);
405
+ for (var n = nodeB; n; n = n.parentNode) {
406
+ if ($.inArray(n, aAncestor) > -1) { return n; }
407
+ }
408
+ return null; // difference document area
409
+ };
410
+
411
+ /**
412
+ * listing all Nodes between two nodes.
413
+ * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
414
+ *
415
+ * @param {Element} nodeA
416
+ * @param {Element} nodeB
417
+ */
418
+ var listBetween = function (nodeA, nodeB) {
419
+ var aNode = [];
420
+
421
+ var bStart = false, bEnd = false;
422
+
423
+ // DFS(depth first search) with commonAcestor.
424
+ (function fnWalk(node) {
425
+ if (!node) { return; } // traverse fisnish
426
+ if (node === nodeA) { bStart = true; } // start point
427
+ if (bStart && !bEnd) { aNode.push(node); } // between
428
+ if (node === nodeB) { bEnd = true; return; } // end point
429
+
430
+ for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
431
+ fnWalk(node.childNodes[idx]);
432
+ }
433
+ })(commonAncestor(nodeA, nodeB));
434
+
435
+ return aNode;
436
+ };
437
+
438
+ /**
439
+ * listing all previous siblings (until predicate hit).
440
+ * @param {Element} node
441
+ * @param {Function} [optional] pred - predicate function
442
+ */
443
+ var listPrev = function (node, pred) {
444
+ pred = pred || func.fail;
445
+
446
+ var aNext = [];
447
+ while (node) {
448
+ aNext.push(node);
449
+ if (pred(node)) { break; }
450
+ node = node.previousSibling;
451
+ }
452
+ return aNext;
453
+ };
454
+
455
+ /**
456
+ * listing next siblings (until predicate hit).
457
+ *
458
+ * @param {Element} node
459
+ * @param {Function} [pred] - predicate function
460
+ */
461
+ var listNext = function (node, pred) {
462
+ pred = pred || func.fail;
463
+
464
+ var aNext = [];
465
+ while (node) {
466
+ aNext.push(node);
467
+ if (pred(node)) { break; }
468
+ node = node.nextSibling;
469
+ }
470
+ return aNext;
471
+ };
472
+
473
+ /**
474
+ * listing descendant nodes
475
+ *
476
+ * @param {Element} node
477
+ * @param {Function} [pred] - predicate function
478
+ */
479
+ var listDescendant = function (node, pred) {
480
+ var aDescendant = [];
481
+ pred = pred || func.ok;
482
+
483
+ // start DFS(depth first search) with node
484
+ (function fnWalk(current) {
485
+ if (node !== current && pred(current)) {
486
+ aDescendant.push(current);
487
+ }
488
+ for (var idx = 0, sz = current.childNodes.length; idx < sz; idx++) {
489
+ fnWalk(current.childNodes[idx]);
490
+ }
491
+ })(node);
492
+
493
+ return aDescendant;
494
+ };
495
+
496
+ /**
497
+ * insert node after preceding
498
+ *
499
+ * @param {Element} node
500
+ * @param {Element} preceding - predicate function
501
+ */
502
+ var insertAfter = function (node, preceding) {
503
+ var next = preceding.nextSibling, parent = preceding.parentNode;
504
+ if (next) {
505
+ parent.insertBefore(node, next);
506
+ } else {
507
+ parent.appendChild(node);
508
+ }
509
+ return node;
510
+ };
511
+
512
+ /**
513
+ * append elements.
514
+ *
515
+ * @param {Element} node
516
+ * @param {Collection} aChild
517
+ */
518
+ var appends = function (node, aChild) {
519
+ $.each(aChild, function (idx, child) {
520
+ node.appendChild(child);
521
+ });
522
+ return node;
523
+ };
524
+
525
+ var isText = makePredByNodeName('#text');
526
+
527
+ /**
528
+ * returns #text's text size or element's childNodes size
529
+ *
530
+ * @param {Element} node
531
+ */
532
+ var length = function (node) {
533
+ if (isText(node)) { return node.nodeValue.length; }
534
+ return node.childNodes.length;
535
+ };
536
+
537
+ /**
538
+ * returns offset from parent.
539
+ *
540
+ * @param {Element} node
541
+ */
542
+ var position = function (node) {
543
+ var offset = 0;
544
+ while ((node = node.previousSibling)) { offset += 1; }
545
+ return offset;
546
+ };
547
+
548
+ /**
549
+ * return offsetPath(array of offset) from ancestor
550
+ *
551
+ * @param {Element} ancestor - ancestor node
552
+ * @param {Element} node
553
+ */
554
+ var makeOffsetPath = function (ancestor, node) {
555
+ var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
556
+ return $.map(aAncestor, position).reverse();
557
+ };
558
+
559
+ /**
560
+ * return element from offsetPath(array of offset)
561
+ *
562
+ * @param {Element} ancestor - ancestor node
563
+ * @param {array} aOffset - offsetPath
564
+ */
565
+ var fromOffsetPath = function (ancestor, aOffset) {
566
+ var current = ancestor;
567
+ for (var i = 0, sz = aOffset.length; i < sz; i++) {
568
+ current = current.childNodes[aOffset[i]];
569
+ }
570
+ return current;
571
+ };
572
+
573
+ /**
574
+ * split element or #text
575
+ *
576
+ * @param {Element} node
577
+ * @param {Number} offset
578
+ */
579
+ var splitData = function (node, offset) {
580
+ if (offset === 0) { return node; }
581
+ if (offset >= length(node)) { return node.nextSibling; }
582
+
583
+ // splitText
584
+ if (isText(node)) { return node.splitText(offset); }
585
+
586
+ // splitElement
587
+ var child = node.childNodes[offset];
588
+ node = insertAfter(node.cloneNode(false), node);
589
+ return appends(node, listNext(child));
590
+ };
591
+
592
+ /**
593
+ * split dom tree by boundaryPoint(pivot and offset)
594
+ *
595
+ * @param {Element} root
596
+ * @param {Element} pivot - this will be boundaryPoint's node
597
+ * @param {Number} offset - this will be boundaryPoint's offset
598
+ */
599
+ var split = function (root, pivot, offset) {
600
+ var aAncestor = listAncestor(pivot, func.eq(root));
601
+ if (aAncestor.length === 1) { return splitData(pivot, offset); }
602
+ return aAncestor.reduce(function (node, parent) {
603
+ var clone = parent.cloneNode(false);
604
+ insertAfter(clone, parent);
605
+ if (node === pivot) {
606
+ node = splitData(node, offset);
607
+ }
608
+ appends(clone, listNext(node));
609
+ return clone;
610
+ });
611
+ };
612
+
613
+ /**
614
+ * remove node, (bRemoveChild: remove child or not)
615
+ * @param {Element} node
616
+ * @param {Boolean} bRemoveChild
617
+ */
618
+ var remove = function (node, bRemoveChild) {
619
+ if (!node || !node.parentNode) { return; }
620
+ if (node.removeNode) { return node.removeNode(bRemoveChild); }
621
+
622
+ var elParent = node.parentNode;
623
+ if (!bRemoveChild) {
624
+ var aNode = [];
625
+ var i, sz;
626
+ for (i = 0, sz = node.childNodes.length; i < sz; i++) {
627
+ aNode.push(node.childNodes[i]);
628
+ }
629
+
630
+ for (i = 0, sz = aNode.length; i < sz; i++) {
631
+ elParent.insertBefore(aNode[i], node);
632
+ }
633
+ }
634
+
635
+ elParent.removeChild(node);
636
+ };
637
+
638
+ var html = function ($node) {
639
+ return dom.isTextarea($node[0]) ? $node.val() : $node.html();
640
+ };
641
+
642
+ return {
643
+ blank: agent.bMSIE ? '&nbsp;' : '<br/>',
644
+ emptyPara: '<p><br/></p>',
645
+ isEditable: isEditable,
646
+ isControlSizing: isControlSizing,
647
+ buildLayoutInfo: buildLayoutInfo,
648
+ isText: isText,
649
+ isPara: isPara,
650
+ isList: isList,
651
+ isTable: makePredByNodeName('TABLE'),
652
+ isCell: isCell,
653
+ isAnchor: makePredByNodeName('A'),
654
+ isDiv: makePredByNodeName('DIV'),
655
+ isLi: makePredByNodeName('LI'),
656
+ isSpan: makePredByNodeName('SPAN'),
657
+ isB: makePredByNodeName('B'),
658
+ isU: makePredByNodeName('U'),
659
+ isS: makePredByNodeName('S'),
660
+ isI: makePredByNodeName('I'),
661
+ isImg: makePredByNodeName('IMG'),
662
+ isTextarea: makePredByNodeName('TEXTAREA'),
663
+ ancestor: ancestor,
664
+ listAncestor: listAncestor,
665
+ listNext: listNext,
666
+ listPrev: listPrev,
667
+ listDescendant: listDescendant,
668
+ commonAncestor: commonAncestor,
669
+ listBetween: listBetween,
670
+ insertAfter: insertAfter,
671
+ position: position,
672
+ makeOffsetPath: makeOffsetPath,
673
+ fromOffsetPath: fromOffsetPath,
674
+ split: split,
675
+ remove: remove,
676
+ html: html
677
+ };
678
+ })();
679
+
680
+ var settings = {
681
+ // version
682
+ version: '0.5.2',
683
+
684
+ /**
685
+ * options
686
+ */
687
+ options: {
688
+ width: null, // set editor width
689
+ height: null, // set editable height, ex) 300
690
+
691
+ focus: false, // set focus after initilize summernote
692
+
693
+ tabsize: 4, // size of tab ex) 2 or 4
694
+ styleWithSpan: true, // style with span (Chrome and FF only)
695
+
696
+ disableLinkTarget: false, // hide link Target Checkbox
697
+ disableDragAndDrop: false, // disable drag and drop event
698
+
699
+ codemirror: { // codemirror options
700
+ mode: 'text/html',
701
+ lineNumbers: true
702
+ },
703
+
704
+ // language
705
+ lang: 'en-US', // language 'en-US', 'ko-KR', ...
706
+ direction: null, // text direction, ex) 'rtl'
707
+
708
+ // toolbar
709
+ toolbar: [
710
+ ['style', ['style']],
711
+ ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
712
+ ['fontname', ['fontname']],
713
+ // ['fontsize', ['fontsize']], // Still buggy
714
+ ['color', ['color']],
715
+ ['para', ['ul', 'ol', 'paragraph']],
716
+ ['height', ['height']],
717
+ ['table', ['table']],
718
+ ['insert', ['link', 'picture', 'video']],
719
+ ['view', ['fullscreen', 'codeview']],
720
+ ['help', ['help']]
721
+ ],
722
+
723
+ // air mode: inline editor
724
+ airMode: false,
725
+ // airPopover: [
726
+ // ['style', ['style']],
727
+ // ['font', ['bold', 'italic', 'underline', 'clear']],
728
+ // ['fontname', ['fontname']],
729
+ // ['fontsize', ['fontsize']], // Still buggy
730
+ // ['color', ['color']],
731
+ // ['para', ['ul', 'ol', 'paragraph']],
732
+ // ['height', ['height']],
733
+ // ['table', ['table']],
734
+ // ['insert', ['link', 'picture', 'video']],
735
+ // ['help', ['help']]
736
+ // ],
737
+ airPopover: [
738
+ ['color', ['color']],
739
+ ['font', ['bold', 'underline', 'clear']],
740
+ ['para', ['ul', 'paragraph']],
741
+ ['table', ['table']],
742
+ ['insert', ['link', 'picture']]
743
+ ],
744
+
745
+ // style tag
746
+ styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
747
+
748
+ // default fontName
749
+ defaultFontName: 'Arial',
750
+
751
+ // fontName
752
+ fontNames: [
753
+ 'Serif', 'Sans', 'Arial', 'Arial Black', 'Courier',
754
+ 'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
755
+ 'Lucida Sans', 'Tahoma', 'Times', 'Times New Roman', 'Verdana'
756
+ ],
757
+
758
+ // pallete colors(n x n)
759
+ colors: [
760
+ ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
761
+ ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
762
+ ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
763
+ ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
764
+ ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
765
+ ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
766
+ ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
767
+ ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
768
+ ],
769
+
770
+ // fontSize
771
+ fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
772
+
773
+ // lineHeight
774
+ lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],
775
+
776
+ // callbacks
777
+ oninit: null, // initialize
778
+ onfocus: null, // editable has focus
779
+ onblur: null, // editable out of focus
780
+ onenter: null, // enter key pressed
781
+ onkeyup: null, // keyup
782
+ onkeydown: null, // keydown
783
+ onImageUpload: null, // imageUploadHandler
784
+ onImageUploadError: null, // imageUploadErrorHandler
785
+ onToolbarClick: null,
786
+
787
+ keyMap: {
788
+ pc: {
789
+ 'CTRL+Z': 'undo',
790
+ 'CTRL+Y': 'redo',
791
+ 'TAB': 'tab',
792
+ 'SHIFT+TAB': 'untab',
793
+ 'CTRL+B': 'bold',
794
+ 'CTRL+I': 'italic',
795
+ 'CTRL+U': 'underline',
796
+ 'CTRL+SHIFT+S': 'strikethrough',
797
+ 'CTRL+BACKSLASH': 'removeFormat',
798
+ 'CTRL+SHIFT+L': 'justifyLeft',
799
+ 'CTRL+SHIFT+E': 'justifyCenter',
800
+ 'CTRL+SHIFT+R': 'justifyRight',
801
+ 'CTRL+SHIFT+J': 'justifyFull',
802
+ 'CTRL+SHIFT+NUM7': 'insertUnorderedList',
803
+ 'CTRL+SHIFT+NUM8': 'insertOrderedList',
804
+ 'CTRL+LEFTBRACKET': 'outdent',
805
+ 'CTRL+RIGHTBRACKET': 'indent',
806
+ 'CTRL+NUM0': 'formatPara',
807
+ 'CTRL+NUM1': 'formatH1',
808
+ 'CTRL+NUM2': 'formatH2',
809
+ 'CTRL+NUM3': 'formatH3',
810
+ 'CTRL+NUM4': 'formatH4',
811
+ 'CTRL+NUM5': 'formatH5',
812
+ 'CTRL+NUM6': 'formatH6',
813
+ 'CTRL+ENTER': 'insertHorizontalRule'
814
+ },
815
+
816
+ mac: {
817
+ 'CMD+Z': 'undo',
818
+ 'CMD+SHIFT+Z': 'redo',
819
+ 'TAB': 'tab',
820
+ 'SHIFT+TAB': 'untab',
821
+ 'CMD+B': 'bold',
822
+ 'CMD+I': 'italic',
823
+ 'CMD+U': 'underline',
824
+ 'CMD+SHIFT+S': 'strikethrough',
825
+ 'CMD+BACKSLASH': 'removeFormat',
826
+ 'CMD+SHIFT+L': 'justifyLeft',
827
+ 'CMD+SHIFT+E': 'justifyCenter',
828
+ 'CMD+SHIFT+R': 'justifyRight',
829
+ 'CMD+SHIFT+J': 'justifyFull',
830
+ 'CMD+SHIFT+NUM7': 'insertUnorderedList',
831
+ 'CMD+SHIFT+NUM8': 'insertOrderedList',
832
+ 'CMD+LEFTBRACKET': 'outdent',
833
+ 'CMD+RIGHTBRACKET': 'indent',
834
+ 'CMD+NUM0': 'formatPara',
835
+ 'CMD+NUM1': 'formatH1',
836
+ 'CMD+NUM2': 'formatH2',
837
+ 'CMD+NUM3': 'formatH3',
838
+ 'CMD+NUM4': 'formatH4',
839
+ 'CMD+NUM5': 'formatH5',
840
+ 'CMD+NUM6': 'formatH6',
841
+ 'CMD+ENTER': 'insertHorizontalRule'
842
+ }
843
+ }
844
+ },
845
+
846
+ // default language: en-US
847
+ lang: {
848
+ 'en-US': {
849
+ font: {
850
+ bold: 'Bold',
851
+ italic: 'Italic',
852
+ underline: 'Underline',
853
+ strikethrough: 'Strikethrough',
854
+ clear: 'Remove Font Style',
855
+ height: 'Line Height',
856
+ name: 'Font Family',
857
+ size: 'Font Size'
858
+ },
859
+ image: {
860
+ image: 'Picture',
861
+ insert: 'Insert Image',
862
+ resizeFull: 'Resize Full',
863
+ resizeHalf: 'Resize Half',
864
+ resizeQuarter: 'Resize Quarter',
865
+ floatLeft: 'Float Left',
866
+ floatRight: 'Float Right',
867
+ floatNone: 'Float None',
868
+ dragImageHere: 'Drag an image here',
869
+ selectFromFiles: 'Select from files',
870
+ url: 'Image URL',
871
+ remove: 'Remove Image'
872
+ },
873
+ link: {
874
+ link: 'Link',
875
+ insert: 'Insert Link',
876
+ unlink: 'Unlink',
877
+ edit: 'Edit',
878
+ textToDisplay: 'Text to display',
879
+ url: 'To what URL should this link go?',
880
+ openInNewWindow: 'Open in new window'
881
+ },
882
+ video: {
883
+ video: 'Video',
884
+ videoLink: 'Video Link',
885
+ insert: 'Insert Video',
886
+ url: 'Video URL?',
887
+ providers: '(YouTube, Vimeo, Vine, Instagram, or DailyMotion)'
888
+ },
889
+ table: {
890
+ table: 'Table'
891
+ },
892
+ hr: {
893
+ insert: 'Insert Horizontal Rule'
894
+ },
895
+ style: {
896
+ style: 'Style',
897
+ normal: 'Normal',
898
+ blockquote: 'Quote',
899
+ pre: 'Code',
900
+ h1: 'Header 1',
901
+ h2: 'Header 2',
902
+ h3: 'Header 3',
903
+ h4: 'Header 4',
904
+ h5: 'Header 5',
905
+ h6: 'Header 6'
906
+ },
907
+ lists: {
908
+ unordered: 'Unordered list',
909
+ ordered: 'Ordered list'
910
+ },
911
+ options: {
912
+ help: 'Help',
913
+ fullscreen: 'Full Screen',
914
+ codeview: 'Code View'
915
+ },
916
+ paragraph: {
917
+ paragraph: 'Paragraph',
918
+ outdent: 'Outdent',
919
+ indent: 'Indent',
920
+ left: 'Align left',
921
+ center: 'Align center',
922
+ right: 'Align right',
923
+ justify: 'Justify full'
924
+ },
925
+ color: {
926
+ recent: 'Recent Color',
927
+ more: 'More Color',
928
+ background: 'BackColor',
929
+ foreground: 'FontColor',
930
+ transparent: 'Transparent',
931
+ setTransparent: 'Set transparent',
932
+ reset: 'Reset',
933
+ resetToDefault: 'Reset to default'
934
+ },
935
+ shortcut: {
936
+ shortcuts: 'Keyboard shortcuts',
937
+ close: 'Close',
938
+ textFormatting: 'Text formatting',
939
+ action: 'Action',
940
+ paragraphFormatting: 'Paragraph formatting',
941
+ documentStyle: 'Document Style'
942
+ },
943
+ history: {
944
+ undo: 'Undo',
945
+ redo: 'Redo'
946
+ }
947
+ }
948
+ }
949
+ };
950
+
951
+ /**
952
+ * Async functions which returns `Promise`
953
+ */
954
+ var async = (function () {
955
+ /**
956
+ * read contents of file as representing URL
957
+ *
958
+ * @param {File} file
959
+ * @return {Promise} - then: sDataUrl
960
+ */
961
+ var readFileAsDataURL = function (file) {
962
+ return $.Deferred(function (deferred) {
963
+ $.extend(new FileReader(), {
964
+ onload: function (e) {
965
+ var sDataURL = e.target.result;
966
+ deferred.resolve(sDataURL);
967
+ },
968
+ onerror: function () {
969
+ deferred.reject(this);
970
+ }
971
+ }).readAsDataURL(file);
972
+ }).promise();
973
+ };
974
+
975
+ /**
976
+ * create `<image>` from url string
977
+ *
978
+ * @param {String} sUrl
979
+ * @return {Promise} - then: $image
980
+ */
981
+ var createImage = function (sUrl) {
982
+ return $.Deferred(function (deferred) {
983
+ $('<img>').one('load', function () {
984
+ deferred.resolve($(this));
985
+ }).one('error abort', function () {
986
+ deferred.reject($(this));
987
+ }).css({
988
+ display: 'none'
989
+ }).appendTo(document.body).attr('src', sUrl);
990
+ }).promise();
991
+ };
992
+
993
+ return {
994
+ readFileAsDataURL: readFileAsDataURL,
995
+ createImage: createImage
996
+ };
997
+ })();
998
+
999
+ /**
1000
+ * Object for keycodes.
1001
+ */
1002
+ var key = {
1003
+ isEdit: function (keyCode) {
1004
+ return [8, 9, 13, 32].indexOf(keyCode) !== -1;
1005
+ },
1006
+ nameFromCode: {
1007
+ '8': 'BACKSPACE',
1008
+ '9': 'TAB',
1009
+ '13': 'ENTER',
1010
+ '32': 'SPACE',
1011
+
1012
+ // Number: 0-9
1013
+ '48': 'NUM0',
1014
+ '49': 'NUM1',
1015
+ '50': 'NUM2',
1016
+ '51': 'NUM3',
1017
+ '52': 'NUM4',
1018
+ '53': 'NUM5',
1019
+ '54': 'NUM6',
1020
+ '55': 'NUM7',
1021
+ '56': 'NUM8',
1022
+
1023
+ // Alphabet: a-z
1024
+ '66': 'B',
1025
+ '69': 'E',
1026
+ '73': 'I',
1027
+ '74': 'J',
1028
+ '75': 'K',
1029
+ '76': 'L',
1030
+ '82': 'R',
1031
+ '83': 'S',
1032
+ '85': 'U',
1033
+ '89': 'Y',
1034
+ '90': 'Z',
1035
+
1036
+ '191': 'SLASH',
1037
+ '219': 'LEFTBRACKET',
1038
+ '220': 'BACKSLASH',
1039
+ '221': 'RIGHTBRACKET'
1040
+ }
1041
+ };
1042
+
1043
+ /**
1044
+ * Style
1045
+ * @class
1046
+ */
1047
+ var Style = function () {
1048
+ /**
1049
+ * passing an array of style properties to .css()
1050
+ * will result in an object of property-value pairs.
1051
+ * (compability with version < 1.9)
1052
+ *
1053
+ * @param {jQuery} $obj
1054
+ * @param {Array} propertyNames - An array of one or more CSS properties.
1055
+ * @returns {Object}
1056
+ */
1057
+ var jQueryCSS = function ($obj, propertyNames) {
1058
+ if (agent.jqueryVersion < 1.9) {
1059
+ var result = {};
1060
+ $.each(propertyNames, function (idx, propertyName) {
1061
+ result[propertyName] = $obj.css(propertyName);
1062
+ });
1063
+ return result;
1064
+ }
1065
+ return $obj.css.call($obj, propertyNames);
1066
+ };
1067
+
1068
+ /**
1069
+ * paragraph level style
1070
+ *
1071
+ * @param {WrappedRange} rng
1072
+ * @param {Object} oStyle
1073
+ */
1074
+ this.stylePara = function (rng, oStyle) {
1075
+ $.each(rng.nodes(dom.isPara), function (idx, elPara) {
1076
+ $(elPara).css(oStyle);
1077
+ });
1078
+ };
1079
+
1080
+ /**
1081
+ * get current style on cursor
1082
+ *
1083
+ * @param {WrappedRange} rng
1084
+ * @param {Element} elTarget - target element on event
1085
+ * @return {Object} - object contains style properties.
1086
+ */
1087
+ this.current = function (rng, elTarget) {
1088
+ var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
1089
+ var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
1090
+ var oStyle = jQueryCSS($cont, properties) || {};
1091
+
1092
+ oStyle['font-size'] = parseInt(oStyle['font-size'], 10);
1093
+
1094
+ // document.queryCommandState for toggle state
1095
+ oStyle['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
1096
+ oStyle['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
1097
+ oStyle['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
1098
+ oStyle['font-strikethrough'] = document.queryCommandState('strikeThrough') ? 'strikethrough' : 'normal';
1099
+ oStyle['font-superscript'] = document.queryCommandState('superscript') ? 'superscript' : 'normal';
1100
+ oStyle['font-subscript'] = document.queryCommandState('subscript') ? 'subscript' : 'normal';
1101
+
1102
+ // list-style-type to list-style(unordered, ordered)
1103
+ if (!rng.isOnList()) {
1104
+ oStyle['list-style'] = 'none';
1105
+ } else {
1106
+ var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
1107
+ var bUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
1108
+ oStyle['list-style'] = bUnordered ? 'unordered' : 'ordered';
1109
+ }
1110
+
1111
+ var elPara = dom.ancestor(rng.sc, dom.isPara);
1112
+ if (elPara && elPara.style['line-height']) {
1113
+ oStyle['line-height'] = elPara.style.lineHeight;
1114
+ } else {
1115
+ var lineHeight = parseInt(oStyle['line-height'], 10) / parseInt(oStyle['font-size'], 10);
1116
+ oStyle['line-height'] = lineHeight.toFixed(1);
1117
+ }
1118
+
1119
+ oStyle.image = dom.isImg(elTarget) && elTarget;
1120
+ oStyle.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
1121
+ oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
1122
+ oStyle.range = rng;
1123
+
1124
+ return oStyle;
1125
+ };
1126
+ };
1127
+
1128
+ /**
1129
+ * range module
1130
+ */
1131
+ var range = (function () {
1132
+ var bW3CRangeSupport = !!document.createRange;
1133
+
1134
+ /**
1135
+ * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
1136
+ * @param {TextRange} textRange
1137
+ * @param {Boolean} bStart
1138
+ * @return {BoundaryPoint}
1139
+ */
1140
+ var textRange2bp = function (textRange, bStart) {
1141
+ var elCont = textRange.parentElement(), nOffset;
1142
+
1143
+ var tester = document.body.createTextRange(), elPrevCont;
1144
+ var aChild = list.from(elCont.childNodes);
1145
+ for (nOffset = 0; nOffset < aChild.length; nOffset++) {
1146
+ if (dom.isText(aChild[nOffset])) { continue; }
1147
+ tester.moveToElementText(aChild[nOffset]);
1148
+ if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; }
1149
+ elPrevCont = aChild[nOffset];
1150
+ }
1151
+
1152
+ if (nOffset !== 0 && dom.isText(aChild[nOffset - 1])) {
1153
+ var textRangeStart = document.body.createTextRange(), elCurText = null;
1154
+ textRangeStart.moveToElementText(elPrevCont || elCont);
1155
+ textRangeStart.collapse(!elPrevCont);
1156
+ elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
1157
+
1158
+ var pointTester = textRange.duplicate();
1159
+ pointTester.setEndPoint('StartToStart', textRangeStart);
1160
+ var nTextCount = pointTester.text.replace(/[\r\n]/g, '').length;
1161
+
1162
+ while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
1163
+ nTextCount -= elCurText.nodeValue.length;
1164
+ elCurText = elCurText.nextSibling;
1165
+ }
1166
+
1167
+ /* jshint ignore:start */
1168
+ var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText, hack
1169
+ /* jshint ignore:end */
1170
+
1171
+ if (bStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
1172
+ nTextCount === elCurText.nodeValue.length) {
1173
+ nTextCount -= elCurText.nodeValue.length;
1174
+ elCurText = elCurText.nextSibling;
1175
+ }
1176
+
1177
+ elCont = elCurText;
1178
+ nOffset = nTextCount;
1179
+ }
1180
+
1181
+ return {cont: elCont, offset: nOffset};
1182
+ };
1183
+
1184
+ /**
1185
+ * return TextRange from boundary point (inspired by google closure-library)
1186
+ * @param {BoundaryPoint} bp
1187
+ * @return {TextRange}
1188
+ */
1189
+ var bp2textRange = function (bp) {
1190
+ var textRangeInfo = function (elCont, nOffset) {
1191
+ var elNode, bCollapseToStart;
1192
+
1193
+ if (dom.isText(elCont)) {
1194
+ var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
1195
+ var elPrevCont = list.last(aPrevText).previousSibling;
1196
+ elNode = elPrevCont || elCont.parentNode;
1197
+ nOffset += list.sum(list.tail(aPrevText), dom.length);
1198
+ bCollapseToStart = !elPrevCont;
1199
+ } else {
1200
+ elNode = elCont.childNodes[nOffset] || elCont;
1201
+ if (dom.isText(elNode)) {
1202
+ return textRangeInfo(elNode, nOffset);
1203
+ }
1204
+
1205
+ nOffset = 0;
1206
+ bCollapseToStart = false;
1207
+ }
1208
+
1209
+ return {cont: elNode, collapseToStart: bCollapseToStart, offset: nOffset};
1210
+ };
1211
+
1212
+ var textRange = document.body.createTextRange();
1213
+ var info = textRangeInfo(bp.cont, bp.offset);
1214
+
1215
+ textRange.moveToElementText(info.cont);
1216
+ textRange.collapse(info.collapseToStart);
1217
+ textRange.moveStart('character', info.offset);
1218
+ return textRange;
1219
+ };
1220
+
1221
+ /**
1222
+ * Wrapped Range
1223
+ *
1224
+ * @param {Element} sc - start container
1225
+ * @param {Number} so - start offset
1226
+ * @param {Element} ec - end container
1227
+ * @param {Number} eo - end offset
1228
+ */
1229
+ var WrappedRange = function (sc, so, ec, eo) {
1230
+ this.sc = sc;
1231
+ this.so = so;
1232
+ this.ec = ec;
1233
+ this.eo = eo;
1234
+
1235
+ // nativeRange: get nativeRange from sc, so, ec, eo
1236
+ var nativeRange = function () {
1237
+ if (bW3CRangeSupport) {
1238
+ var w3cRange = document.createRange();
1239
+ w3cRange.setStart(sc, so);
1240
+ w3cRange.setEnd(ec, eo);
1241
+ return w3cRange;
1242
+ } else {
1243
+ var textRange = bp2textRange({cont: sc, offset: so});
1244
+ textRange.setEndPoint('EndToEnd', bp2textRange({cont: ec, offset: eo}));
1245
+ return textRange;
1246
+ }
1247
+ };
1248
+
1249
+ /**
1250
+ * select update visible range
1251
+ */
1252
+ this.select = function () {
1253
+ var nativeRng = nativeRange();
1254
+ if (bW3CRangeSupport) {
1255
+ var selection = document.getSelection();
1256
+ if (selection.rangeCount > 0) { selection.removeAllRanges(); }
1257
+ selection.addRange(nativeRng);
1258
+ } else {
1259
+ nativeRng.select();
1260
+ }
1261
+ };
1262
+
1263
+ /**
1264
+ * returns matched nodes on range
1265
+ *
1266
+ * @param {Function} pred - predicate function
1267
+ * @return {Element[]}
1268
+ */
1269
+ this.nodes = function (pred) {
1270
+ var aNode = dom.listBetween(sc, ec);
1271
+ var aMatched = list.compact($.map(aNode, function (node) {
1272
+ return dom.ancestor(node, pred);
1273
+ }));
1274
+ return $.map(list.clusterBy(aMatched, func.eq2), list.head);
1275
+ };
1276
+
1277
+ /**
1278
+ * returns commonAncestor of range
1279
+ * @return {Element} - commonAncestor
1280
+ */
1281
+ this.commonAncestor = function () {
1282
+ return dom.commonAncestor(sc, ec);
1283
+ };
1284
+
1285
+ /**
1286
+ * makeIsOn: return isOn(pred) function
1287
+ */
1288
+ var makeIsOn = function (pred) {
1289
+ return function () {
1290
+ var elAncestor = dom.ancestor(sc, pred);
1291
+ return !!elAncestor && (elAncestor === dom.ancestor(ec, pred));
1292
+ };
1293
+ };
1294
+
1295
+ // isOnEditable: judge whether range is on editable or not
1296
+ this.isOnEditable = makeIsOn(dom.isEditable);
1297
+ // isOnList: judge whether range is on list node or not
1298
+ this.isOnList = makeIsOn(dom.isList);
1299
+ // isOnAnchor: judge whether range is on anchor node or not
1300
+ this.isOnAnchor = makeIsOn(dom.isAnchor);
1301
+ // isOnAnchor: judge whether range is on cell node or not
1302
+ this.isOnCell = makeIsOn(dom.isCell);
1303
+ // isCollapsed: judge whether range was collapsed
1304
+ this.isCollapsed = function () { return sc === ec && so === eo; };
1305
+
1306
+ /**
1307
+ * insert node at current cursor
1308
+ * @param {Element} node
1309
+ */
1310
+ this.insertNode = function (node) {
1311
+ var nativeRng = nativeRange();
1312
+ if (bW3CRangeSupport) {
1313
+ nativeRng.insertNode(node);
1314
+ } else {
1315
+ nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
1316
+ }
1317
+ };
1318
+
1319
+ this.toString = function () {
1320
+ var nativeRng = nativeRange();
1321
+ return bW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
1322
+ };
1323
+
1324
+ /**
1325
+ * create offsetPath bookmark
1326
+ * @param {Element} elEditable
1327
+ */
1328
+ this.bookmark = function (elEditable) {
1329
+ return {
1330
+ s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
1331
+ e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
1332
+ };
1333
+ };
1334
+
1335
+ /**
1336
+ * getClientRects
1337
+ * @return {Rect[]}
1338
+ */
1339
+ this.getClientRects = function () {
1340
+ var nativeRng = nativeRange();
1341
+ return nativeRng.getClientRects();
1342
+ };
1343
+ };
1344
+
1345
+ return {
1346
+ /**
1347
+ * create Range Object From arguments or Browser Selection
1348
+ *
1349
+ * @param {Element} sc - start container
1350
+ * @param {Number} so - start offset
1351
+ * @param {Element} ec - end container
1352
+ * @param {Number} eo - end offset
1353
+ */
1354
+ create : function (sc, so, ec, eo) {
1355
+ if (arguments.length === 0) { // from Browser Selection
1356
+ if (bW3CRangeSupport) { // webkit, firefox
1357
+ var selection = document.getSelection();
1358
+ if (selection.rangeCount === 0) { return null; }
1359
+
1360
+ var nativeRng = selection.getRangeAt(0);
1361
+ sc = nativeRng.startContainer;
1362
+ so = nativeRng.startOffset;
1363
+ ec = nativeRng.endContainer;
1364
+ eo = nativeRng.endOffset;
1365
+ } else { // IE8: TextRange
1366
+ var textRange = document.selection.createRange();
1367
+ var textRangeEnd = textRange.duplicate();
1368
+ textRangeEnd.collapse(false);
1369
+ var textRangeStart = textRange;
1370
+ textRangeStart.collapse(true);
1371
+
1372
+ var bpStart = textRange2bp(textRangeStart, true),
1373
+ bpEnd = textRange2bp(textRangeEnd, false);
1374
+
1375
+ sc = bpStart.cont;
1376
+ so = bpStart.offset;
1377
+ ec = bpEnd.cont;
1378
+ eo = bpEnd.offset;
1379
+ }
1380
+ } else if (arguments.length === 2) { //collapsed
1381
+ ec = sc;
1382
+ eo = so;
1383
+ }
1384
+ return new WrappedRange(sc, so, ec, eo);
1385
+ },
1386
+
1387
+ /**
1388
+ * create WrappedRange from node
1389
+ *
1390
+ * @param {Element} node
1391
+ * @return {WrappedRange}
1392
+ */
1393
+ createFromNode: function (node) {
1394
+ return this.create(node, 0, node, 1);
1395
+ },
1396
+
1397
+ /**
1398
+ * create WrappedRange from Bookmark
1399
+ *
1400
+ * @param {Element} elEditable
1401
+ * @param {Obkect} bookmark
1402
+ * @return {WrappedRange}
1403
+ */
1404
+ createFromBookmark : function (elEditable, bookmark) {
1405
+ var sc = dom.fromOffsetPath(elEditable, bookmark.s.path);
1406
+ var so = bookmark.s.offset;
1407
+ var ec = dom.fromOffsetPath(elEditable, bookmark.e.path);
1408
+ var eo = bookmark.e.offset;
1409
+ return new WrappedRange(sc, so, ec, eo);
1410
+ }
1411
+ };
1412
+ })();
1413
+
1414
+ /**
1415
+ * Table
1416
+ * @class
1417
+ */
1418
+ var Table = function () {
1419
+ /**
1420
+ * handle tab key
1421
+ *
1422
+ * @param {WrappedRange} rng
1423
+ * @param {Boolean} bShift
1424
+ */
1425
+ this.tab = function (rng, bShift) {
1426
+ var elCell = dom.ancestor(rng.commonAncestor(), dom.isCell);
1427
+ var elTable = dom.ancestor(elCell, dom.isTable);
1428
+ var aCell = dom.listDescendant(elTable, dom.isCell);
1429
+
1430
+ var elNext = list[bShift ? 'prev' : 'next'](aCell, elCell);
1431
+ if (elNext) {
1432
+ range.create(elNext, 0).select();
1433
+ }
1434
+ };
1435
+
1436
+ /**
1437
+ * create empty table element
1438
+ *
1439
+ * @param {Number} nRow
1440
+ * @param {Number} nCol
1441
+ */
1442
+ this.createTable = function (nCol, nRow) {
1443
+ var aTD = [], sTD;
1444
+ for (var idxCol = 0; idxCol < nCol; idxCol++) {
1445
+ aTD.push('<td>' + dom.blank + '</td>');
1446
+ }
1447
+ sTD = aTD.join('');
1448
+
1449
+ var aTR = [], sTR;
1450
+ for (var idxRow = 0; idxRow < nRow; idxRow++) {
1451
+ aTR.push('<tr>' + sTD + '</tr>');
1452
+ }
1453
+ sTR = aTR.join('');
1454
+ var sTable = '<table class="table table-bordered">' + sTR + '</table>';
1455
+
1456
+ return $(sTable)[0];
1457
+ };
1458
+ };
1459
+
1460
+ /**
1461
+ * Editor
1462
+ * @class
1463
+ */
1464
+ var Editor = function () {
1465
+
1466
+ var style = new Style();
1467
+ var table = new Table();
1468
+
1469
+ /**
1470
+ * save current range
1471
+ *
1472
+ * @param {jQuery} $editable
1473
+ */
1474
+ this.saveRange = function ($editable) {
1475
+ $editable.data('range', range.create());
1476
+ };
1477
+
1478
+ /**
1479
+ * restore lately range
1480
+ *
1481
+ * @param {jQuery} $editable
1482
+ */
1483
+ this.restoreRange = function ($editable) {
1484
+ var rng = $editable.data('range');
1485
+ if (rng) { rng.select(); }
1486
+ };
1487
+
1488
+ /**
1489
+ * current style
1490
+ * @param {Element} elTarget
1491
+ */
1492
+ this.currentStyle = function (elTarget) {
1493
+ var rng = range.create();
1494
+ return rng.isOnEditable() && style.current(rng, elTarget);
1495
+ };
1496
+
1497
+ /**
1498
+ * undo
1499
+ * @param {jQuery} $editable
1500
+ */
1501
+ this.undo = function ($editable) {
1502
+ $editable.data('NoteHistory').undo($editable);
1503
+ };
1504
+
1505
+ /**
1506
+ * redo
1507
+ * @param {jQuery} $editable
1508
+ */
1509
+ this.redo = function ($editable) {
1510
+ $editable.data('NoteHistory').redo($editable);
1511
+ };
1512
+
1513
+ /**
1514
+ * record Undo
1515
+ * @param {jQuery} $editable
1516
+ */
1517
+ var recordUndo = this.recordUndo = function ($editable) {
1518
+ $editable.data('NoteHistory').recordUndo($editable);
1519
+ };
1520
+
1521
+ /* jshint ignore:start */
1522
+ // native commands(with execCommand), generate function for execCommand
1523
+ var aCmd = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
1524
+ 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
1525
+ 'insertOrderedList', 'insertUnorderedList',
1526
+ 'indent', 'outdent', 'formatBlock', 'removeFormat',
1527
+ 'backColor', 'foreColor', 'insertHorizontalRule', 'fontName'];
1528
+
1529
+ for (var idx = 0, len = aCmd.length; idx < len; idx ++) {
1530
+ this[aCmd[idx]] = (function (sCmd) {
1531
+ return function ($editable, sValue) {
1532
+ recordUndo($editable);
1533
+ document.execCommand(sCmd, false, sValue);
1534
+ };
1535
+ })(aCmd[idx]);
1536
+ }
1537
+ /* jshint ignore:end */
1538
+
1539
+ /**
1540
+ * @param {jQuery} $editable
1541
+ * @param {WrappedRange} rng
1542
+ * @param {Number} nTabsize
1543
+ */
1544
+ var insertTab = function ($editable, rng, nTabsize) {
1545
+ recordUndo($editable);
1546
+ var sNbsp = new Array(nTabsize + 1).join('&nbsp;');
1547
+ rng.insertNode($('<span id="noteTab">' + sNbsp + '</span>')[0]);
1548
+ var $tab = $('#noteTab').removeAttr('id');
1549
+ rng = range.create($tab[0], 1);
1550
+ rng.select();
1551
+ dom.remove($tab[0]);
1552
+ };
1553
+
1554
+ /**
1555
+ * handle tab key
1556
+ * @param {jQuery} $editable
1557
+ * @param {Number} nTabsize
1558
+ * @param {Boolean} bShift
1559
+ */
1560
+ this.tab = function ($editable, options) {
1561
+ var rng = range.create();
1562
+ if (rng.isCollapsed() && rng.isOnCell()) {
1563
+ table.tab(rng);
1564
+ } else {
1565
+ insertTab($editable, rng, options.tabsize);
1566
+ }
1567
+ };
1568
+
1569
+ /**
1570
+ * handle shift+tab key
1571
+ */
1572
+ this.untab = function () {
1573
+ var rng = range.create();
1574
+ if (rng.isCollapsed() && rng.isOnCell()) {
1575
+ table.tab(rng, true);
1576
+ }
1577
+ };
1578
+
1579
+ /**
1580
+ * insert image
1581
+ *
1582
+ * @param {jQuery} $editable
1583
+ * @param {String} sUrl
1584
+ */
1585
+ this.insertImage = function ($editable, sUrl) {
1586
+ async.createImage(sUrl).then(function ($image) {
1587
+ recordUndo($editable);
1588
+ $image.css({
1589
+ display: '',
1590
+ width: Math.min($editable.width(), $image.width())
1591
+ });
1592
+ range.create().insertNode($image[0]);
1593
+ }).fail(function () {
1594
+ var callbacks = $editable.data('callbacks');
1595
+ if (callbacks.onImageUploadError) {
1596
+ callbacks.onImageUploadError();
1597
+ }
1598
+ });
1599
+ };
1600
+
1601
+ /**
1602
+ * insert video
1603
+ * @param {jQuery} $editable
1604
+ * @param {String} sUrl
1605
+ */
1606
+ this.insertVideo = function ($editable, sUrl) {
1607
+ recordUndo($editable);
1608
+
1609
+ // video url patterns(youtube, instagram, vimeo, dailymotion)
1610
+ var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
1611
+ var ytMatch = sUrl.match(ytRegExp);
1612
+
1613
+ var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
1614
+ var igMatch = sUrl.match(igRegExp);
1615
+
1616
+ var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
1617
+ var vMatch = sUrl.match(vRegExp);
1618
+
1619
+ var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
1620
+ var vimMatch = sUrl.match(vimRegExp);
1621
+
1622
+ var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
1623
+ var dmMatch = sUrl.match(dmRegExp);
1624
+
1625
+ var $video;
1626
+ if (ytMatch && ytMatch[2].length === 11) {
1627
+ var youtubeId = ytMatch[2];
1628
+ $video = $('<iframe>')
1629
+ .attr('src', '//www.youtube.com/embed/' + youtubeId)
1630
+ .attr('width', '640').attr('height', '360');
1631
+ } else if (igMatch && igMatch[0].length > 0) {
1632
+ $video = $('<iframe>')
1633
+ .attr('src', igMatch[0] + '/embed/')
1634
+ .attr('width', '612').attr('height', '710')
1635
+ .attr('scrolling', 'no')
1636
+ .attr('allowtransparency', 'true');
1637
+ } else if (vMatch && vMatch[0].length > 0) {
1638
+ $video = $('<iframe>')
1639
+ .attr('src', vMatch[0] + '/embed/simple')
1640
+ .attr('width', '600').attr('height', '600')
1641
+ .attr('class', 'vine-embed');
1642
+ } else if (vimMatch && vimMatch[3].length > 0) {
1643
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
1644
+ .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
1645
+ .attr('width', '640').attr('height', '360');
1646
+ } else if (dmMatch && dmMatch[2].length > 0) {
1647
+ $video = $('<iframe>')
1648
+ .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
1649
+ .attr('width', '640').attr('height', '360');
1650
+ } else {
1651
+ // this is not a known video link. Now what, Cat? Now what?
1652
+ }
1653
+
1654
+ if ($video) {
1655
+ $video.attr('frameborder', 0);
1656
+ range.create().insertNode($video[0]);
1657
+ }
1658
+ };
1659
+
1660
+ /**
1661
+ * formatBlock
1662
+ *
1663
+ * @param {jQuery} $editable
1664
+ * @param {String} sTagName
1665
+ */
1666
+ this.formatBlock = function ($editable, sTagName) {
1667
+ recordUndo($editable);
1668
+ sTagName = agent.bMSIE ? '<' + sTagName + '>' : sTagName;
1669
+ document.execCommand('FormatBlock', false, sTagName);
1670
+ };
1671
+
1672
+ this.formatPara = function ($editable) {
1673
+ this.formatBlock($editable, 'P');
1674
+ };
1675
+
1676
+ /* jshint ignore:start */
1677
+ for (var idx = 1; idx <= 6; idx ++) {
1678
+ this['formatH' + idx] = function (idx) {
1679
+ return function ($editable) {
1680
+ this.formatBlock($editable, 'H' + idx);
1681
+ };
1682
+ }(idx);
1683
+ };
1684
+ /* jshint ignore:end */
1685
+
1686
+ /**
1687
+ * fontsize
1688
+ * FIXME: Still buggy
1689
+ *
1690
+ * @param {jQuery} $editable
1691
+ * @param {String} sValue - px
1692
+ */
1693
+ this.fontSize = function ($editable, sValue) {
1694
+ recordUndo($editable);
1695
+ document.execCommand('fontSize', false, 3);
1696
+ if (agent.bFF) {
1697
+ // firefox: <font size="3"> to <span style='font-size={sValue}px;'>, buggy
1698
+ $editable.find('font[size=3]').removeAttr('size').css('font-size', sValue + 'px');
1699
+ } else {
1700
+ // chrome: <span style="font-size: medium"> to <span style='font-size={sValue}px;'>
1701
+ $editable.find('span').filter(function () {
1702
+ return this.style.fontSize === 'medium';
1703
+ }).css('font-size', sValue + 'px');
1704
+ }
1705
+ };
1706
+
1707
+ /**
1708
+ * lineHeight
1709
+ * @param {jQuery} $editable
1710
+ * @param {String} sValue
1711
+ */
1712
+ this.lineHeight = function ($editable, sValue) {
1713
+ recordUndo($editable);
1714
+ style.stylePara(range.create(), {lineHeight: sValue});
1715
+ };
1716
+
1717
+ /**
1718
+ * unlink
1719
+ * @param {jQuery} $editable
1720
+ */
1721
+ this.unlink = function ($editable) {
1722
+ var rng = range.create();
1723
+ if (rng.isOnAnchor()) {
1724
+ recordUndo($editable);
1725
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1726
+ rng = range.createFromNode(elAnchor);
1727
+ rng.select();
1728
+ document.execCommand('unlink');
1729
+ }
1730
+ };
1731
+
1732
+ /**
1733
+ * create link
1734
+ *
1735
+ * @param {jQuery} $editable
1736
+ * @param {String} sLinkUrl
1737
+ * @param {Boolean} bNewWindow
1738
+ */
1739
+ this.createLink = function ($editable, sLinkText, sLinkUrl, bNewWindow) {
1740
+ var rng = range.create();
1741
+ recordUndo($editable);
1742
+
1743
+ // protocol
1744
+ var sLinkUrlWithProtocol = sLinkUrl;
1745
+ if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
1746
+ sLinkUrlWithProtocol = 'mailto:' + sLinkUrl;
1747
+ } else if (sLinkUrl.indexOf('://') === -1) {
1748
+ sLinkUrlWithProtocol = 'http://' + sLinkUrl;
1749
+ }
1750
+
1751
+ // createLink when range collapsed (IE, Firefox).
1752
+ if ((agent.bMSIE || agent.bFF) && rng.isCollapsed()) {
1753
+ rng.insertNode($('<A id="linkAnchor">' + sLinkText + '</A>')[0]);
1754
+ var $anchor = $('#linkAnchor').attr('href', sLinkUrlWithProtocol).removeAttr('id');
1755
+ rng = range.createFromNode($anchor[0]);
1756
+ rng.select();
1757
+ } else {
1758
+ document.execCommand('createlink', false, sLinkUrlWithProtocol);
1759
+ }
1760
+
1761
+ // target
1762
+ $.each(rng.nodes(dom.isAnchor), function (idx, elAnchor) {
1763
+ // update link text
1764
+ $(elAnchor).html(sLinkText);
1765
+ if (bNewWindow) {
1766
+ $(elAnchor).attr('target', '_blank');
1767
+ } else {
1768
+ $(elAnchor).removeAttr('target');
1769
+ }
1770
+ });
1771
+ };
1772
+
1773
+ /**
1774
+ * get link info
1775
+ *
1776
+ * @return {Promise}
1777
+ */
1778
+ this.getLinkInfo = function () {
1779
+ var rng = range.create();
1780
+ var bNewWindow = true;
1781
+ var sUrl = '';
1782
+
1783
+ // If range on anchor expand range on anchor(for edit link).
1784
+ if (rng.isOnAnchor()) {
1785
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1786
+ rng = range.createFromNode(elAnchor);
1787
+ bNewWindow = $(elAnchor).attr('target') === '_blank';
1788
+ sUrl = elAnchor.href;
1789
+ }
1790
+
1791
+ return {
1792
+ text: rng.toString(),
1793
+ url: sUrl,
1794
+ newWindow: bNewWindow
1795
+ };
1796
+ };
1797
+
1798
+ /**
1799
+ * get video info
1800
+ *
1801
+ * @return {Object}
1802
+ */
1803
+ this.getVideoInfo = function () {
1804
+ var rng = range.create();
1805
+
1806
+ if (rng.isOnAnchor()) {
1807
+ var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
1808
+ rng = range.createFromNode(elAnchor);
1809
+ }
1810
+
1811
+ return {
1812
+ text: rng.toString()
1813
+ };
1814
+ };
1815
+
1816
+ this.color = function ($editable, sObjColor) {
1817
+ var oColor = JSON.parse(sObjColor);
1818
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
1819
+
1820
+ recordUndo($editable);
1821
+ if (foreColor) { document.execCommand('foreColor', false, foreColor); }
1822
+ if (backColor) { document.execCommand('backColor', false, backColor); }
1823
+ };
1824
+
1825
+ this.insertTable = function ($editable, sDim) {
1826
+ recordUndo($editable);
1827
+ var aDim = sDim.split('x');
1828
+ range.create().insertNode(table.createTable(aDim[0], aDim[1]));
1829
+ };
1830
+
1831
+ /**
1832
+ * @param {jQuery} $editable
1833
+ * @param {String} sValue
1834
+ * @param {jQuery} $target
1835
+ */
1836
+ this.floatMe = function ($editable, sValue, $target) {
1837
+ recordUndo($editable);
1838
+ $target.css('float', sValue);
1839
+ };
1840
+
1841
+ /**
1842
+ * resize overlay element
1843
+ * @param {jQuery} $editable
1844
+ * @param {String} sValue
1845
+ * @param {jQuery} $target - target element
1846
+ */
1847
+ this.resize = function ($editable, sValue, $target) {
1848
+ recordUndo($editable);
1849
+
1850
+ $target.css({
1851
+ width: $editable.width() * sValue + 'px',
1852
+ height: ''
1853
+ });
1854
+ };
1855
+
1856
+ /**
1857
+ * @param {Position} pos
1858
+ * @param {jQuery} $target - target element
1859
+ * @param {Boolean} [bKeepRatio] - keep ratio
1860
+ */
1861
+ this.resizeTo = function (pos, $target, bKeepRatio) {
1862
+ var szImage;
1863
+ if (bKeepRatio) {
1864
+ var newRatio = pos.y / pos.x;
1865
+ var ratio = $target.data('ratio');
1866
+ szImage = {
1867
+ width: ratio > newRatio ? pos.x : pos.y / ratio,
1868
+ height: ratio > newRatio ? pos.x * ratio : pos.y
1869
+ };
1870
+ } else {
1871
+ szImage = {
1872
+ width: pos.x,
1873
+ height: pos.y
1874
+ };
1875
+ }
1876
+
1877
+ $target.css(szImage);
1878
+ };
1879
+
1880
+ /**
1881
+ * remove media object
1882
+ *
1883
+ * @param {jQuery} $editable
1884
+ * @param {String} sValue - dummy argument (for keep interface)
1885
+ * @param {jQuery} $target - target element
1886
+ */
1887
+ this.removeMedia = function ($editable, sValue, $target) {
1888
+ recordUndo($editable);
1889
+ $target.detach();
1890
+ };
1891
+ };
1892
+
1893
+ /**
1894
+ * History
1895
+ * @class
1896
+ */
1897
+ var History = function () {
1898
+ var aUndo = [], aRedo = [];
1899
+
1900
+ var makeSnap = function ($editable) {
1901
+ var elEditable = $editable[0], rng = range.create();
1902
+ return {
1903
+ contents: $editable.html(),
1904
+ bookmark: rng.bookmark(elEditable),
1905
+ scrollTop: $editable.scrollTop()
1906
+ };
1907
+ };
1908
+
1909
+ var applySnap = function ($editable, oSnap) {
1910
+ $editable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
1911
+ range.createFromBookmark($editable[0], oSnap.bookmark).select();
1912
+ };
1913
+
1914
+ this.undo = function ($editable) {
1915
+ var oSnap = makeSnap($editable);
1916
+ if (aUndo.length === 0) { return; }
1917
+ applySnap($editable, aUndo.pop());
1918
+ aRedo.push(oSnap);
1919
+ };
1920
+
1921
+ this.redo = function ($editable) {
1922
+ var oSnap = makeSnap($editable);
1923
+ if (aRedo.length === 0) { return; }
1924
+ applySnap($editable, aRedo.pop());
1925
+ aUndo.push(oSnap);
1926
+ };
1927
+
1928
+ this.recordUndo = function ($editable) {
1929
+ aRedo = [];
1930
+ aUndo.push(makeSnap($editable));
1931
+ };
1932
+ };
1933
+
1934
+ /**
1935
+ * Button
1936
+ */
1937
+ var Button = function () {
1938
+ /**
1939
+ * update button status
1940
+ *
1941
+ * @param {jQuery} $container
1942
+ * @param {Object} oStyle
1943
+ */
1944
+ this.update = function ($container, oStyle) {
1945
+ /**
1946
+ * handle dropdown's check mark (for fontname, fontsize, lineHeight).
1947
+ * @param {jQuery} $btn
1948
+ * @param {Number} nValue
1949
+ */
1950
+ var checkDropdownMenu = function ($btn, nValue) {
1951
+ $btn.find('.dropdown-menu li a').each(function () {
1952
+ // always compare string to avoid creating another func.
1953
+ var bChecked = ($(this).data('value') + '') === (nValue + '');
1954
+ this.className = bChecked ? 'checked' : '';
1955
+ });
1956
+ };
1957
+
1958
+ /**
1959
+ * update button state(active or not).
1960
+ *
1961
+ * @param {String} sSelector
1962
+ * @param {Function} pred
1963
+ */
1964
+ var btnState = function (sSelector, pred) {
1965
+ var $btn = $container.find(sSelector);
1966
+ $btn.toggleClass('active', pred());
1967
+ };
1968
+
1969
+ // fontname
1970
+ var $fontname = $container.find('.note-fontname');
1971
+ if ($fontname.length > 0) {
1972
+ var selectedFont = oStyle['font-family'];
1973
+ if (!!selectedFont) {
1974
+ selectedFont = list.head(selectedFont.split(','));
1975
+ selectedFont = selectedFont.replace(/\'/g, '');
1976
+ $fontname.find('.note-current-fontname').text(selectedFont);
1977
+ checkDropdownMenu($fontname, selectedFont);
1978
+ }
1979
+ }
1980
+
1981
+ // fontsize
1982
+ var $fontsize = $container.find('.note-fontsize');
1983
+ $fontsize.find('.note-current-fontsize').text(oStyle['font-size']);
1984
+ checkDropdownMenu($fontsize, parseFloat(oStyle['font-size']));
1985
+
1986
+ // lineheight
1987
+ var $lineHeight = $container.find('.note-height');
1988
+ checkDropdownMenu($lineHeight, parseFloat(oStyle['line-height']));
1989
+
1990
+ btnState('button[data-event="bold"]', function () {
1991
+ return oStyle['font-bold'] === 'bold';
1992
+ });
1993
+ btnState('button[data-event="italic"]', function () {
1994
+ return oStyle['font-italic'] === 'italic';
1995
+ });
1996
+ btnState('button[data-event="underline"]', function () {
1997
+ return oStyle['font-underline'] === 'underline';
1998
+ });
1999
+ btnState('button[data-event="strikethrough"]', function () {
2000
+ return oStyle['font-strikethrough'] === 'strikethrough';
2001
+ });
2002
+ btnState('button[data-event="superscript"]', function () {
2003
+ return oStyle['font-superscript'] === 'superscript';
2004
+ });
2005
+ btnState('button[data-event="subscript"]', function () {
2006
+ return oStyle['font-subscript'] === 'subscript';
2007
+ });
2008
+ btnState('button[data-event="justifyLeft"]', function () {
2009
+ return oStyle['text-align'] === 'left' || oStyle['text-align'] === 'start';
2010
+ });
2011
+ btnState('button[data-event="justifyCenter"]', function () {
2012
+ return oStyle['text-align'] === 'center';
2013
+ });
2014
+ btnState('button[data-event="justifyRight"]', function () {
2015
+ return oStyle['text-align'] === 'right';
2016
+ });
2017
+ btnState('button[data-event="justifyFull"]', function () {
2018
+ return oStyle['text-align'] === 'justify';
2019
+ });
2020
+ btnState('button[data-event="insertUnorderedList"]', function () {
2021
+ return oStyle['list-style'] === 'unordered';
2022
+ });
2023
+ btnState('button[data-event="insertOrderedList"]', function () {
2024
+ return oStyle['list-style'] === 'ordered';
2025
+ });
2026
+ };
2027
+
2028
+ /**
2029
+ * update recent color
2030
+ *
2031
+ * @param {Element} elBtn
2032
+ * @param {String} sEvent
2033
+ * @param {sValue} sValue
2034
+ */
2035
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2036
+ var $color = $(elBtn).closest('.note-color');
2037
+ var $recentColor = $color.find('.note-recent-color');
2038
+ var oColor = JSON.parse($recentColor.attr('data-value'));
2039
+ oColor[sEvent] = sValue;
2040
+ $recentColor.attr('data-value', JSON.stringify(oColor));
2041
+ var sKey = sEvent === 'backColor' ? 'background-color' : 'color';
2042
+ $recentColor.find('i').css(sKey, sValue);
2043
+ };
2044
+ };
2045
+
2046
+ /**
2047
+ * Toolbar
2048
+ */
2049
+ var Toolbar = function () {
2050
+ var button = new Button();
2051
+
2052
+ this.update = function ($toolbar, oStyle) {
2053
+ button.update($toolbar, oStyle);
2054
+ };
2055
+
2056
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2057
+ button.updateRecentColor(elBtn, sEvent, sValue);
2058
+ };
2059
+
2060
+ /**
2061
+ * activate buttons exclude codeview
2062
+ * @param {jQuery} $toolbar
2063
+ */
2064
+ this.activate = function ($toolbar) {
2065
+ $toolbar.find('button').not('button[data-event="codeview"]').removeClass('disabled');
2066
+ };
2067
+
2068
+ /**
2069
+ * deactivate buttons exclude codeview
2070
+ * @param {jQuery} $toolbar
2071
+ */
2072
+ this.deactivate = function ($toolbar) {
2073
+ $toolbar.find('button').not('button[data-event="codeview"]').addClass('disabled');
2074
+ };
2075
+
2076
+ this.updateFullscreen = function ($container, bFullscreen) {
2077
+ var $btn = $container.find('button[data-event="fullscreen"]');
2078
+ $btn.toggleClass('active', bFullscreen);
2079
+ };
2080
+
2081
+ this.updateCodeview = function ($container, bCodeview) {
2082
+ var $btn = $container.find('button[data-event="codeview"]');
2083
+ $btn.toggleClass('active', bCodeview);
2084
+ };
2085
+ };
2086
+
2087
+ /**
2088
+ * Popover (http://getbootstrap.com/javascript/#popovers)
2089
+ */
2090
+ var Popover = function () {
2091
+ var button = new Button();
2092
+
2093
+ /**
2094
+ * show popover
2095
+ * @param {jQuery} popover
2096
+ * @param {Element} elPlaceholder - placeholder for popover
2097
+ */
2098
+ var showPopover = function ($popover, elPlaceholder) {
2099
+ var $placeholder = $(elPlaceholder);
2100
+ var pos = $placeholder.position();
2101
+
2102
+ // include margin
2103
+ var height = $placeholder.outerHeight(true);
2104
+
2105
+ // display popover below placeholder.
2106
+ $popover.css({
2107
+ display: 'block',
2108
+ left: pos.left,
2109
+ top: pos.top + height
2110
+ });
2111
+ };
2112
+
2113
+ var PX_POPOVER_ARROW_OFFSET_X = 20;
2114
+
2115
+ /**
2116
+ * update current state
2117
+ * @param {jQuery} $popover - popover container
2118
+ * @param {Object} oStyle - style object
2119
+ * @param {Boolean} isAirMode
2120
+ */
2121
+ this.update = function ($popover, oStyle, isAirMode) {
2122
+ button.update($popover, oStyle);
2123
+
2124
+ var $linkPopover = $popover.find('.note-link-popover');
2125
+
2126
+ if (oStyle.anchor) {
2127
+ var $anchor = $linkPopover.find('a');
2128
+ $anchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
2129
+ showPopover($linkPopover, oStyle.anchor);
2130
+ } else {
2131
+ $linkPopover.hide();
2132
+ }
2133
+
2134
+ var $imagePopover = $popover.find('.note-image-popover');
2135
+ if (oStyle.image) {
2136
+ showPopover($imagePopover, oStyle.image);
2137
+ } else {
2138
+ $imagePopover.hide();
2139
+ }
2140
+
2141
+ if (isAirMode) {
2142
+ var $airPopover = $popover.find('.note-air-popover');
2143
+ if (!oStyle.range.isCollapsed()) {
2144
+ var bnd = func.rect2bnd(list.last(oStyle.range.getClientRects()));
2145
+ $airPopover.css({
2146
+ display: 'block',
2147
+ left: Math.max(bnd.left + bnd.width / 2 - PX_POPOVER_ARROW_OFFSET_X, 0),
2148
+ top: bnd.top + bnd.height
2149
+ });
2150
+ } else {
2151
+ $airPopover.hide();
2152
+ }
2153
+ }
2154
+ };
2155
+
2156
+ this.updateRecentColor = function (elBtn, sEvent, sValue) {
2157
+ button.updateRecentColor(elBtn, sEvent, sValue);
2158
+ };
2159
+
2160
+ /**
2161
+ * hide all popovers
2162
+ * @param {jQuery} $popover - popover contaienr
2163
+ */
2164
+ this.hide = function ($popover) {
2165
+ $popover.children().hide();
2166
+ };
2167
+ };
2168
+
2169
+ /**
2170
+ * Handle
2171
+ */
2172
+ var Handle = function () {
2173
+ /**
2174
+ * update handle
2175
+ * @param {jQuery} $handle
2176
+ * @param {Object} oStyle
2177
+ */
2178
+ this.update = function ($handle, oStyle) {
2179
+ var $selection = $handle.find('.note-control-selection');
2180
+ if (oStyle.image) {
2181
+ var $image = $(oStyle.image);
2182
+ var pos = $image.position();
2183
+
2184
+ // include margin
2185
+ var szImage = {
2186
+ w: $image.outerWidth(true),
2187
+ h: $image.outerHeight(true)
2188
+ };
2189
+
2190
+ $selection.css({
2191
+ display: 'block',
2192
+ left: pos.left,
2193
+ top: pos.top,
2194
+ width: szImage.w,
2195
+ height: szImage.h
2196
+ }).data('target', oStyle.image); // save current image element.
2197
+ var sSizing = szImage.w + 'x' + szImage.h;
2198
+ $selection.find('.note-control-selection-info').text(sSizing);
2199
+ } else {
2200
+ $selection.hide();
2201
+ }
2202
+ };
2203
+
2204
+ this.hide = function ($handle) {
2205
+ $handle.children().hide();
2206
+ };
2207
+ };
2208
+
2209
+ /**
2210
+ * Dialog
2211
+ *
2212
+ * @class
2213
+ */
2214
+ var Dialog = function () {
2215
+
2216
+ /**
2217
+ * toggle button status
2218
+ *
2219
+ * @param {jQuery} $btn
2220
+ * @param {Boolean} bEnable
2221
+ */
2222
+ var toggleBtn = function ($btn, bEnable) {
2223
+ $btn.toggleClass('disabled', !bEnable);
2224
+ $btn.attr('disabled', !bEnable);
2225
+ };
2226
+
2227
+ /**
2228
+ * show image dialog
2229
+ *
2230
+ * @param {jQuery} $editable
2231
+ * @param {jQuery} $dialog
2232
+ * @return {Promise}
2233
+ */
2234
+ this.showImageDialog = function ($editable, $dialog) {
2235
+ return $.Deferred(function (deferred) {
2236
+ var $imageDialog = $dialog.find('.note-image-dialog');
2237
+
2238
+ var $imageInput = $dialog.find('.note-image-input'),
2239
+ $imageUrl = $dialog.find('.note-image-url'),
2240
+ $imageBtn = $dialog.find('.note-image-btn');
2241
+
2242
+ $imageDialog.one('shown.bs.modal', function () {
2243
+ // Cloning imageInput to clear element.
2244
+ $imageInput.replaceWith($imageInput.clone()
2245
+ .on('change', function () {
2246
+ $imageDialog.modal('hide');
2247
+ deferred.resolve(this.files);
2248
+ })
2249
+ );
2250
+
2251
+ $imageBtn.click(function (event) {
2252
+ event.preventDefault();
2253
+
2254
+ $imageDialog.modal('hide');
2255
+ deferred.resolve($imageUrl.val());
2256
+ });
2257
+
2258
+ $imageUrl.keyup(function () {
2259
+ toggleBtn($imageBtn, $imageUrl.val());
2260
+ }).val('').focus();
2261
+ }).one('hidden.bs.modal', function () {
2262
+ $editable.focus();
2263
+ $imageInput.off('change');
2264
+ $imageUrl.off('keyup');
2265
+ $imageBtn.off('click');
2266
+ }).modal('show');
2267
+ });
2268
+ };
2269
+
2270
+ /**
2271
+ * Show video dialog and set event handlers on dialog controls.
2272
+ *
2273
+ * @param {jQuery} $dialog
2274
+ * @param {Object} videoInfo
2275
+ * @return {Promise}
2276
+ */
2277
+ this.showVideoDialog = function ($editable, $dialog, videoInfo) {
2278
+ return $.Deferred(function (deferred) {
2279
+ var $videoDialog = $dialog.find('.note-video-dialog');
2280
+ var $videoUrl = $videoDialog.find('.note-video-url'),
2281
+ $videoBtn = $videoDialog.find('.note-video-btn');
2282
+
2283
+ $videoDialog.one('shown.bs.modal', function () {
2284
+ $videoUrl.val(videoInfo.text).keyup(function () {
2285
+ toggleBtn($videoBtn, $videoUrl.val());
2286
+ }).trigger('keyup').trigger('focus');
2287
+
2288
+ $videoBtn.click(function (event) {
2289
+ event.preventDefault();
2290
+
2291
+ $videoDialog.modal('hide');
2292
+ deferred.resolve($videoUrl.val());
2293
+ });
2294
+ }).one('hidden.bs.modal', function () {
2295
+ $editable.focus();
2296
+ $videoUrl.off('keyup');
2297
+ $videoBtn.off('click');
2298
+ }).modal('show');
2299
+ });
2300
+ };
2301
+
2302
+ /**
2303
+ * Show link dialog and set event handlers on dialog controls.
2304
+ *
2305
+ * @param {jQuery} $dialog
2306
+ * @param {Object} linkInfo
2307
+ * @return {Promise}
2308
+ */
2309
+ this.showLinkDialog = function ($editable, $dialog, linkInfo) {
2310
+ return $.Deferred(function (deferred) {
2311
+ var $linkDialog = $dialog.find('.note-link-dialog');
2312
+
2313
+ var $linkText = $linkDialog.find('.note-link-text'),
2314
+ $linkUrl = $linkDialog.find('.note-link-url'),
2315
+ $linkBtn = $linkDialog.find('.note-link-btn'),
2316
+ $openInNewWindow = $linkDialog.find('input[type=checkbox]');
2317
+
2318
+ $linkDialog.one('shown.bs.modal', function () {
2319
+ $linkText.val(linkInfo.text);
2320
+
2321
+ $linkText.keyup(function () {
2322
+ // if linktext was modified by keyup,
2323
+ // stop cloning text from linkUrl
2324
+ linkInfo.text = $linkText.val();
2325
+ });
2326
+
2327
+ // if no url was given, copy text to url
2328
+ if (!linkInfo.url) {
2329
+ linkInfo.url = linkInfo.text;
2330
+ toggleBtn($linkBtn, linkInfo.text);
2331
+ }
2332
+
2333
+ $linkUrl.keyup(function () {
2334
+ toggleBtn($linkBtn, $linkUrl.val());
2335
+ // display same link on `Text to display` input
2336
+ // when create a new link
2337
+ if (!linkInfo.text) {
2338
+ $linkText.val($linkUrl.val());
2339
+ }
2340
+ }).val(linkInfo.url).trigger('focus').trigger('select');
2341
+
2342
+ $openInNewWindow.prop('checked', linkInfo.newWindow);
2343
+
2344
+ $linkBtn.one('click', function (event) {
2345
+ event.preventDefault();
2346
+
2347
+ $linkDialog.modal('hide');
2348
+ deferred.resolve($linkText.val(), $linkUrl.val(), $openInNewWindow.is(':checked'));
2349
+ });
2350
+ }).one('hidden.bs.modal', function () {
2351
+ $editable.focus();
2352
+ $linkUrl.off('keyup');
2353
+ }).modal('show');
2354
+ }).promise();
2355
+ };
2356
+
2357
+ /**
2358
+ * show help dialog
2359
+ *
2360
+ * @param {jQuery} $dialog
2361
+ */
2362
+ this.showHelpDialog = function ($editable, $dialog) {
2363
+ var $helpDialog = $dialog.find('.note-help-dialog');
2364
+
2365
+ $helpDialog.one('hidden.bs.modal', function () {
2366
+ $editable.focus();
2367
+ }).modal('show');
2368
+ };
2369
+ };
2370
+
2371
+ /**
2372
+ * EventHandler
2373
+ */
2374
+ var EventHandler = function () {
2375
+ var $document = $(document);
2376
+
2377
+ var editor = new Editor();
2378
+ var toolbar = new Toolbar(), popover = new Popover();
2379
+ var handle = new Handle(), dialog = new Dialog();
2380
+
2381
+ /**
2382
+ * returns makeLayoutInfo from editor's descendant node.
2383
+ *
2384
+ * @param {Element} descendant
2385
+ * @returns {Object}
2386
+ */
2387
+ var makeLayoutInfo = function (descendant) {
2388
+ var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
2389
+
2390
+ if ($target.length === 0) { return null; }
2391
+
2392
+ var $editor;
2393
+ if ($target.is('.note-editor, .note-air-editor')) {
2394
+ $editor = $target;
2395
+ } else {
2396
+ $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
2397
+ }
2398
+
2399
+ return dom.buildLayoutInfo($editor);
2400
+ };
2401
+
2402
+ /**
2403
+ * insert Images from file array.
2404
+ *
2405
+ * @param {jQuery} $editable
2406
+ * @param {File[]} files
2407
+ */
2408
+ var insertImages = function ($editable, files) {
2409
+ editor.restoreRange($editable);
2410
+ var callbacks = $editable.data('callbacks');
2411
+
2412
+ // If onImageUpload options setted
2413
+ if (callbacks.onImageUpload) {
2414
+ callbacks.onImageUpload(files, editor, $editable);
2415
+ // else insert Image as dataURL
2416
+ } else {
2417
+ $.each(files, function (idx, file) {
2418
+ async.readFileAsDataURL(file).then(function (sDataURL) {
2419
+ editor.insertImage($editable, sDataURL);
2420
+ }).fail(function () {
2421
+ if (callbacks.onImageUploadError) {
2422
+ callbacks.onImageUploadError();
2423
+ }
2424
+ });
2425
+ });
2426
+ }
2427
+ };
2428
+
2429
+ var hMousedown = function (event) {
2430
+ //preventDefault Selection for FF, IE8+
2431
+ if (dom.isImg(event.target)) {
2432
+ event.preventDefault();
2433
+ }
2434
+ };
2435
+
2436
+ var hToolbarAndPopoverUpdate = function (event) {
2437
+ // delay for range after mouseup
2438
+ setTimeout(function () {
2439
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2440
+ var oStyle = editor.currentStyle(event.target);
2441
+ if (!oStyle) { return; }
2442
+
2443
+ var isAirMode = oLayoutInfo.editor().data('options').airMode;
2444
+ if (!isAirMode) {
2445
+ toolbar.update(oLayoutInfo.toolbar(), oStyle);
2446
+ }
2447
+
2448
+ popover.update(oLayoutInfo.popover(), oStyle, isAirMode);
2449
+ handle.update(oLayoutInfo.handle(), oStyle);
2450
+ }, 0);
2451
+ };
2452
+
2453
+ var hScroll = function (event) {
2454
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2455
+ //hide popover and handle when scrolled
2456
+ popover.hide(oLayoutInfo.popover());
2457
+ handle.hide(oLayoutInfo.handle());
2458
+ };
2459
+
2460
+ /**
2461
+ * paste clipboard image
2462
+ *
2463
+ * @param {Event} event
2464
+ */
2465
+ var hPasteClipboardImage = function (event) {
2466
+ var originalEvent = event.originalEvent;
2467
+ if (!originalEvent.clipboardData ||
2468
+ !originalEvent.clipboardData.items ||
2469
+ !originalEvent.clipboardData.items.length) {
2470
+ return;
2471
+ }
2472
+
2473
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2474
+ var item = list.head(originalEvent.clipboardData.items);
2475
+ var bClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
2476
+
2477
+ if (bClipboardImage) {
2478
+ insertImages(oLayoutInfo.editable(), [item.getAsFile()]);
2479
+ }
2480
+ };
2481
+
2482
+ /**
2483
+ * `mousedown` event handler on $handle
2484
+ * - controlSizing: resize image
2485
+ *
2486
+ * @param {MouseEvent} event
2487
+ */
2488
+ var hHandleMousedown = function (event) {
2489
+ if (dom.isControlSizing(event.target)) {
2490
+ event.preventDefault();
2491
+ event.stopPropagation();
2492
+
2493
+ var oLayoutInfo = makeLayoutInfo(event.target),
2494
+ $handle = oLayoutInfo.handle(), $popover = oLayoutInfo.popover(),
2495
+ $editable = oLayoutInfo.editable();
2496
+
2497
+ var elTarget = $handle.find('.note-control-selection').data('target'),
2498
+ $target = $(elTarget), posStart = $target.offset(),
2499
+ scrollTop = $document.scrollTop();
2500
+
2501
+ $document.on('mousemove', function (event) {
2502
+ editor.resizeTo({
2503
+ x: event.clientX - posStart.left,
2504
+ y: event.clientY - (posStart.top - scrollTop)
2505
+ }, $target, !event.shiftKey);
2506
+
2507
+ handle.update($handle, {image: elTarget});
2508
+ popover.update($popover, {image: elTarget});
2509
+ }).one('mouseup', function () {
2510
+ $document.off('mousemove');
2511
+ });
2512
+
2513
+ if (!$target.data('ratio')) { // original ratio.
2514
+ $target.data('ratio', $target.height() / $target.width());
2515
+ }
2516
+
2517
+ editor.recordUndo($editable);
2518
+ }
2519
+ };
2520
+
2521
+ var hToolbarAndPopoverMousedown = function (event) {
2522
+ // prevent default event when insertTable (FF, Webkit)
2523
+ var $btn = $(event.target).closest('[data-event]');
2524
+ if ($btn.length > 0) {
2525
+ event.preventDefault();
2526
+ }
2527
+ };
2528
+
2529
+ var toggleFullscreen = function (oLayoutInfo) {
2530
+ var $editor = oLayoutInfo.editor(),
2531
+ $toolbar = oLayoutInfo.toolbar(),
2532
+ $editable = oLayoutInfo.editable(),
2533
+ $codable = oLayoutInfo.codable();
2534
+
2535
+ var options = $editor.data('options');
2536
+
2537
+ var $scrollbar = $('html, body');
2538
+
2539
+ var resize = function (size) {
2540
+ $editor.css('width', size.w);
2541
+ $editable.css('height', size.h);
2542
+ $codable.css('height', size.h);
2543
+ if ($codable.data('cmEditor')) {
2544
+ $codable.data('cmEditor').setSize(null, size.h);
2545
+ }
2546
+ };
2547
+
2548
+ $editor.toggleClass('fullscreen');
2549
+ var isFullscreen = $editor.hasClass('fullscreen');
2550
+ if (isFullscreen) {
2551
+ $editable.data('orgHeight', $editable.css('height'));
2552
+
2553
+ $(window).on('resize', function () {
2554
+ resize({
2555
+ w: $(window).width(),
2556
+ h: $(window).height() - $toolbar.outerHeight()
2557
+ });
2558
+ }).trigger('resize');
2559
+
2560
+ $scrollbar.css('overflow', 'hidden');
2561
+ } else {
2562
+ $(window).off('resize');
2563
+ resize({
2564
+ w: options.width || '',
2565
+ h: $editable.data('orgHeight')
2566
+ });
2567
+ $scrollbar.css('overflow', 'visible');
2568
+ }
2569
+
2570
+ toolbar.updateFullscreen($toolbar, isFullscreen);
2571
+ };
2572
+
2573
+ var toggleCodeView = function (oLayoutInfo) {
2574
+ var $editor = oLayoutInfo.editor(),
2575
+ $toolbar = oLayoutInfo.toolbar(),
2576
+ $editable = oLayoutInfo.editable(),
2577
+ $codable = oLayoutInfo.codable(),
2578
+ $popover = oLayoutInfo.popover();
2579
+
2580
+ var options = $editor.data('options');
2581
+
2582
+ var cmEditor, server;
2583
+
2584
+ $editor.toggleClass('codeview');
2585
+
2586
+ var bCodeview = $editor.hasClass('codeview');
2587
+ if (bCodeview) {
2588
+ $codable.val($editable.html());
2589
+ $codable.height($editable.height());
2590
+ toolbar.deactivate($toolbar);
2591
+ popover.hide($popover);
2592
+ $codable.focus();
2593
+
2594
+ // activate CodeMirror as codable
2595
+ if (agent.bCodeMirror) {
2596
+ cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
2597
+
2598
+ // CodeMirror TernServer
2599
+ if (options.codemirror.tern) {
2600
+ server = new CodeMirror.TernServer(options.codemirror.tern);
2601
+ cmEditor.ternServer = server;
2602
+ cmEditor.on('cursorActivity', function (cm) {
2603
+ server.updateArgHints(cm);
2604
+ });
2605
+ }
2606
+
2607
+ // CodeMirror hasn't Padding.
2608
+ cmEditor.setSize(null, $editable.outerHeight());
2609
+ // autoFormatRange If formatting included
2610
+ if (cmEditor.autoFormatRange) {
2611
+ cmEditor.autoFormatRange({line: 0, ch: 0}, {
2612
+ line: cmEditor.lineCount(),
2613
+ ch: cmEditor.getTextArea().value.length
2614
+ });
2615
+ }
2616
+ $codable.data('cmEditor', cmEditor);
2617
+ }
2618
+ } else {
2619
+ // deactivate CodeMirror as codable
2620
+ if (agent.bCodeMirror) {
2621
+ cmEditor = $codable.data('cmEditor');
2622
+ $codable.val(cmEditor.getValue());
2623
+ cmEditor.toTextArea();
2624
+ }
2625
+
2626
+ $editable.html($codable.val() || dom.emptyPara);
2627
+ $editable.height(options.height ? $codable.height() : 'auto');
2628
+
2629
+ toolbar.activate($toolbar);
2630
+ $editable.focus();
2631
+ }
2632
+
2633
+ toolbar.updateCodeview(oLayoutInfo.toolbar(), bCodeview);
2634
+ };
2635
+
2636
+ var hToolbarAndPopoverClick = function (event) {
2637
+ var $btn = $(event.target).closest('[data-event]');
2638
+
2639
+ if ($btn.length > 0) {
2640
+ var sEvent = $btn.attr('data-event'), sValue = $btn.attr('data-value');
2641
+
2642
+ var oLayoutInfo = makeLayoutInfo(event.target);
2643
+ var $dialog = oLayoutInfo.dialog(),
2644
+ $editable = oLayoutInfo.editable();
2645
+
2646
+ // before command: detect control selection element($target)
2647
+ var $target;
2648
+ if ($.inArray(sEvent, ['resize', 'floatMe', 'removeMedia']) !== -1) {
2649
+ var $selection = oLayoutInfo.handle().find('.note-control-selection');
2650
+ $target = $($selection.data('target'));
2651
+ }
2652
+
2653
+ if (editor[sEvent]) { // on command
2654
+ $editable.trigger('focus');
2655
+ editor[sEvent]($editable, sValue, $target);
2656
+ }
2657
+
2658
+ // after command
2659
+ if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
2660
+ var options = oLayoutInfo.editor().data('options', options);
2661
+ var module = options.airMode ? popover : toolbar;
2662
+ module.updateRecentColor(list.head($btn), sEvent, sValue);
2663
+ } else if (sEvent === 'showLinkDialog') { // popover to dialog
2664
+ $editable.focus();
2665
+ var linkInfo = editor.getLinkInfo();
2666
+
2667
+ editor.saveRange($editable);
2668
+ dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (sLinkText, sLinkUrl, bNewWindow) {
2669
+ editor.restoreRange($editable);
2670
+ editor.createLink($editable, sLinkText, sLinkUrl, bNewWindow);
2671
+ // hide popover after creating link
2672
+ popover.hide(oLayoutInfo.popover());
2673
+ });
2674
+ } else if (sEvent === 'showImageDialog') {
2675
+ $editable.focus();
2676
+
2677
+ dialog.showImageDialog($editable, $dialog).then(function (data) {
2678
+ if (typeof data === 'string') {
2679
+ editor.restoreRange($editable);
2680
+ editor.insertImage($editable, data);
2681
+ } else {
2682
+ insertImages($editable, data);
2683
+ }
2684
+ });
2685
+ } else if (sEvent === 'showVideoDialog') {
2686
+ $editable.focus();
2687
+ var videoInfo = editor.getVideoInfo();
2688
+
2689
+ editor.saveRange($editable);
2690
+ dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
2691
+ editor.restoreRange($editable);
2692
+ editor.insertVideo($editable, sUrl);
2693
+ });
2694
+ } else if (sEvent === 'showHelpDialog') {
2695
+ dialog.showHelpDialog($editable, $dialog);
2696
+ } else if (sEvent === 'fullscreen') {
2697
+ toggleFullscreen(oLayoutInfo);
2698
+ } else if (sEvent === 'codeview') {
2699
+ toggleCodeView(oLayoutInfo);
2700
+ }
2701
+
2702
+ hToolbarAndPopoverUpdate(event);
2703
+ }
2704
+ };
2705
+
2706
+ var EDITABLE_PADDING = 24;
2707
+ /**
2708
+ * `mousedown` event handler on statusbar
2709
+ *
2710
+ * @param {MouseEvent} event
2711
+ */
2712
+ var hStatusbarMousedown = function (event) {
2713
+ event.preventDefault();
2714
+ event.stopPropagation();
2715
+
2716
+ var $editable = makeLayoutInfo(event.target).editable();
2717
+ var nEditableTop = $editable.offset().top - $document.scrollTop();
2718
+
2719
+ $document.on('mousemove', function (event) {
2720
+ var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
2721
+ $editable.height(nHeight);
2722
+ }).one('mouseup', function () {
2723
+ $document.off('mousemove');
2724
+ });
2725
+ };
2726
+
2727
+ var PX_PER_EM = 18;
2728
+ var hDimensionPickerMove = function (event) {
2729
+ var $picker = $(event.target.parentNode); // target is mousecatcher
2730
+ var $dimensionDisplay = $picker.next();
2731
+ var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
2732
+ var $highlighted = $picker.find('.note-dimension-picker-highlighted');
2733
+ var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
2734
+
2735
+ var posOffset;
2736
+ // HTML5 with jQuery - e.offsetX is undefined in Firefox
2737
+ if (event.offsetX === undefined) {
2738
+ var posCatcher = $(event.target).offset();
2739
+ posOffset = {
2740
+ x: event.pageX - posCatcher.left,
2741
+ y: event.pageY - posCatcher.top
2742
+ };
2743
+ } else {
2744
+ posOffset = {
2745
+ x: event.offsetX,
2746
+ y: event.offsetY
2747
+ };
2748
+ }
2749
+
2750
+ var dim = {
2751
+ c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
2752
+ r: Math.ceil(posOffset.y / PX_PER_EM) || 1
2753
+ };
2754
+
2755
+ $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
2756
+ $catcher.attr('data-value', dim.c + 'x' + dim.r);
2757
+
2758
+ if (3 < dim.c && dim.c < 10) { // 5~10
2759
+ $unhighlighted.css({ width: dim.c + 1 + 'em'});
2760
+ }
2761
+
2762
+ if (3 < dim.r && dim.r < 10) { // 5~10
2763
+ $unhighlighted.css({ height: dim.r + 1 + 'em'});
2764
+ }
2765
+
2766
+ $dimensionDisplay.html(dim.c + ' x ' + dim.r);
2767
+ };
2768
+
2769
+ /**
2770
+ * attach Drag and Drop Events
2771
+ *
2772
+ * @param {Object} oLayoutInfo - layout Informations
2773
+ */
2774
+ var attachDragAndDropEvent = function (oLayoutInfo) {
2775
+ var collection = $(), $dropzone = oLayoutInfo.dropzone,
2776
+ $dropzoneMessage = oLayoutInfo.dropzone.find('.note-dropzone-message');
2777
+
2778
+ // show dropzone on dragenter when dragging a object to document.
2779
+ $document.on('dragenter', function (e) {
2780
+ var bCodeview = oLayoutInfo.editor.hasClass('codeview');
2781
+ if (!bCodeview && collection.length === 0) {
2782
+ oLayoutInfo.editor.addClass('dragover');
2783
+ $dropzone.width(oLayoutInfo.editor.width());
2784
+ $dropzone.height(oLayoutInfo.editor.height());
2785
+ $dropzoneMessage.text('Drag Image Here');
2786
+ }
2787
+ collection = collection.add(e.target);
2788
+ }).on('dragleave', function (e) {
2789
+ collection = collection.not(e.target);
2790
+ if (collection.length === 0) {
2791
+ oLayoutInfo.editor.removeClass('dragover');
2792
+ }
2793
+ }).on('drop', function () {
2794
+ collection = $();
2795
+ oLayoutInfo.editor.removeClass('dragover');
2796
+ });
2797
+
2798
+ // change dropzone's message on hover.
2799
+ $dropzone.on('dragenter', function () {
2800
+ $dropzone.addClass('hover');
2801
+ $dropzoneMessage.text('Drop Image');
2802
+ }).on('dragleave', function () {
2803
+ $dropzone.removeClass('hover');
2804
+ $dropzoneMessage.text('Drag Image Here');
2805
+ });
2806
+
2807
+ // attach dropImage
2808
+ $dropzone.on('drop', function (event) {
2809
+ event.preventDefault();
2810
+
2811
+ var dataTransfer = event.originalEvent.dataTransfer;
2812
+ if (dataTransfer && dataTransfer.files) {
2813
+ var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
2814
+ oLayoutInfo.editable().focus();
2815
+ insertImages(oLayoutInfo.editable(), dataTransfer.files);
2816
+ }
2817
+ }).on('dragover', false); // prevent default dragover event
2818
+ };
2819
+
2820
+
2821
+ /**
2822
+ * bind KeyMap on keydown
2823
+ *
2824
+ * @param {Object} oLayoutInfo
2825
+ * @param {Object} keyMap
2826
+ */
2827
+ this.bindKeyMap = function (oLayoutInfo, keyMap) {
2828
+ var $editor = oLayoutInfo.editor;
2829
+ var $editable = oLayoutInfo.editable;
2830
+
2831
+ $editable.on('keydown', function (event) {
2832
+ var aKey = [];
2833
+
2834
+ // modifier
2835
+ if (event.metaKey) { aKey.push('CMD'); }
2836
+ if (event.ctrlKey) { aKey.push('CTRL'); }
2837
+ if (event.shiftKey) { aKey.push('SHIFT'); }
2838
+
2839
+ // keycode
2840
+ var keyName = key.nameFromCode[event.keyCode];
2841
+ if (keyName) { aKey.push(keyName); }
2842
+
2843
+ var handler = keyMap[aKey.join('+')];
2844
+ if (handler) {
2845
+ event.preventDefault();
2846
+
2847
+ editor[handler]($editable, $editor.data('options'));
2848
+ } else if (key.isEdit(event.keyCode)) {
2849
+ editor.recordUndo($editable);
2850
+ }
2851
+ });
2852
+ };
2853
+
2854
+ /**
2855
+ * attach eventhandler
2856
+ *
2857
+ * @param {Object} oLayoutInfo - layout Informations
2858
+ * @param {Object} options - user options include custom event handlers
2859
+ * @param {Function} options.enter - enter key handler
2860
+ */
2861
+ this.attach = function (oLayoutInfo, options) {
2862
+ // handlers for editable
2863
+ this.bindKeyMap(oLayoutInfo, options.keyMap[agent.bMac ? 'mac' : 'pc']);
2864
+ oLayoutInfo.editable.on('mousedown', hMousedown);
2865
+ oLayoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
2866
+ oLayoutInfo.editable.on('scroll', hScroll);
2867
+ oLayoutInfo.editable.on('paste', hPasteClipboardImage);
2868
+
2869
+ // handler for handle and popover
2870
+ oLayoutInfo.handle.on('mousedown', hHandleMousedown);
2871
+ oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
2872
+ oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
2873
+
2874
+ // handlers for frame mode (toolbar, statusbar)
2875
+ if (!options.airMode) {
2876
+ // handler for drag and drop
2877
+ if (!options.disableDragAndDrop) {
2878
+ attachDragAndDropEvent(oLayoutInfo);
2879
+ }
2880
+
2881
+ // handler for toolbar
2882
+ oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
2883
+ oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
2884
+
2885
+ // handler for statusbar
2886
+ oLayoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
2887
+ }
2888
+
2889
+ // handler for table dimension
2890
+ var $catcherContainer = options.airMode ? oLayoutInfo.popover :
2891
+ oLayoutInfo.toolbar;
2892
+ var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
2893
+ $catcher.on('mousemove', hDimensionPickerMove);
2894
+
2895
+ // save selection when focusout
2896
+ oLayoutInfo.editable.on('blur', function () {
2897
+ editor.saveRange(oLayoutInfo.editable);
2898
+ });
2899
+
2900
+ // save options on editor
2901
+ oLayoutInfo.editor.data('options', options);
2902
+
2903
+ // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
2904
+ if (options.styleWithSpan && !agent.bMSIE) {
2905
+ // protect FF Error: NS_ERROR_FAILURE: Failure
2906
+ setTimeout(function () {
2907
+ document.execCommand('styleWithCSS', 0, true);
2908
+ }, 0);
2909
+ }
2910
+
2911
+ // History
2912
+ oLayoutInfo.editable.data('NoteHistory', new History());
2913
+
2914
+ // basic event callbacks (lowercase)
2915
+ // enter, focus, blur, keyup, keydown
2916
+ if (options.onenter) {
2917
+ oLayoutInfo.editable.keypress(function (event) {
2918
+ if (event.keyCode === key.ENTER) { options.onenter(event); }
2919
+ });
2920
+ }
2921
+
2922
+ if (options.onfocus) { oLayoutInfo.editable.focus(options.onfocus); }
2923
+ if (options.onblur) { oLayoutInfo.editable.blur(options.onblur); }
2924
+ if (options.onkeyup) { oLayoutInfo.editable.keyup(options.onkeyup); }
2925
+ if (options.onkeydown) { oLayoutInfo.editable.keydown(options.onkeydown); }
2926
+ if (options.onpaste) { oLayoutInfo.editable.on('paste', options.onpaste); }
2927
+
2928
+ // callbacks for advanced features (camel)
2929
+ if (options.onToolbarClick) { oLayoutInfo.toolbar.click(options.onToolbarClick); }
2930
+ if (options.onChange) {
2931
+ var hChange = function () {
2932
+ options.onChange(oLayoutInfo.editable, oLayoutInfo.editable.html());
2933
+ };
2934
+
2935
+ if (agent.bMSIE) {
2936
+ var sDomEvents = 'DOMCharacterDataModified, DOMSubtreeModified, DOMNodeInserted';
2937
+ oLayoutInfo.editable.on(sDomEvents, hChange);
2938
+ } else {
2939
+ oLayoutInfo.editable.on('input', hChange);
2940
+ }
2941
+ }
2942
+
2943
+ // All editor status will be saved on editable with jquery's data
2944
+ // for support multiple editor with singleton object.
2945
+ oLayoutInfo.editable.data('callbacks', {
2946
+ onAutoSave: options.onAutoSave,
2947
+ onImageUpload: options.onImageUpload,
2948
+ onImageUploadError: options.onImageUploadError,
2949
+ onFileUpload: options.onFileUpload,
2950
+ onFileUploadError: options.onFileUpload
2951
+ });
2952
+ };
2953
+
2954
+ this.dettach = function (oLayoutInfo) {
2955
+ oLayoutInfo.editable.off();
2956
+
2957
+ oLayoutInfo.popover.off();
2958
+ oLayoutInfo.handle.off();
2959
+ oLayoutInfo.dialog.off();
2960
+
2961
+ if (oLayoutInfo.editor.data('options').airMode) {
2962
+ oLayoutInfo.dropzone.off();
2963
+ oLayoutInfo.toolbar.off();
2964
+ oLayoutInfo.statusbar.off();
2965
+ }
2966
+ };
2967
+ };
2968
+
2969
+ /**
2970
+ * renderer
2971
+ *
2972
+ * rendering toolbar and editable
2973
+ */
2974
+ var Renderer = function () {
2975
+
2976
+ /**
2977
+ * bootstrap button template
2978
+ *
2979
+ * @param {String} sLabel
2980
+ * @param {Object} [options]
2981
+ * @param {String} [options.event]
2982
+ * @param {String} [options.value]
2983
+ * @param {String} [options.title]
2984
+ * @param {String} [options.dropdown]
2985
+ */
2986
+ var tplButton = function (sLabel, options) {
2987
+ var event = options.event;
2988
+ var value = options.value;
2989
+ var title = options.title;
2990
+ var className = options.className;
2991
+ var dropdown = options.dropdown;
2992
+
2993
+ return '<button type="button"' +
2994
+ ' class="btn btn-default btn-sm btn-small' +
2995
+ (className ? ' ' + className : '') +
2996
+ (dropdown ? ' dropdown-toggle' : '') +
2997
+ '"' +
2998
+ (dropdown ? ' data-toggle="dropdown"' : '') +
2999
+ (title ? ' title="' + title + '"' : '') +
3000
+ (event ? ' data-event="' + event + '"' : '') +
3001
+ (value ? ' data-value=\'' + value + '\'' : '') +
3002
+ ' tabindex="-1">' +
3003
+ sLabel +
3004
+ (dropdown ? ' <span class="caret"></span>' : '') +
3005
+ '</button>' +
3006
+ (dropdown || '');
3007
+ };
3008
+
3009
+ /**
3010
+ * bootstrap icon button template
3011
+ *
3012
+ * @param {String} sIconClass
3013
+ * @param {Object} [options]
3014
+ * @param {String} [options.event]
3015
+ * @param {String} [options.value]
3016
+ * @param {String} [options.title]
3017
+ * @param {String} [options.dropdown]
3018
+ */
3019
+ var tplIconButton = function (sIconClass, options) {
3020
+ var sLabel = '<i class="' + sIconClass + '"></i>';
3021
+ return tplButton(sLabel, options);
3022
+ };
3023
+
3024
+ /**
3025
+ * bootstrap popover template
3026
+ *
3027
+ * @param {String} className
3028
+ * @param {String} content
3029
+ */
3030
+ var tplPopover = function (className, content) {
3031
+ return '<div class="' + className + ' popover bottom in" style="display: none;">' +
3032
+ '<div class="arrow"></div>' +
3033
+ '<div class="popover-content">' +
3034
+ content +
3035
+ '</div>' +
3036
+ '</div>';
3037
+ };
3038
+
3039
+ /**
3040
+ * bootstrap dialog template
3041
+ *
3042
+ * @param {String} className
3043
+ * @param {String} [title]
3044
+ * @param {String} body
3045
+ * @param {String} [footer]
3046
+ */
3047
+ var tplDialog = function (className, title, body, footer) {
3048
+ return '<div class="' + className + ' modal" aria-hidden="false">' +
3049
+ '<div class="modal-dialog">' +
3050
+ '<div class="modal-content">' +
3051
+ (title ?
3052
+ '<div class="modal-header">' +
3053
+ '<button type="button" class="close" aria-hidden="true" tabindex="-1">&times;</button>' +
3054
+ '<h4>' + title + '</h4>' +
3055
+ '</div>' : ''
3056
+ ) +
3057
+ '<form class="note-modal-form">' +
3058
+ '<div class="modal-body">' +
3059
+ '<div class="row-fluid">' + body + '</div>' +
3060
+ '</div>' +
3061
+ (footer ?
3062
+ '<div class="modal-footer">' + footer + '</div>' : ''
3063
+ ) +
3064
+ '</form>' +
3065
+ '</div>' +
3066
+ '</div>' +
3067
+ '</div>';
3068
+ };
3069
+
3070
+ var tplButtonInfo = {
3071
+ picture: function (lang) {
3072
+ return tplIconButton('fa fa-picture-o icon-picture', {
3073
+ event: 'showImageDialog',
3074
+ title: lang.image.image
3075
+ });
3076
+ },
3077
+ link: function (lang) {
3078
+ return tplIconButton('fa fa-link icon-link', {
3079
+ event: 'showLinkDialog',
3080
+ title: lang.link.link
3081
+ });
3082
+ },
3083
+ video: function (lang) {
3084
+ return tplIconButton('fa fa-youtube-play icon-play', {
3085
+ event: 'showVideoDialog',
3086
+ title: lang.video.video
3087
+ });
3088
+ },
3089
+ table: function (lang) {
3090
+ var dropdown = '<ul class="dropdown-menu">' +
3091
+ '<div class="note-dimension-picker">' +
3092
+ '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
3093
+ '<div class="note-dimension-picker-highlighted"></div>' +
3094
+ '<div class="note-dimension-picker-unhighlighted"></div>' +
3095
+ '</div>' +
3096
+ '<div class="note-dimension-display"> 1 x 1 </div>' +
3097
+ '</ul>';
3098
+ return tplIconButton('fa fa-table icon-table', {
3099
+ title: lang.table.table,
3100
+ dropdown: dropdown
3101
+ });
3102
+ },
3103
+ style: function (lang, options) {
3104
+ var items = options.styleTags.reduce(function (memo, v) {
3105
+ var label = lang.style[v === 'p' ? 'normal' : v];
3106
+ return memo + '<li><a data-event="formatBlock" data-value="' + v + '">' +
3107
+ (
3108
+ (v === 'p' || v === 'pre') ? label :
3109
+ '<' + v + '>' + label + '</' + v + '>'
3110
+ ) +
3111
+ '</a></li>';
3112
+ }, '');
3113
+
3114
+ return tplIconButton('fa fa-magic icon-magic', {
3115
+ title: lang.style.style,
3116
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3117
+ });
3118
+ },
3119
+ fontname: function (lang, options) {
3120
+ var items = options.fontNames.reduce(function (memo, v) {
3121
+ return memo + '<li><a data-event="fontName" data-value="' + v + '">' +
3122
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3123
+ '</a></li>';
3124
+ }, '');
3125
+ var sLabel = '<span class="note-current-fontname">' +
3126
+ options.defaultFontName +
3127
+ '</span>';
3128
+ return tplButton(sLabel, {
3129
+ title: lang.font.name,
3130
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3131
+ });
3132
+ },
3133
+ fontsize: function (lang, options) {
3134
+ var items = options.fontSizes.reduce(function (memo, v) {
3135
+ return memo + '<li><a data-event="fontSize" data-value="' + v + '">' +
3136
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3137
+ '</a></li>';
3138
+ }, '');
3139
+
3140
+ var sLabel = '<span class="note-current-fontsize">11</span>';
3141
+ return tplButton(sLabel, {
3142
+ title: lang.font.size,
3143
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3144
+ });
3145
+ },
3146
+
3147
+ color: function (lang) {
3148
+ var colorButtonLabel = '<i class="fa fa-font icon-font" style="color:black;background-color:yellow;"></i>';
3149
+ var colorButton = tplButton(colorButtonLabel, {
3150
+ className: 'note-recent-color',
3151
+ title: lang.color.recent,
3152
+ event: 'color',
3153
+ value: '{"backColor":"yellow"}'
3154
+ });
3155
+
3156
+ var dropdown = '<ul class="dropdown-menu">' +
3157
+ '<li>' +
3158
+ '<div class="btn-group">' +
3159
+ '<div class="note-palette-title">' + lang.color.background + '</div>' +
3160
+ '<div class="note-color-reset" data-event="backColor"' +
3161
+ ' data-value="inherit" title="' + lang.color.transparent + '">' +
3162
+ lang.color.setTransparent +
3163
+ '</div>' +
3164
+ '<div class="note-color-palette" data-target-event="backColor"></div>' +
3165
+ '</div>' +
3166
+ '<div class="btn-group">' +
3167
+ '<div class="note-palette-title">' + lang.color.foreground + '</div>' +
3168
+ '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' +
3169
+ lang.color.resetToDefault +
3170
+ '</div>' +
3171
+ '<div class="note-color-palette" data-target-event="foreColor"></div>' +
3172
+ '</div>' +
3173
+ '</li>' +
3174
+ '</ul>';
3175
+
3176
+ var moreButton = tplButton('', {
3177
+ title: lang.color.more,
3178
+ dropdown: dropdown
3179
+ });
3180
+
3181
+ return colorButton + moreButton;
3182
+ },
3183
+ bold: function (lang) {
3184
+ return tplIconButton('fa fa-bold icon-bold', {
3185
+ event: 'bold',
3186
+ title: lang.font.bold
3187
+ });
3188
+ },
3189
+ italic: function (lang) {
3190
+ return tplIconButton('fa fa-italic icon-italic', {
3191
+ event: 'italic',
3192
+ title: lang.font.italic
3193
+ });
3194
+ },
3195
+ underline: function (lang) {
3196
+ return tplIconButton('fa fa-underline icon-underline', {
3197
+ event: 'underline',
3198
+ title: lang.font.underline
3199
+ });
3200
+ },
3201
+ strikethrough: function (lang) {
3202
+ return tplIconButton('fa fa-strikethrough icon-strikethrough', {
3203
+ event: 'strikethrough',
3204
+ title: lang.font.strikethrough
3205
+ });
3206
+ },
3207
+ superscript: function (lang) {
3208
+ return tplIconButton('fa fa-superscript icon-superscript', {
3209
+ event: 'superscript',
3210
+ title: lang.font.superscript
3211
+ });
3212
+ },
3213
+ subscript: function (lang) {
3214
+ return tplIconButton('fa fa-subscript icon-subscript', {
3215
+ event: 'subscript',
3216
+ title: lang.font.subscript
3217
+ });
3218
+ },
3219
+ clear: function (lang) {
3220
+ return tplIconButton('fa fa-eraser icon-eraser', {
3221
+ event: 'removeFormat',
3222
+ title: lang.font.clear
3223
+ });
3224
+ },
3225
+ ul: function (lang) {
3226
+ return tplIconButton('fa fa-list-ul icon-list-ul', {
3227
+ event: 'insertUnorderedList',
3228
+ title: lang.lists.unordered
3229
+ });
3230
+ },
3231
+ ol: function (lang) {
3232
+ return tplIconButton('fa fa-list-ol icon-list-ol', {
3233
+ event: 'insertOrderedList',
3234
+ title: lang.lists.ordered
3235
+ });
3236
+ },
3237
+ paragraph: function (lang) {
3238
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
3239
+ title: lang.paragraph.left,
3240
+ event: 'justifyLeft'
3241
+ });
3242
+ var centerButton = tplIconButton('fa fa-align-center icon-align-center', {
3243
+ title: lang.paragraph.center,
3244
+ event: 'justifyCenter'
3245
+ });
3246
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
3247
+ title: lang.paragraph.right,
3248
+ event: 'justifyRight'
3249
+ });
3250
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
3251
+ title: lang.paragraph.justify,
3252
+ event: 'justifyFull'
3253
+ });
3254
+
3255
+ var outdentButton = tplIconButton('fa fa-outdent icon-indent-left', {
3256
+ title: lang.paragraph.outdent,
3257
+ event: 'outdent'
3258
+ });
3259
+ var indentButton = tplIconButton('fa fa-indent icon-indent-right', {
3260
+ title: lang.paragraph.indent,
3261
+ event: 'indent'
3262
+ });
3263
+
3264
+ var dropdown = '<div class="dropdown-menu">' +
3265
+ '<div class="note-align btn-group">' +
3266
+ leftButton + centerButton + rightButton + justifyButton +
3267
+ '</div>' +
3268
+ '<div class="note-list btn-group">' +
3269
+ indentButton + outdentButton +
3270
+ '</div>' +
3271
+ '</div>';
3272
+
3273
+ return tplIconButton('fa fa-align-left icon-align-left', {
3274
+ title: lang.paragraph.paragraph,
3275
+ dropdown: dropdown
3276
+ });
3277
+ },
3278
+ height: function (lang, options) {
3279
+ var items = options.lineHeights.reduce(function (memo, v) {
3280
+ return memo + '<li><a data-event="lineHeight" data-value="' + parseFloat(v) + '">' +
3281
+ '<i class="fa fa-check icon-ok"></i> ' + v +
3282
+ '</a></li>';
3283
+ }, '');
3284
+
3285
+ return tplIconButton('fa fa-text-height icon-text-height', {
3286
+ title: lang.font.height,
3287
+ dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
3288
+ });
3289
+
3290
+ },
3291
+ help: function (lang) {
3292
+ return tplIconButton('fa fa-question icon-question', {
3293
+ event: 'showHelpDialog',
3294
+ title: lang.options.help
3295
+ });
3296
+ },
3297
+ fullscreen: function (lang) {
3298
+ return tplIconButton('fa fa-arrows-alt icon-fullscreen', {
3299
+ event: 'fullscreen',
3300
+ title: lang.options.fullscreen
3301
+ });
3302
+ },
3303
+ codeview: function (lang) {
3304
+ return tplIconButton('fa fa-code icon-code', {
3305
+ event: 'codeview',
3306
+ title: lang.options.codeview
3307
+ });
3308
+ },
3309
+ undo: function (lang) {
3310
+ return tplIconButton('fa fa-undo icon-undo', {
3311
+ event: 'undo',
3312
+ title: lang.history.undo
3313
+ });
3314
+ },
3315
+ redo: function (lang) {
3316
+ return tplIconButton('fa fa-repeat icon-repeat', {
3317
+ event: 'redo',
3318
+ title: lang.history.redo
3319
+ });
3320
+ }
3321
+ };
3322
+
3323
+ var tplPopovers = function (lang, options) {
3324
+ var tplLinkPopover = function () {
3325
+ var linkButton = tplIconButton('fa fa-edit icon-edit', {
3326
+ title: lang.link.edit,
3327
+ event: 'showLinkDialog'
3328
+ });
3329
+ var unlinkButton = tplIconButton('fa fa-unlink icon-unlink', {
3330
+ title: lang.link.unlink,
3331
+ event: 'unlink'
3332
+ });
3333
+ var content = '<a href="http://www.google.com" target="_blank">www.google.com</a>&nbsp;&nbsp;' +
3334
+ '<div class="note-insert btn-group">' +
3335
+ linkButton + unlinkButton +
3336
+ '</div>';
3337
+ return tplPopover('note-link-popover', content);
3338
+ };
3339
+
3340
+ var tplImagePopover = function () {
3341
+ var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
3342
+ title: lang.image.resizeFull,
3343
+ event: 'resize',
3344
+ value: '1'
3345
+ });
3346
+ var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
3347
+ title: lang.image.resizeHalf,
3348
+ event: 'resize',
3349
+ value: '0.5'
3350
+ });
3351
+ var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
3352
+ title: lang.image.resizeQuarter,
3353
+ event: 'resize',
3354
+ value: '0.25'
3355
+ });
3356
+
3357
+ var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
3358
+ title: lang.image.floatLeft,
3359
+ event: 'floatMe',
3360
+ value: 'left'
3361
+ });
3362
+ var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
3363
+ title: lang.image.floatRight,
3364
+ event: 'floatMe',
3365
+ value: 'right'
3366
+ });
3367
+ var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
3368
+ title: lang.image.floatNone,
3369
+ event: 'floatMe',
3370
+ value: 'none'
3371
+ });
3372
+
3373
+ var removeButton = tplIconButton('fa fa-trash-o icon-trash', {
3374
+ title: lang.image.remove,
3375
+ event: 'removeMedia',
3376
+ value: 'none'
3377
+ });
3378
+
3379
+ var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' +
3380
+ '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
3381
+ '<div class="btn-group">' + removeButton + '</div>';
3382
+ return tplPopover('note-image-popover', content);
3383
+ };
3384
+
3385
+ var tplAirPopover = function () {
3386
+ var content = '';
3387
+ for (var idx = 0, sz = options.airPopover.length; idx < sz; idx ++) {
3388
+ var group = options.airPopover[idx];
3389
+ content += '<div class="note-' + group[0] + ' btn-group">';
3390
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
3391
+ content += tplButtonInfo[group[1][i]](lang, options);
3392
+ }
3393
+ content += '</div>';
3394
+ }
3395
+
3396
+ return tplPopover('note-air-popover', content);
3397
+ };
3398
+
3399
+ return '<div class="note-popover">' +
3400
+ tplLinkPopover() +
3401
+ tplImagePopover() +
3402
+ (options.airMode ? tplAirPopover() : '') +
3403
+ '</div>';
3404
+ };
3405
+
3406
+ var tplHandles = function () {
3407
+ return '<div class="note-handle">' +
3408
+ '<div class="note-control-selection">' +
3409
+ '<div class="note-control-selection-bg"></div>' +
3410
+ '<div class="note-control-holder note-control-nw"></div>' +
3411
+ '<div class="note-control-holder note-control-ne"></div>' +
3412
+ '<div class="note-control-holder note-control-sw"></div>' +
3413
+ '<div class="note-control-sizing note-control-se"></div>' +
3414
+ '<div class="note-control-selection-info"></div>' +
3415
+ '</div>' +
3416
+ '</div>';
3417
+ };
3418
+
3419
+ /**
3420
+ * shortcut table template
3421
+ * @param {String} title
3422
+ * @param {String} body
3423
+ */
3424
+ var tplShortcut = function (title, body) {
3425
+ return '<table class="note-shortcut">' +
3426
+ '<thead>' +
3427
+ '<tr><th></th><th>' + title + '</th></tr>' +
3428
+ '</thead>' +
3429
+ '<tbody>' + body + '</tbody>' +
3430
+ '</table>';
3431
+ };
3432
+
3433
+ var tplShortcutText = function (lang) {
3434
+ var body = '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
3435
+ '<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
3436
+ '<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
3437
+ '<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strikethrough + '</td></tr>' +
3438
+ '<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>';
3439
+
3440
+ return tplShortcut(lang.shortcut.textFormatting, body);
3441
+ };
3442
+
3443
+ var tplShortcutAction = function (lang) {
3444
+ var body = '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
3445
+ '<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
3446
+ '<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
3447
+ '<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
3448
+ '<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>';
3449
+
3450
+ return tplShortcut(lang.shortcut.action, body);
3451
+ };
3452
+
3453
+ var tplShortcutPara = function (lang) {
3454
+ var body = '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
3455
+ '<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
3456
+ '<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
3457
+ '<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
3458
+ '<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
3459
+ '<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>';
3460
+
3461
+ return tplShortcut(lang.shortcut.paragraphFormatting, body);
3462
+ };
3463
+
3464
+ var tplShortcutStyle = function (lang) {
3465
+ var body = '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
3466
+ '<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
3467
+ '<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
3468
+ '<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
3469
+ '<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
3470
+ '<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
3471
+ '<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>';
3472
+
3473
+ return tplShortcut(lang.shortcut.documentStyle, body);
3474
+ };
3475
+
3476
+ var tplExtraShortcuts = function (lang, options) {
3477
+ var extraKeys = options.extraKeys;
3478
+ var body = '';
3479
+ for (var key in extraKeys) {
3480
+ if (extraKeys.hasOwnProperty(key)) {
3481
+ body += '<tr><td>' + key + '</td><td>' + extraKeys[key] + '</td></tr>';
3482
+ }
3483
+ }
3484
+
3485
+ return tplShortcut(lang.shortcut.extraKeys, body);
3486
+ };
3487
+
3488
+ var tplShortcutTable = function (lang, options) {
3489
+ var template = '<table class="note-shortcut-layout">' +
3490
+ '<tbody>' +
3491
+ '<tr><td>' + tplShortcutAction(lang, options) + '</td><td>' + tplShortcutText(lang, options) + '</td></tr>' +
3492
+ '<tr><td>' + tplShortcutStyle(lang, options) + '</td><td>' + tplShortcutPara(lang, options) + '</td></tr>';
3493
+ if (options.extraKeys) {
3494
+ template += '<tr><td colspan="2">' + tplExtraShortcuts(lang, options) + '</td></tr>';
3495
+ }
3496
+ template += '</tbody</table>';
3497
+ return template;
3498
+ };
3499
+
3500
+ var replaceMacKeys = function (sHtml) {
3501
+ return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
3502
+ };
3503
+
3504
+ var tplDialogs = function (lang, options) {
3505
+ var tplImageDialog = function () {
3506
+ var body = '<h5>' + lang.image.selectFromFiles + '</h5>' +
3507
+ '<input class="note-image-input" type="file" name="files" accept="image/*" />' +
3508
+ '<h5>' + lang.image.url + '</h5>' +
3509
+ '<input class="note-image-url form-control span12" type="text" />';
3510
+ var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
3511
+ return tplDialog('note-image-dialog', lang.image.insert, body, footer);
3512
+ };
3513
+
3514
+ var tplLinkDialog = function () {
3515
+ var body = '<div class="form-group">' +
3516
+ '<label>' + lang.link.textToDisplay + '</label>' +
3517
+ '<input class="note-link-text form-control span12" type="text" />' +
3518
+ '</div>' +
3519
+ '<div class="form-group">' +
3520
+ '<label>' + lang.link.url + '</label>' +
3521
+ '<input class="note-link-url form-control span12" type="text" />' +
3522
+ '</div>' +
3523
+ (!options.disableLinkTarget ?
3524
+ '<div class="checkbox">' +
3525
+ '<label>' + '<input type="checkbox" checked> ' +
3526
+ lang.link.openInNewWindow +
3527
+ '</label>' +
3528
+ '</div>' : ''
3529
+ );
3530
+ var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
3531
+ return tplDialog('note-link-dialog', lang.link.insert, body, footer);
3532
+ };
3533
+
3534
+ var tplVideoDialog = function () {
3535
+ var body = '<div class="form-group">' +
3536
+ '<label>' + lang.video.url + '</label>&nbsp;<small class="text-muted">' + lang.video.providers + '</small>' +
3537
+ '<input class="note-video-url form-control span12" type="text" />' +
3538
+ '</div>';
3539
+ var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
3540
+ return tplDialog('note-video-dialog', lang.video.insert, body, footer);
3541
+ };
3542
+
3543
+ var tplHelpDialog = function () {
3544
+ var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
3545
+ '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
3546
+ (agent.bMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
3547
+ '<p class="text-center">' +
3548
+ '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.2</a> · ' +
3549
+ '<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' +
3550
+ '<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' +
3551
+ '</p>';
3552
+ return tplDialog('note-help-dialog', '', body, '');
3553
+ };
3554
+
3555
+ return '<div class="note-dialog">' +
3556
+ tplImageDialog() +
3557
+ tplLinkDialog() +
3558
+ tplVideoDialog() +
3559
+ tplHelpDialog() +
3560
+ '</div>';
3561
+ };
3562
+
3563
+ var tplStatusbar = function () {
3564
+ return '<div class="note-resizebar">' +
3565
+ '<div class="note-icon-bar"></div>' +
3566
+ '<div class="note-icon-bar"></div>' +
3567
+ '<div class="note-icon-bar"></div>' +
3568
+ '</div>';
3569
+ };
3570
+
3571
+ var representShortcut = function (str) {
3572
+ if (agent.bMac) {
3573
+ str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
3574
+ }
3575
+
3576
+ return str.replace('BACKSLASH', '\\')
3577
+ .replace('SLASH', '/')
3578
+ .replace('LEFTBRACKET', '[')
3579
+ .replace('RIGHTBRACKET', ']');
3580
+ };
3581
+
3582
+ /**
3583
+ * createTooltip
3584
+ *
3585
+ * @param {jQuery} $container
3586
+ * @param {Object} keyMap
3587
+ * @param {String} [sPlacement]
3588
+ */
3589
+ var createTooltip = function ($container, keyMap, sPlacement) {
3590
+ var invertedKeyMap = func.invertObject(keyMap);
3591
+ var $buttons = $container.find('button');
3592
+
3593
+ $buttons.each(function (i, elBtn) {
3594
+ var $btn = $(elBtn);
3595
+ var sShortcut = invertedKeyMap[$btn.data('event')];
3596
+ if (sShortcut) {
3597
+ $btn.attr('title', function (i, v) {
3598
+ return v + ' (' + representShortcut(sShortcut) + ')';
3599
+ });
3600
+ }
3601
+ // bootstrap tooltip on btn-group bug
3602
+ // https://github.com/twitter/bootstrap/issues/5687
3603
+ }).tooltip({
3604
+ container: 'body',
3605
+ trigger: 'hover',
3606
+ placement: sPlacement || 'top'
3607
+ }).on('click', function () {
3608
+ $(this).tooltip('hide');
3609
+ });
3610
+ };
3611
+
3612
+ // createPalette
3613
+ var createPalette = function ($container, options) {
3614
+ var aaColor = options.colors;
3615
+ $container.find('.note-color-palette').each(function () {
3616
+ var $palette = $(this), sEvent = $palette.attr('data-target-event');
3617
+ var aPaletteContents = [];
3618
+ for (var row = 0, szRow = aaColor.length; row < szRow; row++) {
3619
+ var aColor = aaColor[row];
3620
+ var aButton = [];
3621
+ for (var col = 0, szCol = aColor.length; col < szCol; col++) {
3622
+ var sColor = aColor[col];
3623
+ aButton.push(['<button type="button" class="note-color-btn" style="background-color:', sColor,
3624
+ ';" data-event="', sEvent,
3625
+ '" data-value="', sColor,
3626
+ '" title="', sColor,
3627
+ '" data-toggle="button" tabindex="-1"></button>'].join(''));
3628
+ }
3629
+ aPaletteContents.push('<div>' + aButton.join('') + '</div>');
3630
+ }
3631
+ $palette.html(aPaletteContents.join(''));
3632
+ });
3633
+ };
3634
+
3635
+ /**
3636
+ * create summernote layout (air mode)
3637
+ *
3638
+ * @param {jQuery} $holder
3639
+ * @param {Object} options
3640
+ */
3641
+ this.createLayoutByAirMode = function ($holder, options) {
3642
+ var keyMap = options.keyMap[agent.bMac ? 'mac' : 'pc'];
3643
+ var langInfo = $.summernote.lang[options.lang];
3644
+
3645
+ var id = func.uniqueId();
3646
+
3647
+ $holder.addClass('note-air-editor note-editable');
3648
+ $holder.attr({
3649
+ 'id': 'note-editor-' + id,
3650
+ 'contentEditable': true
3651
+ });
3652
+
3653
+ var body = document.body;
3654
+
3655
+ // create Popover
3656
+ var $popover = $(tplPopovers(langInfo, options));
3657
+ $popover.addClass('note-air-layout');
3658
+ $popover.attr('id', 'note-popover-' + id);
3659
+ $popover.appendTo(body);
3660
+ createTooltip($popover, keyMap);
3661
+ createPalette($popover, options);
3662
+
3663
+ // create Handle
3664
+ var $handle = $(tplHandles());
3665
+ $handle.addClass('note-air-layout');
3666
+ $handle.attr('id', 'note-handle-' + id);
3667
+ $handle.appendTo(body);
3668
+
3669
+ // create Dialog
3670
+ var $dialog = $(tplDialogs(langInfo, options));
3671
+ $dialog.addClass('note-air-layout');
3672
+ $dialog.attr('id', 'note-dialog-' + id);
3673
+ $dialog.find('button.close, a.modal-close').click(function () {
3674
+ $(this).closest('.modal').modal('hide');
3675
+ });
3676
+ $dialog.appendTo(body);
3677
+ };
3678
+
3679
+ /**
3680
+ * create summernote layout (normal mode)
3681
+ *
3682
+ * @param {jQuery} $holder
3683
+ * @param {Object} options
3684
+ */
3685
+ this.createLayoutByFrame = function ($holder, options) {
3686
+ //01. create Editor
3687
+ var $editor = $('<div class="note-editor"></div>');
3688
+ if (options.width) {
3689
+ $editor.width(options.width);
3690
+ }
3691
+
3692
+ //02. statusbar (resizebar)
3693
+ if (options.height > 0) {
3694
+ $('<div class="note-statusbar">' + tplStatusbar() + '</div>').prependTo($editor);
3695
+ }
3696
+
3697
+ //03. create Editable
3698
+ var isContentEditable = !$holder.is(':disabled');
3699
+ var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>')
3700
+ .prependTo($editor);
3701
+ if (options.height) {
3702
+ $editable.height(options.height);
3703
+ }
3704
+ if (options.direction) {
3705
+ $editable.attr('dir', options.direction);
3706
+ }
3707
+
3708
+ $editable.html(dom.html($holder) || dom.emptyPara);
3709
+
3710
+ //031. create codable
3711
+ $('<textarea class="note-codable"></textarea>').prependTo($editor);
3712
+
3713
+ var langInfo = $.summernote.lang[options.lang];
3714
+
3715
+ //04. create Toolbar
3716
+ var sToolbar = '';
3717
+ for (var idx = 0, sz = options.toolbar.length; idx < sz; idx ++) {
3718
+ var group = options.toolbar[idx];
3719
+ sToolbar += '<div class="note-' + group[0] + ' btn-group">';
3720
+ for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
3721
+ sToolbar += tplButtonInfo[group[1][i]](langInfo, options);
3722
+ }
3723
+ sToolbar += '</div>';
3724
+ }
3725
+
3726
+ sToolbar = '<div class="note-toolbar btn-toolbar">' + sToolbar + '</div>';
3727
+
3728
+ var $toolbar = $(sToolbar).prependTo($editor);
3729
+ var keyMap = options.keyMap[agent.bMac ? 'mac' : 'pc'];
3730
+ createPalette($toolbar, options);
3731
+ createTooltip($toolbar, keyMap, 'bottom');
3732
+
3733
+ //05. create Popover
3734
+ var $popover = $(tplPopovers(langInfo, options)).prependTo($editor);
3735
+ createPalette($popover, options);
3736
+ createTooltip($popover, keyMap);
3737
+
3738
+ //06. handle(control selection, ...)
3739
+ $(tplHandles()).prependTo($editor);
3740
+
3741
+ //07. create Dialog
3742
+ var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
3743
+ $dialog.find('button.close, a.modal-close').click(function () {
3744
+ $(this).closest('.modal').modal('hide');
3745
+ });
3746
+
3747
+ //08. create Dropzone
3748
+ $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
3749
+
3750
+ //09. Editor/Holder switch
3751
+ $editor.insertAfter($holder);
3752
+ $holder.hide();
3753
+ };
3754
+
3755
+ this.noteEditorFromHolder = function ($holder) {
3756
+ if ($holder.hasClass('note-air-editor')) {
3757
+ return $holder;
3758
+ } else if ($holder.next().hasClass('note-editor')) {
3759
+ return $holder.next();
3760
+ } else {
3761
+ return $();
3762
+ }
3763
+ };
3764
+
3765
+ /**
3766
+ * create summernote layout
3767
+ *
3768
+ * @param {jQuery} $holder
3769
+ * @param {Object} options
3770
+ */
3771
+ this.createLayout = function ($holder, options) {
3772
+ if (this.noteEditorFromHolder($holder).length > 0) {
3773
+ return;
3774
+ }
3775
+
3776
+ if (options.airMode) {
3777
+ this.createLayoutByAirMode($holder, options);
3778
+ } else {
3779
+ this.createLayoutByFrame($holder, options);
3780
+ }
3781
+ };
3782
+
3783
+ /**
3784
+ * returns layoutInfo from holder
3785
+ *
3786
+ * @param {jQuery} $holder - placeholder
3787
+ * @returns {Object}
3788
+ */
3789
+ this.layoutInfoFromHolder = function ($holder) {
3790
+ var $editor = this.noteEditorFromHolder($holder);
3791
+ if (!$editor.length) { return; }
3792
+
3793
+ var layoutInfo = dom.buildLayoutInfo($editor);
3794
+ // cache all properties.
3795
+ for (var key in layoutInfo) {
3796
+ if (layoutInfo.hasOwnProperty(key)) {
3797
+ layoutInfo[key] = layoutInfo[key].call();
3798
+ }
3799
+ }
3800
+ return layoutInfo;
3801
+ };
3802
+
3803
+ /**
3804
+ * removeLayout
3805
+ *
3806
+ * @param {jQuery} $holder - placeholder
3807
+ */
3808
+ this.removeLayout = function ($holder) {
3809
+ var info = this.layoutInfoFromHolder($holder);
3810
+ if (!info) { return; }
3811
+ $holder.html(info.editable.html());
3812
+
3813
+ info.editor.remove();
3814
+ $holder.show();
3815
+ };
3816
+ };
3817
+
3818
+ // jQuery namespace for summernote
3819
+ $.summernote = $.summernote || {};
3820
+
3821
+ // extends default `settings`
3822
+ $.extend($.summernote, settings);
3823
+
3824
+ var renderer = new Renderer();
3825
+ var eventHandler = new EventHandler();
3826
+
3827
+ /**
3828
+ * extend jquery fn
3829
+ */
3830
+ $.fn.extend({
3831
+ /**
3832
+ * initialize summernote
3833
+ * - create editor layout and attach Mouse and keyboard events.
3834
+ *
3835
+ * @param {Object} options
3836
+ * @returns {this}
3837
+ */
3838
+ summernote: function (options) {
3839
+ // extend default options
3840
+ options = $.extend({}, $.summernote.options, options);
3841
+
3842
+ this.each(function (idx, elHolder) {
3843
+ var $holder = $(elHolder);
3844
+
3845
+ // createLayout with options
3846
+ renderer.createLayout($holder, options);
3847
+
3848
+ var info = renderer.layoutInfoFromHolder($holder);
3849
+ eventHandler.attach(info, options);
3850
+
3851
+ // Textarea: auto filling the code before form submit.
3852
+ if (dom.isTextarea($holder[0])) {
3853
+ $holder.closest('form').submit(function () {
3854
+ $holder.html($holder.code());
3855
+ });
3856
+ }
3857
+ });
3858
+
3859
+ // focus on first editable element
3860
+ if (this.first().length && options.focus) {
3861
+ var info = renderer.layoutInfoFromHolder(this.first());
3862
+ info.editable.focus();
3863
+ }
3864
+
3865
+ // callback on init
3866
+ if (this.length > 0 && options.oninit) {
3867
+ options.oninit();
3868
+ }
3869
+
3870
+ return this;
3871
+ },
3872
+ //
3873
+
3874
+ /**
3875
+ * get the HTML contents of note or set the HTML contents of note.
3876
+ *
3877
+ * @param {String} [sHTML] - HTML contents(optional, set)
3878
+ * @returns {this|String} - context(set) or HTML contents of note(get).
3879
+ */
3880
+ code: function (sHTML) {
3881
+ // get the HTML contents of note
3882
+ if (sHTML === undefined) {
3883
+ var $holder = this.first();
3884
+ if ($holder.length === 0) { return; }
3885
+ var info = renderer.layoutInfoFromHolder($holder);
3886
+ if (!!(info && info.editable)) {
3887
+ var bCodeview = info.editor.hasClass('codeview');
3888
+ if (bCodeview && agent.bCodeMirror) {
3889
+ info.codable.data('cmEditor').save();
3890
+ }
3891
+ return bCodeview ? info.codable.val() : info.editable.html();
3892
+ }
3893
+ return $holder.html();
3894
+ }
3895
+
3896
+ // set the HTML contents of note
3897
+ this.each(function (i, elHolder) {
3898
+ var info = renderer.layoutInfoFromHolder($(elHolder));
3899
+ if (info && info.editable) { info.editable.html(sHTML); }
3900
+ });
3901
+
3902
+ return this;
3903
+ },
3904
+
3905
+ /**
3906
+ * destroy Editor Layout and dettach Key and Mouse Event
3907
+ * @returns {this}
3908
+ */
3909
+ destroy: function () {
3910
+ this.each(function (idx, elHolder) {
3911
+ var $holder = $(elHolder);
3912
+
3913
+ var info = renderer.layoutInfoFromHolder($holder);
3914
+ if (!info || !info.editable) { return; }
3915
+ eventHandler.dettach(info);
3916
+ renderer.removeLayout($holder);
3917
+ });
3918
+
3919
+ return this;
3920
+ }
3921
+ });
3922
+ }));